Files

251 lines
10 KiB
Python
Raw Permalink Normal View History

"""Tests for OrgRepository."""
from uuid import uuid4
import asyncpg
import pytest
from app.repositories.org import OrgRepository
from app.repositories.user import UserRepository
class TestOrgRepository:
"""Tests for OrgRepository conforming to SPECS.md."""
async def test_create_org_returns_org_data(self, db_conn: asyncpg.Connection) -> None:
"""Creating an org returns the org data."""
repo = OrgRepository(db_conn)
org_id = uuid4()
name = "Test Organization"
slug = "test-org"
result = await repo.create(org_id, name, slug)
assert result["id"] == org_id
assert result["name"] == name
assert result["slug"] == slug
assert result["created_at"] is not None
async def test_create_org_slug_must_be_unique(self, db_conn: asyncpg.Connection) -> None:
"""Org slug uniqueness constraint per SPECS.md orgs table."""
repo = OrgRepository(db_conn)
slug = "unique-slug"
await repo.create(uuid4(), "Org One", slug)
with pytest.raises(asyncpg.UniqueViolationError):
await repo.create(uuid4(), "Org Two", slug)
async def test_get_by_id_returns_org(self, db_conn: asyncpg.Connection) -> None:
"""get_by_id returns the correct organization."""
repo = OrgRepository(db_conn)
org_id = uuid4()
await repo.create(org_id, "My Org", "my-org")
result = await repo.get_by_id(org_id)
assert result is not None
assert result["id"] == org_id
assert result["name"] == "My Org"
async def test_get_by_id_returns_none_for_nonexistent(self, db_conn: asyncpg.Connection) -> None:
"""get_by_id returns None for non-existent org."""
repo = OrgRepository(db_conn)
result = await repo.get_by_id(uuid4())
assert result is None
async def test_get_by_slug_returns_org(self, db_conn: asyncpg.Connection) -> None:
"""get_by_slug returns the correct organization."""
repo = OrgRepository(db_conn)
org_id = uuid4()
slug = "slug-lookup"
await repo.create(org_id, "Slug Test", slug)
result = await repo.get_by_slug(slug)
assert result is not None
assert result["id"] == org_id
async def test_get_by_slug_returns_none_for_nonexistent(self, db_conn: asyncpg.Connection) -> None:
"""get_by_slug returns None for non-existent slug."""
repo = OrgRepository(db_conn)
result = await repo.get_by_slug("nonexistent-slug")
assert result is None
async def test_slug_exists_returns_true_when_exists(self, db_conn: asyncpg.Connection) -> None:
"""slug_exists returns True when slug exists."""
repo = OrgRepository(db_conn)
slug = "existing-slug"
await repo.create(uuid4(), "Existing Org", slug)
result = await repo.slug_exists(slug)
assert result is True
async def test_slug_exists_returns_false_when_not_exists(self, db_conn: asyncpg.Connection) -> None:
"""slug_exists returns False when slug doesn't exist."""
repo = OrgRepository(db_conn)
result = await repo.slug_exists("no-such-slug")
assert result is False
class TestOrgMembership:
"""Tests for org membership operations per SPECS.md org_members table."""
async def _create_user(self, conn: asyncpg.Connection, email: str) -> uuid4:
"""Helper to create a user."""
user_repo = UserRepository(conn)
user_id = uuid4()
await user_repo.create(user_id, email, "hash")
return user_id
async def _create_org(self, conn: asyncpg.Connection, slug: str) -> uuid4:
"""Helper to create an org."""
org_repo = OrgRepository(conn)
org_id = uuid4()
await org_repo.create(org_id, f"Org {slug}", slug)
return org_id
async def test_add_member_creates_membership(self, db_conn: asyncpg.Connection) -> None:
"""add_member creates a membership record."""
user_id = await self._create_user(db_conn, "member@example.com")
org_id = await self._create_org(db_conn, "member-org")
repo = OrgRepository(db_conn)
result = await repo.add_member(uuid4(), user_id, org_id, "member")
assert result["user_id"] == user_id
assert result["org_id"] == org_id
assert result["role"] == "member"
assert result["created_at"] is not None
async def test_add_member_role_must_be_valid(self, db_conn: asyncpg.Connection) -> None:
"""Role must be admin, member, or viewer per SPECS.md."""
org_id = await self._create_org(db_conn, "role-test-org")
repo = OrgRepository(db_conn)
# Valid roles should work
for role in ["admin", "member", "viewer"]:
member_id = uuid4()
# Need a new user for each since user+org must be unique
new_user_id = await self._create_user(db_conn, f"{role}@example.com")
result = await repo.add_member(member_id, new_user_id, org_id, role)
assert result["role"] == role
# Invalid role should fail
another_user = await self._create_user(db_conn, "invalid_role@example.com")
with pytest.raises(asyncpg.CheckViolationError):
await repo.add_member(uuid4(), another_user, org_id, "superuser")
async def test_add_member_user_org_must_be_unique(self, db_conn: asyncpg.Connection) -> None:
"""User can only be member of an org once (unique constraint)."""
user_id = await self._create_user(db_conn, "unique_member@example.com")
org_id = await self._create_org(db_conn, "unique-member-org")
repo = OrgRepository(db_conn)
await repo.add_member(uuid4(), user_id, org_id, "member")
with pytest.raises(asyncpg.UniqueViolationError):
await repo.add_member(uuid4(), user_id, org_id, "admin")
async def test_get_member_returns_membership(self, db_conn: asyncpg.Connection) -> None:
"""get_member returns the membership for user and org."""
user_id = await self._create_user(db_conn, "get_member@example.com")
org_id = await self._create_org(db_conn, "get-member-org")
repo = OrgRepository(db_conn)
await repo.add_member(uuid4(), user_id, org_id, "admin")
result = await repo.get_member(user_id, org_id)
assert result is not None
assert result["user_id"] == user_id
assert result["org_id"] == org_id
assert result["role"] == "admin"
async def test_get_member_returns_none_for_nonmember(self, db_conn: asyncpg.Connection) -> None:
"""get_member returns None if user is not a member."""
user_id = await self._create_user(db_conn, "nonmember@example.com")
org_id = await self._create_org(db_conn, "nonmember-org")
repo = OrgRepository(db_conn)
result = await repo.get_member(user_id, org_id)
assert result is None
async def test_get_members_returns_all_org_members(self, db_conn: asyncpg.Connection) -> None:
"""get_members returns all members with their emails."""
org_id = await self._create_org(db_conn, "all-members-org")
user1 = await self._create_user(db_conn, "user1@example.com")
user2 = await self._create_user(db_conn, "user2@example.com")
user3 = await self._create_user(db_conn, "user3@example.com")
repo = OrgRepository(db_conn)
await repo.add_member(uuid4(), user1, org_id, "admin")
await repo.add_member(uuid4(), user2, org_id, "member")
await repo.add_member(uuid4(), user3, org_id, "viewer")
result = await repo.get_members(org_id)
assert len(result) == 3
emails = {m["email"] for m in result}
assert emails == {"user1@example.com", "user2@example.com", "user3@example.com"}
async def test_get_members_returns_empty_list_for_no_members(self, db_conn: asyncpg.Connection) -> None:
"""get_members returns empty list for org with no members."""
org_id = await self._create_org(db_conn, "empty-org")
repo = OrgRepository(db_conn)
result = await repo.get_members(org_id)
assert result == []
async def test_get_user_orgs_returns_all_user_memberships(self, db_conn: asyncpg.Connection) -> None:
"""get_user_orgs returns all orgs a user belongs to with their role."""
user_id = await self._create_user(db_conn, "multi_org@example.com")
org1 = await self._create_org(db_conn, "user-org-1")
org2 = await self._create_org(db_conn, "user-org-2")
repo = OrgRepository(db_conn)
await repo.add_member(uuid4(), user_id, org1, "admin")
await repo.add_member(uuid4(), user_id, org2, "member")
result = await repo.get_user_orgs(user_id)
assert len(result) == 2
slugs = {o["slug"] for o in result}
assert slugs == {"user-org-1", "user-org-2"}
# Check role is included
roles = {o["role"] for o in result}
assert roles == {"admin", "member"}
async def test_get_user_orgs_returns_empty_for_no_memberships(self, db_conn: asyncpg.Connection) -> None:
"""get_user_orgs returns empty list for user with no memberships."""
user_id = await self._create_user(db_conn, "no_orgs@example.com")
repo = OrgRepository(db_conn)
result = await repo.get_user_orgs(user_id)
assert result == []
async def test_member_requires_valid_user_foreign_key(self, db_conn: asyncpg.Connection) -> None:
"""org_members.user_id must reference existing user."""
org_id = await self._create_org(db_conn, "fk-test-org")
repo = OrgRepository(db_conn)
with pytest.raises(asyncpg.ForeignKeyViolationError):
await repo.add_member(uuid4(), uuid4(), org_id, "member")
async def test_member_requires_valid_org_foreign_key(self, db_conn: asyncpg.Connection) -> None:
"""org_members.org_id must reference existing org."""
user_id = await self._create_user(db_conn, "fk_user@example.com")
repo = OrgRepository(db_conn)
with pytest.raises(asyncpg.ForeignKeyViolationError):
await repo.add_member(uuid4(), user_id, uuid4(), "member")