Loading src/open_exposure_gateway/application/services/edge_application_management_service.py +15 −0 Original line number Diff line number Diff line Loading @@ -459,6 +459,21 @@ class EdgeApplicationManagementService: ): raise RuntimeError("Operation/AppInstance/AppDeployment repositories are not available") # Short-term guard: we have no secret store to hold a callback auth token, # so a sinkCredential would be dropped and every notification POST to the # client's sink would go out unauthenticated (ADR-0008). Fail loudly here # — before any rows are written — instead of shipping a deployment whose # callbacks silently never arrive. Remove once the secret store lands. if ( request.subscriptionRequest is not None and request.subscriptionRequest.sinkCredential is not None ): raise NotImplementedException( "subscriptionRequest.sinkCredential is not supported in this " "release: the gateway cannot yet store callback credentials, so " "an authenticated sink would receive no notifications" ) if idempotency_key is not None: existing_operation = await self._operation_repo.get_by_idempotency_key( tenant_id, idempotency_key Loading tests/unit/test_eam_service.py +59 −0 Original line number Diff line number Diff line Loading @@ -1437,6 +1437,65 @@ class TestCreateAppDeployment: assert app_deployment_repo.rows == {} assert app_instance_repo.rows == {} def _make_request_with_subscription( self, with_credential: bool = False ) -> CreateAppDeploymentRequest: return CreateAppDeploymentRequest( appDeploymentName="video_analytics_eu", appId=APP_ID, edgeCloudZones=[self.ZONE_A, self.ZONE_B], subscriptionRequest=SubscriptionRequest( sink="https://client.example.com/callback", sinkCredential={"credentialType": "ACCESSTOKEN", "accessToken": "raw-token"} if with_credential else None, types=[ "org.camaraproject.edge-application-management.v0.app-deployment-status-change" ], ), ) async def test_sink_credential_rejected_with_501( self, service: EdgeApplicationManagementService ) -> None: with pytest.raises(NotImplementedException, match="sinkCredential"): await service.create_app_deployment( request=self._make_request_with_subscription(with_credential=True), tenant_id="tenant-1", app_provider_id="provider-1", ) async def test_sink_credential_rejection_persists_nothing_and_does_not_publish( self, service: EdgeApplicationManagementService, publisher: AsyncMock, operation_repo: FakeOperationRepository, app_deployment_repo: FakeAppDeploymentRepository, app_instance_repo: FakeAppInstanceRepository, callback_registration_repo: FakeCallbackRegistrationRepository, ) -> None: with pytest.raises(NotImplementedException): await service.create_app_deployment( request=self._make_request_with_subscription(with_credential=True), tenant_id="tenant-1", app_provider_id="provider-1", ) assert operation_repo.rows == {} assert app_deployment_repo.rows == {} assert app_instance_repo.rows == {} assert callback_registration_repo.rows == {} publisher.publish.assert_not_called() async def test_subscription_without_credential_still_accepted( self, service: EdgeApplicationManagementService ) -> None: result = await service.create_app_deployment( request=self._make_request_with_subscription(with_credential=False), tenant_id="tenant-1", app_provider_id="provider-1", ) assert result.appDeploymentId is not None class TestCreateAppDeploymentUnregisteredApp: async def test_raises_bad_request_when_app_not_registered( Loading Loading
src/open_exposure_gateway/application/services/edge_application_management_service.py +15 −0 Original line number Diff line number Diff line Loading @@ -459,6 +459,21 @@ class EdgeApplicationManagementService: ): raise RuntimeError("Operation/AppInstance/AppDeployment repositories are not available") # Short-term guard: we have no secret store to hold a callback auth token, # so a sinkCredential would be dropped and every notification POST to the # client's sink would go out unauthenticated (ADR-0008). Fail loudly here # — before any rows are written — instead of shipping a deployment whose # callbacks silently never arrive. Remove once the secret store lands. if ( request.subscriptionRequest is not None and request.subscriptionRequest.sinkCredential is not None ): raise NotImplementedException( "subscriptionRequest.sinkCredential is not supported in this " "release: the gateway cannot yet store callback credentials, so " "an authenticated sink would receive no notifications" ) if idempotency_key is not None: existing_operation = await self._operation_repo.get_by_idempotency_key( tenant_id, idempotency_key Loading
tests/unit/test_eam_service.py +59 −0 Original line number Diff line number Diff line Loading @@ -1437,6 +1437,65 @@ class TestCreateAppDeployment: assert app_deployment_repo.rows == {} assert app_instance_repo.rows == {} def _make_request_with_subscription( self, with_credential: bool = False ) -> CreateAppDeploymentRequest: return CreateAppDeploymentRequest( appDeploymentName="video_analytics_eu", appId=APP_ID, edgeCloudZones=[self.ZONE_A, self.ZONE_B], subscriptionRequest=SubscriptionRequest( sink="https://client.example.com/callback", sinkCredential={"credentialType": "ACCESSTOKEN", "accessToken": "raw-token"} if with_credential else None, types=[ "org.camaraproject.edge-application-management.v0.app-deployment-status-change" ], ), ) async def test_sink_credential_rejected_with_501( self, service: EdgeApplicationManagementService ) -> None: with pytest.raises(NotImplementedException, match="sinkCredential"): await service.create_app_deployment( request=self._make_request_with_subscription(with_credential=True), tenant_id="tenant-1", app_provider_id="provider-1", ) async def test_sink_credential_rejection_persists_nothing_and_does_not_publish( self, service: EdgeApplicationManagementService, publisher: AsyncMock, operation_repo: FakeOperationRepository, app_deployment_repo: FakeAppDeploymentRepository, app_instance_repo: FakeAppInstanceRepository, callback_registration_repo: FakeCallbackRegistrationRepository, ) -> None: with pytest.raises(NotImplementedException): await service.create_app_deployment( request=self._make_request_with_subscription(with_credential=True), tenant_id="tenant-1", app_provider_id="provider-1", ) assert operation_repo.rows == {} assert app_deployment_repo.rows == {} assert app_instance_repo.rows == {} assert callback_registration_repo.rows == {} publisher.publish.assert_not_called() async def test_subscription_without_credential_still_accepted( self, service: EdgeApplicationManagementService ) -> None: result = await service.create_app_deployment( request=self._make_request_with_subscription(with_credential=False), tenant_id="tenant-1", app_provider_id="provider-1", ) assert result.appDeploymentId is not None class TestCreateAppDeploymentUnregisteredApp: async def test_raises_bad_request_when_app_not_registered( Loading