import unittest from dataclasses import dataclass, field from datetime import datetime, timezone from dns_sync.adapters import ( DNS_RECORDS, PROXY_HOSTS, Capability, CapabilityOperation, DiscoveredObject, Endpoint, PlanOperation, ) from dns_sync.object_normalizer import normalize_observations from dns_sync.orchestrator import discover_reconcile_plan from dns_sync.persistence import SQLiteStateStore T1 = datetime(2026, 9, 19, 22, 0, tzinfo=timezone.utc) T2 = datetime(2026, 9, 19, 22, 5, tzinfo=timezone.utc) def capabilities( object_type: str, *operations: CapabilityOperation, ) -> frozenset[Capability]: return frozenset( Capability(object_type, operation) for operation in operations ) def proxy_host( local_key: str, domains: list[str], backend_ip: str = "192.168.1.50", enabled: bool = True, ) -> DiscoveredObject: return DiscoveredObject( object_type=PROXY_HOSTS, local_key=local_key, payload={ "domain_names": domains, "forward_scheme": "http", "forward_host": backend_ip, "forward_port": 8080, "enabled": enabled, }, ) def dns_record( local_key: str, hostname: str, ip: str, ) -> DiscoveredObject: return DiscoveredObject( object_type=DNS_RECORDS, local_key=local_key, payload={ "hostname": hostname, "ip": ip, "record_type": "A", }, ) @dataclass class FakeAdapter: endpoint: Endpoint capabilities: frozenset[Capability] discovered: dict[str, list[DiscoveredObject]] = field( default_factory=dict ) connect_error: Exception | None = None connect_count: int = 0 discover_calls: list[str] = field(default_factory=list) apply_count: int = 0 verify_count: int = 0 def connect(self) -> None: self.connect_count += 1 if self.connect_error is not None: raise self.connect_error def discover( self, object_type: str, ) -> list[DiscoveredObject]: self.discover_calls.append(object_type) return list(self.discovered.get(object_type, [])) def apply(self, action): self.apply_count += 1 raise AssertionError("read-only orchestration called apply") def verify(self, object): self.verify_count += 1 raise AssertionError("read-only orchestration called verify") class ReadOnlyOrchestrationTests(unittest.TestCase): def test_discovers_persists_reconciles_and_plans_vip_update(self): npm = FakeAdapter( endpoint=Endpoint("npm", "192.168.1.3:81"), capabilities=capabilities( PROXY_HOSTS, CapabilityOperation.DISCOVER, ), discovered={ PROXY_HOSTS: [ proxy_host( "12", ["app.example.com"], backend_ip="192.168.1.50", ) ] }, ) verizon = FakeAdapter( endpoint=Endpoint("verizon", "192.168.1.1:443"), capabilities=capabilities( DNS_RECORDS, CapabilityOperation.DISCOVER, CapabilityOperation.APPLY, CapabilityOperation.VERIFY, ), discovered={ DNS_RECORDS: [ dns_record( "41", "app.example.com", "192.168.1.50", ) ] }, ) with SQLiteStateStore(":memory:") as store: result = discover_reconcile_plan( [npm, verizon], store, target_ip="192.168.1.101", run_at=T2, ) persisted_desired = store.load_desired_state( DNS_RECORDS ) self.assertEqual(len( [ item for item in result.desired if item.object_type == DNS_RECORDS ] ), 1) self.assertEqual( next( item for item in result.desired if item.object_type == DNS_RECORDS ).payload["ip"], "192.168.1.101", ) self.assertEqual(len(result.plans), 1) self.assertEqual( result.plans[0].endpoint_id, verizon.endpoint.identity, ) self.assertEqual( result.plans[0].actions[0].operation, PlanOperation.UPDATE, ) self.assertEqual( result.plans[0].actions[0].desired.payload["ip"], "192.168.1.101", ) self.assertEqual( persisted_desired[0].object.payload["ip"], "192.168.1.101", ) self.assertEqual(npm.connect_count, 1) self.assertEqual(verizon.connect_count, 1) self.assertEqual(npm.apply_count, 0) self.assertEqual(verizon.apply_count, 0) self.assertEqual(npm.verify_count, 0) self.assertEqual(verizon.verify_count, 0) def test_failed_node_remains_represented_by_cached_observation(self): npm_endpoint = Endpoint("npm", "192.168.1.3:81") cached = normalize_observations( npm_endpoint.identity, [proxy_host("12", ["cached.example.com"])], T1, ) npm = FakeAdapter( endpoint=npm_endpoint, capabilities=capabilities( PROXY_HOSTS, CapabilityOperation.DISCOVER, ), connect_error=RuntimeError("temporarily unavailable"), ) verizon = FakeAdapter( endpoint=Endpoint("verizon", "192.168.1.1:443"), capabilities=capabilities( DNS_RECORDS, CapabilityOperation.DISCOVER, CapabilityOperation.APPLY, ), discovered={DNS_RECORDS: []}, ) with SQLiteStateStore(":memory:") as store: store.replace_observations( npm_endpoint.identity, PROXY_HOSTS, cached, T1, ) result = discover_reconcile_plan( [npm, verizon], store, target_ip="192.168.1.101", run_at=T2, ) status = store.observation_status( npm_endpoint.identity, PROXY_HOSTS, ) self.assertEqual( tuple( item.logical_key for item in result.desired if item.object_type == DNS_RECORDS ), (), ) self.assertEqual( len(result.plans), 1, ) self.assertEqual( result.plans[0].actions, (), ) self.assertFalse(status.is_live) self.assertEqual(status.last_success_at, T1) self.assertEqual(status.last_attempt_at, T2) self.assertEqual( status.last_error, "temporarily unavailable", ) self.assertEqual(len(result.failures), 1) def test_live_empty_snapshot_supersedes_cached_state(self): npm_endpoint = Endpoint("npm", "192.168.1.3:81") npm = FakeAdapter( endpoint=npm_endpoint, capabilities=capabilities( PROXY_HOSTS, CapabilityOperation.DISCOVER, ), discovered={PROXY_HOSTS: []}, ) cached = normalize_observations( npm_endpoint.identity, [proxy_host("12", ["removed.example.com"])], T1, ) with SQLiteStateStore(":memory:") as store: store.replace_observations( npm_endpoint.identity, PROXY_HOSTS, cached, T1, ) result = discover_reconcile_plan( [npm], store, target_ip="192.168.1.101", run_at=T2, ) persisted_desired = store.load_desired_state( DNS_RECORDS ) self.assertEqual(result.desired, ()) self.assertEqual(result.plans, ()) self.assertEqual(persisted_desired, []) def test_domain_filtering_occurs_after_projection(self): npm = FakeAdapter( endpoint=Endpoint("npm", "192.168.1.3:81"), capabilities=capabilities( PROXY_HOSTS, CapabilityOperation.DISCOVER, ), discovered={ PROXY_HOSTS: [ proxy_host( "12", [ "app.example.com", "outside.example.net", ], ) ] }, ) with SQLiteStateStore(":memory:") as store: result = discover_reconcile_plan( [npm], store, target_ip="192.168.1.101", domain_matching=True, domain="example.com", run_at=T2, ) self.assertEqual( tuple( item.logical_key for item in result.desired if item.object_type == DNS_RECORDS ), ("app.example.com",), ) def test_removed_endpoints_do_not_contribute_cached_state(self): removed_endpoint = Endpoint("npm", "192.168.1.4:81") active_endpoint = Endpoint("npm", "192.168.1.3:81") removed_cache = normalize_observations( removed_endpoint.identity, [proxy_host("12", ["removed.example.com"])], T1, ) active = FakeAdapter( endpoint=active_endpoint, capabilities=capabilities( PROXY_HOSTS, CapabilityOperation.DISCOVER, ), discovered={ PROXY_HOSTS: [ proxy_host("13", ["active.example.com"]) ] }, ) with SQLiteStateStore(":memory:") as store: store.replace_observations( removed_endpoint.identity, PROXY_HOSTS, removed_cache, T1, ) result = discover_reconcile_plan( [active], store, target_ip="192.168.1.101", run_at=T2, ) self.assertEqual( tuple( item.logical_key for item in result.desired if item.object_type == DNS_RECORDS ), ("active.example.com",), ) if __name__ == "__main__": unittest.main()