summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWojtek Kosior <koszko@koszko.org>2022-02-18 16:02:16 +0100
committerWojtek Kosior <koszko@koszko.org>2022-02-18 16:12:14 +0100
commitfb6dd284fad64a4b69e44aea38852b38819b5eb1 (patch)
tree244c100b744a7040ce9185b82ff99d7da4e8771f
parentf5fd82f430d765f3ed76104133ebee5d8375dc03 (diff)
downloadhaketilo-hydrilla-fb6dd284fad64a4b69e44aea38852b38819b5eb1.tar.gz
haketilo-hydrilla-fb6dd284fad64a4b69e44aea38852b38819b5eb1.zip
make "uuid" an optional property
-rw-r--r--pyproject.toml2
-rw-r--r--pytest.ini4
-rw-r--r--src/hydrilla/server/serve.py7
m---------src/test/source-package-example0
-rw-r--r--src/test/test_server.py150
5 files changed, 115 insertions, 48 deletions
diff --git a/pyproject.toml b/pyproject.toml
index f053c4f..566d0ad 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@
[build-system]
build-backend = "setuptools.build_meta"
-requires = ["setuptools>=45", "wheel", "setuptools_scm>=6.2"]
+requires = ["setuptools>=45", "wheel", "setuptools_scm>=5.0"]
[tool.setuptools_scm]
write_to = "src/hydrilla/server/_version.py"
diff --git a/pytest.ini b/pytest.ini
index 030df26..b4ea538 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -10,3 +10,7 @@
filterwarnings =
ignore::DeprecationWarning:werkzeug.*:
ignore::DeprecationWarning:jinja2.*:
+
+markers =
+ mod_before_build: define a callback to use to modify test packages before their build
+ mod_after_build: define a callback to use to modify test packages after their build
diff --git a/src/hydrilla/server/serve.py b/src/hydrilla/server/serve.py
index bb53c0a..9bcfb93 100644
--- a/src/hydrilla/server/serve.py
+++ b/src/hydrilla/server/serve.py
@@ -57,7 +57,7 @@ class ItemInfo(ABC):
"""Initialize ItemInfo using item definition read from JSON."""
self.version = util.normalize_version(item_obj['version'])
self.identifier = item_obj['identifier']
- self.uuid = item_obj['uuid']
+ self.uuid = item_obj.get('uuid')
self.long_name = item_obj['long_name']
def path(self) -> str:
@@ -111,8 +111,11 @@ class VersionedItemInfo:
"""
if self.identifier is None:
self.identifier = item_info.identifier
+
+ if self.uuid is None:
self.uuid = item_info.uuid
- elif self.uuid != item_info.uuid:
+
+ if self.uuid is not None and self.uuid != item_info.uuid:
raise ValueError(f_('uuid_mismatch_{identifier}')
.format(identifier=self.identifier))
diff --git a/src/test/source-package-example b/src/test/source-package-example
-Subproject 48606f288c89aaadb28a021ab71945e1e87fd14
+Subproject 92a4d31c659b2336e5e188877d1ce6bfad2fa31
diff --git a/src/test/test_server.py b/src/test/test_server.py
index bbea257..2c2d50e 100644
--- a/src/test/test_server.py
+++ b/src/test/test_server.py
@@ -32,10 +32,11 @@ import json
from pathlib import Path
from hashlib import sha256
from tempfile import TemporaryDirectory
-from typing import Iterable, Callable
+from typing import Callable, Optional
from flask.testing import FlaskClient
from markupsafe import escape
+from werkzeug import Response
from hydrilla import util as hydrilla_util
from hydrilla.builder import Build
@@ -50,98 +51,157 @@ expected_generated_by = {
'version': _version.version
}
-@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)
- }
+SetupMod = Optional[Callable['Setup', None]]
- setup['config_path'].symlink_to(config_path)
+source_files = (
+ 'index.json', 'hello.js', 'bye.js', 'message.js', 'README.txt',
+ 'README.txt.license', '.reuse/dep5', 'LICENSES/CC0-1.0.txt'
+)
- build = Build(source_path, Path('index.json'))
- build.write_package_files(setup['malcontent_dir'])
+class Setup:
+ """
+ Facilitate preparing test malcontent directory, Hydrilla config file and the
+ actual Flask client. In a customizable way.
+ """
+ def __init__(self, modify_before_build: SetupMod=None,
+ modify_after_build: SetupMod=None) -> None:
+ """Initialize Setup."""
+ self._modify_before_build = modify_before_build
+ self._modify_after_build = modify_after_build
+ self._config = None
+ self._client = None
+
+ def _prepare(self) -> None:
+ """Perform the build and call the callbacks as appropriate."""
+ self.tmpdir = TemporaryDirectory()
+
+ self.containing_dir = Path(self.tmpdir.name)
+ self.malcontent_dir = self.containing_dir / 'sample_malcontent'
+ self.index_json = Path('index.json')
+
+ self.source_dir = self.containing_dir / 'sample_source_package'
+ for source_file in source_files:
+ dst_path = self.source_dir / source_file
+ dst_path.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copyfile(source_path / source_file, dst_path)
+
+ self.config_path = self.containing_dir / 'config.json'
+ shutil.copyfile(config_path, self.config_path)
+
+ if self._modify_before_build:
+ self._modify_before_build(self)
+
+ build = Build(self.source_dir, self.index_json)
+ build.write_package_files(self.malcontent_dir)
- yield setup
+ if self._modify_after_build:
+ self._modify_after_build(self)
-@pytest.fixture(scope="session")
-def test_config(default_setup) -> Iterable[dict]:
- """Provide the contents of JSON config file fed to the client."""
- yield config.load([default_setup['config_path']])
+ def config(self) -> dict:
+ """Provide the contents of JSON config file used."""
+ if self._config is None:
+ self._prepare()
+ self._config = config.load([self.config_path])
-@pytest.fixture(scope="session")
-def client(test_config: dict) -> Iterable[FlaskClient]:
- """Provide app client that serves the object from built sample package."""
- app = HydrillaApp(test_config, flask_config={'TESTING': True})
+ return self._config
- with app.test_client() as client:
- yield client
+ def client(self) -> FlaskClient:
+ """
+ Provide app client that serves the objects from built sample package.
+ """
+ if self._client is None:
+ app = HydrillaApp(self.config(), flask_config={'TESTING': True})
+ self._client = app.test_client()
-def test_project_url(client: FlaskClient, test_config: dict) -> None:
+ return self._client
+
+def remove_all_uuids(setup: Setup) -> None:
+ """Modify sample packages before build to contain no (optional) UUIDs"""
+ index_json = (setup.source_dir / 'index.json').read_text()
+ index_json = json.loads(hydrilla_util.strip_json_comments(index_json))
+
+ for definition in index_json['definitions']:
+ del definition['uuid']
+
+ index_json = ("// SPDX-License-Identifier: CC0-1.0\n" +
+ "// Copyright (C) 2021, 2022 Wojtek Kosior\n" +
+ json.dumps(index_json))
+
+ (setup.source_dir / 'index.json').write_text(index_json)
+
+default_setup = Setup()
+uuidless_setup = Setup(modify_before_build=remove_all_uuids)
+
+def def_get(url: str) -> Response:
+ """Convenience wrapper for def_get()"""
+ return default_setup.client().get(url)
+
+def test_project_url() -> None:
"""Fetch index.html and verify project URL from config is present there."""
- response = client.get('/')
+ response = def_get('/')
assert b'html' in response.data
- project_url = test_config['hydrilla_project_url']
+ project_url = default_setup.config()['hydrilla_project_url']
assert escape(project_url).encode() in response.data
+@pytest.mark.parametrize('setup', [default_setup, uuidless_setup])
@pytest.mark.parametrize('item_type', ['resource', 'mapping'])
-def test_get_newest(client: FlaskClient, item_type: str) -> None:
+def test_get_newest(setup: Setup, 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')
+ response = setup.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')
+ response = setup.client().get(f'/{item_type}/helloapple/2021.11.10')
assert response.status_code == 200
assert definition == json.loads(response.data.decode())
+ assert ('uuid' in definition) == (setup is not uuidless_setup)
+
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:
+def test_get_nonexistent(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')
+ response = def_get(f'/{item_type}/nonexistentapple.json')
assert response.status_code == 404
- response = client.get(f'/{item_type}/helloapple/1.2.3.999')
+ response = def_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:
+def test_file_refs(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')
+ response = def_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}')
+ response = def_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:
+def test_empty_query() -> 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')
+ response = def_get(f'/query?url=https://nonexiste.nt/example')
assert response.status_code == 200
response_object = json.loads(response.data.decode())
@@ -155,12 +215,12 @@ def test_empty_query(client: FlaskClient) -> None:
hydrilla_util.validator_for('api_query_result-1.schema.json')\
.validate(response_object)
-def test_query(client: FlaskClient) -> None:
+def test_query() -> 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/')
+ response = def_get(f'/query?url=https://hydrillabugs.koszko.org/')
assert response.status_code == 200
response_object = json.loads(response.data.decode())
@@ -178,9 +238,9 @@ def test_query(client: FlaskClient) -> None:
hydrilla_util.validator_for('api_query_result-1.schema.json')\
.validate(response_object)
-def test_source(client: FlaskClient) -> None:
+def test_source() -> None:
"""Verify source descriptions are properly served."""
- response = client.get(f'/source/hello.json')
+ response = def_get(f'/source/hello.json')
assert response.status_code == 200
description = json.loads(response.data.decode())
@@ -190,18 +250,18 @@ def test_source(client: FlaskClient) -> None:
['hello-message', 'helloapple', 'helloapple']
zipfile_hash = description['source_archives']['zip']['sha256']
- response = client.get(f'/source/hello.zip')
+ response = def_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:
+def test_missing_source() -> None:
"""Verify requests for nonexistent sources result in 404."""
- response = client.get(f'/source/nonexistent.json')
+ response = def_get(f'/source/nonexistent.json')
assert response.status_code == 404
- response = client.get(f'/source/nonexistent.zip')
+ response = def_get(f'/source/nonexistent.zip')
assert response.status_code == 404
def test_normalize_version():