diff --git a/node_registry.schema.json b/node_registry.schema.json index 688963c..7859e5a 100644 --- a/node_registry.schema.json +++ b/node_registry.schema.json @@ -54,88 +54,17 @@ } }, { - "contains": { - "type": "object", - "properties": { - "rel": { - "type": "string", - "const": "version" - } - } - } + "$ref": "#/$defs/link-version" }, { - "contains": { - "type": "object", - "properties": { - "rel": { - "type": "string", - "const": "service" - } - } - } + "$ref": "#/$defs/link-service" } ] }, "services": { "type": "array", "items": { - "$anchor": "service", - "type": "object", - "required": [ - "name", - "keywords", - "description", - "links" - ], - "properties": { - "name": { - "type": "string", - "minLength": 1 - }, - "keywords": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "pattern": "^catalog|data|jupyterhub|other|service-(wps|wms|wfs|wcs|ogcapi_processes)$" - } - }, - "description": { - "type": "string", - "minLength": 1 - }, - "links": { - "type": "array", - "items": { - "$ref": "https://json-schema.org/draft/2020-12/links" - }, - "allOf": [ - { - "contains": { - "type": "object", - "properties": { - "rel": { - "type": "string", - "const": "service" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "rel": { - "type": "string", - "const": "service-doc" - } - } - } - } - ] - } - } + "$ref": "#/$defs/service" } }, "last_updated": { @@ -176,5 +105,91 @@ "additionalProperties": false } }, - "additionalProperties": false + "additionalProperties": false, + "$defs": { + "link-service": { + "contains": { + "type": "object", + "required": ["rel"], + "properties": { + "rel": { + "type": "string", + "const": "service" + } + } + } + }, + "link-service-doc": { + "contains": { + "type": "object", + "required": ["rel"], + "properties": { + "rel": { + "type": "string", + "const": "service-doc" + } + } + } + }, + "link-version": { + "contains": { + "type": "object", + "required": ["rel"], + "properties": { + "rel": { + "type": "string", + "const": "version" + } + } + } + }, + "service": { + "$anchor": "service", + "type": "object", + "required": [ + "name", + "keywords", + "description", + "links" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "keywords": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^catalog|data|jupyterhub|other|service-(wps|wms|wfs|wcs|ogcapi_processes)$" + } + }, + "description": { + "type": "string", + "minLength": 1 + }, + "version": { + "type": "string", + "$comment": "Pattern from 'https://semver.org/'.", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + "links": { + "$comment": "Links for individual services does not enforce 'rel: version' for backward compatibility.", + "type": "array", + "items": { + "$ref": "https://json-schema.org/draft/2020-12/links" + }, + "allOf": [ + { + "$ref": "#/$defs/link-service" + }, + { + "$ref": "#/$defs/link-service-doc" + } + ] + } + } + } + } } diff --git a/tests/test_registry.py b/tests/test_registry.py index 0a82dbb..b94105e 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -1,3 +1,4 @@ +import copy import os import json @@ -26,6 +27,33 @@ def registry_content(request): return content +@pytest.fixture +def registry_content_with_services(registry_content): + any_svc_key = list(registry_content)[0] + registry_content["test"] = copy.deepcopy(registry_content[any_svc_key]) + registry_content["test"]["services"] = [ + { + "name": "test-service", + "keywords": ["other"], + "description": "test service", + "version": "1.2.3", + "links": [ + { + "rel": "service", + "href": "https://example.com/test", + "type": "application/json" + }, + { + "rel": "service-doc", + "href": "https://readthedocs.com/example", + "type": "text/html" + } + ] + } + ] + return registry_content + + @pytest.fixture def schema_content(request): """Return the content contained in node_registry.schema.json""" @@ -38,3 +66,43 @@ def schema_content(request): def test_valid_registry(registry_content, schema_content): jsonschema.validate(registry_content, schema_content) + + +def test_services_good_version(registry_content_with_services, schema_content): + jsonschema.validate(registry_content_with_services, schema_content) + + +def test_services_bad_version(registry_content_with_services, schema_content): + registry_content_with_services["test"]["services"][0]["version"] = "bad_version" + with pytest.raises(jsonschema.exceptions.ValidationError) as exc: + jsonschema.validate(registry_content_with_services, schema_content) + assert "bad_version" in exc.value.message + assert list(exc.value.path) == ["test", "services", 0, "version"] + assert list(exc.value.schema_path)[-6:] == ["properties", "services", "items", "properties", "version", "pattern"] + + +def test_services_bad_links(registry_content_with_services, schema_content): + registry_content_with_services["test"]["services"][0].pop("links") + with pytest.raises(jsonschema.exceptions.ValidationError) as exc: + jsonschema.validate(registry_content_with_services, schema_content) + assert exc.value.message == "'links' is a required property" + assert list(exc.value.path) == ["test", "services", 0] + assert list(exc.value.schema_path)[-4:] == ["properties", "services", "items", "required"] + + +@pytest.mark.parametrize( + ["required_link_rel", "expected_link_pos"], + [ + ("service", 0), + ("service-doc", 1), + ] +) +def test_services_missing_link(registry_content_with_services, schema_content, required_link_rel, expected_link_pos): + links = registry_content_with_services["test"]["services"][0]["links"] + links = [link for link in links if link["rel"] != required_link_rel] + registry_content_with_services["test"]["services"][0]["links"] = links + with pytest.raises(jsonschema.exceptions.ValidationError) as exc: + jsonschema.validate(registry_content_with_services, schema_content) + assert "does not contain" in exc.value.message + assert list(exc.value.path) == ["test", "services", 0, "links"] + assert list(exc.value.schema_path)[-4:] == ["links", "allOf", expected_link_pos, "contains"]