From edadb75783741b066b2faaf4235c58711c1248a7 Mon Sep 17 00:00:00 2001 From: Marcus Robinson Date: Fri, 7 Feb 2025 21:27:21 +0000 Subject: [PATCH 1/4] Add validation to Template API endpoints Fixes #1123 Add validation to API endpoints to ensure correct resourceType in template registration payloads. * **Workspace Service Templates**: Add validation in `api_app/api/routes/workspace_service_templates.py` to check if `resourceType` is `WorkspaceService` before registering the template. Return a 422 Unprocessable Entity error if the `resourceType` does not match. * **Workspace Templates**: Add validation in `api_app/api/routes/workspace_templates.py` to check if `resourceType` is `Workspace` before registering the template. Return a 422 Unprocessable Entity error if the `resourceType` does not match. * **User Resource Templates**: Add validation in `api_app/api/routes/user_resource_templates.py` to check if `resourceType` is `UserResource` before registering the template. Return a 422 Unprocessable Entity error if the `resourceType` does not match. * **Shared Service Templates**: Add validation in `api_app/api/routes/shared_service_templates.py` to check if `resourceType` is `SharedService` before registering the template. Return a 422 Unprocessable Entity error if the `resourceType` does not match. * **Common Error Message**: Add a common error message string `INVALID_RESOURCE_TYPE` in `api_app/resources/strings.py` for validation errors, including the expected and received resourceType. * **Tests**: Add tests in `api_app/tests_ma/test_api/test_routes/test_workspace_service_templates.py`, `api_app/tests_ma/test_api/test_routes/test_workspace_templates.py`, `api_app/tests_ma/test_api/test_routes/test_user_resource_templates.py`, and `api_app/tests_ma/test_api/test_routes/test_shared_service_templates.py` to cover validation for each template registration --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/microsoft/AzureTRE/issues/1123?shareId=XXXX-XXXX-XXXX-XXXX). --- .../api/routes/shared_service_templates.py | 2 ++ api_app/api/routes/user_resource_templates.py | 3 ++- .../api/routes/workspace_service_templates.py | 2 ++ api_app/api/routes/workspace_templates.py | 2 ++ api_app/resources/strings.py | 3 +++ .../test_shared_service_templates.py | 19 +++++++++++++++++++ .../test_user_resource_templates.py | 19 +++++++++++++++++++ .../test_workspace_service_templates.py | 19 +++++++++++++++++++ .../test_routes/test_workspace_templates.py | 19 +++++++++++++++++++ 9 files changed, 87 insertions(+), 1 deletion(-) diff --git a/api_app/api/routes/shared_service_templates.py b/api_app/api/routes/shared_service_templates.py index b7801c3789..e8eaef5cbf 100644 --- a/api_app/api/routes/shared_service_templates.py +++ b/api_app/api/routes/shared_service_templates.py @@ -33,6 +33,8 @@ async def get_shared_service_template(shared_service_template_name: str, is_upda @shared_service_templates_core_router.post("/shared-service-templates", status_code=status.HTTP_201_CREATED, response_model=SharedServiceTemplateInResponse, response_model_exclude_none=True, name=strings.API_CREATE_SHARED_SERVICE_TEMPLATES, dependencies=[Depends(get_current_admin_user)]) async def register_shared_service_template(template_input: SharedServiceTemplateInCreate, template_repo=Depends(get_repository(ResourceTemplateRepository))) -> ResourceTemplateInResponse: + if template_input.resourceType != ResourceType.SharedService: + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format('SharedService', template_input.resourceType)) try: return await template_repo.create_and_validate_template(template_input, ResourceType.SharedService) except EntityVersionExist: diff --git a/api_app/api/routes/user_resource_templates.py b/api_app/api/routes/user_resource_templates.py index f4cb24cb5f..6e7916426b 100644 --- a/api_app/api/routes/user_resource_templates.py +++ b/api_app/api/routes/user_resource_templates.py @@ -1,4 +1,3 @@ - from typing import Optional from fastapi import APIRouter, Depends, HTTPException, status from pydantic import parse_obj_as @@ -32,6 +31,8 @@ async def get_user_resource_template(service_template_name: str, user_resource_t @user_resource_templates_core_router.post("/workspace-service-templates/{service_template_name}/user-resource-templates", status_code=status.HTTP_201_CREATED, response_model=UserResourceTemplateInResponse, response_model_exclude_none=True, name=strings.API_CREATE_USER_RESOURCE_TEMPLATES, dependencies=[Depends(get_current_admin_user)]) async def register_user_resource_template(template_input: UserResourceTemplateInCreate, template_repo=Depends(get_repository(ResourceTemplateRepository)), workspace_service_template=Depends(get_workspace_service_template_by_name_from_path)) -> UserResourceTemplateInResponse: + if template_input.resourceType != ResourceType.UserResource: + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format('UserResource', template_input.resourceType)) try: return await template_repo.create_and_validate_template(template_input, ResourceType.UserResource, workspace_service_template.name) except EntityVersionExist: diff --git a/api_app/api/routes/workspace_service_templates.py b/api_app/api/routes/workspace_service_templates.py index e6df3fadba..58aa608217 100644 --- a/api_app/api/routes/workspace_service_templates.py +++ b/api_app/api/routes/workspace_service_templates.py @@ -30,6 +30,8 @@ async def get_workspace_service_template(service_template_name: str, is_update: @workspace_service_templates_core_router.post("/workspace-service-templates", status_code=status.HTTP_201_CREATED, response_model=WorkspaceServiceTemplateInResponse, response_model_exclude_none=True, name=strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES, dependencies=[Depends(get_current_admin_user)]) async def register_workspace_service_template(template_input: WorkspaceServiceTemplateInCreate, template_repo=Depends(get_repository(ResourceTemplateRepository))) -> ResourceTemplateInResponse: + if template_input.resourceType != ResourceType.WorkspaceService: + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format('WorkspaceService', template_input.resourceType)) try: return await template_repo.create_and_validate_template(template_input, ResourceType.WorkspaceService) except EntityVersionExist: diff --git a/api_app/api/routes/workspace_templates.py b/api_app/api/routes/workspace_templates.py index c61b6f7e82..a5eabe3c96 100644 --- a/api_app/api/routes/workspace_templates.py +++ b/api_app/api/routes/workspace_templates.py @@ -30,6 +30,8 @@ async def get_workspace_template(workspace_template_name: str, is_update: bool = @workspace_templates_admin_router.post("/workspace-templates", status_code=status.HTTP_201_CREATED, response_model=WorkspaceTemplateInResponse, response_model_exclude_none=True, name=strings.API_CREATE_WORKSPACE_TEMPLATES) async def register_workspace_template(template_input: WorkspaceTemplateInCreate, template_repo=Depends(get_repository(ResourceTemplateRepository))) -> ResourceTemplateInResponse: + if template_input.resourceType != ResourceType.Workspace: + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format('Workspace', template_input.resourceType)) try: return await template_repo.create_and_validate_template(template_input, ResourceType.Workspace) except EntityVersionExist: diff --git a/api_app/resources/strings.py b/api_app/resources/strings.py index a9184cf289..ccd842df11 100644 --- a/api_app/resources/strings.py +++ b/api_app/resources/strings.py @@ -254,3 +254,6 @@ # Value that a sensitive is replaced with in Cosmos REDACTED_SENSITIVE_VALUE = "REDACTED" + +# Common error message for invalid resource type +INVALID_RESOURCE_TYPE = "Invalid resourceType. Expected '{}'. Received '{}'." diff --git a/api_app/tests_ma/test_api/test_routes/test_shared_service_templates.py b/api_app/tests_ma/test_api/test_routes/test_shared_service_templates.py index ed09dbd60a..e111a0c817 100644 --- a/api_app/tests_ma/test_api/test_routes/test_shared_service_templates.py +++ b/api_app/tests_ma/test_api/test_routes/test_shared_service_templates.py @@ -112,3 +112,22 @@ async def test_creating_a_shared_service_template_raises_http_422_if_step_ids_ar response = await client.post(app.url_path_for(strings.API_CREATE_SHARED_SERVICE_TEMPLATES), json=input_shared_service_template.dict()) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + + # POST /shared-service-templates + async def test_post_shared_service_template_with_invalid_resource_type(self, app, client): + input_data = { + "name": "invalid-template", + "description": "Invalid template", + "version": "0.1.0", + "resourceType": "InvalidType", + "current": True, + "type": "object", + "required": [], + "properties": {}, + "actions": [] + } + + response = await client.post(app.url_path_for(strings.API_CREATE_SHARED_SERVICE_TEMPLATES), json=input_data) + + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + assert response.json()["detail"] == strings.INVALID_RESOURCE_TYPE.format('SharedService', input_data.resourceType) diff --git a/api_app/tests_ma/test_api/test_routes/test_user_resource_templates.py b/api_app/tests_ma/test_api/test_routes/test_user_resource_templates.py index 92389de163..ab9c15aa68 100644 --- a/api_app/tests_ma/test_api/test_routes/test_user_resource_templates.py +++ b/api_app/tests_ma/test_api/test_routes/test_user_resource_templates.py @@ -102,6 +102,25 @@ async def test_creating_a_user_resource_template_raises_http_422_if_step_ids_are assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + # POST /workspace-service-templates/{template_name}/user-resource-templates + async def test_post_user_resource_template_with_invalid_resource_type(self, app, client): + input_data = { + "name": "invalid-template", + "description": "Invalid template", + "version": "0.1.0", + "resourceType": "InvalidType", + "current": True, + "type": "object", + "required": [], + "properties": {}, + "actions": [] + } + + response = await client.post(app.url_path_for(strings.API_CREATE_USER_RESOURCE_TEMPLATES, service_template_name="guacamole"), json=input_data) + + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + assert response.json()["detail"] == strings.INVALID_RESOURCE_TYPE.format('UserResource', input_data.resourceType) + class TestUserResourceTemplatesNotRequiringAdminRights: @pytest.fixture(autouse=True, scope='class') diff --git a/api_app/tests_ma/test_api/test_routes/test_workspace_service_templates.py b/api_app/tests_ma/test_api/test_routes/test_workspace_service_templates.py index 910ddf151b..67744c44fc 100644 --- a/api_app/tests_ma/test_api/test_routes/test_workspace_service_templates.py +++ b/api_app/tests_ma/test_api/test_routes/test_workspace_service_templates.py @@ -176,3 +176,22 @@ async def test_workspace_service_templates_by_name_returns_error_status_based_on response = await client.get(app.url_path_for(strings.API_GET_WORKSPACE_SERVICE_TEMPLATE_BY_NAME, service_template_name="template1")) assert response.status_code == expected_status + + # POST /workspace-service-templates/ + async def test_post_workspace_service_template_with_invalid_resource_type(self, app, client): + input_data = { + "name": "invalid-template", + "description": "Invalid template", + "version": "0.1.0", + "resourceType": "InvalidType", + "current": True, + "type": "object", + "required": [], + "properties": {}, + "actions": [] + } + + response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES), json=input_data) + + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + assert response.json()["detail"] == strings.INVALID_RESOURCE_TYPE.format('WorkspaceService', input_data.resourceType) diff --git a/api_app/tests_ma/test_api/test_routes/test_workspace_templates.py b/api_app/tests_ma/test_api/test_routes/test_workspace_templates.py index 09b79b51e3..d06d5d4e2c 100644 --- a/api_app/tests_ma/test_api/test_routes/test_workspace_templates.py +++ b/api_app/tests_ma/test_api/test_routes/test_workspace_templates.py @@ -222,3 +222,22 @@ async def test_when_creating_workspace_service_template_service_resource_type_is await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES), json=input_workspace_template.dict()) create_template_mock.assert_called_once_with(input_workspace_template, ResourceType.WorkspaceService, '') + + # POST /workspace-templates + async def test_post_workspace_template_with_invalid_resource_type(self, app, client): + input_data = { + "name": "invalid-template", + "description": "Invalid template", + "version": "0.1.0", + "resourceType": "InvalidType", + "current": True, + "type": "object", + "required": [], + "properties": {}, + "customActions": [] + } + + response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_TEMPLATES), json=input_data) + + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + assert response.json()["detail"] == strings.INVALID_RESOURCE_TYPE.format('Workspace', input_data.resourceType) From 59970200ba2fa6b97f6a6c10d0d72d03f585fd22 Mon Sep 17 00:00:00 2001 From: Marcus Robinson Date: Fri, 7 Feb 2025 22:21:27 +0000 Subject: [PATCH 2/4] Fix tests --- .../api/routes/shared_service_templates.py | 2 +- api_app/api/routes/user_resource_templates.py | 2 +- .../api/routes/workspace_service_templates.py | 2 +- api_app/api/routes/workspace_templates.py | 2 +- api_app/models/schemas/resource_template.py | 1 + api_app/tests_ma/conftest.py | 4 +++ .../test_shared_service_templates.py | 18 +++------- .../test_user_resource_templates.py | 19 +++------- .../test_workspace_service_templates.py | 36 +++++++------------ .../test_routes/test_workspace_templates.py | 21 ++++------- 10 files changed, 38 insertions(+), 69 deletions(-) diff --git a/api_app/api/routes/shared_service_templates.py b/api_app/api/routes/shared_service_templates.py index e8eaef5cbf..1481430026 100644 --- a/api_app/api/routes/shared_service_templates.py +++ b/api_app/api/routes/shared_service_templates.py @@ -34,7 +34,7 @@ async def get_shared_service_template(shared_service_template_name: str, is_upda @shared_service_templates_core_router.post("/shared-service-templates", status_code=status.HTTP_201_CREATED, response_model=SharedServiceTemplateInResponse, response_model_exclude_none=True, name=strings.API_CREATE_SHARED_SERVICE_TEMPLATES, dependencies=[Depends(get_current_admin_user)]) async def register_shared_service_template(template_input: SharedServiceTemplateInCreate, template_repo=Depends(get_repository(ResourceTemplateRepository))) -> ResourceTemplateInResponse: if template_input.resourceType != ResourceType.SharedService: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format('SharedService', template_input.resourceType)) + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format(ResourceType.SharedService, template_input.resourceType)) try: return await template_repo.create_and_validate_template(template_input, ResourceType.SharedService) except EntityVersionExist: diff --git a/api_app/api/routes/user_resource_templates.py b/api_app/api/routes/user_resource_templates.py index 6e7916426b..0834cde9c5 100644 --- a/api_app/api/routes/user_resource_templates.py +++ b/api_app/api/routes/user_resource_templates.py @@ -32,7 +32,7 @@ async def get_user_resource_template(service_template_name: str, user_resource_t @user_resource_templates_core_router.post("/workspace-service-templates/{service_template_name}/user-resource-templates", status_code=status.HTTP_201_CREATED, response_model=UserResourceTemplateInResponse, response_model_exclude_none=True, name=strings.API_CREATE_USER_RESOURCE_TEMPLATES, dependencies=[Depends(get_current_admin_user)]) async def register_user_resource_template(template_input: UserResourceTemplateInCreate, template_repo=Depends(get_repository(ResourceTemplateRepository)), workspace_service_template=Depends(get_workspace_service_template_by_name_from_path)) -> UserResourceTemplateInResponse: if template_input.resourceType != ResourceType.UserResource: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format('UserResource', template_input.resourceType)) + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format(ResourceType.UserResource, template_input.resourceType)) try: return await template_repo.create_and_validate_template(template_input, ResourceType.UserResource, workspace_service_template.name) except EntityVersionExist: diff --git a/api_app/api/routes/workspace_service_templates.py b/api_app/api/routes/workspace_service_templates.py index 58aa608217..1630b5c5c2 100644 --- a/api_app/api/routes/workspace_service_templates.py +++ b/api_app/api/routes/workspace_service_templates.py @@ -31,7 +31,7 @@ async def get_workspace_service_template(service_template_name: str, is_update: @workspace_service_templates_core_router.post("/workspace-service-templates", status_code=status.HTTP_201_CREATED, response_model=WorkspaceServiceTemplateInResponse, response_model_exclude_none=True, name=strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES, dependencies=[Depends(get_current_admin_user)]) async def register_workspace_service_template(template_input: WorkspaceServiceTemplateInCreate, template_repo=Depends(get_repository(ResourceTemplateRepository))) -> ResourceTemplateInResponse: if template_input.resourceType != ResourceType.WorkspaceService: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format('WorkspaceService', template_input.resourceType)) + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format(ResourceType.WorkspaceService, template_input.resourceType)) try: return await template_repo.create_and_validate_template(template_input, ResourceType.WorkspaceService) except EntityVersionExist: diff --git a/api_app/api/routes/workspace_templates.py b/api_app/api/routes/workspace_templates.py index a5eabe3c96..5c8449755b 100644 --- a/api_app/api/routes/workspace_templates.py +++ b/api_app/api/routes/workspace_templates.py @@ -31,7 +31,7 @@ async def get_workspace_template(workspace_template_name: str, is_update: bool = @workspace_templates_admin_router.post("/workspace-templates", status_code=status.HTTP_201_CREATED, response_model=WorkspaceTemplateInResponse, response_model_exclude_none=True, name=strings.API_CREATE_WORKSPACE_TEMPLATES) async def register_workspace_template(template_input: WorkspaceTemplateInCreate, template_repo=Depends(get_repository(ResourceTemplateRepository))) -> ResourceTemplateInResponse: if template_input.resourceType != ResourceType.Workspace: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format('Workspace', template_input.resourceType)) + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=strings.INVALID_RESOURCE_TYPE.format(ResourceType.Workspace, template_input.resourceType)) try: return await template_repo.create_and_validate_template(template_input, ResourceType.Workspace) except EntityVersionExist: diff --git a/api_app/models/schemas/resource_template.py b/api_app/models/schemas/resource_template.py index dd3b75722e..5779ab4f78 100644 --- a/api_app/models/schemas/resource_template.py +++ b/api_app/models/schemas/resource_template.py @@ -8,6 +8,7 @@ class ResourceTemplateInCreate(BaseModel): name: str = Field(title="Template name") version: str = Field(title="Template version") + resourceType: str = Field(title="Resource type") current: bool = Field(title="Mark this version as current") json_schema: Dict = Field(title="JSON Schema compliant template") customActions: List[CustomAction] = Field(default=[], title="Custom actions") diff --git a/api_app/tests_ma/conftest.py b/api_app/tests_ma/conftest.py index 0bd06e076d..d6d5d96cc1 100644 --- a/api_app/tests_ma/conftest.py +++ b/api_app/tests_ma/conftest.py @@ -33,6 +33,7 @@ def input_workspace_template(): return WorkspaceTemplateInCreate( name="my-tre-workspace", + resourceType=ResourceType.Workspace, version="0.0.1", current=True, json_schema={ @@ -99,6 +100,7 @@ def input_workspace_service_template(): return WorkspaceServiceTemplateInCreate( name="my-tre-workspace-service", version="0.0.1", + resourceType=ResourceType.WorkspaceService, current=True, json_schema={ "$schema": "http://json-schema.org/draft-07/schema", @@ -119,6 +121,7 @@ def input_workspace_service_template(): def input_user_resource_template(): return UserResourceTemplateInCreate( name="my-tre-user-resource", + resourceType=ResourceType.UserResource, version="0.0.1", current=True, json_schema={ @@ -140,6 +143,7 @@ def input_user_resource_template(): def input_shared_service_template(): return SharedServiceTemplateInCreate( name="my-tre-shared-service", + resourceType=ResourceType.SharedService, version="0.0.1", current=True, json_schema={ diff --git a/api_app/tests_ma/test_api/test_routes/test_shared_service_templates.py b/api_app/tests_ma/test_api/test_routes/test_shared_service_templates.py index e111a0c817..b7d9941aea 100644 --- a/api_app/tests_ma/test_api/test_routes/test_shared_service_templates.py +++ b/api_app/tests_ma/test_api/test_routes/test_shared_service_templates.py @@ -114,20 +114,12 @@ async def test_creating_a_shared_service_template_raises_http_422_if_step_ids_ar assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY # POST /shared-service-templates - async def test_post_shared_service_template_with_invalid_resource_type(self, app, client): - input_data = { - "name": "invalid-template", - "description": "Invalid template", - "version": "0.1.0", - "resourceType": "InvalidType", - "current": True, - "type": "object", - "required": [], - "properties": {}, - "actions": [] - } + async def test_post_shared_service_template_with_invalid_resource_type(self, app, client, input_shared_service_template): + + input_data = input_shared_service_template.dict() + input_data["resourceType"] = ResourceType.WorkspaceService response = await client.post(app.url_path_for(strings.API_CREATE_SHARED_SERVICE_TEMPLATES), json=input_data) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - assert response.json()["detail"] == strings.INVALID_RESOURCE_TYPE.format('SharedService', input_data.resourceType) + assert response.text == strings.INVALID_RESOURCE_TYPE.format(ResourceType.SharedService, input_data["resourceType"]) diff --git a/api_app/tests_ma/test_api/test_routes/test_user_resource_templates.py b/api_app/tests_ma/test_api/test_routes/test_user_resource_templates.py index ab9c15aa68..f762dfbfed 100644 --- a/api_app/tests_ma/test_api/test_routes/test_user_resource_templates.py +++ b/api_app/tests_ma/test_api/test_routes/test_user_resource_templates.py @@ -102,24 +102,15 @@ async def test_creating_a_user_resource_template_raises_http_422_if_step_ids_are assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - # POST /workspace-service-templates/{template_name}/user-resource-templates - async def test_post_user_resource_template_with_invalid_resource_type(self, app, client): - input_data = { - "name": "invalid-template", - "description": "Invalid template", - "version": "0.1.0", - "resourceType": "InvalidType", - "current": True, - "type": "object", - "required": [], - "properties": {}, - "actions": [] - } + @patch("api.dependencies.workspace_service_templates.ResourceTemplateRepository.get_current_template") + async def test_post_user_resource_template_with_invalid_resource_type(self, _, app, client, input_user_resource_template): + input_data = input_user_resource_template.dict() + input_data["resourceType"] = ResourceType.WorkspaceService response = await client.post(app.url_path_for(strings.API_CREATE_USER_RESOURCE_TEMPLATES, service_template_name="guacamole"), json=input_data) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - assert response.json()["detail"] == strings.INVALID_RESOURCE_TYPE.format('UserResource', input_data.resourceType) + assert response.text == strings.INVALID_RESOURCE_TYPE.format(ResourceType.UserResource, input_data["resourceType"]) class TestUserResourceTemplatesNotRequiringAdminRights: diff --git a/api_app/tests_ma/test_api/test_routes/test_workspace_service_templates.py b/api_app/tests_ma/test_api/test_routes/test_workspace_service_templates.py index 67744c44fc..67bf722974 100644 --- a/api_app/tests_ma/test_api/test_routes/test_workspace_service_templates.py +++ b/api_app/tests_ma/test_api/test_routes/test_workspace_service_templates.py @@ -84,12 +84,12 @@ async def test_get_workspace_service_templates_returns_template_names_and_descri @patch("api.routes.workspace_service_templates.ResourceTemplateRepository.create_template") @patch("api.routes.workspace_service_templates.ResourceTemplateRepository.get_current_template") @patch("api.routes.workspace_service_templates.ResourceTemplateRepository.get_template_by_name_and_version") - async def test_when_updating_current_and_service_template_not_found_create_one(self, get_name_ver_mock, get_current_mock, create_template_mock, app, client, input_workspace_template, basic_workspace_service_template): + async def test_when_updating_current_and_service_template_not_found_create_one(self, get_name_ver_mock, get_current_mock, create_template_mock, app, client, input_workspace_service_template, basic_workspace_service_template): get_name_ver_mock.side_effect = EntityDoesNotExist get_current_mock.side_effect = EntityDoesNotExist create_template_mock.return_value = basic_workspace_service_template - response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES), json=input_workspace_template.dict()) + response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES), json=input_workspace_service_template.dict()) assert response.status_code == status.HTTP_201_CREATED @@ -98,29 +98,29 @@ async def test_when_updating_current_and_service_template_not_found_create_one(s @patch("api.routes.workspace_service_templates.ResourceTemplateRepository.update_item") @patch("api.routes.workspace_service_templates.ResourceTemplateRepository.get_current_template") @patch("api.routes.workspace_service_templates.ResourceTemplateRepository.get_template_by_name_and_version") - async def test_when_updating_current_and_service_template_found_update_and_add(self, get_template_by_name_and_version_mock, get_current_template_mock, update_item_mock, create_template_mock, app, client, input_workspace_template, basic_workspace_service_template): + async def test_when_updating_current_and_service_template_found_update_and_add(self, get_template_by_name_and_version_mock, get_current_template_mock, update_item_mock, create_template_mock, app, client, input_workspace_service_template, basic_workspace_service_template): get_template_by_name_and_version_mock.side_effect = EntityDoesNotExist get_current_template_mock.return_value = basic_workspace_service_template create_template_mock.return_value = basic_workspace_service_template - response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES), json=input_workspace_template.dict()) + response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES), json=input_workspace_service_template.dict()) - updated_current_workspace_template = basic_workspace_service_template - updated_current_workspace_template.current = False - update_item_mock.assert_called_once_with(updated_current_workspace_template.dict()) + updated_current_workspace_service_template = basic_workspace_service_template + updated_current_workspace_service_template.current = False + update_item_mock.assert_called_once_with(updated_current_workspace_service_template.dict()) assert response.status_code == status.HTTP_201_CREATED # POST /workspace-service-templates/ @patch("api.routes.workspace_service_templates.ResourceTemplateRepository.create_template") @patch("api.routes.workspace_service_templates.ResourceTemplateRepository.get_current_template") @patch("api.routes.workspace_service_templates.ResourceTemplateRepository.get_template_by_name_and_version") - async def test_when_creating_service_template_enriched_service_template_is_returned(self, get_template_by_name_and_version_mock, get_current_template_mock, create_template_mock, app, client, input_workspace_template, basic_workspace_service_template): + async def test_when_creating_service_template_enriched_service_template_is_returned(self, get_template_by_name_and_version_mock, get_current_template_mock, create_template_mock, app, client, input_workspace_service_template, basic_workspace_service_template): get_template_by_name_and_version_mock.side_effect = EntityDoesNotExist get_current_template_mock.side_effect = EntityDoesNotExist create_template_mock.return_value = basic_workspace_service_template - response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES), json=input_workspace_template.dict()) + response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES), json=input_workspace_service_template.dict()) expected_template = parse_obj_as(WorkspaceTemplateInResponse, enrich_workspace_service_template(basic_workspace_service_template)) assert json.loads(response.text)["required"] == expected_template.dict(exclude_unset=True)["required"] @@ -178,20 +178,10 @@ async def test_workspace_service_templates_by_name_returns_error_status_based_on assert response.status_code == expected_status # POST /workspace-service-templates/ - async def test_post_workspace_service_template_with_invalid_resource_type(self, app, client): - input_data = { - "name": "invalid-template", - "description": "Invalid template", - "version": "0.1.0", - "resourceType": "InvalidType", - "current": True, - "type": "object", - "required": [], - "properties": {}, - "actions": [] - } - + async def test_post_workspace_service_template_with_invalid_resource_type(self, app, client, input_workspace_service_template): + input_data = input_workspace_service_template.dict() + input_data['resourceType'] = ResourceType.UserResource response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES), json=input_data) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - assert response.json()["detail"] == strings.INVALID_RESOURCE_TYPE.format('WorkspaceService', input_data.resourceType) + assert response.text == strings.INVALID_RESOURCE_TYPE.format(ResourceType.WorkspaceService, input_data["resourceType"]) diff --git a/api_app/tests_ma/test_api/test_routes/test_workspace_templates.py b/api_app/tests_ma/test_api/test_routes/test_workspace_templates.py index d06d5d4e2c..7f78271302 100644 --- a/api_app/tests_ma/test_api/test_routes/test_workspace_templates.py +++ b/api_app/tests_ma/test_api/test_routes/test_workspace_templates.py @@ -219,25 +219,16 @@ async def test_when_creating_workspace_service_template_service_resource_type_is get_current_template_mock.side_effect = EntityDoesNotExist create_template_mock.return_value = basic_workspace_service_template - await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE_TEMPLATES), json=input_workspace_template.dict()) + await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_TEMPLATES), json=input_workspace_template.dict()) - create_template_mock.assert_called_once_with(input_workspace_template, ResourceType.WorkspaceService, '') + create_template_mock.assert_called_once_with(input_workspace_template, ResourceType.Workspace, '') # POST /workspace-templates - async def test_post_workspace_template_with_invalid_resource_type(self, app, client): - input_data = { - "name": "invalid-template", - "description": "Invalid template", - "version": "0.1.0", - "resourceType": "InvalidType", - "current": True, - "type": "object", - "required": [], - "properties": {}, - "customActions": [] - } + async def test_post_workspace_template_with_invalid_resource_type(self, app, client, input_workspace_template): + input_data = input_workspace_template.dict() + input_data["resourceType"] = ResourceType.WorkspaceService response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_TEMPLATES), json=input_data) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - assert response.json()["detail"] == strings.INVALID_RESOURCE_TYPE.format('Workspace', input_data.resourceType) + assert response.text == strings.INVALID_RESOURCE_TYPE.format(ResourceType.Workspace, input_data["resourceType"]) From 78aa715bb0eb95a8e1a6a27d6144186f8be75c5d Mon Sep 17 00:00:00 2001 From: Marcus Robinson Date: Fri, 7 Feb 2025 22:34:24 +0000 Subject: [PATCH 3/4] Update API version --- api_app/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_app/_version.py b/api_app/_version.py index 8b8252f484..6a726d853b 100644 --- a/api_app/_version.py +++ b/api_app/_version.py @@ -1 +1 @@ -__version__ = "0.20.4" +__version__ = "0.21.0" From 85f14fab166d6ff6a281763ebc001b9a86fed319 Mon Sep 17 00:00:00 2001 From: Marcus Robinson Date: Fri, 7 Feb 2025 22:38:07 +0000 Subject: [PATCH 4/4] fix test --- .../test_repositories/test_resource_templates_repository.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api_app/tests_ma/test_db/test_repositories/test_resource_templates_repository.py b/api_app/tests_ma/test_db/test_repositories/test_resource_templates_repository.py index 813c1b7471..3e070c5d0c 100644 --- a/api_app/tests_ma/test_db/test_repositories/test_resource_templates_repository.py +++ b/api_app/tests_ma/test_db/test_repositories/test_resource_templates_repository.py @@ -41,6 +41,7 @@ async def test_create_workspace_template_succeeds_without_required(uuid_mock, sa expected_type = ResourceType.Workspace input_workspace_template = WorkspaceTemplateInCreate( name="my-tre-workspace", + resourceType=ResourceType.Workspace, version="0.0.1", current=True, json_schema={