"""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.source_file import SourceFile 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_source_file( session: AsyncSession, *, tenant: Tenant, source_file_id: uuid.UUID | None = None, domain: str = "general", status: str = "active", ) -> SourceFile: """A `source_files` row for tests that address a file without uploading one. `DELETE /v1/files/{file_id}` authorizes against this row before touching a single point, so a delete test needs it even though the interesting state lives in Qdrant. """ source_file = SourceFile( id=source_file_id or uuid.uuid4(), tenant_id=tenant.id, domain=domain, source_filename="handbook.docx", source_type="docx", content_sha256="0" * 64, byte_size=1024, storage_uri="s3://bucket/key", status=status, ) session.add(source_file) await session.flush() return source_file 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