"""Object builders for test fixtures (ADR-0016). Insert rows via a caller-supplied `AsyncSession` without committing — callers decide their own transaction boundary (the `db_session` fixture rolls back after each test). """ import uuid from sqlalchemy.ext.asyncio import AsyncSession from src.application.auth.keys import generate_api_key, hash_secret from src.infrastructure.postgres.models.api_key import ApiKey from src.infrastructure.postgres.models.tenant import Tenant from src.infrastructure.postgres.models.tenant_domain import TenantDomain async def create_tenant( session: AsyncSession, *, slug: str | None = None, status: str = "active" ) -> Tenant: slug = slug or f"tenant-{uuid.uuid4().hex[:8]}" tenant = Tenant(id=uuid.uuid4(), slug=slug, name=slug, status=status) session.add(tenant) await session.flush() return tenant async def create_api_key( session: AsyncSession, *, tenant: Tenant, scopes: list[str] | None = None, status: str = "active", ) -> tuple[ApiKey, str]: """Returns `(api_key, full_key)`. `full_key` is the bearer token to send; only its hash is persisted. """ key_prefix, secret, full_key = generate_api_key() api_key = ApiKey( id=uuid.uuid4(), tenant_id=tenant.id, name="test-key", key_prefix=key_prefix, key_hash=hash_secret(secret), scopes=scopes or ["files:write"], status=status, ) session.add(api_key) await session.flush() return api_key, full_key async def create_tenant_domain( session: AsyncSession, *, tenant: Tenant, domain: str = "general", status: str = "active", ) -> TenantDomain: """Register a domain so an upload to it passes the allowlist check. Uploads reject an unregistered domain (ADR-0009), so any test that uploads needs one of these. """ tenant_domain = TenantDomain( id=uuid.uuid4(), tenant_id=tenant.id, domain=domain, display_name=domain, status=status, ) session.add(tenant_domain) await session.flush() return tenant_domain