Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ENHANCEMENTS:
* Make workspace shared storage quota updateable ([#4314](https://github.com/microsoft/AzureTRE/issues/4314))

BUG FIXES:
* Fix deleted workspaces still accessible via URL - get_*_by_id methods now filter out deleted resources ([#4785](https://github.com/microsoft/AzureTRE/issues/4785))
* Fix circular dependancy in base workspace. ([#4756](https://github.com/microsoft/AzureTRE/pull/4756))
* Replaced deprecated `datetime.utcnow()` with `datetime.now(datetime.UTC)` in the API and airlock processor. ([#4743](https://github.com/microsoft/AzureTRE/issues/4743))
* Updated error messages when publishing a template version that is lower than the existing version. ([#4685](https://github.com/microsoft/AzureTRE/issues/4685))
Expand All @@ -23,6 +24,7 @@ BUG FIXES:
* Fix missing metastoreDomains for Databricks, which caused metastore outages for some domains ([#4779](https://github.com/microsoft/AzureTRE/issues/4779))

COMPONENTS:
* API version updated to 0.25.7 ([#4785](https://github.com/microsoft/AzureTRE/issues/4785))

## 0.26.0 (October 12, 2025)
**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.6"
__version__ = "0.25.7"
2 changes: 2 additions & 0 deletions api_app/db/repositories/shared_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ def active_shared_service_with_template_name_query(template_name: str):

async def get_shared_service_by_id(self, shared_service_id: str):
query, parameters = self.shared_service_query(str(shared_service_id))
query += ' AND c.deploymentStatus != @deletedStatus'
parameters.append({'name': '@deletedStatus', 'value': Status.Deleted})
shared_services = await self.query(query=query, parameters=parameters)
if not shared_services:
raise EntityDoesNotExist
Expand Down
3 changes: 2 additions & 1 deletion api_app/db/repositories/user_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ async def get_user_resources_for_workspace_service(self, workspace_id: str, serv

async def get_user_resource_by_id(self, workspace_id: str, service_id: str, resource_id: str) -> UserResource:
query, parameters = self.user_resources_query(str(workspace_id), str(service_id))
query += ' AND c.id = @resourceId'
query += ' AND c.id = @resourceId AND c.deploymentStatus != @deletedStatus'
parameters.append({'name': '@resourceId', 'value': str(resource_id)})
parameters.append({'name': '@deletedStatus', 'value': Status.Deleted})
user_resources = await self.query(query=query, parameters=parameters)
if not user_resources:
raise EntityDoesNotExist
Expand Down
3 changes: 2 additions & 1 deletion api_app/db/repositories/workspace_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ async def get_deployed_workspace_service_by_id(self, workspace_id: str, service_

async def get_workspace_service_by_id(self, workspace_id: str, service_id: str) -> WorkspaceService:
query, parameters = self.workspace_services_query(str(workspace_id))
query += ' AND c.id = @serviceId'
query += ' AND c.id = @serviceId AND c.deploymentStatus != @deletedStatus'
parameters.append({'name': '@serviceId', 'value': str(service_id)})
parameters.append({'name': '@deletedStatus', 'value': Status.Deleted})
workspace_services = await self.query(query=query, parameters=parameters)
if not workspace_services:
raise EntityDoesNotExist
Expand Down
3 changes: 2 additions & 1 deletion api_app/db/repositories/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ async def get_deployed_workspace_by_id(self, workspace_id: str, operations_repo:

async def get_workspace_by_id(self, workspace_id: str) -> Workspace:
query, parameters = self.workspaces_query_string()
query += ' AND c.id = @workspaceId'
query += ' AND c.id = @workspaceId AND c.deploymentStatus != @deletedStatus'
parameters.append({'name': '@workspaceId', 'value': str(workspace_id)})
parameters.append({'name': '@deletedStatus', 'value': Status.Deleted})
workspaces = await self.query(query=query, parameters=parameters)
if not workspaces:
raise EntityDoesNotExist
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ async def test_get_shared_service_by_id_raises_if_does_not_exist(shared_service_
await shared_service_repo.get_shared_service_by_id(SHARED_SERVICE_ID)


async def test_get_shared_service_by_id_raises_if_deleted(shared_service_repo):
shared_service_repo.query = AsyncMock(return_value=[])

with pytest.raises(EntityDoesNotExist):
await shared_service_repo.get_shared_service_by_id(SHARED_SERVICE_ID)


async def test_get_active_shared_services_for_shared_queries_db(shared_service_repo):
shared_service_repo.query = AsyncMock(return_value=[])
query, parameters = SharedServiceRepository.active_shared_services_query()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,13 @@ async def test_get_user_resource_returns_resource_if_found(query_mock, user_reso
@patch('db.repositories.user_resources.UserResourceRepository.query')
async def test_get_user_resource_by_id_queries_db(query_mock, user_resource_repo, user_resource):
query_mock.return_value = [user_resource.dict()]
expected_query = 'SELECT * FROM c WHERE c.resourceType = @resourceType AND c.parentWorkspaceServiceId = @serviceId AND c.workspaceId = @workspaceId AND c.id = @resourceId'
expected_query = 'SELECT * FROM c WHERE c.resourceType = @resourceType AND c.parentWorkspaceServiceId = @serviceId AND c.workspaceId = @workspaceId AND c.id = @resourceId AND c.deploymentStatus != @deletedStatus'
expected_parameters = [
{'name': '@resourceType', 'value': ResourceType.UserResource},
{'name': '@serviceId', 'value': SERVICE_ID},
{'name': '@workspaceId', 'value': WORKSPACE_ID},
{'name': '@resourceId', 'value': RESOURCE_ID}
{'name': '@resourceId', 'value': RESOURCE_ID},
{'name': '@deletedStatus', 'value': Status.Deleted}
]

await user_resource_repo.get_user_resource_by_id(WORKSPACE_ID, SERVICE_ID, RESOURCE_ID)
Expand All @@ -112,3 +113,9 @@ async def test_get_user_resource_by_id_queries_db(query_mock, user_resource_repo
async def test_get_user_resource_by_id_raises_entity_does_not_exist_if_not_found(_, user_resource_repo):
with pytest.raises(EntityDoesNotExist):
await user_resource_repo.get_user_resource_by_id(WORKSPACE_ID, SERVICE_ID, RESOURCE_ID)


@patch('db.repositories.user_resources.UserResourceRepository.query', return_value=[])
async def test_get_user_resource_by_id_raises_entity_does_not_exist_if_resource_is_deleted(_, user_resource_repo):
with pytest.raises(EntityDoesNotExist):
await user_resource_repo.get_user_resource_by_id(WORKSPACE_ID, SERVICE_ID, RESOURCE_ID)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from db.errors import EntityDoesNotExist, InvalidInput, ResourceIsNotDeployed
from db.repositories.operations import OperationRepository
from db.repositories.workspaces import WorkspaceRepository
from models.domain.operation import Status
from models.domain.resource import ResourceType
from models.domain.workspace import Workspace
from models.schemas.workspace import WorkspaceInCreate
Expand Down Expand Up @@ -84,15 +85,27 @@ async def test_get_workspace_by_id_raises_entity_does_not_exist_if_item_does_not
await workspace_repo.get_workspace_by_id(workspace_id)


@pytest.mark.asyncio
async def test_get_workspace_by_id_raises_entity_does_not_exist_if_workspace_is_deleted(workspace_repo, workspace):
workspace_id = workspace.id
workspace_repo.container.query_items = MagicMock()
workspace_repo.container.query_items.return_value = AsyncMock()
workspace_repo.container.query_items.return_value.__aiter__.return_value = []

with pytest.raises(EntityDoesNotExist):
await workspace_repo.get_workspace_by_id(workspace_id)


@pytest.mark.asyncio
async def test_get_workspace_by_id_queries_db(workspace_repo, workspace):
workspace_query_item_result = AsyncMock()
workspace_query_item_result.__aiter__.return_value = [workspace.dict()]
workspace_repo.container.query_items = MagicMock(return_value=workspace_query_item_result)
expected_query = 'SELECT * FROM c WHERE c.resourceType = @resourceType AND c.id = @workspaceId'
expected_query = 'SELECT * FROM c WHERE c.resourceType = @resourceType AND c.id = @workspaceId AND c.deploymentStatus != @deletedStatus'
expected_parameters = [
{'name': '@resourceType', 'value': ResourceType.Workspace},
{'name': '@workspaceId', 'value': workspace.id}
{'name': '@workspaceId', 'value': workspace.id},
{'name': '@deletedStatus', 'value': Status.Deleted}
]

await workspace_repo.get_workspace_by_id(workspace.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from db.errors import EntityDoesNotExist, ResourceIsNotDeployed
from db.repositories.workspace_services import WorkspaceServiceRepository
from db.repositories.operations import OperationRepository
from models.domain.operation import Status
from models.domain.resource import ResourceType
from models.domain.workspace_service import WorkspaceService
from models.schemas.workspace_service import WorkspaceServiceInCreate
Expand Down Expand Up @@ -85,13 +86,21 @@ async def test_get_workspace_service_by_id_raises_entity_does_not_exist_if_no_av
await workspace_service_repo.get_workspace_service_by_id(WORKSPACE_ID, SERVICE_ID)


async def test_get_workspace_service_by_id_raises_entity_does_not_exist_if_service_is_deleted(workspace_service_repo):
workspace_service_repo.query = AsyncMock(return_value=[])

with pytest.raises(EntityDoesNotExist):
await workspace_service_repo.get_workspace_service_by_id(WORKSPACE_ID, SERVICE_ID)


async def test_get_workspace_service_by_id_queries_db(workspace_service_repo, workspace_service):
workspace_service_repo.query = AsyncMock(return_value=[workspace_service])
expected_query = 'SELECT * FROM c WHERE c.resourceType = @resourceType AND c.workspaceId = @workspaceId AND c.id = @serviceId'
expected_query = 'SELECT * FROM c WHERE c.resourceType = @resourceType AND c.workspaceId = @workspaceId AND c.id = @serviceId AND c.deploymentStatus != @deletedStatus'
expected_parameters = [
{'name': '@resourceType', 'value': ResourceType.WorkspaceService},
{'name': '@workspaceId', 'value': WORKSPACE_ID},
{'name': '@serviceId', 'value': SERVICE_ID}
{'name': '@serviceId', 'value': SERVICE_ID},
{'name': '@deletedStatus', 'value': Status.Deleted}
]

await workspace_service_repo.get_workspace_service_by_id(WORKSPACE_ID, SERVICE_ID)
Expand Down
Loading