diff --git a/.github/workflows/push-tests.yml b/.github/workflows/push-tests.yml index a85e84c..2fe6396 100644 --- a/.github/workflows/push-tests.yml +++ b/.github/workflows/push-tests.yml @@ -25,3 +25,29 @@ jobs: - uses: actions/setup-python@v5 - name: lint checks uses: pre-commit/action@v3.0.0 + + unit-test-ontonolgy-builder: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.ref }} + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.11 + - name: Python cache + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install dependencies + run: | + pip install -r tools/ontology-builder/requirements.txt + pip install -r tools/ontology-builder/requirements-dev.txt + - name: Ontology Dry Run Unit Tests + run: | + cd tools/ontology-builder && make unit-tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 22dd087..f96d6bb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,3 +34,4 @@ repos: - id: mypy args: [--strict, --ignore-missing-imports] additional_dependencies: [types-PyYAML] + files: ^(api/python|tools/ontology-builder/src)/ diff --git a/Makefile b/Makefile index 0be50d2..6d5d8a4 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,2 @@ lint: pre-commit run --all-files - diff --git a/tools/ontology-builder/Makefile b/tools/ontology-builder/Makefile new file mode 100644 index 0000000..3422404 --- /dev/null +++ b/tools/ontology-builder/Makefile @@ -0,0 +1,2 @@ +unit-tests: + python -m pytest tests diff --git a/tools/ontology-builder/README.md b/tools/ontology-builder/README.md index e69de29..80b3104 100644 --- a/tools/ontology-builder/README.md +++ b/tools/ontology-builder/README.md @@ -0,0 +1,4 @@ +# Run Tests + +1. `pip install -r requirements.txt -r requirements-dev.txt` +2. `make unit-tests` diff --git a/tools/ontology-builder/pyproject.toml b/tools/ontology-builder/pyproject.toml new file mode 100644 index 0000000..748eaf7 --- /dev/null +++ b/tools/ontology-builder/pyproject.toml @@ -0,0 +1,4 @@ +[tool.pytest.ini_options] +pythonpath = [ + "src" +] diff --git a/tools/ontology-builder/requirements-dev.txt b/tools/ontology-builder/requirements-dev.txt new file mode 100644 index 0000000..e079f8a --- /dev/null +++ b/tools/ontology-builder/requirements-dev.txt @@ -0,0 +1 @@ +pytest diff --git a/tools/ontology-builder/tests/test_all_ontology_generator.py b/tools/ontology-builder/tests/test_all_ontology_generator.py new file mode 100644 index 0000000..4439b25 --- /dev/null +++ b/tools/ontology-builder/tests/test_all_ontology_generator.py @@ -0,0 +1,85 @@ +import os +import urllib.request +from unittest.mock import MagicMock, patch + +import pytest +from all_ontology_generator import _download_ontologies, _parse_ontologies + + +@pytest.fixture +def mock_ontology_info(tmpdir): + # Create a temporary ontology info yaml file + onto_info_yml = tmpdir.join("ontology_info.yml") + onto_info_yml.write("ontology_name:\n source: http://example.com\n version: v1\n filetype: owl\n") + return str(onto_info_yml) + + +@pytest.fixture +def mock_raw_ontology_dir(tmpdir): + # Create a temporary directory for raw ontology files + raw_ontology_dir = tmpdir.mkdir("raw_ontology") + return str(raw_ontology_dir) + + +@pytest.fixture +def mock_parsed_ontology_file(tmpdir): + # Create a temporary gzipped json file for parsed ontology data + parsed_ontology_file = tmpdir.join("parsed_ontologies.json.gz") + return str(parsed_ontology_file) + + +def test_download_ontologies(mock_ontology_info, mock_raw_ontology_dir): + # Mocking urllib.request.urlretrieve, urllib.request.urlopen + with patch("urllib.request.urlretrieve") as mock_urlretrieve, patch("urllib.request.urlopen") as mock_urlopen: + # Mock HTTP response + mock_response = MagicMock() + mock_response.code = 200 + mock_urlopen.return_value = mock_response + + # Call the function + _download_ontologies(onto_info_yml=mock_ontology_info, output_dir=mock_raw_ontology_dir) + + mock_urlretrieve.assert_called_once() + + +def test_parse_ontologies(mock_raw_ontology_dir, mock_parsed_ontology_file): + # Mocking _load_ontology_object and _extract_ontology_term_metadata + with patch("all_ontology_generator._load_ontology_object") as mock_load_ontology, patch( + "all_ontology_generator._extract_ontology_term_metadata" + ) as mock_extract_metadata: + # Mock return values + mock_load_ontology.return_value = MagicMock(name="ontology_object") + mock_extract_metadata.return_value = {"term_id": {"label": "Term Label", "deprecated": False, "ancestors": []}} + + # Call the function + _parse_ontologies(working_dir=mock_raw_ontology_dir, output_json_file=mock_parsed_ontology_file) + + # Assert _load_ontology_object is called for each ontology file + assert mock_load_ontology.call_count == len(os.listdir(mock_raw_ontology_dir)) + + # Assert _extract_ontology_term_metadata is called for each ontology object + assert mock_extract_metadata.call_count == len(os.listdir(mock_raw_ontology_dir)) + + +def test_download_ontologies_http_error(mock_ontology_info, mock_raw_ontology_dir): + # Mocking urllib.request.urlopen to raise HTTPError + with patch("urllib.request.urlopen") as mock_urlopen: + mock_urlopen.side_effect = urllib.error.HTTPError( + url="http://example.com", code=404, msg="Not Found", hdrs={}, fp=None + ) + + # Assertion + with pytest.raises(Exception) as exc_info: + _download_ontologies(onto_info_yml=mock_ontology_info, output_dir=mock_raw_ontology_dir) + assert "returns status code 404" in str(exc_info.value) + + +def test_download_ontologies_url_error(mock_ontology_info, mock_raw_ontology_dir): + # Mocking urllib.request.urlopen to raise URLError + with patch("urllib.request.urlopen") as mock_urlopen: + mock_urlopen.side_effect = urllib.error.URLError(reason="Connection refused") + + # Assertion + with pytest.raises(Exception) as exc_info: + _download_ontologies(onto_info_yml=mock_ontology_info, output_dir=mock_raw_ontology_dir) + assert "fails due to Connection refused" in str(exc_info.value)