aboutsummaryrefslogtreecommitdiff
path: root/src/test/test_server.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/test_server.py')
-rw-r--r--src/test/test_server.py199
1 files changed, 199 insertions, 0 deletions
diff --git a/src/test/test_server.py b/src/test/test_server.py
new file mode 100644
index 0000000..def48dc
--- /dev/null
+++ b/src/test/test_server.py
@@ -0,0 +1,199 @@
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+# Repository tests
+#
+# This file is part of Hydrilla
+#
+# Copyright (C) 2021, 2022 Wojtek Kosior
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+#
+# I, Wojtek Kosior, thereby promise not to sue for violation of this
+# file's license. Although I request that you do not make use this code
+# in a proprietary program, I am not going to enforce this in court.
+
+import pytest
+import sys
+import shutil
+import json
+
+from pathlib import Path
+from hashlib import sha256
+from tempfile import TemporaryDirectory
+from typing import Iterable, Callable
+
+from flask.testing import FlaskClient
+from markupsafe import escape
+
+from hydrilla import util as hydrilla_util
+from hydrilla.builder import Build
+from hydrilla.server import create_app
+
+here = Path(__file__).resolve().parent
+config_path = here / 'development_config.json'
+source_path = here / 'source-package-example'
+
+@pytest.fixture(scope="session")
+def default_setup() -> Iterable[dict[str, Path]]:
+ with TemporaryDirectory() as tmpdir:
+ setup = {
+ 'malcontent_dir': Path(tmpdir) / 'sample_malcontent',
+ 'config_path': Path(tmpdir) / 'config.json',
+ 'containing_dir': Path(tmpdir)
+ }
+
+ setup['config_path'].symlink_to(config_path)
+
+ build = Build(source_path, Path('index.json'))
+ build.write_package_files(setup['malcontent_dir'])
+
+ yield setup
+
+@pytest.fixture(scope="session")
+def client(default_setup: dict[str, Path]) -> Iterable[FlaskClient]:
+ """Provide app client that serves the object from built sample package."""
+ app = create_app(default_setup['config_path'],
+ flask_config={'TESTING': True})
+
+ with app.test_client() as client:
+ yield client
+
+@pytest.fixture(scope="session")
+def development_config(default_setup) -> Iterable[dict]:
+ """Provide the contents of JSON config file fed to the client."""
+ contents = default_setup['config_path'].read_text()
+ yield json.loads(hydrilla_util.strip_json_comments(contents))
+
+def test_project_url(client: FlaskClient, development_config: dict) -> None:
+ """Fetch index.html and verify project URL fro config is present there."""
+ response = client.get('/')
+ assert b'html' in response.data
+ project_url = development_config['hydrilla_project_url']
+ assert escape(project_url).encode() in response.data
+
+@pytest.mark.parametrize('item_type', ['resource', 'mapping'])
+def test_get_newest(client: FlaskClient, item_type: str) -> None:
+ """
+ Verify that
+ GET '/{item_type}/{item_identifier}.json'
+ returns proper definition that is also served at:
+ GET '/{item_type}/{item_identifier}/{item_version}'
+ """
+ response = client.get(f'/{item_type}/helloapple.json')
+ assert response.status_code == 200
+ definition = json.loads(response.data.decode())
+ assert definition['type'] == item_type
+ assert definition['identifier'] == 'helloapple'
+
+ response = client.get(f'/{item_type}/helloapple/2021.11.10')
+ assert response.status_code == 200
+ assert definition == json.loads(response.data.decode())
+
+ hydrilla_util.validator_for(f'api_{item_type}_description-1.schema.json')\
+ .validate(definition)
+
+@pytest.mark.parametrize('item_type', ['resource', 'mapping'])
+def test_get_nonexistent(client: FlaskClient, item_type: str) -> None:
+ """
+ Verify that attempts to GET a JSON definition of a nonexistent item or item
+ version result in 404.
+ """
+ response = client.get(f'/{item_type}/nonexistentapple.json')
+ assert response.status_code == 404
+ response = client.get(f'/{item_type}/helloapple/1.2.3.999')
+ assert response.status_code == 404
+
+@pytest.mark.parametrize('item_type', ['resource', 'mapping'])
+def test_file_refs(client: FlaskClient, item_type: str) -> None:
+ """
+ Verify that files referenced by definitions are accessible under their
+ proper URLs and that their hashes match.
+ """
+ response = client.get(f'/{item_type}/helloapple/2021.11.10')
+ assert response.status_code == 200
+ definition = json.loads(response.data.decode())
+
+ for file_ref in [*definition.get('scripts', []),
+ *definition['source_copyright']]:
+ hash_sum = file_ref["sha256"]
+ response = client.get(f'/file/sha256-{hash_sum}')
+
+ assert response.status_code == 200
+ assert sha256(response.data).digest().hex() == hash_sum
+
+def test_empty_query(client: FlaskClient) -> None:
+ """
+ Verify that querying mappings for URL gives an empty list when there're no
+ mathes.
+ """
+ response = client.get(f'/query?url=https://nonexiste.nt/example')
+ assert response.status_code == 200
+
+ response_object = json.loads(response.data.decode())
+
+ assert response_object['mappings'] == []
+
+ hydrilla_util.validator_for('api_query_result-1.schema.json')\
+ .validate(response_object)
+
+def test_query(client: FlaskClient) -> None:
+ """
+ Verify that querying mappings for URL gives a list with reference(s) the the
+ matching mapping(s).
+ """
+ response = client.get(f'/query?url=https://hydrillabugs.koszko.org/')
+ assert response.status_code == 200
+
+ response_object = json.loads(response.data.decode())
+
+ assert response_object['mappings'] == [{
+ 'identifier': 'helloapple',
+ 'long_name': 'Hello Apple',
+ 'version': [2021, 11, 10]
+ }]
+
+ hydrilla_util.validator_for('api_query_result-1.schema.json')\
+ .validate(response_object)
+
+def test_source(client: FlaskClient) -> None:
+ """Verify source descriptions are properly served."""
+ response = client.get(f'/source/hello.json')
+ assert response.status_code == 200
+
+ description = json.loads(response.data.decode())
+ assert description['source_name'] == 'hello'
+
+ assert sorted([d['identifier'] for d in description['definitions']]) == \
+ ['hello-message', 'helloapple', 'helloapple']
+
+ zipfile_hash = description['source_archives']['zip']['sha256']
+ response = client.get(f'/source/hello.zip')
+ assert sha256(response.data).digest().hex() == zipfile_hash
+
+ hydrilla_util.validator_for('api_source_description-1.schema.json')\
+ .validate(description)
+
+def test_missing_source(client: FlaskClient) -> None:
+ """Verify requests for nonexistent sources result in 404."""
+ response = client.get(f'/source/nonexistent.json')
+ assert response.status_code == 404
+
+ response = client.get(f'/source/nonexistent.zip')
+ assert response.status_code == 404
+
+def test_normalize_version():
+ assert hydrilla_util.normalize_version([4, 5, 3, 0, 0]) == [4, 5, 3]
+ assert hydrilla_util.normalize_version([1, 0, 5, 0]) == [1, 0, 5]
+ assert hydrilla_util.normalize_version([3, 3]) == [3, 3]