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" diff --git a/api_app/api/routes/shared_service_templates.py b/api_app/api/routes/shared_service_templates.py index b7801c3789..1481430026 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(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 f4cb24cb5f..0834cde9c5 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(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 e6df3fadba..1630b5c5c2 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(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 c61b6f7e82..5c8449755b 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(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/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/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 ed09dbd60a..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 @@ -112,3 +112,14 @@ 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_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.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 92389de163..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,6 +102,16 @@ async def test_creating_a_user_resource_template_raises_http_422_if_step_ids_are assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + @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.text == strings.INVALID_RESOURCE_TYPE.format(ResourceType.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..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"] @@ -176,3 +176,12 @@ 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_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.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 09b79b51e3..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,6 +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.Workspace, '') + + # POST /workspace-templates + 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) - create_template_mock.assert_called_once_with(input_workspace_template, ResourceType.WorkspaceService, '') + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + assert response.text == strings.INVALID_RESOURCE_TYPE.format(ResourceType.Workspace, input_data["resourceType"]) 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={