from __future__ import annotations from datetime import UTC, datetime import unittest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from govoplan_access.backend.db import models as access_models # noqa: F401 from govoplan_calendar.backend.db.models import CalendarOutboxOperation, CalendarSyncSource from govoplan_calendar.backend.manifest import ( CALDAV_PROVIDER_ID, EWS_PROVIDER_ID, GRAPH_PROVIDER_ID, ICS_PROVIDER_ID, manifest, ) from govoplan_calendar.backend.provider_state import ( caldav_provider_states, ews_provider_states, graph_provider_states, ics_provider_states, ) from govoplan_calendar.backend.schemas import ( CalendarCalDavSourceCreateRequest, CalendarCollectionCreateRequest, ) from govoplan_calendar.backend.service import create_caldav_source, create_calendar from govoplan_core.core.provider_governance import ExternalProviderStateContext from govoplan_core.db.base import Base from govoplan_core.tenancy.scope import create_scope_tables from govoplan_tenancy.backend.db.models import Tenant class CalendarProviderStateTests(unittest.TestCase): def setUp(self) -> None: self.engine = create_engine("sqlite+pysqlite:///:memory:", future=True) create_scope_tables(self.engine) Base.metadata.create_all(self.engine) self.Session = sessionmaker(bind=self.engine, expire_on_commit=False) self.session = self.Session() self.session.add(Tenant(id="tenant-1", slug="tenant-1", name="Tenant")) self.calendar = create_calendar( self.session, tenant_id="tenant-1", user_id=None, payload=CalendarCollectionCreateRequest(name="Remote"), ) self.source = create_caldav_source( self.session, tenant_id="tenant-1", user_id=None, payload=CalendarCalDavSourceCreateRequest( calendar_id=self.calendar.id, collection_url="https://dav.example.test/cal", ), ) self.session.commit() def tearDown(self) -> None: self.session.close() self.engine.dispose() def test_state_projects_health_freshness_and_unresolved_outcomes(self) -> None: self.source.last_status = "ok" self.source.last_synced_at = datetime.now(UTC) self.session.flush() healthy = caldav_provider_states( ExternalProviderStateContext( session=self.session, tenant_id="tenant-1", ) )[0] self.assertEqual("healthy", healthy.health) self.assertEqual("current", healthy.freshness) self.assertEqual("clear", healthy.conflict) self.assertEqual("ready", healthy.recovery) self.assertEqual( f"calendar:sync-source:{self.source.id}", healthy.binding_ref, ) self.assertNotIn("dav.example.test", str(healthy.to_dict())) self.session.add( CalendarOutboxOperation( tenant_id="tenant-1", source_id=self.source.id, operation_kind="put", resource_href="event.ics", idempotency_key="a" * 64, status="conflict", ) ) self.session.flush() conflicted = caldav_provider_states( ExternalProviderStateContext( session=self.session, tenant_id="tenant-1", ) )[0] self.assertEqual("warning", conflicted.health) self.assertEqual("pending", conflicted.conflict) self.assertEqual("attention", conflicted.recovery) def test_state_is_tenant_bounded_and_manifest_registered(self) -> None: self.assertEqual( (), caldav_provider_states( ExternalProviderStateContext( session=self.session, tenant_id="tenant-2", ) ), ) self.assertEqual( {CALDAV_PROVIDER_ID, ICS_PROVIDER_ID, GRAPH_PROVIDER_ID, EWS_PROVIDER_ID}, {item.id for item in manifest.external_providers}, ) self.assertEqual( {CALDAV_PROVIDER_ID, ICS_PROVIDER_ID, GRAPH_PROVIDER_ID, EWS_PROVIDER_ID}, { item.provider_id for item in manifest.external_provider_state_providers }, ) def test_read_only_sources_have_distinct_secret_free_provider_state(self) -> None: now = datetime.now(UTC) sources = ( ("ics", "https://feeds.example.test/team.ics", "none"), ("graph", "https://graph.microsoft.com/v1.0/me/calendar/events", "bearer"), ("ews", "https://exchange.example.test/EWS/Exchange.asmx", "basic"), ) for source_kind, collection_url, auth_type in sources: self.session.add( CalendarSyncSource( tenant_id="tenant-1", calendar_id=self.calendar.id, source_kind=source_kind, collection_url=collection_url, auth_type=auth_type, sync_enabled=True, sync_direction="read_only", last_status="ok", last_synced_at=now, ) ) self.session.flush() states = ( ics_provider_states( ExternalProviderStateContext(session=self.session, tenant_id="tenant-1") )[0], graph_provider_states( ExternalProviderStateContext(session=self.session, tenant_id="tenant-1") )[0], ews_provider_states( ExternalProviderStateContext(session=self.session, tenant_id="tenant-1") )[0], ) self.assertTrue(all(item.authority_mode == "external_mirror" for item in states)) self.assertTrue(all(item.health == "healthy" for item in states)) rendered = str([item.to_dict() for item in states]) self.assertNotIn("feeds.example.test", rendered) self.assertNotIn("exchange.example.test", rendered) if __name__ == "__main__": unittest.main()