feat(skills): add metadata and branch fields to skill packs and marketplace skills
This commit is contained in:
@@ -3,11 +3,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from app.models.boards import Board
|
||||
from app.models.organization_board_access import OrganizationBoardAccess
|
||||
@@ -15,6 +17,7 @@ from app.models.organization_invite_board_access import OrganizationInviteBoardA
|
||||
from app.models.organization_invites import OrganizationInvite
|
||||
from app.models.organization_members import OrganizationMember
|
||||
from app.models.organizations import Organization
|
||||
from app.models.skill_packs import SkillPack
|
||||
from app.models.users import User
|
||||
from app.schemas.organizations import OrganizationBoardAccessSpec, OrganizationMemberAccessUpdate
|
||||
from app.services import organizations
|
||||
@@ -107,6 +110,44 @@ def test_normalize_role(value: str, expected: str) -> None:
|
||||
assert organizations.normalize_role(value) == expected
|
||||
|
||||
|
||||
def test_normalize_skill_pack_source_url_normalizes_trivial_variants() -> None:
|
||||
assert (
|
||||
organizations._normalize_skill_pack_source_url("https://github.com/org/repo")
|
||||
== "https://github.com/org/repo"
|
||||
)
|
||||
assert (
|
||||
organizations._normalize_skill_pack_source_url("https://github.com/org/repo/")
|
||||
== "https://github.com/org/repo"
|
||||
)
|
||||
assert (
|
||||
organizations._normalize_skill_pack_source_url(" https://github.com/org/repo.git ")
|
||||
== "https://github.com/org/repo"
|
||||
)
|
||||
|
||||
|
||||
def test_get_default_skill_pack_records_deduplicates_normalized_urls(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr(
|
||||
organizations,
|
||||
"DEFAULT_INSTALLER_SKILL_PACKS",
|
||||
(
|
||||
("owner/repo", "pack one", "first"),
|
||||
("owner/repo/", "pack duplicate", "duplicate"),
|
||||
("owner/repo.git", "pack duplicate again", "duplicate again"),
|
||||
("owner/other", "other", "other"),
|
||||
),
|
||||
)
|
||||
now = datetime(2025, 1, 1)
|
||||
records = organizations._get_default_skill_pack_records(org_id=uuid4(), now=now)
|
||||
|
||||
assert len(records) == 2
|
||||
assert {pack.source_url for pack in records} == {
|
||||
"https://github.com/owner/repo",
|
||||
"https://github.com/owner/other",
|
||||
}
|
||||
|
||||
|
||||
def test_role_rank_unknown_role_falls_back_to_member_rank() -> None:
|
||||
assert organizations._role_rank("madeup") == 0
|
||||
assert organizations._role_rank(None) == 0
|
||||
@@ -218,7 +259,119 @@ async def test_ensure_member_for_user_creates_personal_org_and_owner(
|
||||
assert any(
|
||||
isinstance(item, Organization) and item.id == out.organization_id for item in session.added
|
||||
)
|
||||
assert session.committed == 1
|
||||
skill_packs = [
|
||||
item
|
||||
for item in [*session.added, *[record for batch in session.added_all for record in batch]]
|
||||
if isinstance(item, SkillPack)
|
||||
]
|
||||
assert len(skill_packs) == 2
|
||||
pack_sources = {pack.source_url: pack.description for pack in skill_packs}
|
||||
assert (
|
||||
pack_sources["https://github.com/sickn33/antigravity-awesome-skills"]
|
||||
== "The Ultimate Collection of 800+ Agentic Skills for Claude Code/Antigravity/Cursor. "
|
||||
"Battle-tested, high-performance skills for AI agents including official skills from "
|
||||
"Anthropic and Vercel."
|
||||
)
|
||||
assert (
|
||||
pack_sources["https://github.com/BrianRWagner/ai-marketing-skills"]
|
||||
== "Marketing frameworks that AI actually executes. Use for Claude Code, OpenClaw, etc."
|
||||
)
|
||||
assert session.committed == 3
|
||||
assert len(session.added_all) == 0
|
||||
assert {pack.source_url for pack in skill_packs} == {
|
||||
"https://github.com/sickn33/antigravity-awesome-skills",
|
||||
"https://github.com/BrianRWagner/ai-marketing-skills",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ensure_member_for_user_skips_already_existing_default_pack_by_source_url(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
user = User(clerk_user_id="u1", email=None)
|
||||
existing_pack_source = "https://github.com/sickn33/antigravity-awesome-skills/"
|
||||
|
||||
async def _fake_get_active(_session: Any, _user: User) -> None:
|
||||
return None
|
||||
|
||||
async def _fake_get_first(_session: Any, _user_id: Any) -> None:
|
||||
return None
|
||||
|
||||
async def _fake_fetch_existing_pack_sources(
|
||||
_session: Any,
|
||||
_org_id: Any,
|
||||
) -> set[str]:
|
||||
return {existing_pack_source}
|
||||
|
||||
monkeypatch.setattr(organizations, "get_active_membership", _fake_get_active)
|
||||
monkeypatch.setattr(organizations, "get_first_membership", _fake_get_first)
|
||||
monkeypatch.setattr(
|
||||
organizations,
|
||||
"_fetch_existing_default_pack_sources",
|
||||
_fake_fetch_existing_pack_sources,
|
||||
)
|
||||
|
||||
session = _FakeSession(exec_results=[_FakeExecResult()])
|
||||
|
||||
out = await organizations.ensure_member_for_user(session, user)
|
||||
assert out.user_id == user.id
|
||||
assert out.role == "owner"
|
||||
assert out.organization_id == user.active_organization_id
|
||||
skill_packs = [item for item in session.added if isinstance(item, SkillPack)]
|
||||
assert len(skill_packs) == 1
|
||||
assert skill_packs[0].source_url == "https://github.com/BrianRWagner/ai-marketing-skills"
|
||||
assert session.committed == 2
|
||||
assert len(session.added_all) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ensure_member_for_user_recovers_on_default_install_integrity_error(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
org_id = uuid4()
|
||||
user = User(clerk_user_id="u1", email=None, active_organization_id=org_id)
|
||||
existing_member = OrganizationMember(
|
||||
organization_id=org_id,
|
||||
user_id=user.id,
|
||||
role="owner",
|
||||
)
|
||||
|
||||
call_count = 0
|
||||
|
||||
async def _fake_get_active(_session: Any, _user: User) -> None:
|
||||
return None
|
||||
|
||||
async def _fake_get_first(_session: Any, _user_id: Any) -> OrganizationMember | None:
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
if call_count == 1:
|
||||
return None
|
||||
return existing_member
|
||||
|
||||
async def _fake_fetch_existing_pack_sources(
|
||||
_session: Any,
|
||||
_org_id: Any,
|
||||
) -> set[str]:
|
||||
return set()
|
||||
|
||||
monkeypatch.setattr(organizations, "get_active_membership", _fake_get_active)
|
||||
monkeypatch.setattr(organizations, "get_first_membership", _fake_get_first)
|
||||
monkeypatch.setattr(
|
||||
organizations,
|
||||
"_fetch_existing_default_pack_sources",
|
||||
_fake_fetch_existing_pack_sources,
|
||||
)
|
||||
|
||||
session = _FakeSession(
|
||||
exec_results=[_FakeExecResult(), _FakeExecResult()],
|
||||
commit_side_effects=[IntegrityError("statement", [], None)],
|
||||
)
|
||||
|
||||
out = await organizations.ensure_member_for_user(session, user)
|
||||
assert out is existing_member
|
||||
assert out.organization_id == org_id
|
||||
assert call_count == 2
|
||||
assert user.active_organization_id == org_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user