diff --git a/services/capability/src/capability/routes.py b/services/capability/src/capability/routes.py index 98d471798068118b24732a19ce7ef17f5d2d6ebc..ce095dc657f9e42d791cf948960f8b96cffbe37e 100644 --- a/services/capability/src/capability/routes.py +++ b/services/capability/src/capability/routes.py @@ -42,7 +42,10 @@ def capability_routes(config: Configurator): request_method="POST", ) config.add_route( - name="deactivate_capability", pattern=f"{capability_url}/deactivate", request_method="POST" + name="enable_capability", pattern=f"{capability_url}/enable", request_method="POST" + ) + config.add_route( + name="disable_capability", pattern=f"{capability_url}/disable", request_method="POST" ) diff --git a/services/capability/src/capability/views/capability.py b/services/capability/src/capability/views/capability.py index 9e97c4c9015609a97624c0240c0c1b4afcb6d25e..14638b4993b9df0e7befba0684b89a192f1e895d 100644 --- a/services/capability/src/capability/views/capability.py +++ b/services/capability/src/capability/views/capability.py @@ -42,6 +42,7 @@ def create_capability(request: Request) -> Response: URL: capability/create :param request: POST request, expecting JSON parameters ["capability_name", "steps", "max_jobs"] + optionally, accepts a boolean "enabled" parameter :return: Response with JSON-formatted capability info of newly created capability or 400 response (HTTPBadRequest) if expected parameters not given or 412 response (HTTPPreconditionFailed) if capability with given name already exists @@ -67,6 +68,7 @@ def create_capability(request: Request) -> Response: name=params["capability_name"], steps=params["steps"], max_jobs=params["max_jobs"], + enabled=params.get("enabled", True), ) request.capability_info.save_entity(new_capability) return Response(json_body=new_capability.__json__()) @@ -98,11 +100,11 @@ def edit_capability(request: Request) -> Response: return HTTPBadRequest(detail=params_not_given_msg) elif not request.capability_info.lookup_capability(capability_name): # Capability with given name does not exist - already_exists_msg = ( + does_not_exist_msg = ( f"Capability {capability_name} does not exist.", f"To instead create a new capability, use capability/create with the same parameters.", ) - return HTTPPreconditionFailed(detail=already_exists_msg) + return HTTPPreconditionFailed(detail=does_not_exist_msg) else: steps = params.get("steps", None) max_jobs = params.get("max_jobs", None) @@ -111,3 +113,35 @@ def edit_capability(request: Request) -> Response: return Response(body="Capability successfully edited!") else: return HTTPExpectationFailed(detail=f"Unable to edit capability {capability_name}.") + + +@view_config(route_name="enable_capability", renderer="json") +def enable_capability(request: Request) -> Response: + capability_name = request.matchdict["capability_name"] + + if not request.capability_info.lookup_capability(capability_name): + # Capability with given name does not exist + does_not_exist_msg = f"Capability {capability_name} does not exist. Cannot enable." + return HTTPPreconditionFailed(detail=does_not_exist_msg) + else: + success = request.capability_info.edit_capability(capability_name, enabled=True) + if success: + return Response(body="Capability successfully enabled!") + else: + return HTTPExpectationFailed(detail=f"Unable to enable capability {capability_name}.") + + +@view_config(route_name="disable_capability", renderer="json") +def disable_capability(request: Request) -> Response: + capability_name = request.matchdict["capability_name"] + + if not request.capability_info.lookup_capability(capability_name): + # Capability with given name does not exist + does_not_exist_msg = f"Capability {capability_name} does not exist. Cannot disable." + return HTTPPreconditionFailed(detail=does_not_exist_msg) + else: + success = request.capability_info.edit_capability(capability_name, enabled=False) + if success: + return Response(body="Capability successfully disabled!") + else: + return HTTPExpectationFailed(detail=f"Unable to disable capability {capability_name}.") diff --git a/services/capability/test/conftest.py b/services/capability/test/conftest.py index 1609c8e7f87a440eb65cf8ea9324a2b092c001bb..9731368c82b81acb4faf1d4cead1e0fc744bccca 100644 --- a/services/capability/test/conftest.py +++ b/services/capability/test/conftest.py @@ -47,7 +47,9 @@ def request_null_capability() -> DummyRequest: ), ] - def edit_capability(self, name: str, steps: str = None, max_jobs: int = None) -> bool: + def edit_capability( + self, name: str, steps: str = None, max_jobs: int = None, enabled: bool = None + ) -> bool: if name == "error": # This is here to mimic the case where an update fails to happen return False @@ -57,6 +59,8 @@ def request_null_capability() -> DummyRequest: capability.steps = steps if max_jobs: capability.max_jobs = max_jobs + if enabled: + capability.enabled = enabled return True return False diff --git a/services/capability/test/test_capability_server.py b/services/capability/test/test_capability_server.py index a4f1c1c884996d1baa3bcddeff71426ae87ab977..348033447ab4ed4b3a6b635fd25241c1975f3223 100644 --- a/services/capability/test/test_capability_server.py +++ b/services/capability/test/test_capability_server.py @@ -14,7 +14,8 @@ def capability_routes() -> RouteList: "view_capability", "create_capability", "edit_capability", - "deactivate_capability", + "enable_capability", + "disable_capability", "view_capability_request", "create_capability_request", "edit_capability_request", diff --git a/services/capability/test/test_capability_views.py b/services/capability/test/test_capability_views.py index bc3d4b174459d3b4c5264edd635f0f39edca8bad..9fa8feab6c6648d65908f72b650739f51f8b895d 100644 --- a/services/capability/test/test_capability_views.py +++ b/services/capability/test/test_capability_views.py @@ -18,7 +18,7 @@ def test_view_capability(test_config: Configurator, request_null_capability: Dum """ from capability.views.capability import view_capability - expected_response = '{"name": "null", "max_jobs": 2, "steps": "test"}' + expected_response = '{"name": "null", "max_jobs": 2, "steps": "test", "enabled": null}' request_null_capability.matchdict["capability_name"] = "null" response = view_capability(request_null_capability) assert response.status_code == 200 @@ -65,7 +65,7 @@ def test_create_capability(test_config: Configurator, request_null_capability: D response = create_capability(request_null_capability) assert response.status_code == 200 - expected_response = '{"name": "test_create", "max_jobs": 1, "steps": "test"}' + expected_response = '{"name": "test_create", "max_jobs": 1, "steps": "test", "enabled": true}' assert response.json_body == expected_response # Assert test capability has been added to list of capabilities (mocked) assert request_null_capability.capability_info.lookup_capability("test_create") @@ -143,3 +143,73 @@ def test_edit_capability_error(test_config: Configurator, request_null_capabilit response_edit_failed = edit_capability(request_null_capability) assert response_edit_failed.status_code == 417 assert type(response_edit_failed) is HTTPExpectationFailed + + +def test_enable_capability(test_config: Configurator, request_null_capability: DummyRequest): + """ + Tests that capabilities can be properly enabled + + :param test_config: Dummy Pyramid Configurator object set up for testing + :param request_null_capability: Dummy Pyramid request object set up with mocked DB access + supporting the null capability + """ + from capability.views.capability import enable_capability + + request_null_capability.matchdict["capability_name"] = "null" + response = enable_capability(request_null_capability) + assert response.status_code == 200 + + +def test_enable_capability_error(test_config: Configurator, request_null_capability: DummyRequest): + """ + Tests that enable_capability view properly responds with HTTP exceptions given bad input + + :param test_config: Dummy Pyramid Configurator object set up for testing + :param request_null_capability: Dummy Pyramid request object set up with mocked DB access + supporting the null capability + """ + from capability.views.capability import enable_capability + + request_null_capability.matchdict["capability_name"] = "does_not_exist" + response_does_not_exist = enable_capability(request_null_capability) + assert response_does_not_exist.status_code == 412 + assert type(response_does_not_exist) is HTTPPreconditionFailed + request_null_capability.matchdict["capability_name"] = "error" + response_enable_failed = enable_capability(request_null_capability) + assert response_enable_failed.status_code == 417 + assert type(response_enable_failed) is HTTPExpectationFailed + + +def test_disable_capability(test_config: Configurator, request_null_capability: DummyRequest): + """ + Tests that capabilities can be properly disabled + + :param test_config: Dummy Pyramid Configurator object set up for testing + :param request_null_capability: Dummy Pyramid request object set up with mocked DB access + supporting the null capability + """ + from capability.views.capability import disable_capability + + request_null_capability.matchdict["capability_name"] = "null" + response = disable_capability(request_null_capability) + assert response.status_code == 200 + + +def test_disable_capability_error(test_config: Configurator, request_null_capability: DummyRequest): + """ + Tests that disable_capability view properly responds with HTTP exceptions given bad input + + :param test_config: Dummy Pyramid Configurator object set up for testing + :param request_null_capability: Dummy Pyramid request object set up with mocked DB access + supporting the null capability + """ + from capability.views.capability import disable_capability + + request_null_capability.matchdict["capability_name"] = "does_not_exist" + response_does_not_exist = disable_capability(request_null_capability) + assert response_does_not_exist.status_code == 412 + assert type(response_does_not_exist) is HTTPPreconditionFailed + request_null_capability.matchdict["capability_name"] = "error" + response_disable_failed = disable_capability(request_null_capability) + assert response_disable_failed.status_code == 417 + assert type(response_disable_failed) is HTTPExpectationFailed diff --git a/shared/workspaces/workspaces/capability/services/capability_info.py b/shared/workspaces/workspaces/capability/services/capability_info.py index 944e9ae807f0b6015c61f3e76db2b26c0d7d3779..71cb7a5d3e7a8114b3d15ea9a5a56668a2406292 100644 --- a/shared/workspaces/workspaces/capability/services/capability_info.py +++ b/shared/workspaces/workspaces/capability/services/capability_info.py @@ -49,7 +49,11 @@ class CapabilityInfo(CapabilityInfoIF): return capability def edit_capability( - self, name: CapabilityName, steps: CapabilitySequence = None, max_jobs: int = None + self, + name: CapabilityName, + steps: CapabilitySequence = None, + max_jobs: int = None, + enabled: bool = None, ) -> bool: """ Edit existing capability definition @@ -57,13 +61,16 @@ class CapabilityInfo(CapabilityInfoIF): :param name: Name of capability to edit :param steps: New capability sequence or None if wanting to leave unchanged :param max_jobs: New number of max jobs or None if wanting to leave unchanged + :param enabled: New enabled state or None is wanting to leave unchanges :return: True if the capability was successfully edited, else False """ changes = {} - if steps: + if steps is not None: changes[Capability.steps] = steps - if max_jobs: + if max_jobs is not None: changes[Capability.max_jobs] = max_jobs + if enabled is not None: + changes[Capability.enabled] = enabled if changes: rows_changed = self.session.query(Capability).filter_by(name=name).update(changes)