Files

202 lines
8.2 KiB
Python
Raw Permalink Normal View History

"""Tests for ServiceRepository."""
from uuid import uuid4
import asyncpg
import pytest
from app.repositories.org import OrgRepository
from app.repositories.service import ServiceRepository
class TestServiceRepository:
"""Tests for ServiceRepository conforming to SPECS.md."""
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_create_service_returns_service_data(self, db_conn: asyncpg.Connection) -> None:
"""Creating a service returns the service data."""
org_id = await self._create_org(db_conn, "service-org")
repo = ServiceRepository(db_conn)
service_id = uuid4()
result = await repo.create(service_id, org_id, "API Gateway", "api-gateway")
assert result["id"] == service_id
assert result["org_id"] == org_id
assert result["name"] == "API Gateway"
assert result["slug"] == "api-gateway"
assert result["created_at"] is not None
async def test_create_service_slug_unique_per_org(self, db_conn: asyncpg.Connection) -> None:
"""Service slug must be unique within an org per SPECS.md."""
org_id = await self._create_org(db_conn, "unique-slug-org")
repo = ServiceRepository(db_conn)
await repo.create(uuid4(), org_id, "Service One", "my-service")
with pytest.raises(asyncpg.UniqueViolationError):
await repo.create(uuid4(), org_id, "Service Two", "my-service")
async def test_same_slug_allowed_in_different_orgs(self, db_conn: asyncpg.Connection) -> None:
"""Same slug can exist in different orgs."""
org1 = await self._create_org(db_conn, "org-one")
org2 = await self._create_org(db_conn, "org-two")
repo = ServiceRepository(db_conn)
slug = "shared-slug"
# Both should succeed
result1 = await repo.create(uuid4(), org1, "Service Org1", slug)
result2 = await repo.create(uuid4(), org2, "Service Org2", slug)
assert result1["slug"] == slug
assert result2["slug"] == slug
assert result1["org_id"] != result2["org_id"]
async def test_get_by_id_returns_service(self, db_conn: asyncpg.Connection) -> None:
"""get_by_id returns the correct service."""
org_id = await self._create_org(db_conn, "get-service-org")
repo = ServiceRepository(db_conn)
service_id = uuid4()
await repo.create(service_id, org_id, "My Service", "my-service")
result = await repo.get_by_id(service_id)
assert result is not None
assert result["id"] == service_id
assert result["name"] == "My Service"
async def test_get_by_id_returns_none_for_nonexistent(self, db_conn: asyncpg.Connection) -> None:
"""get_by_id returns None for non-existent service."""
repo = ServiceRepository(db_conn)
result = await repo.get_by_id(uuid4())
assert result is None
async def test_get_by_org_returns_all_org_services(self, db_conn: asyncpg.Connection) -> None:
"""get_by_org returns all services for an organization."""
org_id = await self._create_org(db_conn, "multi-service-org")
repo = ServiceRepository(db_conn)
await repo.create(uuid4(), org_id, "Service A", "service-a")
await repo.create(uuid4(), org_id, "Service B", "service-b")
await repo.create(uuid4(), org_id, "Service C", "service-c")
result = await repo.get_by_org(org_id)
assert len(result) == 3
names = {s["name"] for s in result}
assert names == {"Service A", "Service B", "Service C"}
async def test_get_by_org_returns_empty_for_no_services(self, db_conn: asyncpg.Connection) -> None:
"""get_by_org returns empty list for org with no services."""
org_id = await self._create_org(db_conn, "empty-service-org")
repo = ServiceRepository(db_conn)
result = await repo.get_by_org(org_id)
assert result == []
async def test_get_by_org_only_returns_own_services(self, db_conn: asyncpg.Connection) -> None:
"""get_by_org doesn't return services from other orgs (tenant isolation)."""
org1 = await self._create_org(db_conn, "isolated-org-1")
org2 = await self._create_org(db_conn, "isolated-org-2")
repo = ServiceRepository(db_conn)
await repo.create(uuid4(), org1, "Org1 Service", "org1-service")
await repo.create(uuid4(), org2, "Org2 Service", "org2-service")
result = await repo.get_by_org(org1)
assert len(result) == 1
assert result[0]["name"] == "Org1 Service"
async def test_get_by_slug_returns_service(self, db_conn: asyncpg.Connection) -> None:
"""get_by_slug returns service by org and slug."""
org_id = await self._create_org(db_conn, "slug-lookup-org")
repo = ServiceRepository(db_conn)
service_id = uuid4()
await repo.create(service_id, org_id, "Slug Service", "slug-service")
result = await repo.get_by_slug(org_id, "slug-service")
assert result is not None
assert result["id"] == service_id
async def test_get_by_slug_returns_none_for_wrong_org(self, db_conn: asyncpg.Connection) -> None:
"""get_by_slug returns None if slug exists but in different org."""
org1 = await self._create_org(db_conn, "slug-org-1")
org2 = await self._create_org(db_conn, "slug-org-2")
repo = ServiceRepository(db_conn)
await repo.create(uuid4(), org1, "Service", "the-slug")
result = await repo.get_by_slug(org2, "the-slug")
assert result is None
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."""
org_id = await self._create_org(db_conn, "no-slug-org")
repo = ServiceRepository(db_conn)
result = await repo.get_by_slug(org_id, "nonexistent")
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 in org."""
org_id = await self._create_org(db_conn, "exists-org")
repo = ServiceRepository(db_conn)
await repo.create(uuid4(), org_id, "Exists Service", "exists-slug")
result = await repo.slug_exists(org_id, "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 in org."""
org_id = await self._create_org(db_conn, "not-exists-org")
repo = ServiceRepository(db_conn)
result = await repo.slug_exists(org_id, "no-such-slug")
assert result is False
async def test_slug_exists_returns_false_for_other_org(self, db_conn: asyncpg.Connection) -> None:
"""slug_exists returns False for slug in different org."""
org1 = await self._create_org(db_conn, "other-org-1")
org2 = await self._create_org(db_conn, "other-org-2")
repo = ServiceRepository(db_conn)
await repo.create(uuid4(), org1, "Service", "cross-org-slug")
result = await repo.slug_exists(org2, "cross-org-slug")
assert result is False
async def test_service_requires_valid_org_foreign_key(self, db_conn: asyncpg.Connection) -> None:
"""services.org_id must reference existing org."""
repo = ServiceRepository(db_conn)
with pytest.raises(asyncpg.ForeignKeyViolationError):
await repo.create(uuid4(), uuid4(), "Orphan Service", "orphan")
async def test_get_by_org_orders_by_name(self, db_conn: asyncpg.Connection) -> None:
"""get_by_org returns services ordered by name."""
org_id = await self._create_org(db_conn, "ordered-org")
repo = ServiceRepository(db_conn)
await repo.create(uuid4(), org_id, "Zebra", "zebra")
await repo.create(uuid4(), org_id, "Alpha", "alpha")
await repo.create(uuid4(), org_id, "Middle", "middle")
result = await repo.get_by_org(org_id)
names = [s["name"] for s in result]
assert names == ["Alpha", "Middle", "Zebra"]