"""Tenant-domain management and the upload-time allowlist (ADR-0009).""" import pytest from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from src.application.domains import ( DomainAlreadyExistsError, UnknownDomainError, create_domain, ensure_domain_allowed, list_domains, set_domain_status, update_domain, ) from tests.support.factories import create_tenant, create_tenant_domain pytestmark = [ pytest.mark.integration, pytest.mark.postgres, pytest.mark.asyncio(loop_scope="session"), ] async def test_ensure_domain_allowed_passes_for_a_registered_active_domain( db_session: AsyncSession, ) -> None: tenant = await create_tenant(db_session) await create_tenant_domain(db_session, tenant=tenant, domain="fire") await ensure_domain_allowed(db_session, tenant_id=tenant.id, domain="fire") async def test_ensure_domain_allowed_rejects_an_unregistered_domain( db_session: AsyncSession, ) -> None: """The typo case: `fier` must not silently become a new Qdrant partition.""" tenant = await create_tenant(db_session) await create_tenant_domain(db_session, tenant=tenant, domain="fire") with pytest.raises(UnknownDomainError, match="fier"): await ensure_domain_allowed(db_session, tenant_id=tenant.id, domain="fier") async def test_ensure_domain_allowed_rejects_a_disabled_domain( db_session: AsyncSession, ) -> None: tenant = await create_tenant(db_session) await create_tenant_domain(db_session, tenant=tenant, domain="fire", status="disabled") with pytest.raises(UnknownDomainError, match="disabled"): await ensure_domain_allowed(db_session, tenant_id=tenant.id, domain="fire") async def test_ensure_domain_allowed_rejects_another_tenants_domain( db_session: AsyncSession, ) -> None: """Domain lists are per-tenant; one tenant's `fire` is not another's.""" owner = await create_tenant(db_session) other = await create_tenant(db_session) await create_tenant_domain(db_session, tenant=owner, domain="fire") with pytest.raises(UnknownDomainError): await ensure_domain_allowed(db_session, tenant_id=other.id, domain="fire") async def test_tenants_hold_independent_domain_sets_of_different_sizes( db_session: AsyncSession, db_sessionmaker: async_sessionmaker[AsyncSession] ) -> None: big = await create_tenant(db_session) small = await create_tenant(db_session) for index in range(14): await create_tenant_domain(db_session, tenant=big, domain=f"line-{index:02d}") for index in range(6): await create_tenant_domain(db_session, tenant=small, domain=f"line-{index:02d}") await db_session.commit() assert len(await list_domains(db_sessionmaker, tenant_id=big.id)) == 14 assert len(await list_domains(db_sessionmaker, tenant_id=small.id)) == 6 async def test_create_domain_then_upload_is_allowed( db_session: AsyncSession, db_sessionmaker: async_sessionmaker[AsyncSession] ) -> None: tenant = await create_tenant(db_session) await db_session.commit() created = await create_domain( db_sessionmaker, tenant_id=tenant.id, domain="car", display_name="Car insurance" ) assert created.domain == "car" assert created.status == "active" async with db_sessionmaker() as session: await ensure_domain_allowed(session, tenant_id=tenant.id, domain="car") async def test_create_domain_rejects_a_duplicate_key_for_the_same_tenant( db_session: AsyncSession, db_sessionmaker: async_sessionmaker[AsyncSession] ) -> None: tenant = await create_tenant(db_session) await create_tenant_domain(db_session, tenant=tenant, domain="fire") await db_session.commit() with pytest.raises(DomainAlreadyExistsError): await create_domain( db_sessionmaker, tenant_id=tenant.id, domain="fire", display_name="Fire again" ) async def test_create_domain_allows_the_same_key_for_different_tenants( db_session: AsyncSession, db_sessionmaker: async_sessionmaker[AsyncSession] ) -> None: first = await create_tenant(db_session) second = await create_tenant(db_session) await db_session.commit() await create_domain(db_sessionmaker, tenant_id=first.id, domain="fire", display_name="Fire") await create_domain(db_sessionmaker, tenant_id=second.id, domain="fire", display_name="Fire") assert len(await list_domains(db_sessionmaker, tenant_id=first.id)) == 1 assert len(await list_domains(db_sessionmaker, tenant_id=second.id)) == 1 async def test_update_domain_changes_only_the_display_name( db_session: AsyncSession, db_sessionmaker: async_sessionmaker[AsyncSession] ) -> None: tenant = await create_tenant(db_session) await create_tenant_domain(db_session, tenant=tenant, domain="fire") await db_session.commit() updated = await update_domain( db_sessionmaker, tenant_id=tenant.id, domain="fire", display_name="Fire & perils" ) assert updated.display_name == "Fire & perils" # The key is immutable: it is denormalized into every point payload. assert updated.domain == "fire" async def test_disabling_a_domain_blocks_new_uploads_without_deleting_it( db_session: AsyncSession, db_sessionmaker: async_sessionmaker[AsyncSession] ) -> None: tenant = await create_tenant(db_session) await create_tenant_domain(db_session, tenant=tenant, domain="fire") await db_session.commit() disabled = await set_domain_status( db_sessionmaker, tenant_id=tenant.id, domain="fire", status="disabled" ) assert disabled.status == "disabled" async with db_sessionmaker() as session: with pytest.raises(UnknownDomainError): await ensure_domain_allowed(session, tenant_id=tenant.id, domain="fire") # Still there, just hidden from the default listing. assert await list_domains(db_sessionmaker, tenant_id=tenant.id) == [] assert len(await list_domains(db_sessionmaker, tenant_id=tenant.id, include_disabled=True)) == 1 async def test_re_enabling_a_domain_restores_uploads( db_session: AsyncSession, db_sessionmaker: async_sessionmaker[AsyncSession] ) -> None: tenant = await create_tenant(db_session) await create_tenant_domain(db_session, tenant=tenant, domain="fire", status="disabled") await db_session.commit() await set_domain_status(db_sessionmaker, tenant_id=tenant.id, domain="fire", status="active") async with db_sessionmaker() as session: await ensure_domain_allowed(session, tenant_id=tenant.id, domain="fire") async def test_update_domain_rejects_another_tenants_domain( db_session: AsyncSession, db_sessionmaker: async_sessionmaker[AsyncSession] ) -> None: owner = await create_tenant(db_session) other = await create_tenant(db_session) await create_tenant_domain(db_session, tenant=owner, domain="fire") await db_session.commit() with pytest.raises(UnknownDomainError): await update_domain( db_sessionmaker, tenant_id=other.id, domain="fire", display_name="hijacked" )