Loading src/open_exposure_gateway/adapters/database/repos/app_instances.py +5 −2 Original line number Diff line number Diff line Loading @@ -5,9 +5,11 @@ from sqlalchemy.ext.asyncio import AsyncSession from open_exposure_gateway.adapters.database.mappers import AppInstanceMapper from open_exposure_gateway.adapters.database.sql import AppInstanceRow from open_exposure_gateway.domain.models import AppInstance from open_exposure_gateway.domain.models import AppInstance, AppInstanceState from open_exposure_gateway.ports.database.instances import AppInstanceRepository _TERMINAL_STATES = (AppInstanceState.FAILED, AppInstanceState.TERMINATED) class SqlAppInstanceRepository(AppInstanceRepository): def __init__(self, session: AsyncSession) -> None: Loading @@ -25,7 +27,8 @@ class SqlAppInstanceRepository(AppInstanceRepository): async def exists_for_app_registration(self, app_registration_id: UUID) -> bool: stmt = select(AppInstanceRow.app_instance_id).where( AppInstanceRow.app_registration_id == app_registration_id AppInstanceRow.app_registration_id == app_registration_id, AppInstanceRow.state.notin_(_TERMINAL_STATES), ) row = await self._session.scalar(stmt.limit(1)) return row is not None Loading tests/integration/test_postgres.py +20 −0 Original line number Diff line number Diff line Loading @@ -197,6 +197,26 @@ async def test_app_instance_repo_persists_and_loads(db_session: AsyncSession) -> assert reloaded.edge_cloud_zone_id == instance.edge_cloud_zone_id @pytest.mark.parametrize("state", [AppInstanceState.FAILED, AppInstanceState.TERMINATED]) async def test_app_instance_repo_exists_for_app_registration_ignores_terminal_states( db_session: AsyncSession, state: AppInstanceState ) -> None: registration = await SqlAppRegistrationRepository(db_session).save(_app_registration()) operation = await SqlOperationRepository(db_session).save( _operation(app_registration_id=registration.app_registration_id) ) repo = SqlAppInstanceRepository(db_session) instance = _app_instance( operation_id=operation.operation_id, app_registration_id=registration.app_registration_id, ) instance.state = state await repo.save(instance) assert await repo.exists_for_app_registration(registration.app_registration_id) is False async def test_callback_registration_repo_persists_and_loads(db_session: AsyncSession) -> None: operation = await SqlOperationRepository(db_session).save(_operation()) repo = SqlCallbackRegistrationRepository(db_session) Loading tests/unit/fakes.py +6 −1 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ from open_exposure_gateway.domain.edge_application_management import ( ) from open_exposure_gateway.domain.models import ( AppInstance, AppInstanceState, AppRegistration, CallbackDelivery, CallbackRegistration, Loading Loading @@ -356,7 +357,11 @@ class FakeAppInstanceRepository(AppInstanceRepository): return None async def exists_for_app_registration(self, app_registration_id: UUID) -> bool: return any(row.app_registration_id == app_registration_id for row in self.rows.values()) terminal_states = (AppInstanceState.FAILED, AppInstanceState.TERMINATED) return any( row.app_registration_id == app_registration_id and row.state not in terminal_states for row in self.rows.values() ) async def save(self, app_instance: AppInstance) -> AppInstance: stored = app_instance.model_copy(deep=True) Loading tests/unit/test_eam_service.py +36 −0 Original line number Diff line number Diff line Loading @@ -548,6 +548,42 @@ class TestDeleteApp: srm_client.delete_app.assert_not_called() assert await app_registration_repo.get_by_app_id(APP_ID) is not None @pytest.mark.parametrize("state", [AppInstanceState.FAILED, AppInstanceState.TERMINATED]) async def test_allows_delete_when_app_only_has_instances_in_a_terminal_state( self, state: AppInstanceState, service: EdgeApplicationManagementService, srm_client: AsyncMock, app_registration_repo: FakeAppRegistrationRepository, app_instance_repo: FakeAppInstanceRepository, ) -> None: app_registration_id = uuid4() await app_registration_repo.save( AppRegistration( app_registration_id=app_registration_id, app_id=APP_ID, tenant_id="tenant-1", name="myvideoapp", version="1.0.0", package_type=PackageType.HELM, status=AppRegistrationStatus.REGISTERED, ) ) await app_instance_repo.save( AppInstance( app_instance_id=INSTANCE_ID, operation_id=uuid4(), app_registration_id=app_registration_id, edge_cloud_zone_id=ZONE_ID, state=state, ) ) await service.delete_app(app_id=APP_ID, app_provider_id="VideoAppsCo") srm_client.delete_app.assert_called_once() assert await app_registration_repo.get_by_app_id(APP_ID) is None class TestCreateAppInstance: def _make_request(self) -> CreateAppInstanceRequest: Loading Loading
src/open_exposure_gateway/adapters/database/repos/app_instances.py +5 −2 Original line number Diff line number Diff line Loading @@ -5,9 +5,11 @@ from sqlalchemy.ext.asyncio import AsyncSession from open_exposure_gateway.adapters.database.mappers import AppInstanceMapper from open_exposure_gateway.adapters.database.sql import AppInstanceRow from open_exposure_gateway.domain.models import AppInstance from open_exposure_gateway.domain.models import AppInstance, AppInstanceState from open_exposure_gateway.ports.database.instances import AppInstanceRepository _TERMINAL_STATES = (AppInstanceState.FAILED, AppInstanceState.TERMINATED) class SqlAppInstanceRepository(AppInstanceRepository): def __init__(self, session: AsyncSession) -> None: Loading @@ -25,7 +27,8 @@ class SqlAppInstanceRepository(AppInstanceRepository): async def exists_for_app_registration(self, app_registration_id: UUID) -> bool: stmt = select(AppInstanceRow.app_instance_id).where( AppInstanceRow.app_registration_id == app_registration_id AppInstanceRow.app_registration_id == app_registration_id, AppInstanceRow.state.notin_(_TERMINAL_STATES), ) row = await self._session.scalar(stmt.limit(1)) return row is not None Loading
tests/integration/test_postgres.py +20 −0 Original line number Diff line number Diff line Loading @@ -197,6 +197,26 @@ async def test_app_instance_repo_persists_and_loads(db_session: AsyncSession) -> assert reloaded.edge_cloud_zone_id == instance.edge_cloud_zone_id @pytest.mark.parametrize("state", [AppInstanceState.FAILED, AppInstanceState.TERMINATED]) async def test_app_instance_repo_exists_for_app_registration_ignores_terminal_states( db_session: AsyncSession, state: AppInstanceState ) -> None: registration = await SqlAppRegistrationRepository(db_session).save(_app_registration()) operation = await SqlOperationRepository(db_session).save( _operation(app_registration_id=registration.app_registration_id) ) repo = SqlAppInstanceRepository(db_session) instance = _app_instance( operation_id=operation.operation_id, app_registration_id=registration.app_registration_id, ) instance.state = state await repo.save(instance) assert await repo.exists_for_app_registration(registration.app_registration_id) is False async def test_callback_registration_repo_persists_and_loads(db_session: AsyncSession) -> None: operation = await SqlOperationRepository(db_session).save(_operation()) repo = SqlCallbackRegistrationRepository(db_session) Loading
tests/unit/fakes.py +6 −1 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ from open_exposure_gateway.domain.edge_application_management import ( ) from open_exposure_gateway.domain.models import ( AppInstance, AppInstanceState, AppRegistration, CallbackDelivery, CallbackRegistration, Loading Loading @@ -356,7 +357,11 @@ class FakeAppInstanceRepository(AppInstanceRepository): return None async def exists_for_app_registration(self, app_registration_id: UUID) -> bool: return any(row.app_registration_id == app_registration_id for row in self.rows.values()) terminal_states = (AppInstanceState.FAILED, AppInstanceState.TERMINATED) return any( row.app_registration_id == app_registration_id and row.state not in terminal_states for row in self.rows.values() ) async def save(self, app_instance: AppInstance) -> AppInstance: stored = app_instance.model_copy(deep=True) Loading
tests/unit/test_eam_service.py +36 −0 Original line number Diff line number Diff line Loading @@ -548,6 +548,42 @@ class TestDeleteApp: srm_client.delete_app.assert_not_called() assert await app_registration_repo.get_by_app_id(APP_ID) is not None @pytest.mark.parametrize("state", [AppInstanceState.FAILED, AppInstanceState.TERMINATED]) async def test_allows_delete_when_app_only_has_instances_in_a_terminal_state( self, state: AppInstanceState, service: EdgeApplicationManagementService, srm_client: AsyncMock, app_registration_repo: FakeAppRegistrationRepository, app_instance_repo: FakeAppInstanceRepository, ) -> None: app_registration_id = uuid4() await app_registration_repo.save( AppRegistration( app_registration_id=app_registration_id, app_id=APP_ID, tenant_id="tenant-1", name="myvideoapp", version="1.0.0", package_type=PackageType.HELM, status=AppRegistrationStatus.REGISTERED, ) ) await app_instance_repo.save( AppInstance( app_instance_id=INSTANCE_ID, operation_id=uuid4(), app_registration_id=app_registration_id, edge_cloud_zone_id=ZONE_ID, state=state, ) ) await service.delete_app(app_id=APP_ID, app_provider_id="VideoAppsCo") srm_client.delete_app.assert_called_once() assert await app_registration_repo.get_by_app_id(APP_ID) is None class TestCreateAppInstance: def _make_request(self) -> CreateAppInstanceRequest: Loading