From 3bc814aaeaa1f45e113c5a0244d8c604bcd83958 Mon Sep 17 00:00:00 2001 From: Kash Blanc <100367078+Kashatlast2@users.noreply.github.com> Date: Tue, 13 Jun 2023 11:21:18 -0400 Subject: [PATCH] Added API Contract testcase for Coordinates endpoint (#7576) coordinates test passing Co-authored-by: yblanc545 --- .../testing/api_contract/v4/conftest.py | 28 +++++++ .../v4/data/request_template.json | 7 ++ .../v4/data/response_template.json | 27 ++++++ .../api_contract/v4/test_coordinates.py | 84 +++++++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 traffic_ops/testing/api_contract/v4/test_coordinates.py diff --git a/traffic_ops/testing/api_contract/v4/conftest.py b/traffic_ops/testing/api_contract/v4/conftest.py index f9f1b81f20..b75e45e0b4 100644 --- a/traffic_ops/testing/api_contract/v4/conftest.py +++ b/traffic_ops/testing/api_contract/v4/conftest.py @@ -990,3 +990,31 @@ def asn_data_post(to_session: TOSession, request_template_data: list[JSONData], response: tuple[JSONData, requests.Response] = to_session.create_asn(data=asn) resp_obj = check_template_data(response, "asn") return resp_obj + +@pytest.fixture(name="coordinate_post_data") +def coordinate_data_post(to_session: TOSession, request_template_data: list[JSONData] + ) -> dict[str, object]: + """ + PyTest Fixture to create POST data for coordinates endpoint. + :param to_session: Fixture to get Traffic Ops session. + :param request_template_data: Fixture to get coordinate request template from a prerequisites file. + :returns: Sample POST data and the actual API response. + """ + + coordinate = check_template_data(request_template_data["coordinates"], "coordinates") + + # Return new post data and post response from coordinates POST request + randstr = str(randint(0, 1000)) + try: + name = coordinate["name"] + if not isinstance(name, str): + raise TypeError(f"name must be str, not '{type(name)}'") + coordinate["name"] = name[:4] + randstr + except KeyError as e: + raise TypeError(f"missing coordinate property '{e.args[0]}'") from e + + logger.info("New coordinate data to hit POST method %s", coordinate) + # Hitting coordinates POST methed + response: tuple[JSONData, requests.Response] = to_session.create_coordinates(data=coordinate) + resp_obj = check_template_data(response, "coordinate") + return resp_obj diff --git a/traffic_ops/testing/api_contract/v4/data/request_template.json b/traffic_ops/testing/api_contract/v4/data/request_template.json index 3e2c7a66c6..94ebcb03ff 100644 --- a/traffic_ops/testing/api_contract/v4/data/request_template.json +++ b/traffic_ops/testing/api_contract/v4/data/request_template.json @@ -219,5 +219,12 @@ "asn": 1, "cachegroupId": 10 } + ], + "coordinates": [ + { + "name": "test", + "latitude": 38.897663, + "longitude": -77.036574 + } ] } diff --git a/traffic_ops/testing/api_contract/v4/data/response_template.json b/traffic_ops/testing/api_contract/v4/data/response_template.json index 82a2376edc..5f25791443 100644 --- a/traffic_ops/testing/api_contract/v4/data/response_template.json +++ b/traffic_ops/testing/api_contract/v4/data/response_template.json @@ -1122,5 +1122,32 @@ "type": "string" } } + }, + "coordinates": { + "type": "object", + "required": [ + "id", + "name", + "latitude", + "longitude", + "lastUpdated" + ], + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + }, + "lastUpdated": { + "type": "string" + } + } } } diff --git a/traffic_ops/testing/api_contract/v4/test_coordinates.py b/traffic_ops/testing/api_contract/v4/test_coordinates.py new file mode 100644 index 0000000000..799573fb81 --- /dev/null +++ b/traffic_ops/testing/api_contract/v4/test_coordinates.py @@ -0,0 +1,84 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""API Contract Test Case for coordinates endpoint.""" +import logging +from typing import Union + +import pytest +import requests +from jsonschema import validate + +from trafficops.tosession import TOSession + +# Create and configure logger +logger = logging.getLogger() + +Primitive = Union[bool, int, float, str, None] + + +def test_coordinate_contract(to_session: TOSession, + response_template_data: dict[str, Union[Primitive, list[Union[Primitive, + dict[str, object], list[object]]], dict[object, object]]], + coordinate_post_data: dict[str, object] + ) -> None: + """ + Test step to validate keys, values and data types from coordinates response. + :param to_session: Fixture to get Traffic Ops session. + :param response_template_data: Fixture to get response template data from a prerequisites file. + :param coordinate_post_data: Fixture to get sample coordinate data and actual coordinate response. + """ + # validate coordinate keys from coordinates get response + logger.info("Accessing /coordinates endpoint through Traffic ops session.") + + coordinate_name = coordinate_post_data.get("name") + if not isinstance(coordinate_name, str): + raise TypeError("malformed coordinate in prerequisite data; 'name' not a string") + + coordinate_get_response: tuple[ + Union[dict[str, object], list[Union[dict[str, object], list[object], Primitive]], Primitive], + requests.Response + ] = to_session.get_coordinates(query_params={"name": coordinate_name}) + try: + coordinate_data = coordinate_get_response[0] + if not isinstance(coordinate_data, list): + raise TypeError("malformed API response; 'response' property not an array") + + first_coordinate = coordinate_data[0] + if not isinstance(first_coordinate, dict): + raise TypeError("malformed API response; first coordinate in response is not an dict") + + logger.info("Coordinate Api response %s", first_coordinate) + coordinate_response_template = response_template_data.get("coordinates") + if not isinstance(coordinate_response_template, dict): + raise TypeError( + f"coordinate response template data must be a dict, not '{type(coordinate_response_template)}'") + + # validate coordinate values from prereq data in coordinates get response. + keys = ["name", "latitude", "longitude"] + prereq_values = [coordinate_post_data[key] for key in keys] + get_values = [first_coordinate[key] for key in keys] + + # validate keys, data types and values from coordinates get json response. + assert validate(instance=first_coordinate, schema=coordinate_response_template) is None + assert get_values == prereq_values + except IndexError: + logger.error("Either prerequisite data or API response was malformed") + pytest.fail("API contract test failed for coordinates endpoint: API response was malformed") + finally: + # Delete coordinate after test execution to avoid redundancy. + coordinate_id = coordinate_post_data.get("id") + if to_session.delete_coordinates(query_params={"id": coordinate_id}) is None: + logger.error("coordinate returned by Traffic Ops is missing a 'id' property") + pytest.fail("Response from delete request is empty, Failing test_coordinates_contract")