Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ENHANCEMENTS:

BUG FIXES:
* Fix error handling in airlock processor ([#4929](https://github.com/microsoft/AzureTRE/pull/4929))
* Fix Health check can falsely return OK even if Cosmos is down or inaccessible. ([#4926](https://github.com/microsoft/AzureTRE/issues/4926))

## (0.28.0) (March 2, 2026)
**BREAKING CHANGES**
Expand Down
2 changes: 1 addition & 1 deletion api_app/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.25.16"
__version__ = "0.25.17"
3 changes: 2 additions & 1 deletion api_app/services/health_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ async def create_state_store_status() -> Tuple[StatusEnum, str]:
message = ""
try:
container: ContainerProxy = await Database().get_container_proxy(STATE_STORE_RESOURCES_CONTAINER)
container.query_items("SELECT TOP 1 * FROM c")
async for _ in container.query_items("SELECT TOP 1 * FROM c", max_item_count=1):
break
except exceptions.ServiceRequestError:
status = StatusEnum.not_ok
message = strings.STATE_STORE_ENDPOINT_NOT_RESPONDING
Expand Down
69 changes: 53 additions & 16 deletions api_app/tests_ma/test_services/test_health_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from unittest.mock import AsyncMock, MagicMock
import pytest
from azure.core.exceptions import ServiceRequestError
from azure.cosmos.exceptions import CosmosHttpResponseError
from azure.servicebus.exceptions import ServiceBusConnectionError
from mock import patch
from models.schemas.status import StatusEnum
Expand All @@ -11,8 +12,36 @@
pytestmark = pytest.mark.asyncio


@patch("azure.cosmos.aio.ContainerProxy.query_items", return_value=AsyncMock())
async def test_get_state_store_status_responding(_) -> None:
class AsyncIterator:
def __init__(self, seq):
self.iter = iter(seq)

def __aiter__(self):
return self

async def __anext__(self):
try:
return next(self.iter)
except StopIteration:
raise StopAsyncIteration


class AsyncIteratorWithError:
def __init__(self, exception):
self.exception = exception

def __aiter__(self):
return self

async def __anext__(self):
raise self.exception


@patch("api.dependencies.database.Database.get_container_proxy")
async def test_get_state_store_status_responding(get_container_proxy_mock) -> None:
container_mock = MagicMock()
container_mock.query_items.return_value = AsyncIterator([{"id": "item"}])
get_container_proxy_mock.return_value = container_mock
status, message = await health_checker.create_state_store_status()

assert status == StatusEnum.ok
Expand All @@ -39,6 +68,28 @@ async def test_get_state_store_status_other_exception(container_proxy_mock) -> N
assert message == strings.UNSPECIFIED_ERROR


@patch("api.dependencies.database.Database.get_container_proxy")
async def test_get_state_store_status_cosmos_http_error(get_container_proxy_mock) -> None:
container_mock = MagicMock()
container_mock.query_items.return_value = AsyncIteratorWithError(CosmosHttpResponseError(message="some message"))
get_container_proxy_mock.return_value = container_mock
status, message = await health_checker.create_state_store_status()

assert status == StatusEnum.not_ok
assert message == strings.STATE_STORE_ENDPOINT_NOT_ACCESSIBLE


@patch("api.dependencies.database.Database.get_container_proxy")
async def test_get_state_store_status_service_request_error(get_container_proxy_mock) -> None:
container_mock = MagicMock()
container_mock.query_items.return_value = AsyncIteratorWithError(ServiceRequestError(message="some message"))
get_container_proxy_mock.return_value = container_mock
status, message = await health_checker.create_state_store_status()

assert status == StatusEnum.not_ok
assert message == strings.STATE_STORE_ENDPOINT_NOT_RESPONDING


@patch("core.credentials.get_credential_async_context")
@patch("services.health_checker.ServiceBusClient")
async def test_get_service_bus_status_responding(service_bus_client_mock, get_credential_async_context) -> None:
Expand Down Expand Up @@ -127,17 +178,3 @@ async def test_get_resource_processor_status_other_exception(resource_processor_

assert status == StatusEnum.not_ok
assert message == strings.UNSPECIFIED_ERROR


class AsyncIterator:
def __init__(self, seq):
self.iter = iter(seq)

def __aiter__(self):
return self

async def __anext__(self):
try:
return next(self.iter)
except StopIteration:
raise StopAsyncIteration