diff --git a/.github/workflows/schematic-api-ci.yml b/.github/workflows/schematic-api-ci.yml new file mode 100644 index 0000000000..a961ab7fb6 --- /dev/null +++ b/.github/workflows/schematic-api-ci.yml @@ -0,0 +1,128 @@ +name: Schematic API CI +on: + pull_request: + branches: + - main + +env: + NX_BRANCH: ${{ github.event.number }} + NX_RUN_GROUP: ${{ github.run_id }} + NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }} + NX_CLOUD_ENCRYPTION_KEY: ${{ secrets.NX_CLOUD_ENCRYPTION_KEY }} + NX_CLOUD_ENV_NAME: 'linux' + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + DOCKER_USERNAME: ${{ github.actor }} + DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + +jobs: + pr: + runs-on: ubuntu-22.04-4core-16GBRAM-150GBSSD + # Runs this job if: + # 1. Triggered by a PR + # 2. The PR originate from the Schematic-API-Staging branch + # 3. Targets the main branch + if: | + github.event_name == 'pull_request' && + github.event.pull_request.base.ref == 'main' && + github.event.pull_request.head.ref == 'Schematic-API-Staging' + + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + fetch-depth: 0 + + - name: Derive appropriate SHAs for base and head for `nx affected` commands + uses: nrwl/nx-set-shas@v3 + + - name: Set up Yarn cache + uses: actions/cache@v3 + with: + path: '/tmp/.yarn/cache' + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Set up Poetry cache + uses: actions/cache@v3 + with: + path: '/tmp/.cache/pypoetry' + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + + - name: Set up venv cache + uses: actions/cache@v3 + with: + path: | + /tmp/.local/share/virtualenv + **/.venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock') }} + + - name: Set up Gradle cache + uses: actions/cache@v3 + with: + path: | + /tmp/.gradle/caches + /tmp/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Install the Dev Container CLI + run: npm install -g @devcontainers/cli@0.49.0 + + - name: Start the dev container + run: | + mkdir -p \ + /tmp/.yarn/cache \ + /tmp/.cache/R/renv/cache \ + /tmp/.cache/pypoetry \ + /tmp/.local/share/virtualenv \ + /tmp/.gradle/caches \ + /tmp/.gradle/wrapper + + devcontainer up \ + --mount type=bind,source=/tmp/.yarn/cache,target=/workspaces/sage-monorepo/.yarn/cache \ + --mount type=bind,source=/tmp/.cache/R/renv/cache,target=/home/vscode/.cache/R/renv/cache \ + --mount type=bind,source=/tmp/.cache/pypoetry,target=/home/vscode/.cache/pypoetry \ + --mount type=bind,source=/tmp/.local/share/virtualenv,target=/home/vscode/.local/share/virtualenv \ + --mount type=bind,source=/tmp/.gradle/caches,target=/home/vscode/.gradle/caches \ + --mount type=bind,source=/tmp/.gradle/wrapper,target=/home/vscode/.gradle/wrapper \ + --workspace-folder ../sage-monorepo + + - name: Prepare the workspace + run: | + devcontainer exec --workspace-folder ../sage-monorepo bash -c " + sudo chown -R vscode:vscode \ + /workspaces/sage-monorepo \ + /home/vscode/.cache \ + /home/vscode/.local \ + /home/vscode/.gradle \ + && . ./dev-env.sh \ + && workspace-install" + + - name: create synapse config + run: | + import yaml + secrets = { + "synapse_token": "${{ secrets.SCHEMATIC_SYNAPSE_TOKEN }}", + "test_project": "${{ secrets.SCHEMATIC_TEST_PROJECT }}", + "test_dataset": "${{ secrets.SCHEMATIC_TEST_DATASET }}", + "test_manifest": "${{ secrets.SCHEMATIC_TEST_MANIFEST }}", + "test_asset_view": "${{ secrets.SCHEMATIC_TEST_ASSET_VIEW }}" + } + for key, secret in secrets.items(): + assert secret is not None + assert isinstance(secret, str) + assert len(secret) > 0 + with open('apps/schematic/api/schematic_api/test/data/synapse_config.yaml', 'w') as file: + yaml.dump(secrets, file) + shell: python + + - name: Test the affected projects (all) + run: | + devcontainer exec --workspace-folder ../sage-monorepo bash -c ". ./dev-env.sh \ + && nx affected --target=test-all" + + - name: Remove the dev container + run: docker rm -f sage_devcontainer diff --git a/apps/schematic/api/.gitignore b/apps/schematic/api/.gitignore index 4d4b343335..e1b70a7db1 100644 --- a/apps/schematic/api/.gitignore +++ b/apps/schematic/api/.gitignore @@ -69,6 +69,7 @@ target/ #secrets *secrets* +synapse_config.yaml #schematic downloaded files manifests diff --git a/apps/schematic/api/project.json b/apps/schematic/api/project.json index 0b57e4ef4f..c06fc4af3c 100644 --- a/apps/schematic/api/project.json +++ b/apps/schematic/api/project.json @@ -64,10 +64,7 @@ "generate": { "executor": "nx:run-commands", "options": { - "commands": [ - "xargs rm -fr <.openapi-generator/FILES", - "openapi-generator-cli generate" - ], + "commands": ["xargs rm -fr <.openapi-generator/FILES", "openapi-generator-cli generate"], "cwd": "{projectRoot}", "parallel": false }, @@ -108,7 +105,21 @@ "test": { "executor": "nx:run-commands", "options": { - "command": "poetry run tox", + "command": "poetry run pytest -m 'not secrets'", + "cwd": "apps/schematic/api" + } + }, + "test-integration": { + "executor": "nx:run-commands", + "options": { + "command": "poetry run pytest -m 'secrets'", + "cwd": "apps/schematic/api" + } + }, + "test-all": { + "executor": "nx:run-commands", + "options": { + "command": "poetry run pytest", "cwd": "apps/schematic/api" } }, diff --git a/apps/schematic/api/pytest.ini b/apps/schematic/api/pytest.ini new file mode 100644 index 0000000000..a3cfeabd41 --- /dev/null +++ b/apps/schematic/api/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +markers = + synapse: tests that interact with synapse + secrets: tests that require secrets \ No newline at end of file diff --git a/apps/schematic/api/schematic_api/controllers/schema_controller_impl.py b/apps/schematic/api/schematic_api/controllers/schema_controller_impl.py index d41ddeb450..415ee5acc8 100644 --- a/apps/schematic/api/schematic_api/controllers/schema_controller_impl.py +++ b/apps/schematic/api/schematic_api/controllers/schema_controller_impl.py @@ -73,8 +73,8 @@ def get_connected_nodes_from_schematic( @handle_exceptions def get_connected_nodes( - relationship_type: str, schema_url: str, + relationship_type: str, ) -> tuple[Union[ConnectedNodesPage, BasicError], int]: """Gets a list of connected node pairs via the provide relationship diff --git a/apps/schematic/api/schematic_api/controllers/utils.py b/apps/schematic/api/schematic_api/controllers/utils.py index 4ed584a749..a705fab090 100644 --- a/apps/schematic/api/schematic_api/controllers/utils.py +++ b/apps/schematic/api/schematic_api/controllers/utils.py @@ -3,6 +3,7 @@ import urllib.request import shutil import tempfile +from urllib.error import HTTPError from flask import request # type: ignore from synapseclient.core.exceptions import ( # type: ignore @@ -59,6 +60,11 @@ def func(*args: Any, **kwargs: Any) -> tuple[Union[Any, BasicError], int]: res = BasicError("Synapse entity access error", status, str(error)) return res, status + except InvalidSchemaURL as error: + status = 404 + res = BasicError("Invalid URL", status, str(error)) + return res, status + except Exception as error: # pylint: disable=broad-exception-caught status = 500 res = BasicError("Internal error", status, str(error)) @@ -67,18 +73,52 @@ def func(*args: Any, **kwargs: Any) -> tuple[Union[Any, BasicError], int]: return func +class InvalidSchemaURL(Exception): + """Raised when a provided url for a schema is incorrect""" + + def __init__(self, message: str, url: str): + """ + Args: + message (str): The error message + url (str): The provided incorrect URL + """ + self.message = message + self.url = url + super().__init__(self.message) + + def __str__(self) -> str: + return f"{self.message}: {self.url}" + + def download_schema_file_as_jsonld(schema_url: str) -> str: """Downloads a schema and saves it as temp file Args: schema_url (str): The URL of the schema + Raises: + InvalidSchemaURL: When the schema url doesn't exist or is badly formatted + Returns: str: The path fo the schema jsonld file """ - with urllib.request.urlopen(schema_url) as response: - with tempfile.NamedTemporaryFile( - delete=False, suffix=".model.jsonld" - ) as tmp_file: - shutil.copyfileobj(response, tmp_file) - return tmp_file.name + try: + with urllib.request.urlopen(schema_url) as response: + with tempfile.NamedTemporaryFile( + delete=False, suffix=".model.jsonld" + ) as tmp_file: + shutil.copyfileobj(response, tmp_file) + return tmp_file.name + except ValueError as error: + # checks for specific ValueError where the url isn't correctly formatted + if str(error).startswith("unknown url type"): + raise InvalidSchemaURL( + "The provided URL is incorrectly formatted", schema_url + ) from error + # reraises the ValueError if it isn't the specific type above + else: + raise + except HTTPError as error: + raise InvalidSchemaURL( + "The provided URL could not be found", schema_url + ) from error diff --git a/apps/schematic/api/schematic_api/test/conftest.py b/apps/schematic/api/schematic_api/test/conftest.py index 2d4379914e..5ae30b730c 100644 --- a/apps/schematic/api/schematic_api/test/conftest.py +++ b/apps/schematic/api/schematic_api/test/conftest.py @@ -8,7 +8,7 @@ def csv_to_bytes(path: str) -> str: """reads in a csv file and returns as bytes""" dataframe = pd.read_csv(path) - csv_string = dataframe.to_csv(line_terminator="\r\n", index=False) + csv_string = dataframe.to_csv(lineterminator="\r\n", index=False) return bytes(csv_string, encoding="utf-8") diff --git a/apps/schematic/api/schematic_api/test/data/synapse_config_example.yaml b/apps/schematic/api/schematic_api/test/data/synapse_config_example.yaml new file mode 100644 index 0000000000..e4eab7ee67 --- /dev/null +++ b/apps/schematic/api/schematic_api/test/data/synapse_config_example.yaml @@ -0,0 +1,5 @@ +synapse_token: xxx +test_project: syn53072007 +test_dataset: syn53072009 +test_manifest: syn53072014 +test_asset_view: syn53072011 diff --git a/apps/schematic/api/schematic_api/test/test_schema_controller_endpoints.py b/apps/schematic/api/schematic_api/test/test_schema_controller_endpoints.py index 948454c214..8381baa9d4 100644 --- a/apps/schematic/api/schematic_api/test/test_schema_controller_endpoints.py +++ b/apps/schematic/api/schematic_api/test/test_schema_controller_endpoints.py @@ -1,26 +1,24 @@ """Tests for schema endpoints""" # pylint: disable=duplicate-code -from __future__ import absolute_import import unittest -from unittest.mock import patch -import schematic_api.controllers.schema_controller_impl from schematic_api.test import BaseTestCase +from .conftest import TEST_SCHEMA_URL HEADERS = { "Accept": "application/json", "Authorization": "Bearer xxx", } -COMPONENT_URL = "/api/v1/components/componentLabel/" -CONNECTED_NODES_URL = "/api/v1/connectedNodes?schemaUrl=url1&relationshipType=type" -NODE_IS_REQUIRED_URL = "/api/v1/nodes/node1/isRequired?schemaUrl=url1" -PROPERTY_LABEL_URL = "/api/v1/nodes/node1/propertyLabel?schemaUrl=url1" -SCHEMA_ATTRIBUTES_URL = "/api/v1/schemaAttributes" -NODE_PROPERTIES_URL = "/api/v1/nodes/node1/nodeProperties?schemaUrl=url1" -NODE_VALIDATION_RULES_URL = "/api/v1/nodes/node1/validationRules?schemaUrl=url1" -NODE_DEPENDENCIES_URL = "/api/v1/nodes/node1/dependencies?schemaUrl=url1" +COMPONENT_URL = "/api/v1/components/Patient/?schemaUrl=" +CONNECTED_NODES_URL = "/api/v1/connectedNodes?schemaUrl=" +NODE_IS_REQUIRED_URL = "/api/v1/nodes/FamilyHistory/isRequired?schemaUrl=" +PROPERTY_LABEL_URL = "/api/v1/nodes/node_label/propertyLabel?schemaUrl=" +SCHEMA_ATTRIBUTES_URL = "/api/v1/schemaAttributes?schemaUrl=" +NODE_PROPERTIES_URL = "/api/v1/nodes/MolecularEntity/nodeProperties?schemaUrl=" +NODE_VALIDATION_RULES_URL = "/api/v1/nodes/CheckRegexList/validationRules?schemaUrl=" +NODE_DEPENDENCIES_URL = "/api/v1/nodes/Patient/dependencies?schemaUrl=" class TestGetComponent(BaseTestCase): @@ -28,48 +26,23 @@ class TestGetComponent(BaseTestCase): def test_success(self) -> None: """Test for successful result""" - - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_component", - return_value="xxx", - ) as mock_function: - url = f"{COMPONENT_URL}?schemaUrl=url" - response = self.client.open(url, method="GET", headers=HEADERS) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - mock_function.assert_called_once_with("componentLabel", "url", False) - assert response.json == "xxx" + url = f"{COMPONENT_URL}{TEST_SCHEMA_URL}" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, str) def test_parameters(self) -> None: """Test for successful result""" + url = f"{COMPONENT_URL}{TEST_SCHEMA_URL}&includeIndex=True" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, str) - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_component", - return_value="xxx", - ) as mock_function: - url = f"{COMPONENT_URL}?schemaUrl=url2&includeIndex=True" - response = self.client.open(url, method="GET", headers=HEADERS) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - mock_function.assert_called_once_with("componentLabel", "url2", True) - assert response.json == "xxx" - - def test_500(self) -> None: - """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_component", - side_effect=TypeError, - ): - url = f"{COMPONENT_URL}?schemaUrl=url" - response = self.client.open(url, method="GET", headers=HEADERS) - self.assert500( - response, f"Response body is : {response.data.decode('utf-8')}" - ) + def test_404(self) -> None: + """Test for 404 result""" + url = f"{COMPONENT_URL}not_a_url" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert404(response, f"Response body is : {response.data.decode('utf-8')}") class TestGetConnectedNodes(BaseTestCase): @@ -77,100 +50,40 @@ class TestGetConnectedNodes(BaseTestCase): def test_success(self) -> None: """Test for successful result""" - - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_connected_nodes_from_schematic", - return_value=[["nodeA", "nodeB"], ["nodeB", "nodeC"]], - ) as mock_function: - response = self.client.open( - CONNECTED_NODES_URL, method="GET", headers=HEADERS - ) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - - mock_function.assert_called_once_with("url1", "type") - - assert not response.json["hasNext"] - assert not response.json["hasPrevious"] - assert response.json["number"] == 0 - assert response.json["size"] == 100 - assert response.json["totalElements"] == 2 - assert response.json["totalPages"] == 1 - connected_nodes = response.json["connected_nodes"] - assert len(connected_nodes) == 2 - connected_nodes = connected_nodes[0] - assert list(connected_nodes.keys()) == ["node1", "node2"] - assert connected_nodes["node1"] == "nodeA" - assert connected_nodes["node2"] == "nodeB" + url = f"{CONNECTED_NODES_URL}{TEST_SCHEMA_URL}&relationshipType=requiresDependency" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert not response.json["hasNext"] + assert not response.json["hasPrevious"] + assert response.json["number"] == 0 + assert response.json["size"] == 100 + assert response.json["totalPages"] == 1 + connected_nodes = response.json["connected_nodes"] + assert connected_nodes + assert isinstance(connected_nodes, list) def test_500(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_connected_nodes_from_schematic", - side_effect=TypeError, - ): - response = self.client.open( - CONNECTED_NODES_URL, method="GET", headers=HEADERS - ) - self.assert500( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - - -class TestGetComponentIsRequired(BaseTestCase): - """Test case for component is required endpoint""" + url = f"{CONNECTED_NODES_URL}not_a_url&relationshipType=requiresDependency" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert500(response, f"Response body is : {response.data.decode('utf-8')}") - def test_success(self) -> None: - """Test for successful result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_is_required_from_schematic", - return_value=True, - ) as mock_function: - response = self.client.open( - NODE_IS_REQUIRED_URL, method="GET", headers=HEADERS - ) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - - mock_function.assert_called_once_with("node1", "url1") - assert response.json - - def test_success2(self) -> None: +class TestGetNodeIsRequired(BaseTestCase): + """Test case for node is required endpoint""" + + def test_success(self) -> None: """Test for successful result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_is_required_from_schematic", - return_value=False, - ) as mock_function: - response = self.client.open( - NODE_IS_REQUIRED_URL, method="GET", headers=HEADERS - ) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - - mock_function.assert_called_once_with("node1", "url1") - assert not response.json + url = f"{NODE_IS_REQUIRED_URL}{TEST_SCHEMA_URL}" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert response.json def test_500(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_is_required_from_schematic", - side_effect=TypeError, - ): - response = self.client.open( - NODE_IS_REQUIRED_URL, method="GET", headers=HEADERS - ) - self.assert500( - response, f"Response body is : {response.data.decode('utf-8')}" - ) + url = f"{NODE_IS_REQUIRED_URL}not_a_url" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert500(response, f"Response body is : {response.data.decode('utf-8')}") class TestGetPropertyLabel(BaseTestCase): @@ -178,35 +91,16 @@ class TestGetPropertyLabel(BaseTestCase): def test_success(self) -> None: """Test for successful result""" - - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_property_label_from_schematic", - return_value="label1", - ) as mock_function: - response = self.client.open( - PROPERTY_LABEL_URL, method="GET", headers=HEADERS - ) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - - mock_function.assert_called_once_with("node1", "url1", True) - assert response.json == "label1" + url = f"{PROPERTY_LABEL_URL}{TEST_SCHEMA_URL}" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert response.json == "nodeLabel" def test_500(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_property_label_from_schematic", - side_effect=TypeError, - ): - response = self.client.open( - PROPERTY_LABEL_URL, method="GET", headers=HEADERS - ) - self.assert500( - response, f"Response body is : {response.data.decode('utf-8')}" - ) + url = f"{PROPERTY_LABEL_URL}not_a_url" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert500(response, f"Response body is : {response.data.decode('utf-8')}") class TestGetSchemaAttributes(BaseTestCase): @@ -214,50 +108,16 @@ class TestGetSchemaAttributes(BaseTestCase): def test_success(self) -> None: """Test for successful result""" + url = f"{SCHEMA_ATTRIBUTES_URL}{TEST_SCHEMA_URL}" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, str) - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_schema_attributes", - return_value="xxx", - ) as mock_function: - url = f"{SCHEMA_ATTRIBUTES_URL}?schemaUrl=url" - response = self.client.open(url, method="GET", headers=HEADERS) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - - mock_function.assert_called_once_with("url") - assert response.json == "xxx" - - def test_parameters(self) -> None: - """Test for successful result""" - - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_schema_attributes", - return_value="xxx", - ) as mock_function: - url = f"{SCHEMA_ATTRIBUTES_URL}?schemaUrl=url2" - response = self.client.open(url, method="GET", headers=HEADERS) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - - mock_function.assert_called_once_with("url2") - assert response.json == "xxx" - - def test_500(self) -> None: - """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_schema_attributes", - side_effect=TypeError, - ): - url = f"{SCHEMA_ATTRIBUTES_URL}?schemaUrl=url" - response = self.client.open(url, method="GET", headers=HEADERS) - self.assert500( - response, f"Response body is : {response.data.decode('utf-8')}" - ) + def test_404(self) -> None: + """Test for 404 result""" + url = f"{SCHEMA_ATTRIBUTES_URL}not_a_url" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert404(response, f"Response body is : {response.data.decode('utf-8')}") class TestGetNodeProperties(BaseTestCase): @@ -265,46 +125,22 @@ class TestGetNodeProperties(BaseTestCase): def test_success(self) -> None: """Test for successful result""" - - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_properties_from_schematic", - return_value=["property1", "property2"], - ) as mock_function: - response = self.client.open( - NODE_PROPERTIES_URL, method="GET", headers=HEADERS - ) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - - mock_function.assert_called_once_with("node1", "url1") - - assert not response.json["hasNext"] - assert not response.json["hasPrevious"] - assert response.json["number"] == 0 - assert response.json["size"] == 100 - assert response.json["totalElements"] == 2 - assert response.json["totalPages"] == 1 - node_properties = response.json["node_properties"] - assert len(node_properties) == 2 - node_property = node_properties[0] - assert list(node_property.keys()) == ["name"] - assert node_property["name"] == "property1" + url = f"{NODE_PROPERTIES_URL}{TEST_SCHEMA_URL}" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert not response.json["hasNext"] + assert not response.json["hasPrevious"] + assert response.json["number"] == 0 + assert response.json["size"] == 100 + assert response.json["totalPages"] == 1 + node_properties = response.json["node_properties"] + assert isinstance(node_properties, list) def test_500(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_properties_from_schematic", - side_effect=TypeError, - ): - response = self.client.open( - NODE_PROPERTIES_URL, method="GET", headers=HEADERS - ) - self.assert500( - response, f"Response body is : {response.data.decode('utf-8')}" - ) + url = f"{NODE_PROPERTIES_URL}not_a_url" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert500(response, f"Response body is : {response.data.decode('utf-8')}") class TestNodeValidationRules(BaseTestCase): @@ -312,46 +148,23 @@ class TestNodeValidationRules(BaseTestCase): def test_success(self) -> None: """Test for successful result""" - - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_validation_rules", - return_value=["rule1", "rule2"], - ) as mock_function: - response = self.client.open( - NODE_VALIDATION_RULES_URL, method="GET", headers=HEADERS - ) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - - mock_function.assert_called_once_with("node1", "url1") - - assert not response.json["hasNext"] - assert not response.json["hasPrevious"] - assert response.json["number"] == 0 - assert response.json["size"] == 100 - assert response.json["totalElements"] == 2 - assert response.json["totalPages"] == 1 - validation_rules = response.json["validation_rules"] - assert len(validation_rules) == 2 - validation_rule = validation_rules[0] - assert list(validation_rule.keys()) == ["name"] - assert validation_rule["name"] == "rule1" + url = f"{NODE_VALIDATION_RULES_URL}{TEST_SCHEMA_URL}" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + + assert not response.json["hasNext"] + assert not response.json["hasPrevious"] + assert response.json["number"] == 0 + assert response.json["size"] == 100 + assert response.json["totalPages"] == 1 + validation_rules = response.json["validation_rules"] + assert validation_rules def test_500(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_validation_rules", - side_effect=TypeError, - ): - response = self.client.open( - NODE_VALIDATION_RULES_URL, method="GET", headers=HEADERS - ) - self.assert500( - response, f"Response body is : {response.data.decode('utf-8')}" - ) + url = f"{NODE_VALIDATION_RULES_URL}not_a_url" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert500(response, f"Response body is : {response.data.decode('utf-8')}") class TestNodeDependencies(BaseTestCase): @@ -359,90 +172,42 @@ class TestNodeDependencies(BaseTestCase): def test_success(self) -> None: """Test for successful result""" - - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_dependencies", - return_value=["dependency1", "dependency2"], - ) as mock_function: - response = self.client.open( - NODE_DEPENDENCIES_URL, method="GET", headers=HEADERS - ) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - - mock_function.assert_called_once_with("node1", "url1", True, True) - - assert not response.json["hasNext"] - assert not response.json["hasPrevious"] - assert response.json["number"] == 0 - assert response.json["size"] == 100 - assert response.json["totalElements"] == 2 - assert response.json["totalPages"] == 1 - dependencies = response.json["nodes"] - assert len(dependencies) == 2 - dependency = dependencies[0] - assert list(dependency.keys()) == ["name"] - assert dependency["name"] == "dependency1" + url = f"{NODE_DEPENDENCIES_URL}{TEST_SCHEMA_URL}" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert not response.json["hasNext"] + assert not response.json["hasPrevious"] + assert response.json["number"] == 0 + assert response.json["size"] == 100 + assert response.json["totalPages"] == 1 + dependencies = response.json["nodes"] + assert dependencies def test_return_display_names(self) -> None: """Test for returnDisplayNames parameter""" + url = f"{NODE_DEPENDENCIES_URL}{TEST_SCHEMA_URL}&returnDisplayNames=true" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_dependencies", - return_value=["dependency1", "dependency2"], - ) as mock_function: - url = f"{NODE_DEPENDENCIES_URL}&returnDisplayNames=true" - response = self.client.open(url, method="GET", headers=HEADERS) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - mock_function.assert_called_with("node1", "url1", True, True) - - url = f"{NODE_DEPENDENCIES_URL}&returnDisplayNames=false" - response = self.client.open(url, method="GET", headers=HEADERS) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - mock_function.assert_called_with("node1", "url1", False, True) + url = f"{NODE_DEPENDENCIES_URL}{TEST_SCHEMA_URL}&returnDisplayNames=false" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") def test_return_ordered_by_schema(self) -> None: """Test for returnOrderedBySchema parameter""" + url = f"{NODE_DEPENDENCIES_URL}{TEST_SCHEMA_URL}&returnOrderedBySchema=true" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_dependencies", - return_value=["dependency1", "dependency2"], - ) as mock_function: - url = f"{NODE_DEPENDENCIES_URL}&returnOrderedBySchema=true" - response = self.client.open(url, method="GET", headers=HEADERS) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - mock_function.assert_called_with("node1", "url1", True, True) - - url = f"{NODE_DEPENDENCIES_URL}&returnOrderedBySchema=false" - response = self.client.open(url, method="GET", headers=HEADERS) - self.assert200( - response, f"Response body is : {response.data.decode('utf-8')}" - ) - mock_function.assert_called_with("node1", "url1", True, False) + url = f"{NODE_DEPENDENCIES_URL}{TEST_SCHEMA_URL}&returnOrderedBySchema=false" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") def test_500(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_dependencies", - side_effect=TypeError, - ): - response = self.client.open( - NODE_DEPENDENCIES_URL, method="GET", headers=HEADERS - ) - self.assert500( - response, f"Response body is : {response.data.decode('utf-8')}" - ) + url = f"{NODE_DEPENDENCIES_URL}not_a_url" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert500(response, f"Response body is : {response.data.decode('utf-8')}") if __name__ == "__main__": diff --git a/apps/schematic/api/schematic_api/test/test_schema_controller_impl.py b/apps/schematic/api/schematic_api/test/test_schema_controller_impl.py index a0f8453e8a..15cf4b966c 100644 --- a/apps/schematic/api/schematic_api/test/test_schema_controller_impl.py +++ b/apps/schematic/api/schematic_api/test/test_schema_controller_impl.py @@ -1,13 +1,10 @@ """Tests for schema endpoint functions""" -from unittest.mock import patch - from schematic_api.models.basic_error import BasicError from schematic_api.models.node_properties_page import NodePropertiesPage from schematic_api.models.validation_rules_page import ValidationRulesPage from schematic_api.models.nodes_page import NodesPage from schematic_api.models.connected_nodes_page import ConnectedNodesPage -import schematic_api.controllers.schema_controller_impl from schematic_api.controllers.schema_controller_impl import ( get_component, get_node_is_required, @@ -43,95 +40,67 @@ def test_internal_error(self, test_schema_url: str) -> None: class TestGetConnectedNodes: """Tests forget_connected_nodes""" - def test_success(self) -> None: + def test_success(self, test_schema_url: str) -> None: """Test for successful result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_connected_nodes_from_schematic", - return_value=[["node1", "node2"], ["node2", "node3"]], - ): - result, status = get_connected_nodes( - schema_url="xxx", - relationship_type="type", - ) - assert status == 200 - assert isinstance(result, ConnectedNodesPage) + result, status = get_connected_nodes( + schema_url=test_schema_url, + relationship_type="requiresDependency", + ) + assert status == 200 + assert isinstance(result, ConnectedNodesPage) def test_internal_error(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_connected_nodes_from_schematic", - side_effect=TypeError, - ): - result, status = get_connected_nodes( - schema_url="xxx", - relationship_type="type", - ) - assert status == 500 - assert isinstance(result, BasicError) + result, status = get_connected_nodes( + schema_url="not_a_url", + relationship_type="requiresDependency", + ) + assert status == 500 + assert isinstance(result, BasicError) class TestGetNodeIsRequired: """Test case for get_node_is_required""" - def test_success(self) -> None: + def test_success(self, test_schema_url: str) -> None: """Test for successful result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_is_required_from_schematic", - return_value=True, - ): - result, status = get_node_is_required( - node_display="name", - schema_url="xxx", - ) - assert status == 200 - assert result + result, status = get_node_is_required( + node_display="FamilyHistory", + schema_url=test_schema_url, + ) + assert status == 200 + assert result def test_internal_error(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_is_required_from_schematic", - side_effect=TypeError, - ): - result, status = get_node_is_required( - node_display="name", - schema_url="xxx", - ) - assert status == 500 - assert isinstance(result, BasicError) + result, status = get_node_is_required( + node_display="name", + schema_url="not_a_url", + ) + assert status == 500 + assert isinstance(result, BasicError) class TestGetPropertyLabel: """Test case for get_property_label""" - def test_success(self) -> None: + def test_success(self, test_schema_url: str) -> None: """Test for successful result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_property_label_from_schematic", - return_value="label1", - ): - result, status = get_property_label( - node_display="display", schema_url="xxx", use_strict_camel_case=True - ) - assert status == 200 - assert result == "label1" + result, status = get_property_label( + node_display="display_name", + schema_url=test_schema_url, + use_strict_camel_case=True, + ) + assert status == 200 + assert result == "displayName" def test_internal_error(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_property_label_from_schematic", - side_effect=TypeError, - ): - result, status = get_property_label( - node_display="display", schema_url="xxx", use_strict_camel_case=True - ) - assert status == 500 - assert isinstance(result, BasicError) + result, status = get_property_label( + node_display="display", schema_url="not_a_url", use_strict_camel_case=True + ) + assert status == 500 + assert isinstance(result, BasicError) class TestGetSchemaAttributes: @@ -143,106 +112,76 @@ def test_success(self, test_schema_url: str) -> None: assert status == 200 assert isinstance(result, str) - def test_internal_error( + def test_404_error( self, ) -> None: - """Test for 500 result""" + """Test for 404 result""" result, status = get_schema_attributes(schema_url="not_a_url") - assert status == 500 + assert status == 404 assert isinstance(result, BasicError) class TestGetNodeProperties: """Test case for get_node_properties""" - def test_success(self) -> None: + def test_success(self, test_schema_url: str) -> None: """Test for successful result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_properties_from_schematic", - return_value=["property1", "property2"], - ): - result, status = get_node_properties( - node_label="label", - schema_url="xxx", - ) - assert status == 200 - assert isinstance(result, NodePropertiesPage) + result, status = get_node_properties( + node_label="MolecularEntity", + schema_url=test_schema_url, + ) + assert status == 200 + assert isinstance(result, NodePropertiesPage) def test_internal_error(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_properties_from_schematic", - side_effect=TypeError, - ): - result, status = get_node_properties( - node_label="label", - schema_url="xxx", - ) - assert status == 500 - assert isinstance(result, BasicError) + result, status = get_node_properties( + node_label="MolecularEntity", + schema_url="not_a_url", + ) + assert status == 500 + assert isinstance(result, BasicError) class TestNodeValidationRules: """Test case for list_node_validation_rules""" - def test_success(self) -> None: + def test_success(self, test_schema_url: str) -> None: """Test for successful result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_validation_rules", - return_value=["rule1", "rule2"], - ): - result, status = list_node_validation_rules( - node_display="name", - schema_url="xxx", - ) - assert status == 200 - assert isinstance(result, ValidationRulesPage) + result, status = list_node_validation_rules( + node_display="CheckRegexList", + schema_url=test_schema_url, + ) + assert status == 200 + assert isinstance(result, ValidationRulesPage) def test_internal_error(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_validation_rules", - side_effect=TypeError, - ): - result, status = list_node_validation_rules( - node_display="name", - schema_url="xxx", - ) - assert status == 500 - assert isinstance(result, BasicError) + result, status = list_node_validation_rules( + node_display="CheckRegexList", + schema_url="not_a_url", + ) + assert status == 500 + assert isinstance(result, BasicError) class TestListNodeDependencies: """Test case for list_node_dependencies""" - def test_success(self) -> None: + def test_success(self, test_schema_url: str) -> None: """Test for successful result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_dependencies", - return_value=["attribute1", "attribute2"], - ): - result, status = list_node_dependencies( - schema_url="xxx", - node_label="label", - ) - assert status == 200 - assert isinstance(result, NodesPage) + result, status = list_node_dependencies( + schema_url=test_schema_url, + node_label="Patient", + ) + assert status == 200 + assert isinstance(result, NodesPage) def test_internal_error(self) -> None: """Test for 500 result""" - with patch.object( - schematic_api.controllers.schema_controller_impl, - "get_node_dependencies", - side_effect=TypeError, - ): - result, status = list_node_dependencies( - schema_url="xxx", - node_label="label", - ) - assert status == 500 - assert isinstance(result, BasicError) + result, status = list_node_dependencies( + schema_url="not_a_url", + node_label="Patient", + ) + assert status == 500 + assert isinstance(result, BasicError) diff --git a/apps/schematic/api/schematic_api/test/test_synapse_endpoints.py b/apps/schematic/api/schematic_api/test/test_synapse_endpoints.py new file mode 100644 index 0000000000..514278b6eb --- /dev/null +++ b/apps/schematic/api/schematic_api/test/test_synapse_endpoints.py @@ -0,0 +1,178 @@ +"""Tests for endpoints that use Synapse without mocking the Synapse client""" + +import json +import os + +import pytest +import yaml +import pandas as pd + +from schematic_api.test import BaseTestCase +from .conftest import ( + TEST_SCHEMA_URL, + CORRECT_MANIFEST_PATH, + csv_to_bytes, + csv_to_json_str, +) + +SECRETS_FILE = "schematic_api/test/data/synapse_config.yaml" +EXAMPLE_SECRETS_FILE = "schematic_api/test/data/synapse_config_example.yaml" + +if os.path.exists(SECRETS_FILE): + with open(SECRETS_FILE, "r", encoding="utf-8") as file: + secrets = yaml.safe_load(file) +else: + with open(EXAMPLE_SECRETS_FILE, "r", encoding="utf-8") as file: + secrets = yaml.safe_load(file) + +SYNAPSE_TOKEN = secrets["synapse_token"] +TEST_DATASET = secrets["test_dataset"] +TEST_MANIFEST = secrets["test_manifest"] +TEST_ASSET_VIEW = secrets["test_asset_view"] +TEST_PROJECT = secrets["test_project"] + +HEADERS = { + "Accept": "application/json", + "Authorization": f"Bearer {SYNAPSE_TOKEN}", +} + + +@pytest.mark.synapse +@pytest.mark.secrets +class TestValidationEndpoints(BaseTestCase): + """Integration tests""" + + def test_submit_manifest_csv(self) -> None: + """Test for successful result""" + url = ( + f"/api/v1/submitManifestCsv?schemaUrl={TEST_SCHEMA_URL}" + "&component=Biospecimen" + f"&datasetId={TEST_DATASET}" + f"&assetViewId={TEST_ASSET_VIEW}" + "&storageMethod=file_only" + ) + body = csv_to_bytes(CORRECT_MANIFEST_PATH) + response = self.client.open(url, method="POST", headers=HEADERS, data=body) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, str) + + def test_submit_manifest_json(self) -> None: + """Test for successful result""" + url = ( + f"/api/v1/submitManifestJson?schemaUrl={TEST_SCHEMA_URL}" + "&component=Biospecimen" + f"&datasetId={TEST_DATASET}" + f"&assetViewId={TEST_ASSET_VIEW}" + "&storageMethod=file_only" + ) + body = csv_to_json_str(CORRECT_MANIFEST_PATH) + response = self.client.open(url, method="POST", headers=HEADERS, data=body) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, str) + + +@pytest.mark.synapse +@pytest.mark.secrets +class TestStorageEndpoints(BaseTestCase): + """Integration tests""" + + def test_get_asset_view_json(self) -> None: + """Test for successful result""" + url = "/api/v1/assetTypes/synapse/" f"assetViews/{TEST_ASSET_VIEW}/json" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, str) + response_dict = json.loads(response.json) + assert isinstance(response_dict, dict) + dataframe = pd.DataFrame.from_dict(response_dict) + assert isinstance(dataframe, pd.DataFrame) + + def test_get_dataset_files(self) -> None: + """Test for successful result""" + url = ( + "/api/v1/assetTypes/synapse/" + f"datasets/{TEST_DATASET}/files" + f"?assetViewId={TEST_ASSET_VIEW}" + ) + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, dict) + assert "files" in response.json + assert isinstance(response.json["files"], list) + for fle in response.json["files"]: + assert isinstance(fle, dict) + for key in ["id", "name"]: + assert key in fle + + def test_get_dataset_manifest_json(self) -> None: + """Test for successful result""" + url = ( + "/api/v1/assetTypes/synapse/" + f"datasets/{TEST_DATASET}/manifestJson" + f"?assetViewId={TEST_ASSET_VIEW}" + ) + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, str) + response_dict = json.loads(response.json) + assert isinstance(response_dict, dict) + dataframe = pd.DataFrame.from_dict(response_dict) + assert isinstance(dataframe, pd.DataFrame) + + def test_get_manifest_json(self) -> None: + """Test for successful result""" + url = "/api/v1/assetTypes/synapse/" f"manifests/{TEST_MANIFEST}/json" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, str) + response_dict = json.loads(response.json) + assert isinstance(response_dict, dict) + dataframe = pd.DataFrame.from_dict(response_dict) + assert isinstance(dataframe, pd.DataFrame) + + def test_get_projects(self) -> None: + """Test for successful result""" + url = "/api/v1/assetTypes/synapse/" f"assetViews/{TEST_ASSET_VIEW}/projects" + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, dict) + assert "projects" in response.json + assert isinstance(response.json["projects"], list) + for project in response.json["projects"]: + assert isinstance(project, dict) + for key in ["id", "name"]: + assert key in project + + def test_get_project_datasets(self) -> None: + """Test for successful result""" + url = ( + "/api/v1/assetTypes/synapse/" + f"projects/{TEST_PROJECT}/datasets" + f"?assetViewId={TEST_ASSET_VIEW}" + ) + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, dict) + assert "datasets" in response.json + assert isinstance(response.json["datasets"], list) + for dataset in response.json["datasets"]: + assert isinstance(dataset, dict) + for key in ["id", "name"]: + assert key in dataset + + def test_get_project_manifests(self) -> None: + """Test for successful result""" + url = ( + "/api/v1/assetTypes/synapse/" + f"projects/{TEST_PROJECT}/manifests" + f"?assetViewId={TEST_ASSET_VIEW}" + ) + response = self.client.open(url, method="GET", headers=HEADERS) + self.assert200(response, f"Response body is : {response.data.decode('utf-8')}") + assert isinstance(response.json, dict) + assert "manifests" in response.json + assert isinstance(response.json["manifests"], list) + for manifest in response.json["manifests"]: + assert isinstance(manifest, dict) + for key in ["componentName", "datasetId", "datasetName", "id", "name"]: + assert key in manifest diff --git a/apps/schematic/api/schematic_api/test/test_tangled_tree_impl.py b/apps/schematic/api/schematic_api/test/test_tangled_tree_impl.py index 8e98d5b0ee..bc34aeb2bd 100644 --- a/apps/schematic/api/schematic_api/test/test_tangled_tree_impl.py +++ b/apps/schematic/api/schematic_api/test/test_tangled_tree_impl.py @@ -19,12 +19,12 @@ def test_success(self, test_schema_url: str) -> None: assert status == 200 assert isinstance(result, str) - def test_internal_error(self) -> None: - """Test for 500 result""" + def test_404_error(self) -> None: + """Test for 404 result""" result, status = get_tangled_tree_layers( schema_url="not_a_url", figure_type="component" ) - assert status == 500 + assert status == 404 assert isinstance(result, BasicError) @@ -40,11 +40,11 @@ def test_success(self, test_schema_url: str) -> None: assert status == 200 assert isinstance(result, str) - def test_internal_error(self) -> None: - """Test for 500 result""" + def test_404_error(self) -> None: + """Test for 404 result""" res = get_tangled_tree_text( schema_url="not_a_url", figure_type="component", text_format="plain" ) (result, status) = res # pylint: disable=unpacking-non-sequence - assert status == 500 + assert status == 404 assert isinstance(result, BasicError) diff --git a/apps/schematic/api/schematic_api/test/test_utils.py b/apps/schematic/api/schematic_api/test/test_utils.py new file mode 100644 index 0000000000..9925217458 --- /dev/null +++ b/apps/schematic/api/schematic_api/test/test_utils.py @@ -0,0 +1,78 @@ +"""Tests for utils""" + +import pytest + +from synapseclient.core.exceptions import ( # type: ignore + SynapseNoCredentialsError, + SynapseAuthenticationError, +) +from schematic.exceptions import AccessCredentialsError # type: ignore + +from schematic_api.controllers.utils import ( + handle_exceptions, + download_schema_file_as_jsonld, + InvalidSchemaURL, +) +from schematic_api.models.basic_error import BasicError + + +@handle_exceptions +def func(exc: BaseException, raise_error: bool = True) -> tuple[str, int]: + """used to test decorator""" + if raise_error: + raise exc + return ("xxx", 200) + + +class TestHandleExceptions: + """Tests handle_exceptions""" + + def test_401(self) -> None: + "Tests for 401 status" + res, status = func(SynapseNoCredentialsError) + assert status == 401 + assert isinstance(res, BasicError) + + res, status = func(SynapseAuthenticationError) + assert status == 401 + assert isinstance(res, BasicError) + + def test_403(self) -> None: + "Tests for 403 status" + res, status = func(AccessCredentialsError("project")) + assert status == 403 + assert isinstance(res, BasicError) + + def test_404(self) -> None: + "Tests for 404 status" + res, status = func(InvalidSchemaURL("message", "url")) + assert status == 404 + assert isinstance(res, BasicError) + + def test_500(self) -> None: + "Tests for 500 status" + res, status = func(TypeError) + assert status == 500 + assert isinstance(res, BasicError) + + +class TestDownloadSchemaFileAsJsonLD: + "tests download_schema_file_as_jsonld" + + def test_success(self, test_schema_url: str) -> None: + "tests for successful download" + file_path = download_schema_file_as_jsonld(test_schema_url) + assert file_path + + def test_failure(self) -> None: + "tests for exception" + with pytest.raises( + InvalidSchemaURL, match="The provided URL is incorrectly formatted: xxx" + ): + download_schema_file_as_jsonld("xxx") + + with pytest.raises( + InvalidSchemaURL, + match="The provided URL could not be found: https://raw.github.com/model.jsonld", + ): + download_schema_file_as_jsonld("https://raw.github.com/model.jsonld")