Fix health check query not executed - #4927
Conversation
* Health check can falsely return OK even if Cosmos is down or inaccessible. ([microsoft#4926](microsoft#4926))
There was a problem hiding this comment.
Pull request overview
This pull request fixes the Cosmos DB health check so it actually executes a query (preventing false “OK” results when Cosmos is unreachable), and updates the API’s unit tests, changelog, and version accordingly.
Changes:
- Execute the Cosmos query by iterating the async result (
async for ... break) withmax_item_count=1. - Add unit tests covering Cosmos HTTP errors and query-time request errors.
- Update
CHANGELOG.mdand bumpapi_appversion.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
api_app/services/health_checker.py |
Forces Cosmos query execution during health check and maps CosmosHttpResponseError to “not accessible”. |
api_app/tests_ma/test_services/test_health_checker.py |
Adds tests for Cosmos HTTP/query-time failures (but one existing “responding” test needs updating to reflect the new iteration behavior). |
CHANGELOG.md |
Documents the bug fix under Unreleased “BUG FIXES”. |
api_app/_version.py |
Bumps API version from 0.25.16 to 0.25.17. |
Unit Test Results727 tests 727 ✅ 10s ⏱️ Results for commit 0056beb. ♻️ This comment has been updated with latest results. |
api.dependencies.database.Database.
get_container_proxy instead of azure.cosmos.aio.ContainerProxy.query_items .
• Configured the return value of get_container_proxy_mock to be a mock container whose
query_items function returns a real async iterator: AsyncIterator([{"id": "item"}]) .
• Verified that all unit tests pass, and successfully ran the entire test suite (all 675 tests
passed).
|
/test |
|
🤖 pr-bot 🤖 🏃 Running tests: https://github.com/microsoft/AzureTRE/actions/runs/28439249321 (with refid (in response to this comment from Jack Morris (@rudolphjacksonm)) |
Marcus Robinson (marrobi)
left a comment
There was a problem hiding this comment.
From Opus 4.8:
Blocking concerns: (1) create_state_store_status() still has ambiguous success semantics—ok is returned whenever iteration does not raise, including the zero-result path, which can mask real Cosmos connectivity/auth/partition misconfiguration depending on SDK behavior; please make success depend on a definitive successful probe operation (not just “no exception while iterating”) and add an explicit test for empty results.
(2) The new tests rely on custom async iterator doubles + MagicMock that may drift from actual azure.cosmos.aio.ContainerProxy.query_items behavior and create false confidence across SDK changes; please tighten fidelity (e.g., AsyncMock/shared fixture with realistic async iterable behavior) and assert the exact query_items call contract ("SELECT TOP 1 * FROM c", max_item_count=1) so regressions are caught.
In health_checker.py: • Added an explicit await container.read() call before iterating over container.query_items . 2. High-Fidelity Mocking & Exact Call Contract AssertionsIn test_health_checker.py: • Replaced the custom class-based async iterator double ( AsyncIteratorWithError ) with Python's built-in AsyncMock to configure asynchronous iteration. This uses native mocking behavior ( |
| await container.read() | ||
| async for _ in container.query_items("SELECT TOP 1 * FROM c", max_item_count=1): | ||
| break |
There was a problem hiding this comment.
This extra read was added in response to opus feedback to "make success depend on a definitive successful probe operation".
implementation with await container.read() + query_items("SELECT TOP 1 * FROM c") is the only approach that fully satisfies the Opus 4.8 code review. It ensures we have a definitive probe that prevents masked failures, while still allowing the health check to succeed on an, empty database.
There was a problem hiding this comment.
Wonder what the RU consumption would be related to this... how often does it run etc.
There was a problem hiding this comment.
Opus:
The extra await container.read() isn't needed to satisfy the "definitive probe" requirement — iterating the query (async for ... break) already forces a network round-trip to Cosmos, and any failure surfaces as ServiceRequestError/CosmosHttpResponseError and is caught below. An empty container is still a successful probe (the request goes out; the loop body just doesn't run), so read() adds no correctness value.
It does add cost: an extra round-trip on every poll and potentially broader RBAC requirements, which could fail the check for permission reasons unrelated to actual availability. I'd drop read() and use a minimal projection:
async for _ in container.query_items("SELECT TOP 1 c.id FROM c", max_item_count=1):
breakThe unit tests will need updating to match.
There was a problem hiding this comment.
The costs were only pennies but I'm happy to revert it if Opus has now changed it's mind
There was a problem hiding this comment.
Leave it to you to decide.
There was a problem hiding this comment.
reverted
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
api_app/tests_ma/test_services/test_health_checker.py:23
create_mock_containerconfiguresquery_items_mock.return_value.__aiter__.return_valuewith a plain list on aMagicMockreturn value.async forexpects an async iterator (__anext__), so this setup will raiseTypeErrorwhencreate_state_store_status()iterates the results. Use anAsyncMock(or a small async-iterator helper) as thequery_items()return value so async iteration works.
query_items_mock = MagicMock()
if query_error:
query_items_mock.return_value.__aiter__.side_effect = query_error
else:
query_items_mock.return_value.__aiter__.return_value = query_results or []
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
api_app/tests_ma/test_services/test_health_checker.py:23
container.query_items()returns an async iterable; in these tests the mockedquery_items()currently returns a plainMagicMock, which can behave differently from an async iterable and is inconsistent with other repo tests (they typically return anAsyncMockwith__aiter__). Makingquery_items_mockreturn anAsyncMockwill better match the Cosmos SDK contract and avoid brittle async-iteration behavior.
query_items_mock = MagicMock()
if query_error:
query_items_mock.return_value.__aiter__.side_effect = query_error
else:
query_items_mock.return_value.__aiter__.return_value = query_results or []
There was a problem hiding this comment.
🟢 Ready to approve
The fix is small, directly addresses the reported lazy-query issue, and includes targeted unit test coverage for key success and failure paths.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (2)
api_app/tests_ma/test_services/test_health_checker.py:32
- The new health-check query is
SELECT TOP 1 VALUE 1 ..., so the mocked query result should be a scalar (e.g.,1) rather than a dict to match the actual SDK return shape and keep the test intent clear.
container_mock = create_mock_container(query_results=[{"id": "item"}])
get_container_proxy_mock.return_value = container_mock
CHANGELOG.md:20
- Changelog bullet uses inconsistent capitalization for a generic term; other entries use sentence case after “Fix”, so “health check” should not be capitalized.
* Fix Health check can falsely return OK even if Cosmos is down or inaccessible. ([#4926](https://github.com/microsoft/AzureTRE/issues/4926))
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Resolves #4926
What is being addressed
The existing code
container.query_items("SELECT TOP 1 * FROM c")does not actually execute the query so the health check can falsely return OK even if Cosmos is down or inaccessible.How is this addressed