This is an automated email from the ASF dual-hosted git repository.
mitchell852 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/master by this push:
new 8d364358c9 Added API Contract test case for servers endpoint and
refactored existing ones. (#7523)
8d364358c9 is described below
commit 8d364358c97f9cb4c4d99d7b4192ebd2377a4f94
Author: Gokula Krishnan <[email protected]>
AuthorDate: Thu May 18 01:57:30 2023 +0530
Added API Contract test case for servers endpoint and refactored existing
ones. (#7523)
* Refactored conftest.py and replaced request and response templates with
json-schema formats
* Refactored cachegroups, cdns, parameters, profiles, roles,
server_capabilities and tenants test cases
* Refactored divisions, regions and physical locations test cases
* Added servers test case
* Modified requirements file
* modified code
* Addressed review comments
* Re added version in tosession
---
.../clients/python/trafficops/tosession.py | 2 +-
traffic_ops/testing/api_contract/v4/conftest.py | 555 +++++++++----------
.../api_contract/v4/data/request_template.json | 138 +++++
.../api_contract/v4/data/response_template.json | 592 +++++++++++++++++++++
.../api_contract/v4/{ => data}/to_data.json | 0
.../testing/api_contract/v4/request_template.json | 86 ---
.../testing/api_contract/v4/requirements.txt | 1 +
.../testing/api_contract/v4/response_template.json | 268 ----------
.../testing/api_contract/v4/test_cachegroups.py | 97 ++--
traffic_ops/testing/api_contract/v4/test_cdns.py | 57 +-
.../testing/api_contract/v4/test_divisions.py | 55 +-
.../testing/api_contract/v4/test_parameters.py | 59 +-
.../testing/api_contract/v4/test_phys_locations.py | 75 +--
.../testing/api_contract/v4/test_profiles.py | 55 +-
.../testing/api_contract/v4/test_regions.py | 60 +--
traffic_ops/testing/api_contract/v4/test_roles.py | 54 +-
.../api_contract/v4/test_server_capabilities.py | 61 +--
.../testing/api_contract/v4/test_servers.py | 84 +++
.../testing/api_contract/v4/test_tenants.py | 58 +-
19 files changed, 1265 insertions(+), 1092 deletions(-)
diff --git a/traffic_control/clients/python/trafficops/tosession.py
b/traffic_control/clients/python/trafficops/tosession.py
index 14b17dbc9a..19ed6d8558 100644
--- a/traffic_control/clients/python/trafficops/tosession.py
+++ b/traffic_control/clients/python/trafficops/tosession.py
@@ -1396,7 +1396,7 @@ class TOSession(RestApiSession):
"""
@api_request('post', 'phys_locations', ('3.0', '3.1', '4.0', '4.1',
'5.0'))
- def create_physical_locations(self, query_params=None, data=None):
+ def create_physical_locations(self, data=None):
"""
Create a Physical Location
:ref:`to-api-phys_locations`
diff --git a/traffic_ops/testing/api_contract/v4/conftest.py
b/traffic_ops/testing/api_contract/v4/conftest.py
index ab74e6ab87..a44520e935 100644
--- a/traffic_ops/testing/api_contract/v4/conftest.py
+++ b/traffic_ops/testing/api_contract/v4/conftest.py
@@ -22,7 +22,7 @@ import logging
import sys
import os
from random import randint
-from typing import NamedTuple, Union, Optional, TypeAlias
+from typing import Any, NamedTuple, Union, Optional, TypeAlias
from urllib.parse import urlparse
import pytest
@@ -34,9 +34,9 @@ from trafficops.restapi import OperationError
# Create and configure logger
logger = logging.getLogger()
-primitive = bool | int | float | str | None
+Primitive = Union[bool, int, float, str, None]
-JSONData: TypeAlias = Union[dict[str, object], list[object], bool, int, float,
str | None]
+JSONData: TypeAlias = Union[dict[str, object], list[object], bool, int, float,
Optional[str]]
JSONData.__doc__ = """An alias for the kinds of data that JSON can encode."""
class APIVersion(NamedTuple):
@@ -124,23 +124,23 @@ def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption(
"--config",
help="Path to configuration file.",
- default=os.path.join(os.path.dirname(__file__), "to_data.json")
+ default=os.path.join(os.path.dirname(__file__), "data",
"to_data.json")
)
parser.addoption(
"--request-template",
help="Path to request prerequisites file.",
- default=os.path.join(os.path.dirname(__file__),
"request_template.json")
+ default=os.path.join(os.path.dirname(__file__), "data",
"request_template.json")
)
parser.addoption(
"--response-template",
help="Path to response prerequisites file.",
- default=os.path.join(os.path.dirname(__file__),
"response_template.json")
+ default=os.path.join(os.path.dirname(__file__), "data",
"response_template.json")
)
def coalesce_config(
- arg: object | None,
+ arg: Optional[object],
file_key: str,
- file_contents: dict[str, object | None] | None,
+ file_contents: Optional[dict[str, Optional[object]]],
env_key: str
) -> Optional[str]:
"""
@@ -176,7 +176,7 @@ def parse_to_url(raw: str) -> tuple[APIVersion, int]:
"""
Parses the API version and port number from a raw URL string.
- >>> parse_to_url("https://trafficops.example.test:420/api/5.270)
+ >>> parse_to_url("https://trafficops.example.test:420/api/5.270")
(APIVersion(major=5, minor=270), 420)
>>> parse_to_url("trafficops.example.test")
(APIVersion(major=4, minor=0), 443)
@@ -302,7 +302,7 @@ def to_login(to_args: ArgsType) -> TOSession:
@pytest.fixture(name="request_template_data", scope="session")
def request_prerequiste_data(pytestconfig: pytest.Config, request:
pytest.FixtureRequest
- ) -> list[dict[str, object] | list[object] |
primitive]:
+ ) -> list[Union[dict[str, object], list[object],
Primitive]]:
"""
PyTest Fixture to store POST request template data for api endpoint.
:param pytestconfig: Session-scoped fixture that returns the session's
pytest.Config object.
@@ -316,27 +316,27 @@ def request_prerequiste_data(pytestconfig: pytest.Config,
request: pytest.Fixtur
raise ValueError("prereqisites path not configured")
# Response keys for api endpoint
- data: dict[
+ data: Union[dict[
str,
- list[dict[str, object] | list[object] | primitive] |\
- dict[object, object] |\
- primitive
- ] |\
- primitive = None
+ Union[list[Union[dict[str, object], list[object], Primitive]],
dict[object, object], Primitive]
+ ], Primitive] = None
with open(request_template_path, encoding="utf-8", mode="r") as
prereq_file:
data = json.load(prereq_file)
if not isinstance(data, dict):
raise TypeError(f"request template data must be an object, not
'{type(data)}'")
- request_template = data[request.param]
- if not isinstance(request_template, list):
- raise TypeError(f"Request template data must be a list, not
'{type(request_template)}'")
-
+ try:
+ request_template = data[request.param]
+ if not isinstance(request_template, list):
+ raise TypeError(f"Request template data must be a list,
not '{type(request_template)}'")
+ except AttributeError:
+ request_template = data
return request_template
+
@pytest.fixture()
def response_template_data(pytestconfig: pytest.Config
- ) -> dict[str, primitive | list[primitive |
- dict[str, object] | list[object]] |
dict[object, object]]:
+ ) -> dict[str, Union[Primitive, list[
+ Union[Primitive, dict[str, object], list[object]]], dict[object,
object]]]:
"""
PyTest Fixture to store response template data for api endpoint.
:param pytestconfig: Session-scoped fixture that returns the session's
pytest.Config object.
@@ -348,13 +348,10 @@ def response_template_data(pytestconfig: pytest.Config
raise ValueError("prereqisites path not configured")
# Response keys for api endpoint
- response_template: dict[
+ response_template: Union[dict[
str,
- list[dict[str, object] | list[object] | primitive] |\
- dict[object, object] |\
- primitive
- ] |\
- primitive = None
+ Union[list[Union[dict[str, object], list[object], Primitive]],
dict[object, object], Primitive]
+ ], Primitive] = None
with open(prereq_path, encoding="utf-8", mode="r") as prereq_file:
response_template = json.load(prereq_file)
if not isinstance(response_template, dict):
@@ -363,6 +360,97 @@ def response_template_data(pytestconfig: pytest.Config
return response_template
+def api_response_data(api_response: tuple[Union[Primitive, dict[str, object],
list[
+ Union[Primitive, dict[str, object], list[
+ object]]]], requests.Response]) -> dict[str, object]:
+ """
+ Checks API get/post response.
+ :param api_response: Raw api response.
+ :returns: Verified response data
+ """
+ api_data = None
+ if isinstance(api_response, tuple):
+ api_response = api_response[0]
+ if not isinstance(api_response, list):
+ raise ValueError("Malformed API response; 'response'
property not an array")
+ else:
+ raise ValueError("Invalid API response format")
+
+ if api_response:
+ try:
+ api_data = api_response[0]
+ if not isinstance(api_data, dict):
+ raise ValueError("Malformed API response;
'response' property not a dict")
+ except IndexError as e:
+ raise ValueError(f"No response data from API request
'{e.args[0]}'") from e
+
+ return api_data
+
+
+def get_existing_object(to_session: TOSession, object_type: str, query_params:
Optional[
+ dict[str, Any]])-> Union[dict[str, Any], None]:
+ """
+ Check if the given endpoint with the given query params already exists.
+ :param to_session: Fixture to get Traffic Ops session.
+ :param object_type: api call name for get request.
+ :param query_params: query params for api get request.
+ :returns: Api data for the corresponding api request.
+ """
+ api_get_response: tuple[Union[dict[str, object], list[
+ Union[dict[str, object], list[object], Primitive]], Primitive],
requests.Response] = getattr(
+ to_session, f"get_{object_type}")(query_params=query_params)
+ return api_response_data(api_get_response)
+
+
+def create_if_not_exists(to_session: TOSession, object_type: str,
+ data: dict[str, Any]) -> Union[dict[str, Any], None]:
+ """
+ Hits Post request of the given endpoint with the given data.
+ :param to_session: Fixture to get Traffic Ops session.
+ :param object_type: api call name for post request.
+ :param data: Post data for api post request.
+ :returns: Api data for the corresponding api request.
+ """
+ api_post_response: tuple[Union[dict[str, object], list[
+ Union[dict[str, object], list[object], Primitive]], Primitive],
requests.Response] = getattr(
+ to_session, f"create_{object_type}")(data=data)
+ return api_response_data(api_post_response)
+
+
+def create_or_get_existing(to_session: TOSession, get_object_type: str,
post_object_type: str, data:
+ dict[str, Any], query_params: Optional[dict[str, Any]] = None) ->
Union[dict[str, Any], None]:
+ """
+ Get Api data of the given endpoint with the given query params if it
exists. If not, create it.
+ :param to_session: Fixture to get Traffic Ops session.
+ :param get_object_type: api call name for get request.
+ :param post_object_type: api call name for post request.
+ :param query_params: query params for api get request.
+ :returns: Api data for the corresponding api request.
+ @param data:
+ """
+ existing_object = get_existing_object(to_session, get_object_type,
query_params)
+ return existing_object or create_if_not_exists(to_session,
post_object_type, data)
+
+
+def check_template_data(template_data: Union[list[JSONData], tuple[JSONData,
requests.Response]],
+ name: str) -> dict[str, object]:
+ """
+ Checks API request/response template data.
+ :param template_data: Fixture to get template data from a prerequisites
file.
+ :param name: Endpoint name
+ :returns: Verified endpoint data
+ """
+ try:
+ endpoint = template_data[0]
+ except IndexError as e:
+ raise TypeError(
+ f"malformed data; no {name} present in {name} array
property") from e
+
+ if not isinstance(endpoint, dict):
+ raise TypeError(f"malformed data; {name} must be objects, not
'{type(endpoint)}'")
+ return endpoint
+
+
@pytest.fixture()
def cdn_post_data(to_session: TOSession, request_template_data: list[JSONData]
) -> dict[str, object]:
@@ -373,14 +461,7 @@ def cdn_post_data(to_session: TOSession,
request_template_data: list[JSONData]
:param request_template_data: Fixture to get CDN request template data
from a prerequisites file.
:returns: Sample POST data and the actual API response.
"""
-
- try:
- cdn = request_template_data[0]
- except IndexError as e:
- raise TypeError("malformed prerequisite data; no CDNs present
in 'cdns' array property") from e
-
- if not isinstance(cdn, dict):
- raise TypeError(f"malformed prerequisite data; CDNs must be
objects, not '{type(cdn)}'")
+ cdn = check_template_data(request_template_data["cdns"], "cdns")
# Return new post data and post response from cdns POST request
randstr = str(randint(0, 1000))
@@ -396,21 +477,15 @@ def cdn_post_data(to_session: TOSession,
request_template_data: list[JSONData]
except KeyError as e:
raise TypeError(f"missing CDN property '{e.args[0]}'") from e
- logger.info("New cdn data to hit POST method %s", request_template_data)
+ logger.info("New cdn data to hit POST method %s", cdn)
# Hitting cdns POST methed
response: tuple[JSONData, requests.Response] =
to_session.create_cdn(data=cdn)
- try:
- resp_obj = response[0]
- if not isinstance(resp_obj, dict):
- raise TypeError("malformed API response; cdn is not an
object")
- return resp_obj
- except IndexError:
- logger.error("No CDN response data from cdns POST request.")
- sys.exit(1)
+ resp_obj = check_template_data(response, "cdns")
+ return resp_obj
@pytest.fixture()
-def cachegroup_post_data(to_session: TOSession, request_template_data:
list[JSONData]
+def cache_group_post_data(to_session: TOSession, request_template_data:
list[JSONData]
) -> dict[str, object]:
"""
PyTest Fixture to create POST data for cachegroup endpoint.
@@ -419,58 +494,32 @@ def cachegroup_post_data(to_session: TOSession,
request_template_data: list[JSON
:param request_template_data: Fixture to get Cachegroup data from a
prerequisites file.
:returns: Sample POST data and the actual API response.
"""
- try:
- cachegroup = request_template_data[0]
- except IndexError as e:
- raise TypeError(
- "malformed prerequisite data; no Cache group present in
'cachegroup' array property") from e
-
- if not isinstance(cachegroup, dict):
- raise TypeError(
- f"malformed prerequisite data; Cache group must be
objects, not '{type(cachegroup)}'")
-
+ cache_group = check_template_data(request_template_data["cachegroup"],
"cachegroup")
# Return new post data and post response from cachegroups POST request
randstr = str(randint(0, 1000))
try:
- name = cachegroup["name"]
+ name = cache_group["name"]
if not isinstance(name, str):
raise TypeError(f"name must be str, not '{type(name)}'")
- cachegroup["name"] = name[:4] + randstr
- short_name = cachegroup["shortName"]
+ cache_group["name"] = name[:4] + randstr
+ short_name = cache_group["shortName"]
if not isinstance(short_name, str):
raise TypeError(f"shortName must be str, not
'{type(short_name)}")
- cachegroup["shortName"] = short_name[:5] + randstr
+ cache_group["shortName"] = short_name[:5] + randstr
except KeyError as e:
raise TypeError(f"missing Cache group property '{e.args[0]}'")
from e
- # Hitting types GET method to access typeID for cachegroup POST data
- type_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
- requests.Response
- ] = to_session.get_types(query_params={"useInTable": "cachegroup"})
- try:
- type_data = type_get_response[0]
- if not isinstance(type_data, list):
- raise TypeError("malformed API response; 'response'
property not an array")
- first_type = type_data[0]
- if not isinstance(first_type, dict):
- raise TypeError("malformed API response; first Type in
response is not an object")
- cachegroup["typeId"] = first_type["id"]
- type_id = cachegroup["typeId"]
- logger.info("extracted %s from %s", type_id, type_get_response)
- except KeyError as e:
- raise TypeError(f"missing Type property '{e.args[0]}'") from e
- logger.info("New cachegroup data to hit POST method %s",
request_template_data)
+ # Check if type already exists, otherwise create it
+ type_data = check_template_data(request_template_data["types"], "types")
+ type_object = create_or_get_existing(to_session, "types", "type",
type_data,
+ {"useInTable": "cachegroup"})
+ cache_group["typeId"] = type_object["id"]
+
+ logger.info("New cachegroup data to hit POST method %s", cache_group)
# Hitting cachegroup POST method
- response: tuple[JSONData, requests.Response] =
to_session.create_cachegroups(data=cachegroup)
- try:
- resp_obj = response[0]
- if not isinstance(resp_obj, dict):
- raise TypeError("malformed API response; cache group is
not an object")
- return resp_obj
- except IndexError:
- logger.error("No Cache group response data from cdns POST
request.")
- sys.exit(1)
+ response: tuple[JSONData, requests.Response] =
to_session.create_cachegroups(data=cache_group)
+ resp_obj = check_template_data(response, "cachegroup")
+ return resp_obj
@pytest.fixture()
@@ -480,20 +529,10 @@ def parameter_post_data(to_session: TOSession,
request_template_data: list[JSOND
PyTest Fixture to create POST data for parameters endpoint.
:param to_session: Fixture to get Traffic Ops session.
- :param request_template_data: Fixture to get parameter data from a
prerequisites file.
+ :param request_template_data: Fixture to get CDN request template data
from a prerequisites file.
:returns: Sample POST data and the actual API response.
"""
-
- try:
- parameter = request_template_data[0]
- except IndexError as e:
- raise TypeError(
- "malformed prerequisite data; no Parameters present in
'parameters' array property") from e
-
- if not isinstance(parameter, dict):
- raise TypeError(
- f"malformed prerequisite data; Paremeters must be
objects, not '{type(parameter)}'")
-
+ parameter = check_template_data(request_template_data["parameters"],
"parameters")
# Return new post data and post response from parameters POST request
randstr = str(randint(0, 1000))
try:
@@ -508,17 +547,11 @@ def parameter_post_data(to_session: TOSession,
request_template_data: list[JSOND
except KeyError as e:
raise TypeError(f"missing Parameter property '{e.args[0]}'")
from e
- logger.info("New parameter data to hit POST method %s",
request_template_data)
+ logger.info("New parameter data to hit POST method %s", parameter)
# Hitting cdns POST methed
response: tuple[JSONData, requests.Response] =
to_session.create_parameter(data=parameter)
- try:
- resp_obj = response[0]
- if not isinstance(resp_obj, dict):
- raise TypeError("malformed API response; parameter is
not an object")
- return resp_obj
- except IndexError:
- logger.error("No Parameter response data from parameters POST
request.")
- sys.exit(1)
+ resp_obj = check_template_data(response, "parameter")
+ return resp_obj
@pytest.fixture()
@@ -531,16 +564,7 @@ def role_post_data(to_session: TOSession,
request_template_data: list[JSONData]
:param request_template_data: Fixture to get role data from a
prerequisites file.
:returns: Sample POST data and the actual API response.
"""
-
- try:
- role = request_template_data[0]
- except IndexError as e:
- raise TypeError(
- "malformed prerequisite data; no Roles present in
'roles' array property") from e
-
- if not isinstance(role, dict):
- raise TypeError(
- f"malformed prerequisite data; Roles must be objects,
not '{type(role)}'")
+ role = check_template_data(request_template_data["roles"], "roles")
# Return new post data and post response from roles POST request
randstr = str(randint(0, 1000))
@@ -556,23 +580,16 @@ def role_post_data(to_session: TOSession,
request_template_data: list[JSONData]
except KeyError as e:
raise TypeError(f"missing Role property '{e.args[0]}'") from e
- logger.info("New role data to hit POST method %s",
request_template_data)
+ logger.info("New role data to hit POST method %s", role)
# Hitting roles POST methed
response: tuple[JSONData, requests.Response] =
to_session.create_role(data=role)
- logger.info(response[0])
- try:
- resp_obj = response[0]
- if not isinstance(resp_obj, dict):
- raise TypeError("malformed API response; role is not an
object")
- return resp_obj
- except IndexError:
- logger.error("No Role response data from roles POST request.")
- sys.exit(1)
+ resp_obj = check_template_data(response, "role")
+ return resp_obj
@pytest.fixture()
def profile_post_data(to_session: TOSession, request_template_data:
list[JSONData]
- ) -> dict[str, object]:
+ ) -> dict[str, object]:
"""
PyTest Fixture to create POST data for profile endpoint.
@@ -580,17 +597,8 @@ def profile_post_data(to_session: TOSession,
request_template_data: list[JSONDat
:param request_template_data: Fixture to get profile data from a
prerequisites file.
:returns: Sample POST data and the actual API response.
"""
-
- try:
- profile = request_template_data[0]
- except IndexError as e:
- raise TypeError(
- "malformed prerequisite data; no Profile present in
'profile' array property") from e
-
- if not isinstance(profile, dict):
- raise TypeError(f"malformed prerequisite data; profile must be
objects, not '{type(profile)}'")
-
- # Return new post data and post response from cachegroups POST request
+ profile = check_template_data(request_template_data["profiles"],
"profiles")
+ # Return new post data and post response from profiles POST request
randstr = str(randint(0, 1000))
try:
name = profile["name"]
@@ -599,35 +607,17 @@ def profile_post_data(to_session: TOSession,
request_template_data: list[JSONDat
profile["name"] = name[:4] + randstr
except KeyError as e:
raise TypeError(f"missing Profile property '{e.args[0]}'") from
e
- # Hitting types GET method to access typeID for profile POST data
- cdn_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
- requests.Response
- ] = to_session.get_cdns()
- try:
- cdn_data = cdn_get_response[0]
- if not isinstance(cdn_data, list):
- raise TypeError("malformed API response; 'response'
property not an array")
- first_cdn = cdn_data[0]
- if not isinstance(first_cdn, dict):
- raise TypeError("malformed API response; first CDN in
response is not an object")
- profile["cdn"] = first_cdn["id"]
- cdn_id = profile["cdn"]
- logger.info("extracted %s from %s", cdn_id, cdn_get_response)
- except KeyError as e:
- raise TypeError(f"missing CDN property '{e.args[0]}'") from e
- logger.info("New profile data to hit POST method %s",
request_template_data)
+ # Check if cdn already exists, otherwise create it
+ cdn_data = check_template_data(request_template_data["cdns"], "cdns")
+ cdn_object = create_or_get_existing(to_session, "cdns", "cdn", cdn_data)
+ profile["cdn"] = cdn_object["id"]
+ logger.info("New profile data to hit POST method %s", profile)
+
# Hitting profile POST method
response: tuple[JSONData, requests.Response] =
to_session.create_profile(data=profile)
- try:
- resp_obj = response[0]
- if not isinstance(resp_obj, dict):
- raise TypeError("malformed API response; profile is not
an object")
- return resp_obj
- except IndexError:
- logger.error("No Profile response data from cdns POST request.")
- sys.exit(1)
+ resp_obj = check_template_data(response, "profile")
+ return resp_obj
@pytest.fixture()
@@ -640,15 +630,7 @@ def tenant_post_data(to_session: TOSession,
request_template_data: list[JSONData
:returns: Sample POST data and the actual API response.
"""
- try:
- tenant = request_template_data[0]
- except IndexError as e:
- raise TypeError(
- "malformed prerequisite data; no Parameters present in
'tenants' array property") from e
-
- if not isinstance(tenant, dict):
- raise TypeError(
- f"malformed prerequisite data; tenants must be objects,
not '{type(tenant)}'")
+ tenant = check_template_data(request_template_data["tenants"],
"tenants")
# Return new post data and post response from tenants POST request
randstr = str(randint(0, 1000))
@@ -660,17 +642,12 @@ def tenant_post_data(to_session: TOSession,
request_template_data: list[JSONData
except KeyError as e:
raise TypeError(f"missing tenant property '{e.args[0]}'") from e
- logger.info("New tenant data to hit POST method %s",
request_template_data)
+ logger.info("New tenant data to hit POST method %s", tenant)
# Hitting tenants POST methed
response: tuple[JSONData, requests.Response] =
to_session.create_tenant(data=tenant)
- try:
- resp_obj = response[0]
- if not isinstance(resp_obj, dict):
- raise TypeError("malformed API response; parameter is
not an object")
- return resp_obj
- except IndexError:
- logger.error("No Parameter response data from parameters POST
request.")
- sys.exit(1)
+ resp_obj = check_template_data(response, "tenant")
+ return resp_obj
+
@pytest.fixture()
def server_capabilities_post_data(to_session: TOSession,
request_template_data: list[JSONData]
@@ -680,17 +657,11 @@ def server_capabilities_post_data(to_session: TOSession,
request_template_data:
:param to_session: Fixture to get Traffic Ops session.
:param request_template_data: Fixture to get server_capabilities data
from a prerequisites file.
- :returns: Sample POST data and the actual API response.
+ :returns: Sample POST data and the actual API response.
"""
- try:
- server_capabilities = request_template_data[0]
- except IndexError as e:
- raise TypeError(
- "malformed prerequisite data; no data present in
'server_capabilities' array property") from e
- if not isinstance(server_capabilities, dict):
- raise TypeError(
- f"malformed prerequisite data; data must be objects,
not '{type(server_capabilities)}'")
+ server_capabilities =
check_template_data(request_template_data["server_capabilities"],
+ "server_capabilities")
# Return new post data and post response from server_capabilities POST
request
randstr = str(randint(0, 1000))
@@ -706,14 +677,9 @@ def server_capabilities_post_data(to_session: TOSession,
request_template_data:
# Hitting server_capabilities POST method
response: tuple[
JSONData, requests.Response] =
to_session.create_server_capabilities(data=server_capabilities)
- try:
- resp_obj = response[0]
- if not isinstance(resp_obj, dict):
- raise TypeError("malformed API response;
server_capabilities is not an object")
- return resp_obj
- except IndexError:
- logger.error("No server_capabilities response data from
server_capabilities POST request.")
- sys.exit(1)
+ resp_obj = check_template_data(response, "server_capabilities")
+ return resp_obj
+
@pytest.fixture()
def division_post_data(to_session: TOSession, request_template_data:
list[JSONData]
@@ -722,20 +688,11 @@ def division_post_data(to_session: TOSession,
request_template_data: list[JSONDa
PyTest Fixture to create POST data for divisions endpoint.
:param to_session: Fixture to get Traffic Ops session.
- :param request_template_data: Fixture to get divisions request template
data from
- request_template file.
- :returns: Sample POST data and the actual API response.
+ :param request_template_data: Fixture to get divisions data from a
prerequisites file.
+ :returns: Sample POST data and the actual API response.
"""
- try:
- division = request_template_data[0]
- except IndexError as e:
- raise TypeError(
- "malformed prerequisite data; no division present in
'division' array property") from e
-
- if not isinstance(division, dict):
- raise TypeError(
- f"malformed prerequisite data; divisions must be
objects, not '{type(division)}'")
+ division = check_template_data(request_template_data["divisions"],
"divisions")
# Return new post data and post response from division POST request
randstr = str(randint(0, 1000))
@@ -750,17 +707,12 @@ def division_post_data(to_session: TOSession,
request_template_data: list[JSONDa
logger.info("New division data to hit POST method %s",
request_template_data)
# Hitting division POST methed
response: tuple[JSONData, requests.Response] =
to_session.create_division(data=division)
- try:
- resp_obj = response[0]
- if not isinstance(resp_obj, dict):
- raise TypeError("malformed API response; division is
not an object")
- return resp_obj
- except IndexError:
- logger.error("No division response data from division POST
request.")
- sys.exit(1)
+ resp_obj = check_template_data(response, "divisions")
+ return resp_obj
[email protected]()
-def region_post_data(to_session: TOSession, request_template_data:
list[JSONData]
+
[email protected](name="region_post_data")
+def region_data_post(to_session: TOSession, request_template_data:
list[JSONData]
) -> dict[str, object]:
"""
PyTest Fixture to create POST data for region endpoint.
@@ -770,14 +722,7 @@ def region_post_data(to_session: TOSession,
request_template_data: list[JSONData
:returns: Sample POST data and the actual API response.
"""
- try:
- region = request_template_data[0]
- except IndexError as e:
- raise TypeError(
- "malformed prerequisite data; no Region present in
'regions' array property") from e
-
- if not isinstance(region, dict):
- raise TypeError(f"malformed prerequisite data; region must be
objects, not '{type(region)}'")
+ region = check_template_data(request_template_data["regions"],
"regions")
# Return new post data and post response from regions POST request
randstr = str(randint(0, 1000))
@@ -788,55 +733,33 @@ def region_post_data(to_session: TOSession,
request_template_data: list[JSONData
region["name"] = name[:4] + randstr
except KeyError as e:
raise TypeError(f"missing Region property '{e.args[0]}'") from e
- # Hitting types GET method to access typeID for region POST data
- division_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
- requests.Response
- ] = to_session.get_divisions()
- try:
- division_data = division_get_response[0]
- if not isinstance(division_data, list):
- raise TypeError("malformed API response; 'response'
property not an array")
- first_division = division_data[0]
- if not isinstance(first_division, dict):
- raise TypeError("malformed API response; first Division
in response is not an object")
- region["division"] = first_division["id"]
- division_id = region["division"]
- logger.info("extracted %s from %s", division_id,
division_get_response)
- except KeyError as e:
- raise TypeError(f"missing Regions property '{e.args[0]}'") from
e
+
+ # Check if division already exists, otherwise create it
+ division_data = check_template_data(request_template_data["divisions"],
"divisions")
+ division_object = create_or_get_existing(to_session, "divisions",
"division", division_data)
+ region["division"] = division_object["id"]
+ region["divisionName"] = division_object["name"]
logger.info("New region data to hit POST method %s",
request_template_data)
# Hitting region POST method
response: tuple[JSONData, requests.Response] =
to_session.create_region(data=region)
- try:
- resp_obj = response[0]
- if not isinstance(resp_obj, dict):
- raise TypeError("malformed API response; region is not
an object")
- return resp_obj
- except IndexError:
- logger.error("No Region response data from divisions POST
request.")
- sys.exit(1)
+ resp_obj = check_template_data(response, "regions")
+ return resp_obj
[email protected]()
-def phys_locations_post_data(to_session: TOSession, request_template_data:
list[JSONData]
- ) -> dict[str, object]:
+
[email protected](name="phys_locations_post_data")
+def phys_locations_data_post(to_session: TOSession, request_template_data:
list[JSONData],
+ region_post_data: dict[str, object]) -> dict[str,
object]:
"""
PyTest Fixture to create POST data for phys_location endpoint.
:param to_session: Fixture to get Traffic Ops session.
:param request_template_data: Fixture to get phys_location data from a
prerequisites file.
+ :param region_post_data:
:returns: Sample POST data and the actual API response.
"""
- try:
- phys_locations = request_template_data[0]
- except IndexError as e:
- raise TypeError(
- "malformed prerequisite data; no data in
'phys_locations' array property") from e
-
- if not isinstance(phys_locations, dict):
- raise TypeError(f"malformed prerequisite data; PLs must be
objects, not '{type(phys_locations)}'")
+ phys_locations =
check_template_data(request_template_data["phys_locations"], "phys_locations")
# Return new post data and post response from phys_locations POST
request
randstr = str(randint(0, 1000))
@@ -845,39 +768,79 @@ def phys_locations_post_data(to_session: TOSession,
request_template_data: list[
if not isinstance(name, str):
raise TypeError(f"name must be str, not '{type(name)}'")
phys_locations["name"] = name[:4] + randstr
- shortName = phys_locations["shortName"]
+ short_name = phys_locations["shortName"]
if not isinstance(name, str):
- raise TypeError(f"shortName must be str, not
'{type(shortName)}'")
- phys_locations["shortName"] = shortName[:4] + randstr
+ raise TypeError(f"shortName must be str, not
'{type(short_name)}'")
+ phys_locations["shortName"] = short_name[:4] + randstr
except KeyError as e:
raise TypeError(f"missing Phys_location property
'{e.args[0]}'") from e
- # Hitting types GET method to access typeID for phys_locations POST data
- region_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
- requests.Response
- ] = to_session.get_regions()
- try:
- region_data = region_get_response[0]
- if not isinstance(region_data, list):
- raise TypeError("malformed API response; 'response'
property not an array")
- first_region = region_data[0]
- if not isinstance(first_region, dict):
- raise TypeError("malformed API response; first Region
in response is not an object")
- phys_locations["regionId"] = first_region["id"]
- region_id = phys_locations["regionId"]
- logger.info("extracted %s from %s", region_id,
region_get_response)
- except KeyError as e:
- raise TypeError(f"missing Phys_Locations property
'{e.args[0]}'") from e
+
+ # Check if region already exists, otherwise create it
+ region_id = region_post_data["id"]
+ if not isinstance(region_id, int):
+ raise TypeError("malformed API response; 'id' property not a
integer")
+ phys_locations["regionId"] = region_id
logger.info("New Phys_locations data to hit POST method %s",
request_template_data)
# Hitting region POST method
- response: tuple[JSONData, requests.Response] =
to_session.create_physical_locations(data=phys_locations)
- try:
- resp_obj = response[0]
- if not isinstance(resp_obj, dict):
- raise TypeError("malformed API response; phys_location
is not an object")
- return resp_obj
- except IndexError:
- logger.error("No Phys_Location response data from regions POST
request.")
- sys.exit(1)
+ response: tuple[JSONData, requests.Response] =
to_session.create_physical_locations(
+ data=phys_locations)
+ resp_obj = check_template_data(response, "phys_locations")
+ return resp_obj
+
[email protected]()
+def server_post_data(to_session: TOSession, request_template_data:
list[JSONData],
+ phys_locations_post_data: dict[str, object]) -> dict[str,
object]:
+ """
+ PyTest Fixture to create POST data for server endpoint.
+
+ :param to_session: Fixture to get Traffic Ops session.
+ :param request_template_data: Fixture to get profile data from a
prerequisites file.
+ :param phys_locations_post_data:
+ :returns: Sample POST data and the actual API response.
+ """
+ server = check_template_data(request_template_data["servers"],
"servers")
+
+ # Check if type already exists, otherwise create it
+ type_data = check_template_data(request_template_data["types"], "types")
+ type_object = create_or_get_existing(to_session, "types", "type",
type_data,
+ {"useInTable": "server"})
+ type_id = type_object["id"]
+ server["typeId"] = type_id
+
+ # Check if cachegroup with type already exists, otherwise create it
+ cache_group_data =
check_template_data(request_template_data["cachegroup"], "cachegroup")
+ cache_group_object = create_or_get_existing(to_session, "cachegroups",
"cachegroups",
+ cache_group_data, {"typeId":
type_id})
+ server["cachegroupId"]= cache_group_object["id"]
+
+ # Check if cdn already exists, otherwise create it
+ cdn_data = check_template_data(request_template_data["cdns"], "cdns")
+ cdn_object = create_or_get_existing(to_session, "cdns", "cdn",
cdn_data, {"name": "CDN-in-a-Box"})
+ server["cdnId"] = cdn_object["id"]
+ server["domainName"] = cdn_object["domainName"]
+
+ # Check if profile with cdn already exists, otherwise create it
+ profile_data = check_template_data(request_template_data["profiles"],
"profiles")
+ profile_object = create_or_get_existing(to_session, "profiles",
"profile", profile_data,
+ {"name": "test"})
+ server["profileNames"] = [profile_object["name"]]
+
+ # Check if status already exists, otherwise create it
+ status_data = check_template_data(request_template_data["status"],
"status")
+ status_object = create_or_get_existing(to_session, "statuses",
"statuses",
+ status_data, {"name": "REPORTED"})
+ server["statusId"] = status_object["id"]
+
+ # Check if physical location with region already exists, otherwise
create it
+ physical_location_id = phys_locations_post_data["id"]
+ if not isinstance(physical_location_id, int):
+ raise TypeError("malformed API response; 'id' property not a
integer")
+ server["physLocationId"] = physical_location_id
+
+ logger.info("New server data to hit POST method %s", server)
+ # Hitting server POST method
+ response: tuple[JSONData, requests.Response] =
to_session.create_server(data=server)
+ resp_obj = check_template_data(response, "server")
+ 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
new file mode 100644
index 0000000000..da10040eae
--- /dev/null
+++ b/traffic_ops/testing/api_contract/v4/data/request_template.json
@@ -0,0 +1,138 @@
+{
+ "cdns": [
+ {
+ "name": "test",
+ "domainName": "quest",
+ "dnssecEnabled": false
+ }
+ ],
+ "cachegroup": [
+ {
+ "name": "test",
+ "shortName": "test",
+ "latitude": 38.897663,
+ "longitude": -77.036574,
+ "fallbackToClosest": true,
+ "localizationMethods": [
+ "DEEP_CZ",
+ "CZ",
+ "GEO"
+ ],
+ "typeId": 23
+ }
+ ],
+ "parameters": [
+ {
+ "name": "test",
+ "value": "quest",
+ "configFile": "records.config",
+ "secure": false
+ }
+ ],
+ "roles": [
+ {
+ "name": "test",
+ "description": "quest"
+ }
+ ],
+ "profiles": [
+ {
+ "name": "test",
+ "description": "A test profile for API examples",
+ "cdn": 2,
+ "type": "UNK_PROFILE",
+ "routingDisabled": true
+ }
+ ],
+ "tenants": [
+ {
+ "active": true,
+ "name": "test",
+ "parentId": 1
+ }
+ ],
+ "server_capabilities": [
+ {
+ "name": "RAM"
+ }
+ ],
+ "types": [
+ {
+ "name": "MID_LOC",
+ "description": "Mid Logical Location",
+ "useInTable": "cachegroup"
+ }
+ ],
+ "status": [
+ {
+ "description": "Server is online and reported in the
health protocol.",
+ "name": "REPORTED"
+ }
+ ],
+ "divisions": [
+ {
+ "name": "test"
+ }
+ ],
+ "regions": [
+ {
+ "name": "Manchester",
+ "division": "4",
+ "divisionName": "England"
+ }
+ ],
+ "phys_locations": [
+ {
+ "address": "Buckingham Palace",
+ "city": "London",
+ "comments": "Buckingham Palace",
+ "email": "[email protected]",
+ "name": "test",
+ "phone": "0-843-816-6276",
+ "poc": "Her Majesty The Queen Elizabeth Alexandra Mary
Windsor II",
+ "regionId": 1,
+ "shortName": "test",
+ "state": "Westminster",
+ "zip": "SW1A 1AA"
+ }
+ ],
+ "servers": [
+ {
+ "cachegroupId": 79,
+ "cdnId": 2,
+ "domainName": "mycdn.ciab.test",
+ "hostName": "mid",
+ "httpsPort": 443,
+ "interfaces": [
+ {
+ "ipAddresses": [
+ {
+ "address": "127.0.0.1",
+ "gateway": "0.0.0.1",
+ "serviceAddress": true
+ }
+ ],
+ "maxBandwidth": null,
+ "monitor": true,
+ "mtu": 1500,
+ "name": "eth0"
+ }
+ ],
+ "interfaceMtu": 1500,
+ "interfaceName": "eth0",
+ "ip6Address": "::1",
+ "ip6Gateway": "::2",
+ "ipAddress": "127.0.0.1",
+ "ipGateway": "0.0.0.1",
+ "ipNetmask": "255.255.255.0",
+ "physLocationId": 3,
+ "profileNames": [
+ "MID_TIER_ATS_CACHE"
+ ],
+ "statusId": 3,
+ "tcpPort": 80,
+ "typeId": 96,
+ "updPending": false
+ }
+ ]
+}
diff --git a/traffic_ops/testing/api_contract/v4/data/response_template.json
b/traffic_ops/testing/api_contract/v4/data/response_template.json
new file mode 100644
index 0000000000..50f5abe26d
--- /dev/null
+++ b/traffic_ops/testing/api_contract/v4/data/response_template.json
@@ -0,0 +1,592 @@
+{
+ "type": "object",
+ "cdns": {
+ "type": "object",
+ "required": [
+ "dnssecEnabled",
+ "domainName",
+ "id",
+ "lastUpdated",
+ "name"
+ ],
+ "properties": {
+ "dnssecEnabled": {
+ "type": "boolean"
+ },
+ "domainName": {
+ "type": "string"
+ },
+ "id": {
+ "type": "integer"
+ },
+ "lastUpdated": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ "cachegroup": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "shortName",
+ "latitude",
+ "longitude",
+ "parentCachegroupName",
+ "parentCachegroupId",
+ "secondaryParentCachegroupName",
+ "secondaryParentCachegroupId",
+ "fallbackToClosest",
+ "localizationMethods",
+ "typeName",
+ "typeId",
+ "lastUpdated",
+ "fallbacks"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string"
+ },
+ "shortName": {
+ "type": "string"
+ },
+ "latitude": {
+ "type": "number"
+ },
+ "longitude": {
+ "type": "number"
+ },
+ "parentCachegroupName": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "parentCachegroupId": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "secondaryParentCachegroupName": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "secondaryParentCachegroupId": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "fallbackToClosest": {
+ "type": "boolean"
+ },
+ "localizationMethods": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "typeName": {
+ "type": "string"
+ },
+ "typeId": {
+ "type": "integer"
+ },
+ "lastUpdated": {
+ "type": "string"
+ },
+ "fallbacks": {
+ "type": "array",
+ "items": {}
+ }
+ }
+ },
+ "parameters": {
+ "type": "object",
+ "required": [
+ "configFile",
+ "id",
+ "lastUpdated",
+ "name",
+ "profiles",
+ "secure",
+ "value"
+ ],
+ "properties": {
+ "configFile": {
+ "type": "string"
+ },
+ "id": {
+ "type": "integer"
+ },
+ "lastUpdated": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "profiles": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "secure": {
+ "type": "boolean"
+ },
+ "value": {
+ "type": "string"
+ }
+ }
+ },
+ "roles": {
+ "type": "object",
+ "required": [
+ "name",
+ "permissions",
+ "description",
+ "lastUpdated"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "description": {
+ "type": "string"
+ },
+ "lastUpdated": {
+ "type": "string"
+ }
+ }
+ },
+ "profiles": {
+ "type": "object",
+ "required": [
+ "id",
+ "lastUpdated",
+ "name",
+ "description",
+ "cdnName",
+ "cdn",
+ "routingDisabled",
+ "type"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "lastUpdated": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "cdnName": {
+ "type": "string"
+ },
+ "cdn": {
+ "type": "integer"
+ },
+ "routingDisabled": {
+ "type": "boolean"
+ },
+ "type": {
+ "type": "string"
+ }
+ }
+ },
+ "server_capabilities": {
+ "type": "object",
+ "required": [
+ "name",
+ "lastUpdated"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "lastUpdated": {
+ "type": "string"
+ }
+ }
+ },
+ "tenants": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "active",
+ "lastUpdated",
+ "parentId"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string"
+ },
+ "active": {
+ "type": "boolean"
+ },
+ "lastUpdated": {
+ "type": "string"
+ },
+ "parentId": {
+ "type": [
+ "null",
+ "integer"
+ ]
+ },
+ "parentName": {
+ "type": "string"
+ }
+ }
+ },
+ "divisions": {
+ "type": "object",
+ "required": [
+ "id",
+ "lastUpdated",
+ "name"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "lastUpdated": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ "regions": {
+ "type": "object",
+ "required": [
+ "divisionName",
+ "division",
+ "id",
+ "lastUpdated",
+ "name"
+ ],
+ "properties": {
+ "divisionName": {
+ "type": "string"
+ },
+ "division": {
+ "type": "integer"
+ },
+ "id": {
+ "type": "integer"
+ },
+ "lastUpdated": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ "phys_locations": {
+ "type": "object",
+ "required": [
+ "address",
+ "city",
+ "comments",
+ "email",
+ "id",
+ "lastUpdated",
+ "name",
+ "phone",
+ "poc",
+ "regionId",
+ "region",
+ "shortName",
+ "state",
+ "zip"
+ ],
+ "properties": {
+ "address": {
+ "type": "string"
+ },
+ "city": {
+ "type": "string"
+ },
+ "comments": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "id": {
+ "type": "integer"
+ },
+ "lastUpdated": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "phone": {
+ "type": "string"
+ },
+ "poc": {
+ "type": "string"
+ },
+ "regionId": {
+ "type": "integer"
+ },
+ "region": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "shortName": {
+ "type": "string"
+ },
+ "state": {
+ "type": "string"
+ },
+ "zip": {
+ "type": "string"
+ }
+ }
+ },
+ "servers": {
+ "type": "object",
+ "required": [
+ "cachegroup",
+ "cachegroupId",
+ "cdnId",
+ "cdnName",
+ "domainName",
+ "guid",
+ "hostName",
+ "httpsPort",
+ "id",
+ "iloIpAddress",
+ "iloIpGateway",
+ "iloIpNetmask",
+ "iloPassword",
+ "iloUsername",
+ "lastUpdated",
+ "mgmtIpAddress",
+ "mgmtIpGateway",
+ "mgmtIpNetmask",
+ "offlineReason",
+ "physLocation",
+ "physLocationId",
+ "profileNames",
+ "rack",
+ "revalPending",
+ "status",
+ "statusId",
+ "tcpPort",
+ "type",
+ "typeId",
+ "updPending",
+ "xmppId",
+ "xmppPasswd",
+ "interfaces",
+ "statusLastUpdated",
+ "configUpdateTime",
+ "configApplyTime",
+ "revalUpdateTime",
+ "revalApplyTime"
+ ],
+ "properties": {
+ "cachegroup": {
+ "type": "string"
+ },
+ "cachegroupId": {
+ "type": "integer"
+ },
+ "cdnId": {
+ "type": "integer"
+ },
+ "cdnName": {
+ "type": "string"
+ },
+ "domainName": {
+ "type": "string"
+ },
+ "guid": {
+ "type": "null"
+ },
+ "hostName": {
+ "type": "string"
+ },
+ "httpsPort": {
+ "type": "integer"
+ },
+ "id": {
+ "type": "integer"
+ },
+ "iloIpAddress": {
+ "type": "null"
+ },
+ "iloIpGateway": {
+ "type": "null"
+ },
+ "iloIpNetmask": {
+ "type": "null"
+ },
+ "iloPassword": {
+ "type": "null"
+ },
+ "iloUsername": {
+ "type": "null"
+ },
+ "lastUpdated": {
+ "type": "string"
+ },
+ "mgmtIpAddress": {
+ "type": "null"
+ },
+ "mgmtIpGateway": {
+ "type": "null"
+ },
+ "mgmtIpNetmask": {
+ "type": "null"
+ },
+ "offlineReason": {
+ "type": "null"
+ },
+ "physLocation": {
+ "type": "string"
+ },
+ "physLocationId": {
+ "type": "integer"
+ },
+ "profileNames": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "rack": {
+ "type": "null"
+ },
+ "revalPending": {
+ "type": "boolean"
+ },
+ "status": {
+ "type": "string"
+ },
+ "statusId": {
+ "type": "integer"
+ },
+ "tcpPort": {
+ "type": "integer"
+ },
+ "type": {
+ "type": "string"
+ },
+ "typeId": {
+ "type": "integer"
+ },
+ "updPending": {
+ "type": "boolean"
+ },
+ "xmppId": {
+ "type": "string"
+ },
+ "xmppPasswd": {
+ "type": "null"
+ },
+ "interfaces": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "ipAddresses",
+ "maxBandwidth",
+ "monitor",
+ "mtu",
+ "name",
+ "routerHostName",
+ "routerPortName"
+ ],
+ "properties": {
+ "ipAddresses": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "address",
+ "gateway",
+ "serviceAddress"
+ ],
+ "properties": {
+ "address": {
+ "type": "string"
+ },
+ "gateway": {
+ "type": "string"
+ },
+ "serviceAddress": {
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "maxBandwidth": {
+ "type": "null"
+ },
+ "monitor": {
+ "type": "boolean"
+ },
+ "mtu": {
+ "type": "integer"
+ },
+ "name": {
+ "type": "string"
+ },
+ "routerHostName": {
+ "type": "string"
+ },
+ "routerPortName": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "statusLastUpdated": {
+ "type": "null"
+ },
+ "configUpdateTime": {
+ "type": "string"
+ },
+ "configApplyTime": {
+ "type": "string"
+ },
+ "revalUpdateTime": {
+ "type": "string"
+ },
+ "revalApplyTime": {
+ "type": "string"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/traffic_ops/testing/api_contract/v4/to_data.json
b/traffic_ops/testing/api_contract/v4/data/to_data.json
similarity index 100%
rename from traffic_ops/testing/api_contract/v4/to_data.json
rename to traffic_ops/testing/api_contract/v4/data/to_data.json
diff --git a/traffic_ops/testing/api_contract/v4/request_template.json
b/traffic_ops/testing/api_contract/v4/request_template.json
deleted file mode 100644
index c67d746976..0000000000
--- a/traffic_ops/testing/api_contract/v4/request_template.json
+++ /dev/null
@@ -1,86 +0,0 @@
-{
- "cdns": [
- {
- "name": "test",
- "domainName": "quest",
- "dnssecEnabled": false
- }
- ],
- "cachegroup": [
- {
- "name": "test",
- "shortName": "test",
- "latitude": 38.897663,
- "longitude": -77.036574,
- "fallbackToClosest": true,
- "localizationMethods": [
- "DEEP_CZ",
- "CZ",
- "GEO"
- ],
- "typeId": 23
- }
- ],
- "parameters": [
- {
- "name": "test",
- "value": "quest",
- "configFile": "records.config",
- "secure": false
- }
- ],
- "roles": [
- {
- "name": "test",
- "description": "quest"
- }
- ],
- "profiles": [
- {
- "name": "test",
- "description": "A test profile for API examples",
- "cdn": 2,
- "type": "UNK_PROFILE",
- "routingDisabled": true
- }
- ],
- "tenants": [
- {
- "active": true,
- "name": "test",
- "parentId": 1
- }
- ],
- "server_capabilities": [
- {
- "name": "RAM"
- }
- ],
- "divisions":[
- {
- "name":"test"
- }
- ],
- "regions": [
- {
- "divisionName": "Quebec",
- "division": 1,
- "name": "Montreal"
- }
- ],
- "phys_locations": [
- {
- "address": "Buckingham Palace",
- "city": "London",
- "comments": "Buckingham Palace",
- "email": "[email protected]",
- "name": "test",
- "phone": "0-843-816-6276",
- "poc": "Her Majesty The Queen Elizabeth Alexandra Mary
Windsor II",
- "regionId": 3,
- "shortName": "test",
- "state": "Westminster",
- "zip": "SW1A 1AA"
- }
- ]
-}
diff --git a/traffic_ops/testing/api_contract/v4/requirements.txt
b/traffic_ops/testing/api_contract/v4/requirements.txt
index d08099f871..07c255bd51 100644
--- a/traffic_ops/testing/api_contract/v4/requirements.txt
+++ b/traffic_ops/testing/api_contract/v4/requirements.txt
@@ -13,3 +13,4 @@
#
pytest==7.2.1
+jsonschema==4.17.3
\ No newline at end of file
diff --git a/traffic_ops/testing/api_contract/v4/response_template.json
b/traffic_ops/testing/api_contract/v4/response_template.json
deleted file mode 100644
index 72508ede94..0000000000
--- a/traffic_ops/testing/api_contract/v4/response_template.json
+++ /dev/null
@@ -1,268 +0,0 @@
-{
- "type": "object",
- "cdns": {
- "type": "object",
- "properties": {
- "name": {
- "type": "str"
- },
- "domainName": {
- "type": "str"
- },
- "dnssecEnabled": {
- "type": "bool"
- },
- "id": {
- "type": "int"
- },
- "lastUpdated": {
- "type": "str"
- }
- }
- },
- "cachegroup": {
- "type": "object",
- "properties": {
- "id": {
- "type": "int"
- },
- "name": {
- "type": "str"
- },
- "shortName": {
- "type": "str"
- },
- "latitude": {
- "type": "float"
- },
- "longitude": {
- "type": "float"
- },
- "parentCachegroupName": {
- "optional": "True",
- "typeA": "str",
- "typeB": "NoneType"
- },
- "parentCachegroupId": {
- "optional": "True",
- "typeA": "int",
- "typeB": "NoneType"
- },
- "secondaryParentCachegroupName": {
- "optional": "True",
- "typeA": "str",
- "typeB": "NoneType"
- },
- "secondaryParentCachegroupId": {
- "optional": "True",
- "typeA": "int",
- "typeB": "NoneType"
- },
- "fallbackToClosest": {
- "type": "bool"
- },
- "localizationMethods": {
- "type": "list"
- },
- "typeName": {
- "type": "str"
- },
- "typeId": {
- "type": "int"
- },
- "lastUpdated": {
- "type": "str"
- },
- "fallbacks": {
- "type": "list"
- }
- }
- },
- "parameters": {
- "type": "object",
- "properties": {
- "configFile": {
- "type": "str"
- },
- "id": {
- "type": "int"
- },
- "lastUpdated": {
- "type": "str"
- },
- "name": {
- "type": "str"
- },
- "profiles": {
- "type": "list"
- },
- "secure": {
- "type": "bool"
- },
- "value": {
- "type": "str"
- }
- }
- },
- "roles": {
- "type": "object",
- "properties": {
- "name": {
- "type": "str"
- },
- "description": {
- "type": "str"
- },
- "permissions": {
- "type": "list"
- },
- "lastUpdated": {
- "type": "str"
- }
- }
- },
- "profiles": {
- "type": "object",
- "properties": {
- "id": {
- "type": "int"
- },
- "lastUpdated": {
- "type": "str"
- },
- "name": {
- "type": "str"
- },
- "description": {
- "type": "str"
- },
- "cdnName": {
- "type": "str"
- },
- "cdn": {
- "type": "int"
- },
- "routingDisabled": {
- "type": "bool"
- },
- "type": {
- "type": "str"
- }
- }
- },
- "tenants": {
- "type": "object",
- "properties": {
- "id": {
- "type": "int"
- },
- "name": {
- "type": "str"
- },
- "active": {
- "type": "bool"
- },
- "parentId": {
- "type": "int"
- },
- "lastUpdated": {
- "type": "str"
- },
- "parentName": {
- "type": "str"
- }
- }
- },
- "server_capabilities": {
- "type": "object",
- "properties": {
- "lastUpdated": {
- "type": "str"
- },
- "name": {
- "type": "str"
- }
- }
- },
- "divisions": {
- "type": "object",
- "properties": {
- "id": {
- "type": "int"
- },
- "lastUpdated": {
- "type": "str"
- },
- "name": {
- "type": "str"
- }
- }
- },
- "regions": {
- "type": "object",
- "properties": {
- "divisionName": {
- "type": "str"
- },
- "division": {
- "type": "int"
- },
- "id": {
- "type": "int"
- },
- "lastUpdated": {
- "type": "str"
- },
- "name": {
- "type": "str"
- }
- }
- },
- "phys_locations": {
- "type": "object",
- "properties": {
- "address": {
- "type": "str"
- },
- "city": {
- "type": "str"
- },
- "comments": {
- "type": "str"
- },
- "email": {
- "type": "str"
- },
- "id":{
- "type": "int"
- },
- "lastUpdated":{
- "type": "str"
- },
- "name": {
- "type": "str"
- },
- "phone": {
- "type": "str"
- },
- "poc": {
- "type": "str"
- },
- "regionId": {
- "type": "int"
- },
- "region":{
- "type": "str"
- },
- "shortName": {
- "type": "str"
- },
- "state": {
- "type": "str"
- },
- "zip": {
- "type": "str"
- }
- }
- }
-}
diff --git a/traffic_ops/testing/api_contract/v4/test_cachegroups.py
b/traffic_ops/testing/api_contract/v4/test_cachegroups.py
index 3a72583959..b65a445ae5 100644
--- a/traffic_ops/testing/api_contract/v4/test_cachegroups.py
+++ b/traffic_ops/testing/api_contract/v4/test_cachegroups.py
@@ -14,100 +14,63 @@
"""API Contract Test Case for cachegroup 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 = bool | int | float | str | None
+Primitive = Union[bool, int, float, str, None]
+
[email protected]('request_template_data', ["cachegroup"],
indirect=True)
-def test_cachegroup_contract(to_session: TOSession,
- request_template_data: list[dict[str, object] | list[object] |
primitive],
- response_template_data: dict[str, primitive | list[primitive |
dict[str, object]
- | list[object]] |
dict[object, object]],
- cachegroup_post_data: dict[str, object]
- ) -> None:
+def test_cache_group_contract(to_session: TOSession,
+
response_template_data: dict[str, Union[Primitive, list[
+
Union[Primitive, dict[str, object], list[object]]], dict[
+ object,
object]]],
+
cache_group_post_data: dict[str, object]
+ ) -> None:
"""
Test step to validate keys, values and data types from cachegroup
endpoint
response.
:param to_session: Fixture to get Traffic Ops session.
- :param request_template_data: Fixture to get request template data from
a prerequisites file.
:param response_template_data: Fixture to get response template data
from a prerequisites file.
- :param cachegroup_post_data: Fixture to get sample cachegroup data and
actual cachegroup response.
+ :param cache_group_post_data: Fixture to get sample cachegroup data and
actual cachegroup response.
"""
# validate CDN keys from cdns get response
logger.info("Accessing /cachegroup endpoint through Traffic ops
session.")
- cachegroup = request_template_data[0]
- if not isinstance(cachegroup, dict):
- raise TypeError("malformed cachegroup in prerequisite data; not
an object")
-
- cachegroup_name = cachegroup.get("name")
- if not isinstance(cachegroup_name, str):
+ cache_group_name = cache_group_post_data.get("name")
+ if not isinstance(cache_group_name, str):
raise TypeError("malformed cachegroup in prerequisite data;
'name' not a string")
- cachegroup_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
+ cache_group_get_response: tuple[
+ Union[dict[str, object], list[Union[dict[str, object],
list[object], Primitive]], Primitive],
requests.Response
- ] = to_session.get_cachegroups(query_params={"name":
str(cachegroup_name)})
+ ] = to_session.get_cachegroups(query_params={"name":
str(cache_group_name)})
try:
- cachegroup_data = cachegroup_get_response[0]
- if not isinstance(cachegroup_data, list):
+ cache_group_data = cache_group_get_response[0]
+ if not isinstance(cache_group_data, list):
raise TypeError("malformed API response; 'response'
property not an array")
- first_cachegroup = cachegroup_data[0]
- if not isinstance(first_cachegroup, dict):
+ first_cache_group = cache_group_data[0]
+ if not isinstance(first_cache_group, dict):
raise TypeError("malformed API response; first Cache
group in response is not an object")
- cachegroup_keys = set(first_cachegroup.keys())
- logger.info("Cache group Keys from cachegroup endpoint response
%s", cachegroup_keys)
-
- cachegroup_response_template =
response_template_data.get("cachegroup")
- response_template: dict[str, list[dict[str, object] |
list[object] | primitive] |\
- dict[object, object] |\
- primitive
- ]
- response_template =
cachegroup_response_template.get("properties") if isinstance(
- cachegroup_response_template, dict) else None
- if response_template is None or not
isinstance(response_template, dict):
- raise TypeError(
- f"response template data must be a dict, not
'{type(response_template)}'")
+ logger.info("Cachegroup API get response %s", first_cache_group)
+ cache_group_response_template =
response_template_data.get("cachegroup")
# validate cachegroup values from prereq data in cachegroup get
response.
- prereq_values =
[cachegroup_post_data["name"],cachegroup_post_data["shortName"],
-
cachegroup_post_data["fallbackToClosest"],cachegroup_post_data["typeId"]]
-
- get_values =
[first_cachegroup["name"],first_cachegroup["shortName"],
-
first_cachegroup["fallbackToClosest"],first_cachegroup["typeId"]]
-
- get_types = {}
- for key, value in first_cachegroup.items():
- get_types[key] = type(value).__name__
- logger.info("types from cachegroup get response %s", get_types)
-
- response_template_types= {}
- for key, value in response_template.items():
- optional = value.get("optional")
- if optional:
- optional_type = value.get("typeA") if key in
cachegroup else value.get("typeB")
- if not isinstance(optional_type, str):
- raise TypeError(f"Type data must be a
string, not '{type(optional_type)}'")
- response_template_types[key] = optional_type
- else:
- actual_type = value.get("type")
- if not isinstance(actual_type, str):
- raise TypeError(f"Type data must be a
string, not '{type(actual_type)}'")
- response_template_types[key] = actual_type
-
- logger.info("types from cachegroup response template %s",
response_template_types)
+ keys = ["name", "shortName", "fallbackToClosest", "typeId"]
+ prereq_values = [cache_group_post_data[key] for key in keys]
+ get_values = [first_cache_group[key] for key in keys]
- # validate keys,data types for values and actual values for
cachegroup endpoint.
- assert cachegroup_keys == set(response_template.keys())
- assert dict(sorted(get_types.items())) ==
dict(sorted(response_template_types.items()))
+ # validate keys,data types and values for cachegroup endpoint.
+ assert validate(instance=first_cache_group,
schema=cache_group_response_template) is None
assert get_values == prereq_values
except IndexError:
logger.error("Either prerequisite data or API response was
malformed")
@@ -115,8 +78,8 @@ def test_cachegroup_contract(to_session: TOSession,
finally:
# Delete Cache group after test execution to avoid redundancy.
try:
- cachegroup_id = cachegroup_post_data["id"]
-
to_session.delete_cachegroups(cache_group_id=cachegroup_id)
+ cache_group_id = cache_group_post_data["id"]
+
to_session.delete_cachegroups(cache_group_id=cache_group_id)
except IndexError:
logger.error("Cachegroup returned by Traffic Ops is
missing an 'id' property")
pytest.fail("Response from delete request is empty,
Failing test_cachegroup_contract")
diff --git a/traffic_ops/testing/api_contract/v4/test_cdns.py
b/traffic_ops/testing/api_contract/v4/test_cdns.py
index bf07e5f2c4..9901fab773 100644
--- a/traffic_ops/testing/api_contract/v4/test_cdns.py
+++ b/traffic_ops/testing/api_contract/v4/test_cdns.py
@@ -14,45 +14,40 @@
"""API Contract Test Case for cdns 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 = bool | int | float | str | None
+Primitive = Union[bool, int, float, str, None]
+
[email protected]('request_template_data', ["cdns"], indirect=True)
def test_cdn_contract(
to_session: TOSession,
- request_template_data: list[dict[str, object] | list[object] |
primitive],
- response_template_data: dict[str, primitive | list[primitive |
dict[str, object]
- | list[object]] |
dict[object, object]],
- cdn_post_data: dict[str, object]
- ) -> None:
+ response_template_data: dict[str, Union[Primitive,
list[Union[Primitive, dict[str, object],
+ list[object]]], dict[object, object]]], cdn_post_data: dict[str,
object]) -> None:
"""
Test step to validate keys, values and data types from cdns endpoint
response.
:param to_session: Fixture to get Traffic Ops session.
- :param request_template_data: Fixture to get request template data from
a prerequisites file.
:param response_template_data: Fixture to get response template data
from a prerequisites file.
:param cdn_post_data: Fixture to get sample CDN data and actual CDN
response.
"""
# validate CDN keys from cdns get response
logger.info("Accessing /cdns endpoint through Traffic ops session.")
- cdn = request_template_data[0]
- if not isinstance(cdn, dict):
- raise TypeError("malformed cdn in prerequisite data; not an
object")
-
- cdn_name = cdn.get("name")
+ cdn_name = cdn_post_data.get("name")
if not isinstance(cdn_name, str):
raise TypeError("malformed cdn in prerequisite data; 'name' not
a string")
cdn_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
+ Union[dict[str, object], list[Union[dict[str, object],
list[object], Primitive]], Primitive],
requests.Response
] = to_session.get_cdns(query_params={"name": cdn_name})
try:
@@ -63,40 +58,18 @@ def test_cdn_contract(
first_cdn = cdn_data[0]
if not isinstance(first_cdn, dict):
raise TypeError("malformed API response; first CDN in
response is not an object")
- cdn_keys = set(first_cdn.keys())
- logger.info("CDN Keys from cdns endpoint response %s", cdn_keys)
-
+ logger.info("CDN Api get response %s", first_cdn)
cdn_response_template = response_template_data.get("cdns")
if not isinstance(cdn_response_template, dict):
raise TypeError(
f"Cdn response template data must be a dict,
not '{type(cdn_response_template)}'")
- response_template: dict[str, list[dict[str, object] |
list[object] | primitive] |\
- dict[object, object] |\
- primitive
- ]
- response_template = cdn_response_template.get("properties")
- if not isinstance(response_template, dict):
- raise TypeError(
- f"response template data must be a dict, not
'{type(response_template)}'")
+
# validate cdn values from prereq data in cdns get response.
- prereq_values = [cdn_post_data["name"],
cdn_post_data["domainName"],
- cdn_post_data["dnssecEnabled"]]
- get_values = [first_cdn["name"], first_cdn["domainName"],
first_cdn["dnssecEnabled"]]
- get_types = {}
- for key, value in first_cdn.items():
- get_types[key] = type(value).__name__
- logger.info("types from cdn get response %s", get_types)
- response_template_types= {}
- for key, value in response_template.items():
- actual_type = value.get("type")
- if not isinstance(actual_type, str):
- raise TypeError(
- f"Type data must be a string, not
'{type(actual_type)}'")
- response_template_types[key] = actual_type
- logger.info("types from cdn response template %s",
response_template_types)
+ keys = ["name", "domainName", "dnssecEnabled"]
+ prereq_values = [cdn_post_data[key] for key in keys]
+ get_values = [first_cdn[key] for key in keys]
- assert cdn_keys == set(response_template.keys())
- assert dict(sorted(get_types.items())) ==
dict(sorted(response_template_types.items()))
+ assert validate(instance=first_cdn,
schema=cdn_response_template) is None
assert get_values == prereq_values
except IndexError:
logger.error("Either prerequisite data or API response was
malformed")
diff --git a/traffic_ops/testing/api_contract/v4/test_divisions.py
b/traffic_ops/testing/api_contract/v4/test_divisions.py
index a3da4c1bd1..7a464e7cc5 100644
--- a/traffic_ops/testing/api_contract/v4/test_divisions.py
+++ b/traffic_ops/testing/api_contract/v4/test_divisions.py
@@ -14,43 +14,39 @@
"""API Contract Test Case for divisions 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 = bool | int | float | str | None
+Primitive = Union[bool, int, float, str, None]
+
[email protected]('request_template_data', ["divisions"], indirect=True)
-def test_division_contract(
- to_session: TOSession,
- request_template_data: list[dict[str, object] | list[object] |
primitive],
- response_template_data: list[dict[str, object] | list[object] |
primitive],
- division_post_data: dict[str, object]
-) -> None:
+def test_division_contract(to_session: TOSession,
+ response_template_data: list[Union[dict[str, object], list[object],
Primitive]],
+ division_post_data: dict[str, object]) -> None:
"""
Test step to validate keys, values and data types from divisions
endpoint
response.
:param to_session: Fixture to get Traffic Ops session.
- :param get_division_data: Fixture to get division data from a
prerequisites file.
- :param division_prereq: Fixture to get sample division data and actual
division response.
+ :param response_template_data: Fixture to get response template data
from a prerequisites file.
+ :param division_post_dat: Fixture to get sample division data and
actual division response.
"""
# validate division keys from divisions get response
logger.info("Accessing divisions endpoint through Traffic ops session.")
- division = request_template_data[0]
- if not isinstance(division, dict):
- raise TypeError("malformed division in prerequisite data; not
an object")
-
- division_name = division.get("name")
+ division_name = division_post_data.get("name")
if not isinstance(division_name, str):
- raise TypeError("malformed division in prerequisite data;
'name' not a string")
+ raise TypeError("malformed cdn in prerequisite data; 'name' not
a string")
division_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
+ Union[dict[str, object], list[Union[dict[str, object],
list[object], Primitive]], Primitive],
requests.Response
] = to_session.get_divisions(query_params={"name": division_name})
try:
@@ -61,25 +57,18 @@ def test_division_contract(
first_division = division_data[0]
if not isinstance(first_division, dict):
raise TypeError("malformed API response; first division
in response is not an object")
- division_keys = set(first_division.keys())
- logger.info("division Keys from divisions endpoint response
%s", division_keys)
- response_template =
response_template_data.get("divisions").get("properties")
+ logger.info("Division Api get response %s", first_division)
+ division_response_template =
response_template_data.get("divisions")
+ if not isinstance(division_response_template, dict):
+ raise TypeError(
+ f"Division response template data must be a
dict, not '{type(division_response_template)}'")
+
# validate division values from prereq data in divisions get
response.
- prereq_values = [
- division_post_data["name"]]
- get_values = [first_division["name"]]
- get_types = {}
- for key, value in first_division.items():
- get_types[key] = type(value).__name__
- logger.info("types from division get response %s", get_types)
- response_template_types= {}
- for key, value in response_template.items():
- response_template_types[key] = value.get("type")
- logger.info("types from division response template %s",
response_template_types)
+ prereq_values = division_post_data["name"]
+ get_values = first_division["name"]
- assert division_keys == set(response_template.keys())
- assert dict(sorted(get_types.items())) ==
dict(sorted(response_template_types.items()))
+ assert validate(instance=first_division,
schema=division_response_template) is None
assert get_values == prereq_values
except IndexError:
logger.error("Either prerequisite data or API response was
malformed")
diff --git a/traffic_ops/testing/api_contract/v4/test_parameters.py
b/traffic_ops/testing/api_contract/v4/test_parameters.py
index 27b257d401..62935d5e20 100644
--- a/traffic_ops/testing/api_contract/v4/test_parameters.py
+++ b/traffic_ops/testing/api_contract/v4/test_parameters.py
@@ -14,45 +14,40 @@
"""API Contract Test Case for parameters 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 = bool | int | float | str | None
+Primitive = Union[bool, int, float, str, None]
+
[email protected]('request_template_data', ["parameters"],
indirect=True)
-def test_parameter_contract(
- to_session: TOSession,
- request_template_data: list[dict[str, object] | list[object] |
primitive],
- response_template_data: dict[str, primitive | list[primitive |
dict[str, object]
- | list[object]] |
dict[object, object]],
- parameter_post_data: dict[str, object]
-) -> None:
+def test_parameter_contract(to_session: TOSession,
+ response_template_data: dict[str, Union[Primitive, list[Union[Primitive,
+ dict[str, object],
list[object]]],
+ dict[object, object]]], parameter_post_data: dict[str, object]) -> None:
"""
Test step to validate keys, values and data types from parameters
endpoint
response.
:param to_session: Fixture to get Traffic Ops session.
- :param request_template_data: Fixture to get request template data from
a prerequisites file.
:param response_template_data: Fixture to get response template data
from a prerequisites file.
:param parameter_post_data: Fixture to get sample parameter data and
actual parameter response.
"""
# validate Parameter keys from parameters get response
logger.info("Accessing /parameters endpoint through Traffic ops
session.")
- parameter = request_template_data[0]
- if not isinstance(parameter, dict):
- raise TypeError("malformed parameter in prerequisite data; not
an object")
-
- parameter_name = parameter.get("name")
+ parameter_name = parameter_post_data.get("name")
if not isinstance(parameter_name, str):
raise TypeError("malformed parameter in prerequisite data;
'name' not a string")
parameter_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
+ Union[dict[str, object], list[Union[dict[str, object],
list[object], Primitive]], Primitive],
requests.Response
] = to_session.get_parameters(query_params={"name": parameter_name})
try:
@@ -63,38 +58,20 @@ def test_parameter_contract(
first_parameter = parameter_data[0]
if not isinstance(first_parameter, dict):
raise TypeError("malformed API response; first
Parameter in response is not an object")
- parameter_keys = set(first_parameter.keys())
+ logger.info("Parameter Api get response %s", first_parameter)
- logger.info("Parameter Keys from parameters endpoint response
%s", parameter_keys)
parameter_response_template =
response_template_data.get("parameters")
if not isinstance(parameter_response_template, dict):
raise TypeError(
f"Parameter response template data must be a
dict, not '{type(parameter_response_template)}'")
- response_template: dict[str, list[dict[str, object] |
list[object] | primitive] |\
- dict[object, object] |\
- primitive
- ]
- response_template =
parameter_response_template.get("properties")
+
# validate parameter values from prereq data in parameters get
response.
- prereq_values = [parameter_post_data["name"],
parameter_post_data["value"],
- parameter_post_data["configFile"],
parameter_post_data["secure"]]
- get_values = [first_parameter["name"], first_parameter["value"],
- first_parameter["configFile"], first_parameter["secure"]]
- get_types = {}
- for key, value in first_parameter.items():
- get_types[key] = type(value).__name__
- logger.info("types from parameter get response %s", get_types)
- response_template_types= {}
- for key, value in response_template.items():
- actual_type = value.get("type")
- if not isinstance(actual_type, str):
- raise TypeError(
- f"Type data must be a string, not
'{type(actual_type)}'")
- response_template_types[key] = actual_type
- logger.info("types from parameters response template %s",
response_template_types)
+ keys = ["name", "value", "configFile", "secure"]
+ prereq_values = [parameter_post_data[key] for key in keys]
+ get_values = [first_parameter[key] for key in keys]
+
# validate keys, data types and values from parameters get json
response.
- assert parameter_keys == set(response_template.keys())
- assert dict(sorted(get_types.items())) ==
dict(sorted(response_template_types.items()))
+ assert validate(instance=first_parameter,
schema=parameter_response_template) is None
assert get_values == prereq_values
except IndexError:
logger.error("Either prerequisite data or API response was
malformed")
diff --git a/traffic_ops/testing/api_contract/v4/test_phys_locations.py
b/traffic_ops/testing/api_contract/v4/test_phys_locations.py
index 265c739162..f6894864dc 100644
--- a/traffic_ops/testing/api_contract/v4/test_phys_locations.py
+++ b/traffic_ops/testing/api_contract/v4/test_phys_locations.py
@@ -14,44 +14,40 @@
"""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 = bool | int | float | str | None
+Primitive = Union[bool, int, float, str, None]
+
[email protected]('request_template_data', ["phys_locations"],
indirect=True)
-def test_phys_locations_contract(
- to_session: TOSession,
- request_template_data: list[dict[str, object] | list[object] |
primitive],
- response_template_data: list[dict[str, object] | list[object] |
primitive],
+def test_phys_locations_contract(to_session: TOSession,
+ response_template_data: list[Union[dict[str, object], list[object],
Primitive]],
phys_locations_post_data: dict[str, object]
-) -> None:
+ ) -> None:
"""
- Test step to validate keys, values and data types from phys_locations
endpoint
- response.
+ Test step to validate keys, values and data types from phys_locations
response.
:param to_session: Fixture to get Traffic Ops session.
- :param get_phys_location_data: Fixture to get phys_location data from a
prerequisites file.
- :param phys_location_prereq: Fixture to get sample phys_location data
and actual
+ :param response_template_data: Fixture to get response template data
from a prerequisites file.
+ :param phys_location_post_data: Fixture to get sample phys_location
data and actual
phys_location response.
"""
# validate phys_location keys from phys_locations get response
logger.info("Accessing /phys_locations endpoint through Traffic ops
session.")
- phys_location = request_template_data[0]
- if not isinstance(phys_location, dict):
- raise TypeError("malformed phys_location in prerequisite data;
not an object")
-
- phys_location_name = phys_location.get("name")
+ phys_location_name = phys_locations_post_data.get("name")
if not isinstance(phys_location_name, str):
raise TypeError("malformed phys_location in prerequisite data;
'name' not a string")
phys_location_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
+ Union[dict[str, object], list[Union[dict[str, object],
list[object], Primitive]], Primitive],
requests.Response
] = to_session.get_physical_locations(query_params={"name":
phys_location_name})
try:
@@ -62,52 +58,21 @@ def test_phys_locations_contract(
first_phys_location = phys_location_data[0]
if not isinstance(first_phys_location, dict):
raise TypeError("malformed API response; first
phys_location in response is not an object")
- phys_location_keys = set(first_phys_location.keys())
- logger.info("phys_location Keys from phys_locations endpoint
response %s", phys_location_keys)
+ logger.info("phys_location Api response %s",
first_phys_location)
phys_location_response_template =
response_template_data.get("phys_locations")
if not isinstance(phys_location_response_template, dict):
raise TypeError(
f"phys_loc response template data must be a
dict, not'{type(phys_location_response_template)}'")
- response_template: dict[str, list[dict[str, object] |
list[object] | primitive] |\
- dict[object, object] |\
- primitive
- ]
- response_template =
phys_location_response_template.get("properties")
- # validate phys_location values from prereq data in
phys_locations get response.
- prereq_values = [
- phys_locations_post_data["name"],
- phys_locations_post_data["address"],
- phys_locations_post_data["city"],
- phys_locations_post_data["zip"],
- phys_locations_post_data["comments"],
- phys_locations_post_data["email"],
- phys_locations_post_data["phone"],
- phys_locations_post_data["poc"],
- phys_locations_post_data["regionId"],
- phys_locations_post_data["shortName"],
- phys_locations_post_data["state"]]
- get_values = [first_phys_location["name"],
first_phys_location["address"],
- first_phys_location["city"], first_phys_location["zip"],
first_phys_location["comments"],
- first_phys_location["email"], first_phys_location["phone"],
first_phys_location["poc"],
- first_phys_location["regionId"], first_phys_location["shortName"],
first_phys_location["state"]]
+ # validate phys_location values from prereq data in
phys_locations get response.
+ keys = ["name", "address", "city", "zip", "comments", "email",
"phone","poc",
+ "regionId", "shortName", "state"]
+ prereq_values = [phys_locations_post_data[key] for key in keys]
+ get_values = [first_phys_location[key] for key in keys]
- get_types = {}
- for key, value in first_phys_location.items():
- get_types[key] = type(value).__name__
- logger.info("types from phys_location get response %s",
get_types)
- response_template_types= {}
- for key, value in response_template.items():
- actual_type = value.get("type")
- if not isinstance(actual_type, str):
- raise TypeError(
- f"Type data must be a string, not
'{type(actual_type)}'")
- response_template_types[key] = actual_type
- logger.info("types from phys_location response template %s",
response_template_types)
#validate keys, data types and values from regions get json
response.
- assert phys_location_keys == set(response_template.keys())
- assert dict(sorted(get_types.items())) ==
dict(sorted(response_template_types.items()))
+ assert validate(instance=first_phys_location,
schema=phys_location_response_template) is None
assert get_values == prereq_values
except IndexError:
logger.error("Either prerequisite data or API response was
malformed")
diff --git a/traffic_ops/testing/api_contract/v4/test_profiles.py
b/traffic_ops/testing/api_contract/v4/test_profiles.py
index 17164ca3ae..67e124645a 100644
--- a/traffic_ops/testing/api_contract/v4/test_profiles.py
+++ b/traffic_ops/testing/api_contract/v4/test_profiles.py
@@ -14,45 +14,39 @@
"""API Contract Test Case for profiles 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 = bool | int | float | str | None
+Primitive = Union[bool, int, float, str, None]
[email protected]('request_template_data', ['profiles'], indirect=True)
-def test_profile_contract(
- to_session: TOSession,
- request_template_data: list[dict[str, object] | list[object] |
primitive],
- response_template_data: dict[str, primitive | list[primitive |
dict[str, object]
- | list[object]] |
dict[object, object]],
- profile_post_data: dict[str, object]
-) -> None:
+def test_profile_contract(to_session: TOSession,
+ response_template_data: dict[str, Union[Primitive,
+ list[Union[Primitive, dict[str,
object], list[object]]],
+ dict[object, object]]], profile_post_data: dict[str, object]) -> None:
"""
Test step to validate keys, values and data types from profiles endpoint
response.
:param to_session: Fixture to get Traffic Ops session.
- :param request_template_data: Fixture to get request template data from
a prerequisites file.
:param response_template_data: Fixture to get response template data
from a prerequisites file.
:param profile_post_data: Fixture to get sample Profile data and actual
Profile response.
"""
# validate Profile keys from profiles get response
logger.info("Accessing /profiles endpoint through Traffic ops session.")
- profile = request_template_data[0]
- if not isinstance(profile, dict):
- raise TypeError("malformed profile in prerequisite data; not an
object")
-
- profile_name = profile.get("name")
+ profile_name = profile_post_data.get("name")
if not isinstance(profile_name, str):
raise TypeError("malformed profile in prerequisite data; 'name'
not a string")
profile_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
+ Union[dict[str, object], list[Union[dict[str, object],
list[object], Primitive]], Primitive],
requests.Response
] = to_session.get_profiles(query_params={"name": profile_name})
try:
@@ -63,39 +57,18 @@ def test_profile_contract(
first_profile = profile_data[0]
if not isinstance(first_profile, dict):
raise TypeError("malformed API response; first Profile
in response is not an object")
- profile_keys = set(first_profile.keys())
+ logger.info("Profile Api get response %s", first_profile)
- logger.info("Profile Keys from profiles endpoint response %s",
profile_keys)
profile_response_template =
response_template_data.get("profiles")
if not isinstance(profile_response_template, dict):
raise TypeError(
f"Profile response template data must be a
dict, not '{type(profile_response_template)}'")
- response_template: dict[str, list[dict[str, object] |
list[object] | primitive] |\
- dict[object, object] |\
- primitive
- ]
- response_template = profile_response_template.get("properties")
+
# validate profile values from prereq data in profiles get
response.
- prereq_values = [
- profile_post_data["name"],
- profile_post_data["cdn"]
- ]
+ prereq_values = [profile_post_data["name"],
profile_post_data["cdn"]]
get_values = [first_profile["name"], first_profile["cdn"]]
- get_types = {}
- for key, value in first_profile.items():
- get_types[key] = type(value).__name__
- logger.info("Types from profile get response %s", get_types)
- response_template_types= {}
- for key, value in response_template.items():
- actual_type = value.get("type")
- if not isinstance(actual_type, str):
- raise TypeError(
- f"Type data must be a string, not
'{type(actual_type)}'")
- response_template_types[key] = actual_type
- logger.info("Types from profile response template %s",
response_template_types)
- assert profile_keys == set(response_template.keys())
- assert dict(sorted(get_types.items())) ==
dict(sorted(response_template_types.items()))
+ assert validate(instance=first_profile,
schema=profile_response_template) is None
assert get_values == prereq_values
except IndexError:
logger.error("Either prerequisite data or API response was
malformed")
diff --git a/traffic_ops/testing/api_contract/v4/test_regions.py
b/traffic_ops/testing/api_contract/v4/test_regions.py
index 58466984ad..7a0a6fb795 100644
--- a/traffic_ops/testing/api_contract/v4/test_regions.py
+++ b/traffic_ops/testing/api_contract/v4/test_regions.py
@@ -14,45 +14,40 @@
"""API Contract Test Case for regions 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 = bool | int | float | str | None
+Primitive = Union[bool, int, float, str, None]
+
[email protected]('request_template_data', ["regions"], indirect=True)
-def test_region_contract(
- to_session: TOSession,
- request_template_data: list[dict[str, object] | list[object] |
primitive],
- response_template_data: dict[str, primitive | list[primitive |
dict[str, object]
- | list[object]] |
dict[object, object]],
+def test_region_contract(to_session: TOSession,
+ response_template_data: dict[str, Union[Primitive,
list[Union[Primitive,
+ dict[str, object], list[object]]], dict[object, object]]],
region_post_data: dict[str, object]
-) -> None:
+ ) -> None:
"""
- Test step to validate keys, values and data types from regions endpoint
- response.
+ Test step to validate keys, values and data types from regions response.
:param to_session: Fixture to get Traffic Ops session.
- :param request_template_data: Fixture to get request template data from
a prerequisites file.
:param response_template_data: Fixture to get response template data
from a prerequisites file.
:param region_post_data: Fixture to get sample region data and actual
region response.
"""
# validate region keys from regions get response
logger.info("Accessing /regions endpoint through Traffic ops session.")
- region = request_template_data[0]
- if not isinstance(region, dict):
- raise TypeError("malformed region in prerequisite data; not an
object")
-
- region_name = region.get("name")
+ region_name = region_post_data.get("name")
if not isinstance(region_name, str):
raise TypeError("malformed region in prerequisite data; 'name'
not a string")
region_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
+ Union[dict[str, object], list[Union[dict[str, object],
list[object], Primitive]], Primitive],
requests.Response
] = to_session.get_regions(query_params={"name": region_name})
try:
@@ -63,37 +58,20 @@ def test_region_contract(
first_region = region_data[0]
if not isinstance(first_region, dict):
raise TypeError("malformed API response; first region
in response is not an object")
- region_keys = set(first_region.keys())
- logger.info("region Keys from regions endpoint response %s",
region_keys)
+ logger.info("Region Api response %s", first_region)
region_response_template = response_template_data.get("regions")
if not isinstance(region_response_template, dict):
raise TypeError(
f"region response template data must be a dict,
not '{type(region_response_template)}'")
- response_template: dict[str, list[dict[str, object] |
list[object] | primitive] |\
- dict[object, object] |\
- primitive
- ]
- response_template = region_response_template.get("properties")
+
# validate region values from prereq data in regions get
response.
- prereq_values = [region_post_data["name"],
region_post_data["division"],
- region_post_data["divisionName"]]
- get_values = [first_region["name"], first_region["division"],
first_region["divisionName"]]
- get_types = {}
- for key, value in first_region.items():
- get_types[key] = type(value).__name__
- logger.info("types from region get response %s", get_types)
- response_template_types= {}
- for key, value in response_template.items():
- actual_type = value.get("type")
- if not isinstance(actual_type, str):
- raise TypeError(
- f"Type data must be a string, not
'{type(actual_type)}'")
- response_template_types[key] = actual_type
- logger.info("types from regions response template %s",
response_template_types)
+ keys = ["name", "division", "divisionName"]
+ prereq_values = [region_post_data[key] for key in keys]
+ get_values = [first_region[key] for key in keys]
+
# validate keys, data types and values from regions get json
response.
- assert region_keys == set(response_template.keys())
- assert dict(sorted(get_types.items())) ==
dict(sorted(response_template_types.items()))
+ assert validate(instance=first_region,
schema=region_response_template) is None
assert get_values == prereq_values
except IndexError:
logger.error("Either prerequisite data or API response was
malformed")
diff --git a/traffic_ops/testing/api_contract/v4/test_roles.py
b/traffic_ops/testing/api_contract/v4/test_roles.py
index bdfd692647..79b4bda342 100644
--- a/traffic_ops/testing/api_contract/v4/test_roles.py
+++ b/traffic_ops/testing/api_contract/v4/test_roles.py
@@ -14,45 +14,40 @@
"""API Contract Test Case for roles 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 = bool | int | float | str | None
+Primitive = Union[bool, int, float, str, None]
+
[email protected]('request_template_data', ["roles"], indirect=True)
-def test_role_contract(
- to_session: TOSession,
- request_template_data: list[dict[str, object] | list[object] |
primitive],
- response_template_data: dict[str, primitive | list[primitive |
dict[str, object]
- | list[object]] |
dict[object, object]],
- role_post_data: dict[str, object]
-) -> None:
+def test_role_contract(to_session: TOSession,
+ response_template_data: dict[str, Union[Primitive, list[Union[Primitive,
+ dict[str, object],
list[object]]],
+ dict[object, object]]], role_post_data: dict[str, object]) -> None:
"""
Test step to validate keys, values and data types from roles endpoint
response.
:param to_session: Fixture to get Traffic Ops session.
- :param request_template_data: Fixture to get request template data from
a prerequisites file.
:param response_template_data: Fixture to get response template data
from a prerequisites file.
:param role_post_data: Fixture to get sample role data and actual role
response.
"""
# validate Role keys from roles get response
logger.info("Accessing /roles endpoint through Traffic ops session.")
- role = request_template_data[0]
- if not isinstance(role, dict):
- raise TypeError("malformed role in prerequisite data; not an
object")
-
- role_name = role.get("name")
+ role_name = role_post_data.get("name")
if not isinstance(role_name, str):
raise TypeError("malformed role in prerequisite data; 'name'
not a string")
role_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
+ Union[dict[str, object], list[Union[dict[str, object],
list[object], Primitive]], Primitive],
requests.Response
] = to_session.get_roles(query_params={"name": role_name})
try:
@@ -63,42 +58,25 @@ def test_role_contract(
first_role = role_data[0]
if not isinstance(first_role, dict):
raise TypeError("malformed API response; first role in
response is not an object")
- role_keys = set(first_role.keys())
+ logger.info("Role Api get response %s", first_role)
- logger.info("Role Keys from roles endpoint response %s",
role_keys)
role_response_template = response_template_data.get("roles")
if not isinstance(role_response_template, dict):
raise TypeError(
f"Role response template data must be a dict,
not '{type(role_response_template)}'")
- response_template: dict[str, list[dict[str, object] |
list[object] | primitive] |\
- dict[object, object] |\
- primitive
- ]
- response_template = role_response_template.get("properties")
+
# validate roles values from prereq data in roles get response.
prereq_values = [role_post_data["name"],
role_post_data["description"]]
get_values = [first_role["name"], first_role["description"]]
- get_types = {}
- for key, value in first_role.items():
- get_types[key] = type(value).__name__
- logger.info("types from role get response %s", get_types)
- response_template_types= {}
- for key, value in response_template.items():
- actual_type = value.get("type")
- if not isinstance(actual_type, str):
- raise TypeError(
- f"Type data must be a string, not
'{type(actual_type)}'")
- response_template_types[key] = actual_type
- logger.info("types from role response template %s",
response_template_types)
+
# validate keys,data types for values and values from roles get
json response.
- assert role_keys == set(response_template.keys())
- assert dict(sorted(get_types.items())) ==
dict(sorted(response_template_types.items()))
+ assert validate(instance=first_role,
schema=role_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 roles endpoint: API
response was malformed")
finally:
- # Delete Roel after test execution to avoid redundancy.
+ # Delete Role after test execution to avoid redundancy.
try:
role_name = role_post_data["name"]
to_session.delete_role(query_params={"name": role_name})
diff --git a/traffic_ops/testing/api_contract/v4/test_server_capabilities.py
b/traffic_ops/testing/api_contract/v4/test_server_capabilities.py
index fb5723d2a4..59066d7302 100644
--- a/traffic_ops/testing/api_contract/v4/test_server_capabilities.py
+++ b/traffic_ops/testing/api_contract/v4/test_server_capabilities.py
@@ -14,29 +14,28 @@
"""API Contract Test Case for server_capabilities 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 = bool | int | float | str | None
+Primitive = Union[bool, int, float, str, None]
+
[email protected]('request_template_data', ["server_capabilities"],
indirect=True)
-def test_server_capabilities_contract(
- to_session: TOSession,
- request_template_data: list[dict[str, object] | list[object] |
primitive],
- response_template_data: dict[str, primitive | list[primitive |
dict[str, object]
- | list[object]] |
dict[object, object]],
- server_capabilities_post_data: dict[str, object]
-) -> None:
+def test_server_capabilities_contract(to_session: TOSession,
+ response_template_data: dict[str, Union[Primitive, list[Union[Primitive,
+ dict[str, object],
list[object]]], dict[object, object]]],
+ server_capabilities_post_data: dict[str, object]) -> None:
"""
Test step to validate keys, values and data types from
server_capabilities endpoint
response.
:param to_session: Fixture to get Traffic Ops session.
- :param request_template_data: Fixture to get request template data from
a prerequisites file.
:param response_template_data: Fixture to get response template data
from a prerequisites file.
:param server_capabilities_post_data: Fixture to get sample
server_capabilities data
and actual server_capabilities response.
@@ -44,16 +43,12 @@ def test_server_capabilities_contract(
# validate server_capabilities keys from server_capabilities get
response
logger.info("Accessing /server_capabilities endpoint through Traffic
ops session.")
- server_capabilities = request_template_data[0]
- if not isinstance(server_capabilities, dict):
- raise TypeError("malformed server_capabilities in prerequisite
data; not an object")
-
- server_capabilities_name = server_capabilities.get("name")
+ server_capabilities_name = server_capabilities_post_data.get("name")
if not isinstance(server_capabilities_name, str):
raise TypeError("malformed server_capabilities in prerequisite
data; 'name' not a string")
server_capabilities_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
+ Union[dict[str, object], list[Union[dict[str, object],
list[object], Primitive]], Primitive],
requests.Response
] = to_session.get_server_capabilities(query_params={"name":
server_capabilities_name})
try:
@@ -64,36 +59,16 @@ def test_server_capabilities_contract(
first_server_capabilities = server_capabilities_data[0]
if not isinstance(first_server_capabilities, dict):
raise TypeError("malformed API response; first
server_capabilities in response is not an object")
- server_capabilities_keys = set(first_server_capabilities.keys())
+ logger.info("Server capabilities Api get response %s",
first_server_capabilities)
+
+ response_template =
response_template_data.get("server_capabilities")
- logger.info("server_capabilities Keys from endpoint response
%s", server_capabilities_keys)
- server_capabilities_response_template =
response_template_data.get("server_capabilities")
- if not isinstance(server_capabilities_response_template, dict):
- raise TypeError(
- f"server_capabilities data must be a dict, not
'{type(server_capabilities_response_template)}'")
- response_template: dict[str, list[dict[str, object] |
list[object] | primitive] |\
- dict[object, object] |\
- primitive
- ]
- response_template =
server_capabilities_response_template.get("properties")
# validate server_capabilities values from prereq data in api
get response.
- prereq_values = [server_capabilities_post_data["name"]]
- get_values = [first_server_capabilities["name"]]
- get_types = {}
- for key, value in first_server_capabilities.items():
- get_types[key] = type(value).__name__
- logger.info("types from server_capabilities get response %s",
get_types)
- response_template_types= {}
- for key, value in response_template.items():
- actual_type = value.get("type")
- if not isinstance(actual_type, str):
- raise TypeError(
- f"Type data must be a string, not
'{type(actual_type)}'")
- response_template_types[key] = actual_type
- logger.info("types from server_capabilities response template
%s", response_template_types)
+ prereq_values = server_capabilities_post_data["name"]
+ get_values = first_server_capabilities["name"]
+
# validate keys, data types and values from server_capabilities
get json response.
- assert server_capabilities_keys == set(response_template.keys())
- assert dict(sorted(get_types.items())) ==
dict(sorted(response_template_types.items()))
+ assert validate(instance=first_server_capabilities,
schema=response_template) is None
assert get_values == prereq_values
except IndexError:
logger.error("Either prerequisite data or API response was
malformed")
diff --git a/traffic_ops/testing/api_contract/v4/test_servers.py
b/traffic_ops/testing/api_contract/v4/test_servers.py
new file mode 100644
index 0000000000..37d7245cfd
--- /dev/null
+++ b/traffic_ops/testing/api_contract/v4/test_servers.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 servers 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_server_contract(to_session: TOSession,
+ response_template_data: dict[str, Union[Primitive, list[Union[Primitive,
+ dict[str, object],
list[object]]],
+ dict[object, object]]], server_post_data: dict[str, object]) -> None:
+ """
+ Test step to validate keys, values and data types from servers endpoint
+ 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 server_post_data: Fixture to get sample server data and actual
server response.
+ """
+ # validate server keys from server get response
+ logger.info("Accessing /servers endpoint through Traffic ops session.")
+
+ server_id = server_post_data.get("id")
+ if not isinstance(server_id, int):
+ raise TypeError("malformed API response; 'id' property not a
integer")
+
+ server_get_response: tuple[
+ Union[dict[str, object], list[Union[dict[str, object],
list[object], Primitive]], Primitive],
+ requests.Response
+ ] = to_session.get_servers(query_params={"id": server_id})
+ try:
+ server_data = server_get_response[0]
+ if not isinstance(server_data, list):
+ raise TypeError("malformed API response; 'response'
property not an array")
+
+ first_server = server_data[0]
+ if not isinstance(first_server, dict):
+ raise TypeError("malformed API response; first Server
in response is not an object")
+ logger.info("Server Api get response %s", first_server)
+ server_response_template = response_template_data.get("servers")
+ if not isinstance(server_response_template, dict):
+ raise TypeError(
+ f"Server response template data must be a dict,
not '{type(server_response_template)}'")
+
+ keys = ["cachegroupId", "cdnId", "domainName",
"physLocationId", "profileNames",
+ "statusId", "typeId"]
+ prereq_values = [server_post_data[key] for key in keys]
+ get_values = [first_server[key] for key in keys]
+
+ assert validate(instance=first_server,
schema=server_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 server endpoint: API
response was malformed")
+ finally:
+ # Delete Server after test execution to avoid redundancy.
+ try:
+ server_id = server_post_data["id"]
+ to_session.delete_server_by_id(server_id=server_id)
+ except IndexError:
+ logger.error("Profile returned by Traffic Ops is
missing an 'id' property")
+ pytest.fail("Response from delete request is empty,
Failing test_server_contract")
diff --git a/traffic_ops/testing/api_contract/v4/test_tenants.py
b/traffic_ops/testing/api_contract/v4/test_tenants.py
index 9ff9cd0e57..ee643b693b 100644
--- a/traffic_ops/testing/api_contract/v4/test_tenants.py
+++ b/traffic_ops/testing/api_contract/v4/test_tenants.py
@@ -14,45 +14,40 @@
"""API Contract Test Case for tenants 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 = bool | int | float | str | None
+Primitive = Union[bool, int, float, str, None]
+
[email protected]('request_template_data', ["tenants"], indirect=True)
-def test_tenant_contract(
- to_session: TOSession,
- request_template_data: list[dict[str, object] | list[object] |
primitive],
- response_template_data: dict[str, primitive | list[primitive |
dict[str, object]
- | list[object]] |
dict[object, object]],
- tenant_post_data: dict[str, object]
-) -> None:
+def test_tenant_contract(to_session: TOSession,
+ response_template_data: dict[str, Union[Primitive, list[Union[Primitive,
+ dict[str, object],
list[object]]], dict[object, object]]],
+ tenant_post_data: dict[str, object]) -> None:
"""
Test step to validate keys, values and data types from tenants endpoint
response.
:param to_session: Fixture to get Traffic Ops session.
- :param request_template_data: Fixture to get request template data from
a prerequisites file.
:param response_template_data: Fixture to get response template data
from a prerequisites file.
:param tenant_post_data: Fixture to get sample tenant data and actual
tenant response.
"""
# validate tenant keys from tenants get response
logger.info("Accessing /tenants endpoint through Traffic ops session.")
- tenant = request_template_data[0]
- if not isinstance(tenant, dict):
- raise TypeError("malformed tenant in prerequisite data; not an
object")
-
- tenant_name = tenant.get("name")
+ tenant_name = tenant_post_data.get("name")
if not isinstance(tenant_name, str):
raise TypeError("malformed tenant in prerequisite data; 'name'
not a string")
tenant_get_response: tuple[
- dict[str, object] | list[dict[str, object] | list[object] |
primitive] | primitive,
+ Union[dict[str, object], list[Union[dict[str, object],
list[object], Primitive]], Primitive],
requests.Response
] = to_session.get_tenants(query_params={"name": tenant_name})
try:
@@ -63,37 +58,20 @@ def test_tenant_contract(
first_tenant = tenant_data[0]
if not isinstance(first_tenant, dict):
raise TypeError("malformed API response; first tenant
in response is not an object")
- tenant_keys = set(first_tenant.keys())
+ logger.info("Tenant Api get response %s", first_tenant)
- logger.info("tenant Keys from tenants endpoint response %s",
tenant_keys)
tenant_response_template = response_template_data.get("tenants")
if not isinstance(tenant_response_template, dict):
raise TypeError(
f"tenant response template data must be a dict,
not '{type(tenant_response_template)}'")
- response_template: dict[str, list[dict[str, object] |
list[object] | primitive] |\
- dict[object, object] |\
- primitive
- ]
- response_template = tenant_response_template.get("properties")
+
# validate tenant values from prereq data in tenants get
response.
- prereq_values = [tenant_post_data["name"],
tenant_post_data["active"],
- tenant_post_data["parentId"]]
- get_values = [first_tenant["name"], first_tenant["active"],
first_tenant["parentId"]]
- get_types = {}
- for key, value in first_tenant.items():
- get_types[key] = type(value).__name__
- logger.info("types from tenant get response %s", get_types)
- response_template_types= {}
- for key, value in response_template.items():
- actual_type = value.get("type")
- if not isinstance(actual_type, str):
- raise TypeError(
- f"Type data must be a string, not
'{type(actual_type)}'")
- response_template_types[key] = actual_type
- logger.info("types from tenants response template %s",
response_template_types)
+ keys = ["name", "active", "parentId"]
+ prereq_values = [tenant_post_data[key] for key in keys]
+ get_values = [first_tenant[key] for key in keys]
+
# validate keys, data types and values from tenants get json
response.
- assert tenant_keys == set(response_template.keys())
- assert dict(sorted(get_types.items())) ==
dict(sorted(response_template_types.items()))
+ assert validate(instance=first_tenant,
schema=tenant_response_template) is None
assert get_values == prereq_values
except IndexError:
logger.error("Either prerequisite data or API response was
malformed")