Commit 0f5a986f authored by George Papathanail's avatar George Papathanail
Browse files

feat: reject deployment sinkcredential with 501 instead of dropping it

parent ce61f1e2
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -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
+59 −0
Original line number Diff line number Diff line
@@ -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(