diff --git a/traffic_control/clients/python/trafficops/tosession.py b/traffic_control/clients/python/trafficops/tosession.py index dd7cf0dfe0..944bd642fc 100644 --- a/traffic_control/clients/python/trafficops/tosession.py +++ b/traffic_control/clients/python/trafficops/tosession.py @@ -2394,6 +2394,28 @@ def get_types(self, query_params=None): :raises: Union[LoginError, OperationError] """ + @api_request('post', 'types', ('3.0', '4.0', '4.1', '5.0')) + def create_types(self, data=None): + """ + Create a Type + :ref:`to-api-types` + :param data: The Type data to use for Type creation. + :type data: Dict[str, Any] + :rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]]], Response] + :raises: Union[LoginError, OperationError] + """ + + @api_request('delete', 'types', ('3.0', '4.0', '4.1', '5.0')) + def delete_types(self, query_params=None): + """ + Delete a Type + :ref:`to-api-types` + :param query_params: The optional url query parameters for the call + :type query_params: Dict[str, Any] + :rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]]], requests.Response] + :raises: Union[LoginError, OperationError] + """ + # # Users # diff --git a/traffic_ops/testing/api_contract/v4/conftest.py b/traffic_ops/testing/api_contract/v4/conftest.py index 660dbb167d..ba68ceb2e8 100644 --- a/traffic_ops/testing/api_contract/v4/conftest.py +++ b/traffic_ops/testing/api_contract/v4/conftest.py @@ -1969,3 +1969,41 @@ def logs_data(to_session: TOSession, request_template_data: list[JSONData], change_log_id = resp_obj.get("id") yield [change_log_id, resp_obj] + + +@pytest.fixture(name="types_post_data") +def types_data_post(to_session: TOSession, request_template_data: list[JSONData], + ) -> dict[str, object]: + """ + PyTest Fixture to create POST data for types endpoint. + + :param to_session: Fixture to get Traffic Ops session. + :param request_template_data: Fixture to get types data from a prerequisites file. + :returns: Sample POST data and the actual API response. + """ + + types = check_template_data(request_template_data["types"], "types") + + # Return new post data and post response from types POST request + randstr = str(randint(0, 1000)) + try: + name = types["name"] + if not isinstance(name, str): + raise TypeError(f"name must be str, not '{type(name)}'") + type_name = name[:4] + randstr + types["name"] = generate_unique_data(to_session=to_session, + base_name=type_name, object_type="types") + except KeyError as e: + raise TypeError(f"missing Type property '{e.args[0]}'") from e + + logger.info("New Types data to hit POST method %s", types) + # Hitting Types POST method + response: tuple[JSONData, requests.Response] = to_session.create_types(data=types) + resp_obj = check_template_data(response, "types") + yield resp_obj + type_id = resp_obj.get("id") + msg = to_session.delete_types(type_id=type_id) + logger.info("Deleting types data... %s", msg) + if msg is None: + logger.error("Type returned by Traffic Ops is missing an 'id' property") + pytest.fail("Response from delete request is empty, Failing test_case") 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 a2454f3483..edfd438b38 100644 --- a/traffic_ops/testing/api_contract/v4/data/response_template.json +++ b/traffic_ops/testing/api_contract/v4/data/response_template.json @@ -2301,5 +2301,32 @@ ] } } + }, + "types":{ + "type": "object", + "required": [ + "id", + "lastUpdated", + "name", + "description", + "useInTable" + ], + "properties": { + "id": { + "type": "integer" + }, + "lastUpdated": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "useInTable": { + "type": "string" + } + } } } diff --git a/traffic_ops/testing/api_contract/v4/test_types.py b/traffic_ops/testing/api_contract/v4/test_types.py new file mode 100644 index 0000000000..511464661d --- /dev/null +++ b/traffic_ops/testing/api_contract/v4/test_types.py @@ -0,0 +1,78 @@ +# +# 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 phys_locations 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_types_contract(to_session: TOSession, + response_template_data: list[Union[dict[str, object], list[object], Primitive]], + types_post_data: dict[str, object] + ) -> None: + """ + Test step to validate keys, values and data types from types 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 types_post_data: Fixture to get sample types data and actual + types response. + """ + # validate types keys from types get response + logger.info("Accessing /types endpoint through Traffic ops session.") + + type_name = types_post_data.get("name") + if not isinstance(type_name, str): + raise TypeError("malformed type in prerequisite data; 'name' not a string") + + types_get_response: tuple[ + Union[dict[str, object], list[Union[dict[str, object], list[object], Primitive]], Primitive], + requests.Response + ] = to_session.get_types(query_params={"name": type_name}) + try: + types_data = types_get_response[0] + if not isinstance(types_data, list): + raise TypeError("malformed API response; 'response' property not an array") + + first_type = types_data[0] + if not isinstance(first_type, dict): + raise TypeError("malformed API response; first type in response is not an dict") + + logger.info("types Api response %s", first_type) + types_response_template = response_template_data.get("types") + if not isinstance(types_response_template, dict): + raise TypeError( + f"Types response template data must be a dict, not'{type(types_response_template)}'") + + # validate types values from prereq data in types get response. + keys = ["name", "description"] + prereq_values = [types_post_data[key] for key in keys] + get_values = [first_type[key] for key in keys] + + #validate keys, data types and values from regions get json response. + assert validate(instance=first_type, schema=types_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 types endpoint: API response was malformed")