diff --git a/README.md b/README.md index a8a477b..98dbf3e 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,6 @@ FAILED sedr/schemat.py::test_conformance[GET /conformance] - AssertionError: Con ======================== 1 failed, 21 passed in 95.89s (0:01:35) ========================= ``` - ### Components Main components of the validator are: @@ -150,7 +149,8 @@ Main components of the validator are: ## How to contribute -Create an issue or start a discussion. Please do not contriute without discussing it first. +Create an issue or start a discussion. Please do not contriute without +discussing it first. ## Documentation Template diff --git a/sedr/edreq11.py b/sedr/edreq11.py index 9fcda48..523d65b 100644 --- a/sedr/edreq11.py +++ b/sedr/edreq11.py @@ -15,7 +15,7 @@ ] -def requirementA2_2_A5(jsondata: json) -> (bool, str): +def requirementA2_2_A5(jsondata: str) -> tuple[bool, str]: """Check if the conformance page contains the required EDR classes. jsondata should be the "conformsTo"-part of the conformance page. @@ -31,7 +31,7 @@ def requirementA2_2_A5(jsondata: json) -> (bool, str): return True, "" -def requirementA2_2_A7(version: int) -> (bool, str): +def requirementA2_2_A7(version: int) -> tuple[bool, str]: """Check if HTTP1.1 was used.""" spec_url = "https://docs.ogc.org/is/19-086r6/19-086r6.html#_0d0c25a0-850f-2aa5-9acb-06efcc04d452" if version == 11: @@ -40,7 +40,7 @@ def requirementA2_2_A7(version: int) -> (bool, str): return False, f"HTTP version 1.1 was not used. See <{spec_url}> for more info." -def requirementA11_1(jsondata: json) -> (bool, str): +def requirementA11_1(jsondata: str) -> tuple[bool, str]: """Check if the conformance page contains openapi classes, and that they match our version.""" spec_url = "https://docs.ogc.org/is/19-086r6/19-086r6.html#_cc7dd5e3-1d54-41ff-b5ba-c5fcb99fa663" diff --git a/sedr/schemat.py b/sedr/schemat.py index 8be4e0d..1973194 100644 --- a/sedr/schemat.py +++ b/sedr/schemat.py @@ -18,6 +18,15 @@ def set_up_schemathesis(args): """Set up schemathesis.""" if args.openapi_version == "3.1": schemathesis.experimental.OPEN_API_3_1.enable() + + if args.openapi == "": + # Attempt to find schema automatically + args.openapi = util.locate_openapi_url(args.url) + if len(args.openapi) == 0: + raise AssertionError( + f"Unable to find openapi spec for API. Please supply manually with --openapi " + ) + util.logger.info("Found openapi spec: %s", args.openapi) return schemathesis.from_uri(args.openapi, base_url=args.url) @@ -67,34 +76,19 @@ def test_landingpage(case): """Test that the landing page contains required elements.""" spec_ref = "https://docs.ogc.org/is/19-072/19-072.html#_7c772474-7037-41c9-88ca-5c7e95235389" response = case.call() - landingpage_json = None try: landingpage_json = json.loads(response.text) + landing, landing_message = util.parse_landing_json(landingpage_json) + if not landing: + raise AssertionError( + f"Landing page is missing required elements. See <{spec_ref}> for more info. {landing_message}" + ) + + util.logger.debug("Landingpage %s tested OK", response.url) except json.decoder.JSONDecodeError as e: - util.logger.error("Landing page is not valid JSON.") - raise AssertionError( - f"Expected valid JSON but got {e}. See {spec_ref} for more info." - ) from e - - if "title" not in landingpage_json: - util.logger.warning("Landing page does not contain a title.") - if "description" not in landingpage_json: - util.logger.warning("Landing page does not contain a description.") - assert ( - "links" in landingpage_json - ), "Landing page does not contain links. See {spec_ref} for more info." - for link in landingpage_json["links"]: - assert isinstance( - link, dict - ), f"Link {link} is not a dictionary. See {spec_ref} for more info." - assert ( - "href" in link - ), f"Link {link} does not have a href attribute. See {spec_ref} for more info." - assert ( - "rel" in link - ), f"Link {link} does not have a rel attribute. See {spec_ref} for more info." - - util.logger.debug("Landingpage %s tested OK", response.url) + util.logger.warning( + "Landing page is not valid JSON, other formats are not tested yet." + ) @schema.include(path_regex="^" + util.args.base_path + "conformance").parametrize() diff --git a/sedr/util.py b/sedr/util.py index 04e14da..6568500 100644 --- a/sedr/util.py +++ b/sedr/util.py @@ -1,5 +1,6 @@ import sys import logging +import requests import schemathesis import argparse import json @@ -14,13 +15,12 @@ def parse_args() -> argparse.Namespace: """Parse arguments.""" parser = argparse.ArgumentParser(description="schemathesis-edr") - parser.add_argument("-v", "--version", action='version', - version=f'{__version__}') + parser.add_argument("-v", "--version", action="version", version=f"{__version__}") parser.add_argument( "--openapi", type=str, help="URL to openapi spec for API", - default="https://edrisobaric.k8s.met.no/api", + default="", ) parser.add_argument( "--url", type=str, help="URL to API", default="https://edrisobaric.k8s.met.no" @@ -116,9 +116,8 @@ def parse_locations(jsondata): # ) -def test_conformance_links(jsondata): +def test_conformance_links(jsondata): # pylint: disable=unused-argument """Test that all conformance links are valid and resolves. - TODO: http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/collections doesn't work, so postponing this. """ @@ -136,5 +135,45 @@ def test_conformance_links(jsondata): return True, "" +def parse_landing_json(jsondata) -> tuple[bool, str]: + """Parse landing page if it is valid JSON.""" + # See https://github.com/metno/sedr/issues/6 + if "title" not in jsondata: + return False, "Landing page does not contain a title." + if "description" not in jsondata: + util.logger.warning("Landing page does not contain a description.") + if "links" not in jsondata: + return False, "Landing page does not contain links." + for link in jsondata["links"]: + if not isinstance(link, dict): + return False, f"Link {link} is not a dictionary." + if "href" not in link: + return False, f"Link {link} does not have a href attribute." + if "rel" not in link: + return False, f"Link {link} does not have a rel attribute." + return True, "" + + +def locate_openapi_url(url: str) -> str: + """Locate the OpenAPI URL based on main URL.""" + request = requests.get(url, timeout=10) + + # Json + # See https://github.com/metno/sedr/issues/6 + try: + if request.json(): + for link in request.json()["links"]: + if link["rel"] == "service-desc": + return link["href"] + except json.JSONDecodeError: + pass + + # TODO: + # Html + # Yaml + # Xml + return "" + + args = parse_args() logger = set_up_logging(args=args, logfile=args.log_file)