test(BA-4898): add unit service and component tests for Deployment and ModelServing#9698
test(BA-4898): add unit service and component tests for Deployment and ModelServing#9698HyeockJinKim merged 5 commits intomainfrom
Conversation
Add test_deployment_crud_actions.py covering: - CreateLegacyDeploymentAction (draft returns info, CHECK_PENDING marking, domain error) - DestroyDeploymentAction (success with DESTROYING, not found, idempotent) - GetReplicaByIdAction (existing data, none for missing, zero-weight inactive) - SearchReplicasAction (pagination, empty result, next page) - SearchAccessTokensAction (token list, empty list) - SyncReplicaAction (CHECK_REPLICA marking, already synced) - GetRevisionByIdAction (existing data, not found) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add test_model_serving_crud_actions.py with 11 test cases covering: - ModifyEndpointAction: replica count change triggers CHECK_REPLICA, no change skips marking, non-existent endpoint raises error - ForceSyncAction: valid service notifies appproxy, non-existent service raises ModelServiceNotFound, non-owner raises EndpointAccessForbiddenError - DeleteRouteAction: HEALTHY route deletion with session destruction, PROVISIONING raises InvalidAPIParameters, sessionless route skips destroy_session, non-existent route raises RouteNotFound, non-owner raises EndpointAccessForbiddenError Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…K methods - tests/component/deployment/test_deployment_lifecycle.py: SDK client lifecycle tests (create, update/scale, search routes, destroy, get revision, role-based access) - tests/component/model_serving/test_model_serving_routes.py: SDK client route tests (create, scale, sync, generate token, delete, delete route) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds new unit and component test coverage for Deployment and ModelServing service actions / SDK routes.
Changes:
- Added mock-based unit tests for DeploymentService CRUD/replica/revision actions.
- Added mock-based unit tests for ModelServingService actions (modify endpoint, force sync, delete route).
- Added component tests validating SDK route wiring/serialization and common error cases for Deployment and ModelServing.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/manager/services/model_serving/actions/test_model_serving_crud_actions.py | Adds unit-level action tests for ModelServing processors/service behavior. |
| tests/unit/manager/services/deployment/test_deployment_crud_actions.py | Adds unit-level action tests for Deployment processors/service behavior. |
| tests/component/model_serving/test_model_serving_routes.py | Adds SDK-level component tests for legacy model serving client routes. |
| tests/component/deployment/test_deployment_lifecycle.py | Adds SDK-level component tests for deployment lifecycle endpoints. |
| changes/9698.test.md | Adds a changelog entry documenting the test additions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async def test_already_destroying_handled_idempotently( | ||
| self, | ||
| processors: DeploymentProcessors, | ||
| mock_deployment_repository: MagicMock, | ||
| mock_deployment_controller: MagicMock, | ||
| endpoint_info: DeploymentInfo, | ||
| endpoint_id: uuid.UUID, | ||
| ) -> None: | ||
| """Already DESTROYING state handled idempotently.""" | ||
| mock_deployment_repository.get_endpoint_info = AsyncMock(return_value=endpoint_info) | ||
| mock_deployment_controller.destroy_deployment = AsyncMock(return_value=True) | ||
| mock_deployment_controller.mark_lifecycle_needed = AsyncMock() |
There was a problem hiding this comment.
This test claims to cover the 'already DESTROYING' lifecycle, but endpoint_info comes from the shared fixture with lifecycle set to EndpointLifecycle.READY. As written, it doesn't exercise the intended branch. Set endpoint_info.state.lifecycle to the DESTROYING value (or create a dedicated fixture for a DESTROYING endpoint) before calling the action.
| ), | ||
| ), | ||
| ) | ||
| with pytest.raises(Exception): |
There was a problem hiding this comment.
Catching Exception here makes the test less precise and may hide unexpected failures (e.g., client-side validation errors vs server/API errors). Prefer asserting the specific expected SDK exception type (similar to other tests in this file using NotFoundError), and/or assert on the error payload/message if available.
| with pytest.raises(Exception): | |
| with pytest.raises(NotFoundError): |
| async def test_triggers_check_replica_marking( | ||
| self, | ||
| processors: DeploymentProcessors, | ||
| mock_deployment_controller: MagicMock, | ||
| deployment_id: uuid.UUID, | ||
| ) -> None: | ||
| """Replica count mismatch triggers CHECK_REPLICA marking.""" | ||
| mock_deployment_controller.mark_lifecycle_needed = AsyncMock() | ||
|
|
||
| action = SyncReplicaAction(deployment_id=deployment_id) | ||
| result = await processors.sync_replicas.wait_for_complete(action) | ||
|
|
||
| assert result.success is True | ||
| mock_deployment_controller.mark_lifecycle_needed.assert_called_once_with( | ||
| DeploymentLifecycleType.CHECK_REPLICA | ||
| ) | ||
|
|
||
| async def test_already_synced_still_marks( | ||
| self, | ||
| processors: DeploymentProcessors, | ||
| mock_deployment_controller: MagicMock, | ||
| deployment_id: uuid.UUID, | ||
| ) -> None: | ||
| """Already synced state still performs marking.""" | ||
| mock_deployment_controller.mark_lifecycle_needed = AsyncMock() | ||
|
|
||
| action = SyncReplicaAction(deployment_id=deployment_id) | ||
| result = await processors.sync_replicas.wait_for_complete(action) | ||
|
|
||
| assert result.success is True | ||
| mock_deployment_controller.mark_lifecycle_needed.assert_called_once_with( | ||
| DeploymentLifecycleType.CHECK_REPLICA | ||
| ) |
There was a problem hiding this comment.
These two tests are currently identical in setup and assertions, so they don't validate different behavior. Consider parametrizing a single test (or adding distinct setup/assertions that actually differentiate 'mismatch' vs 'already synced' scenarios) to avoid duplication and improve signal.
| async def test_valid_service_returns_success( | ||
| self, | ||
| model_serving_processors: ModelServingProcessors, | ||
| mock_check_user_access: AsyncMock, | ||
| mock_get_endpoint_access_validation_data: AsyncMock, | ||
| mock_notify_appproxy: AsyncMock, | ||
| user_data: UserData, | ||
| service_id: uuid.UUID, | ||
| ) -> None: | ||
| """Valid service_id returns success=true with notify called once.""" | ||
| mock_get_endpoint_access_validation_data.return_value = self._make_validation_data( | ||
| user_data | ||
| ) |
There was a problem hiding this comment.
This test injects mock_check_user_access (so the patch is active) but never asserts it was awaited. Adding an assert_awaited() / assert_awaited_once() expectation would make the test actually enforce that the access check happens (and prevent regressions where authorization is accidentally skipped).
| mock_deployment_controller: MagicMock, | ||
| endpoint_id: uuid.UUID, | ||
| ) -> None: | ||
| """replica_count change (2->5) returns success=true with CHECK_REPLICA marking.""" |
There was a problem hiding this comment.
The docstring claims a '(2->5)' replica transition, but the test setup doesn't establish an initial replica count anywhere—only that the updater spec marks the replica count as modified. Consider rewording the docstring to match what the test actually verifies (e.g., 'replica_count modified triggers CHECK_REPLICA marking').
| """replica_count change (2->5) returns success=true with CHECK_REPLICA marking.""" | |
| """replica_count marked as modified returns success=true with CHECK_REPLICA marking.""" |
…tructor Use `replicas`, `group_name`, `domain_name` (actual Pydantic field names) instead of `desired_session_count`, `group`, `domain` (validation aliases that only work during deserialization, not direct construction). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Test plan
pants test tests/unit/manager/services/deployment/test_deployment_crud_actions.pypassespants test tests/unit/manager/services/model_serving/actions/test_model_serving_crud_actions.pypassespants test tests/component/deployment/test_deployment_lifecycle.pypassespants test tests/component/model_serving/test_model_serving_routes.pypassesResolves BA-4898
🤖 Generated with Claude Code