"""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"]