Loading tests/conformance/conftest.py +3 −1 Original line number Diff line number Diff line Loading @@ -66,7 +66,9 @@ def service_overrides() -> Generator[None, None, None]: operation_repo = FakeOperationRepository() app_instance_repo = FakeAppInstanceRepository() app_deployment_repo = FakeAppDeploymentRepository() wire_operation_consumer(bus, operation_repo, app_instance_repo) wire_operation_consumer( bus, operation_repo, app_instance_repo, app_deployment_repo=app_deployment_repo ) wire_srm_worker(bus, srm) app.dependency_overrides[get_edge_app_service] = lambda: EdgeApplicationManagementService( srm_client=srm, Loading tests/unit/conftest.py +2 −0 Original line number Diff line number Diff line Loading @@ -119,6 +119,7 @@ def live_srm( callback_registration_repo: FakeCallbackRegistrationRepository, callback_delivery_port: FakeCallbackDeliveryPort, callback_delivery_repo: FakeCallbackDeliveryRepository, app_deployment_repo: FakeAppDeploymentRepository, ) -> FakeSRMClient: """Fake SRM with its async side running: consumes commands, publishes completions.""" wire_operation_consumer( Loading @@ -129,6 +130,7 @@ def live_srm( callback_registration_repo=callback_registration_repo, callback_delivery_port=callback_delivery_port, callback_delivery_repo=callback_delivery_repo, app_deployment_repo=app_deployment_repo, ) wire_srm_worker(fake_bus, fake_srm) return fake_srm Loading tests/unit/fakes.py +28 −22 Original line number Diff line number Diff line Loading @@ -220,9 +220,12 @@ def operation_status_payload(operation_id: str, **overrides: Any) -> dict[str, A def wire_srm_worker(bus: FakeDataBus, srm: FakeSRMClient) -> None: async def on_deploy(command: dict[str, Any]) -> None: # POST /appinstances always carries exactly one targets[] entry # (ADR-0005); multi-zone /deployments (N entries) is out of scope here. target = command["targets"][0] # One targets[] entry for single-zone POST /appinstances, N for # multi-zone POST /deployments (ADR-0005) -- both produce one # instances[] entry per target in a single completion event, same as # real SRM would for a multi-zone deploy. instances = [] for target in command["targets"]: # SRM adopts the OEG-minted app_instance_id as its own service_instance_id (ADR-0005). srm_id = target["app_instance_id"] srm.instances[srm_id] = SRMServiceInstance( Loading @@ -233,18 +236,19 @@ def wire_srm_worker(bus: FakeDataBus, srm: FakeSRMClient) -> None: zone_id=target["zone_id"], name=command["deploy"]["instance_name"], ) await bus.publish( Subject.OPERATION_COMPLETED, completion_payload( command["operation_id"], correlation_id=command["correlation_id"], instances=[ instances.append( { "service_instance_id": srm_id, "zone_id": target["zone_id"], "status": "completed", } ], ) await bus.publish( Subject.OPERATION_COMPLETED, completion_payload( command["operation_id"], correlation_id=command["correlation_id"], instances=instances, ), ) Loading Loading @@ -303,12 +307,13 @@ def wire_operation_consumer( callback_registration_repo: CallbackRegistrationRepository | None = None, callback_delivery_port: CallbackDeliveryPort | None = None, callback_delivery_repo: CallbackDeliveryRepository | None = None, app_deployment_repo: AppDeploymentRepository | None = None, ) -> NatsOperationConsumer: """Wires OEG's real completion handler (EdgeApplicationManagementService.handle_completed) behind the fake bus -- pass the same repo/port instances used to build the service under test so a completion event updates the rows (and, for callback tests, deliveries) the test can see. app_registration_repo/callback_* are optional: only needed by tests exercising webhook delivery.""" can see. app_registration_repo/callback_*/app_deployment_repo are optional: only needed by tests exercising webhook delivery or the app_deployments rollup, respectively.""" service = EdgeApplicationManagementService( srm_client=AsyncMock(), app_registration_repo=app_registration_repo, Loading @@ -317,6 +322,7 @@ def wire_operation_consumer( callback_registration_repo=callback_registration_repo, callback_delivery_port=callback_delivery_port, callback_delivery_repo=callback_delivery_repo, app_deployment_repo=app_deployment_repo, ) consumer = NatsOperationConsumer( client=AsyncMock(), subject=Subject.OPERATION_COMPLETED, handler=service.handle_completed Loading tests/unit/test_eam_flows.py +29 −0 Original line number Diff line number Diff line Loading @@ -453,6 +453,35 @@ class TestCreateAppDeploymentFlow: response = api_client.post(f"{EAM_BASE}/deployments", json=body) assert response.status_code == 400 def test_operations_app_deployment_and_app_instances_reach_ready_after_srm_completes( self, api_client: TestClient, live_srm: FakeSRMClient, operation_repo: FakeOperationRepository, app_deployment_repo: FakeAppDeploymentRepository, app_instance_repo: FakeAppInstanceRepository, ) -> None: """End-to-end through the real DI wiring, matching TestCreateAppInstanceFlow's single-zone completion test: proves both the N app_instances rows AND the app_deployments rollup reach their terminal state once SRM completes, not just the operations row.""" response = api_client.post(f"{EAM_BASE}/deployments", json=CREATE_DEPLOYMENT_BODY) deployment_id = UUID(response.json()["appDeploymentId"]) (operation,) = list(operation_repo.rows.values()) assert operation.status == OperationStatus.COMPLETED stored_deployment = app_deployment_repo.rows.get(deployment_id) assert stored_deployment is not None assert stored_deployment.state == AppDeploymentState.READY instances = [ i for i in app_instance_repo.rows.values() if i.app_deployment_id == deployment_id ] assert len(instances) == 2 assert all(i.state == AppInstanceState.READY for i in instances) assert len(live_srm.instances) == 2 class TestCreateAppDeploymentUnregisteredAppFlow: """No autouse app registration here — this class exists to prove the Loading Loading
tests/conformance/conftest.py +3 −1 Original line number Diff line number Diff line Loading @@ -66,7 +66,9 @@ def service_overrides() -> Generator[None, None, None]: operation_repo = FakeOperationRepository() app_instance_repo = FakeAppInstanceRepository() app_deployment_repo = FakeAppDeploymentRepository() wire_operation_consumer(bus, operation_repo, app_instance_repo) wire_operation_consumer( bus, operation_repo, app_instance_repo, app_deployment_repo=app_deployment_repo ) wire_srm_worker(bus, srm) app.dependency_overrides[get_edge_app_service] = lambda: EdgeApplicationManagementService( srm_client=srm, Loading
tests/unit/conftest.py +2 −0 Original line number Diff line number Diff line Loading @@ -119,6 +119,7 @@ def live_srm( callback_registration_repo: FakeCallbackRegistrationRepository, callback_delivery_port: FakeCallbackDeliveryPort, callback_delivery_repo: FakeCallbackDeliveryRepository, app_deployment_repo: FakeAppDeploymentRepository, ) -> FakeSRMClient: """Fake SRM with its async side running: consumes commands, publishes completions.""" wire_operation_consumer( Loading @@ -129,6 +130,7 @@ def live_srm( callback_registration_repo=callback_registration_repo, callback_delivery_port=callback_delivery_port, callback_delivery_repo=callback_delivery_repo, app_deployment_repo=app_deployment_repo, ) wire_srm_worker(fake_bus, fake_srm) return fake_srm Loading
tests/unit/fakes.py +28 −22 Original line number Diff line number Diff line Loading @@ -220,9 +220,12 @@ def operation_status_payload(operation_id: str, **overrides: Any) -> dict[str, A def wire_srm_worker(bus: FakeDataBus, srm: FakeSRMClient) -> None: async def on_deploy(command: dict[str, Any]) -> None: # POST /appinstances always carries exactly one targets[] entry # (ADR-0005); multi-zone /deployments (N entries) is out of scope here. target = command["targets"][0] # One targets[] entry for single-zone POST /appinstances, N for # multi-zone POST /deployments (ADR-0005) -- both produce one # instances[] entry per target in a single completion event, same as # real SRM would for a multi-zone deploy. instances = [] for target in command["targets"]: # SRM adopts the OEG-minted app_instance_id as its own service_instance_id (ADR-0005). srm_id = target["app_instance_id"] srm.instances[srm_id] = SRMServiceInstance( Loading @@ -233,18 +236,19 @@ def wire_srm_worker(bus: FakeDataBus, srm: FakeSRMClient) -> None: zone_id=target["zone_id"], name=command["deploy"]["instance_name"], ) await bus.publish( Subject.OPERATION_COMPLETED, completion_payload( command["operation_id"], correlation_id=command["correlation_id"], instances=[ instances.append( { "service_instance_id": srm_id, "zone_id": target["zone_id"], "status": "completed", } ], ) await bus.publish( Subject.OPERATION_COMPLETED, completion_payload( command["operation_id"], correlation_id=command["correlation_id"], instances=instances, ), ) Loading Loading @@ -303,12 +307,13 @@ def wire_operation_consumer( callback_registration_repo: CallbackRegistrationRepository | None = None, callback_delivery_port: CallbackDeliveryPort | None = None, callback_delivery_repo: CallbackDeliveryRepository | None = None, app_deployment_repo: AppDeploymentRepository | None = None, ) -> NatsOperationConsumer: """Wires OEG's real completion handler (EdgeApplicationManagementService.handle_completed) behind the fake bus -- pass the same repo/port instances used to build the service under test so a completion event updates the rows (and, for callback tests, deliveries) the test can see. app_registration_repo/callback_* are optional: only needed by tests exercising webhook delivery.""" can see. app_registration_repo/callback_*/app_deployment_repo are optional: only needed by tests exercising webhook delivery or the app_deployments rollup, respectively.""" service = EdgeApplicationManagementService( srm_client=AsyncMock(), app_registration_repo=app_registration_repo, Loading @@ -317,6 +322,7 @@ def wire_operation_consumer( callback_registration_repo=callback_registration_repo, callback_delivery_port=callback_delivery_port, callback_delivery_repo=callback_delivery_repo, app_deployment_repo=app_deployment_repo, ) consumer = NatsOperationConsumer( client=AsyncMock(), subject=Subject.OPERATION_COMPLETED, handler=service.handle_completed Loading
tests/unit/test_eam_flows.py +29 −0 Original line number Diff line number Diff line Loading @@ -453,6 +453,35 @@ class TestCreateAppDeploymentFlow: response = api_client.post(f"{EAM_BASE}/deployments", json=body) assert response.status_code == 400 def test_operations_app_deployment_and_app_instances_reach_ready_after_srm_completes( self, api_client: TestClient, live_srm: FakeSRMClient, operation_repo: FakeOperationRepository, app_deployment_repo: FakeAppDeploymentRepository, app_instance_repo: FakeAppInstanceRepository, ) -> None: """End-to-end through the real DI wiring, matching TestCreateAppInstanceFlow's single-zone completion test: proves both the N app_instances rows AND the app_deployments rollup reach their terminal state once SRM completes, not just the operations row.""" response = api_client.post(f"{EAM_BASE}/deployments", json=CREATE_DEPLOYMENT_BODY) deployment_id = UUID(response.json()["appDeploymentId"]) (operation,) = list(operation_repo.rows.values()) assert operation.status == OperationStatus.COMPLETED stored_deployment = app_deployment_repo.rows.get(deployment_id) assert stored_deployment is not None assert stored_deployment.state == AppDeploymentState.READY instances = [ i for i in app_instance_repo.rows.values() if i.app_deployment_id == deployment_id ] assert len(instances) == 2 assert all(i.state == AppInstanceState.READY for i in instances) assert len(live_srm.instances) == 2 class TestCreateAppDeploymentUnregisteredAppFlow: """No autouse app registration here — this class exists to prove the Loading