Commit fe9b3d12 authored by George Papathanail's avatar George Papathanail
Browse files

fix: sync local app registration on delete, protect get_app against malformed catalog entries

parent 3e81fd1b
Loading
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
from uuid import UUID

from sqlalchemy import select
from sqlalchemy import delete, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession

@@ -41,3 +41,7 @@ class SqlAppRegistrationRepository(AppRegistrationRepository):
        if saved is None:
            raise RuntimeError("Saved app registration could not be reloaded")
        return saved

    async def delete_by_app_id(self, app_id: UUID) -> None:
        stmt = delete(AppRegistrationRow).where(AppRegistrationRow.app_id == app_id)
        await self._session.execute(stmt)
+11 −1
Original line number Diff line number Diff line
@@ -148,7 +148,14 @@ class EdgeApplicationManagementService:
        self, app_id: UUID, x_correlator: Optional[str] = None
    ) -> AppManifestEnvelope:
        catalog = await self.srm_client.get_app(app_id=app_id, x_correlator=x_correlator)
        return AppManifestEnvelope(appManifest=build_app_manifest(catalog))
        try:
            manifest = build_app_manifest(catalog)
        except (ValueError, TypeError, IndexError) as exc:
            raise DownstreamServiceException(
                message="SRM returned a malformed service specification",
                details=str(exc),
            ) from exc
        return AppManifestEnvelope(appManifest=manifest)

    async def submit_app(
        self,
@@ -223,6 +230,9 @@ class EdgeApplicationManagementService:
        x_correlator: Optional[str] = None,
    ) -> None:
        await self.srm_client.delete_app(app_id=app_id, x_correlator=x_correlator)
        if self._app_registration_repo is None:
            raise RuntimeError("AppRegistrationRepository is not available")
        await self._app_registration_repo.delete_by_app_id(app_id)

    async def create_app_instance(
        self,
+4 −0
Original line number Diff line number Diff line
@@ -18,3 +18,7 @@ class AppRegistrationRepository(ABC):
    @abstractmethod
    async def save(self, app_registration: AppRegistration) -> AppRegistration:
        pass

    @abstractmethod
    async def delete_by_app_id(self, app_id: UUID) -> None:
        pass
+5 −0
Original line number Diff line number Diff line
@@ -303,6 +303,11 @@ class FakeAppRegistrationRepository(AppRegistrationRepository):
        self.rows[stored.app_registration_id] = stored
        return stored.model_copy(deep=True)

    async def delete_by_app_id(self, app_id: UUID) -> None:
        for registration_id, row in list(self.rows.items()):
            if row.app_id == app_id:
                del self.rows[registration_id]


class FakeOperationRepository(OperationRepository):
    def __init__(self) -> None:
+55 −0
Original line number Diff line number Diff line
@@ -276,6 +276,16 @@ class TestGetApp:
        await service.get_app(app_id=APP_ID, x_correlator="corr-1")
        srm_client.get_app.assert_called_once_with(app_id=APP_ID, x_correlator="corr-1")

    async def test_raises_downstream_exception_for_unmappable_catalog_entry(
        self, service: EdgeApplicationManagementService, srm_client: AsyncMock
    ) -> None:
        catalog = _make_srm_catalog()
        catalog.service_deployment_units = []
        srm_client.get_app.return_value = catalog

        with pytest.raises(DownstreamServiceException):
            await service.get_app(app_id=APP_ID)


class TestSubmitApp:
    async def test_returns_submitted_app_with_id(
@@ -456,6 +466,51 @@ class TestDeleteApp:
        )
        srm_client.delete_app.assert_called_once_with(app_id=APP_ID, x_correlator="corr-1")

    async def test_removes_local_app_registration(
        self,
        service: EdgeApplicationManagementService,
        app_registration_repo: FakeAppRegistrationRepository,
    ) -> None:
        await app_registration_repo.save(
            AppRegistration(
                app_registration_id=uuid4(),
                app_id=APP_ID,
                tenant_id="tenant-1",
                name="myvideoapp",
                version="1.0.0",
                package_type=PackageType.HELM,
                status=AppRegistrationStatus.REGISTERED,
            )
        )

        await service.delete_app(app_id=APP_ID, app_provider_id="VideoAppsCo")

        assert await app_registration_repo.get_by_app_id(APP_ID) is None

    async def test_does_not_delete_local_registration_when_srm_call_fails(
        self,
        service: EdgeApplicationManagementService,
        srm_client: AsyncMock,
        app_registration_repo: FakeAppRegistrationRepository,
    ) -> None:
        await app_registration_repo.save(
            AppRegistration(
                app_registration_id=uuid4(),
                app_id=APP_ID,
                tenant_id="tenant-1",
                name="myvideoapp",
                version="1.0.0",
                package_type=PackageType.HELM,
                status=AppRegistrationStatus.REGISTERED,
            )
        )
        srm_client.delete_app.side_effect = NotFoundException(message="not found")

        with pytest.raises(NotFoundException):
            await service.delete_app(app_id=APP_ID, app_provider_id="VideoAppsCo")

        assert await app_registration_repo.get_by_app_id(APP_ID) is not None


class TestCreateAppInstance:
    def _make_request(self) -> CreateAppInstanceRequest: