diff --git a/tests/test_client.py b/tests/test_client.py index 32c3b9b..08946a9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -54,6 +54,53 @@ "name": "string" } +MOCK_VERSION = { + "author": [ + "author" + ], + 'containerfile': None, + 'descriptor_type': None, + "id": "v1", + "images": None, + "included_apps": [ + "https://bio.tools/tool/mytum.de/SNAP2/1", + "https://bio.tools/bioexcel_seqqc" + ], + "is_production": True, + "meta_version": None, + "name": "name", + "signed": True, + "url": "abcde.com", + "verified": None, + "verified_source": [ + "verified_source", + ] +} +MOCK_TOOL_CLASS_WITH_ID = { + "description": "description", + "id": "234561", + "name": "name", +} +MOCK_TOOL = { + "aliases": [ + "alias_1", + "alias_2", + "alias_3", + ], + "checker_url": "checker_url", + "description": "description", + "has_checker": True, + "id": MOCK_ID, + "meta_version": "1", + "name": "name", + "organization": "organization", + "toolclass": MOCK_TOOL_CLASS_WITH_ID, + "url": "abc.com", + "versions": [ + MOCK_VERSION + ], +} + def _raise(exception) -> None: """General purpose exception raiser.""" @@ -151,6 +198,76 @@ def test_no_success_InvalidResponseError(self, requests_mock): ) +class TestGetTool: + """Test getter for tool with a given id.""" + + cli = TRSClient( + uri=MOCK_TRS_URI, + token=MOCK_TOKEN, + ) + endpoint = f"{cli.uri}/tools/{MOCK_ID}" + + def test_ConnectionError(self, monkeypatch): + """Connection error occurs.""" + monkeypatch.setattr( + 'requests.get', + lambda *args, **kwargs: _raise(requests.exceptions.ConnectionError) + ) + with pytest.raises(requests.exceptions.ConnectionError): + self.cli.get_tool( + tool_id=MOCK_TRS_URI, + token=MOCK_TOKEN, + ) + + def test_success(self, monkeypatch, requests_mock): + """Returns 200 response.""" + requests_mock.get(self.endpoint, json=MOCK_TOOL) + r = self.cli.get_tool( + tool_id=MOCK_ID, + ) + assert r.dict() == MOCK_TOOL + + def test_success_trs_uri(self, monkeypatch, requests_mock): + """Returns 200 response with TRS URI.""" + requests_mock.get(self.endpoint, json=MOCK_TOOL) + r = self.cli.get_tool( + tool_id=MOCK_TRS_URI, + ) + assert r.dict() == MOCK_TOOL + + def test_success_InvalidResponseError(self, requests_mock): + """Returns 200 response but schema validation fails.""" + requests_mock.get(self.endpoint, json=MOCK_RESPONSE_INVALID) + with pytest.raises(InvalidResponseError): + self.cli.get_tool( + tool_id=MOCK_ID, + ) + + def test_no_success_valid_error_response(self, requests_mock): + """Returns no 200 but valid error response.""" + requests_mock.get( + self.endpoint, + json=MOCK_ERROR, + status_code=400, + ) + r = self.cli.get_tool( + tool_id=MOCK_ID, + ) + assert r.dict() == MOCK_ERROR + + def test_no_success_InvalidResponseError(self, requests_mock): + """Returns no 200 and error schema validation fails.""" + requests_mock.get( + self.endpoint, + json=MOCK_RESPONSE_INVALID, + status_code=400, + ) + with pytest.raises(InvalidResponseError): + self.cli.get_tool( + tool_id=MOCK_ID, + ) + + class TestGetDescriptor: """Test getter for primary descriptor of a given descriptor type.""" diff --git a/trs_cli/client.py b/trs_cli/client.py index a1d8fa1..4a50088 100644 --- a/trs_cli/client.py +++ b/trs_cli/client.py @@ -28,6 +28,7 @@ FileWrapper, ToolFile, ToolClassRegister, + Tool, ) logger = logging.getLogger(__name__) @@ -166,6 +167,68 @@ def post_tool_class( logger.info(f"ToolClass registered: {response_val}") return response_val + def get_tool( + self, + tool_id: str, + token: Optional[str] = None, + ) -> Union[Error, Tool]: + """Retrieve TRS tool. + Arguments: + tool_id: Implementation-specific TRS identifier hostname-based + TRS URI pointing to a given tool + token: Bearer token for authentication. Set if required by TRS + implementation and if not provided when instatiating client or + if expired. + Returns: + Unmarshalled TRS response as either an instance of `Tool` + in case of a `200` response, or an instance of `Error` for all + other JSON reponses. + Raises: + requests.exceptions.ConnectionError: A connection to the provided + TRS instance could not be established. + trs_cli.errors.InvalidResponseError: The response could not be + validated against the API schema. + """ + tool_id, _ = self._get_tool_id_version_id(tool_id=tool_id) + url = f"{self.uri}/tools/{tool_id}" + logger.info(f"Request URL: {url}") + if token: + self.token = token + self._get_headers() + try: + response = requests.get( + url=url, + headers=self.headers, + ) + except ( + requests.exceptions.ConnectionError, + socket.gaierror, + urllib3.exceptions.NewConnectionError, + ): + raise requests.exceptions.ConnectionError( + "Could not connect to API endpoint." + ) + if not response.status_code == 200: + try: + response_val = Error(**response.json()) + except ( + json.decoder.JSONDecodeError, + pydantic.ValidationError, + ): + raise InvalidResponseError( + "Response could not be validated against API schema." + ) + logger.warning("Received error response.") + else: + try: + response_val = Tool(**response.json()) + except pydantic.ValidationError: + raise InvalidResponseError( + "Response could not be validated against API schema." + ) + logger.info(f"Retrieved tool: {tool_id}") + return response_val + def get_descriptor( self, type: str,