This is an automated email from the ASF dual-hosted git repository.

shamrick 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 67cf92d665 Various improvements to the API contract tests (#7419)
67cf92d665 is described below

commit 67cf92d665da915b0727145cb718d6fddd3ae1d1
Author: ocket8888 <[email protected]>
AuthorDate: Tue Mar 28 10:23:32 2023 -0600

    Various improvements to the API contract tests (#7419)
    
    * Get rid of removed Pylint config option
    
    * Use real indentation
    
    * foo
    
    * Slight formatting changes
    
    * Greatly improve type safety in conftest main script
    
    * fix use of reserved short option
    
    * Fix URLs not allowed to omit port number
    
    * Fix using deprecated log method
    
    * add context to URL parsing errors
    
    * Make prerequisite data file location configurable
    
    * don't return duplicate data in the prerequisite fixture
    
    * Make "cdns" an array
    
    It's plural, so I assume that was the original intent
    
    * Fix typechecking issues
    
    * Fix linter errors
    
    all but one were about lines being too long
---
 traffic_control/clients/python/pylint.rc           |   7 -
 traffic_ops/testing/api_contract/v4/conftest.py    | 393 +++++++++++++++------
 .../testing/api_contract/v4/prerequisite_data.json |  14 +-
 traffic_ops/testing/api_contract/v4/test_cdns.py   | 152 +++++---
 4 files changed, 399 insertions(+), 167 deletions(-)

diff --git a/traffic_control/clients/python/pylint.rc 
b/traffic_control/clients/python/pylint.rc
index 77d93556e1..aadd4fe56e 100644
--- a/traffic_control/clients/python/pylint.rc
+++ b/traffic_control/clients/python/pylint.rc
@@ -360,13 +360,6 @@ max-line-length=100
 # Maximum number of lines in a module.
 max-module-lines=1000
 
-# List of optional constructs for which whitespace checking is disabled. `dict-
-# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
-# `trailing-comma` allows a space between comma and closing bracket: (a, ).
-# `empty-line` allows space-only lines.
-no-space-check=trailing-comma,
-                          dict-separator
-
 # Allow the body of a class to be on the same line as the declaration if body
 # contains single statement.
 single-line-class-stmt=no
diff --git a/traffic_ops/testing/api_contract/v4/conftest.py 
b/traffic_ops/testing/api_contract/v4/conftest.py
index 684089b6b0..bc2bb16f6f 100644
--- a/traffic_ops/testing/api_contract/v4/conftest.py
+++ b/traffic_ops/testing/api_contract/v4/conftest.py
@@ -12,132 +12,327 @@
 # limitations under the License.
 #
 
-"""This module is used to create a Traffic Ops session 
-and to store prerequisite data for endpoints."""
+"""
+This module is used to create a Traffic Ops session and to store prerequisite
+data for endpoints.
+"""
+
 import json
 import logging
 import sys
 import os
 from random import randint
-from typing import NamedTuple, Optional
+from typing import NamedTuple, Union, Optional, TypeAlias
 from urllib.parse import urlparse
+
 import pytest
 import requests
+
 from trafficops.tosession import TOSession
 from trafficops.restapi import OperationError
 
-
 # Create and configure logger
 logger = logging.getLogger()
 
+JSONData: TypeAlias = Union[dict[str, object], list[object], bool, int, float, 
str | None]
+JSONData.__doc__ = """An alias for the kinds of data that JSON can encode."""
+
+class APIVersion(NamedTuple):
+       """Represents an API version."""
+       major: int
+       minor: int
+
+       @staticmethod
+       def from_string(ver_str: str) -> "APIVersion":
+               """
+               Instantiates a new version from a string.
+
+               >>> APIVersion.from_string("4.0")
+               APIVersion(major=4, minor=0)
+               >>> try:
+               ...     APIVersion("not a version string")
+               ... except ValueError:
+               ...     print("whoops")
+               ...
+               whoops
+               >>>
+               >>> try:
+               ...     APIVersion("4.Q")
+               ... except ValueError:
+               ...     print("whoops")
+               ...
+               whoops
+               """
+               parts = ver_str.split(".", 1)
+               if len(parts) != 2:
+                       raise ValueError("invalid version; must be of the form 
'{{major}}.{{minor}}'")
+               return APIVersion(int(parts[0]), int(parts[1]))
+
+       def __str__(self) -> str:
+               """
+               Coalesces the version to a string.
+
+               >>> print(APIVersion(4, 1))
+               4.1
+               """
+               return f"{self.major}.{self.minor}"
+
+APIVersion.major.__doc__ = """The API's major version number."""
+APIVersion.major.__doc__ = """The API's minor version number."""
 
 class ArgsType(NamedTuple):
-    """Represents the arguments needed to create Traffic Ops session.
-
-    Attributes:
-        user (str): The username used for authentication.
-        password (str): The password used for authentication.
-        url (str): The URL of the environment.
-        port (int): The port number to use for session.
-        api_version (float): The version number of the API to use.
-    """
-    user: str
-    password: str
-    url: str
-    port: int
-    api_version: float
+       """Represents the configuration needed to create Traffic Ops session."""
+       user: str
+       password: str
+       url: str
+       port: int
+       api_version: APIVersion
 
+       def __str__(self) -> str:
+               """
+               Formats the configuration as a string. Omits password and 
extraneous
+               properties.
+
+               >>> print(ArgsType("user", "password", "url", 420, 
APIVersion(4, 0)))
+               User: 'user', URL: 'url'
+               """
+               return f"User: '{self.user}', URL: '{self.url}'"
+
+
+ArgsType.user.__doc__ = """The username used for authentication."""
+ArgsType.password.__doc__ = """The password used for authentication."""
+ArgsType.url.__doc__ = """The URL of the environment."""
+ArgsType.port.__doc__ = """The port number on which to connect to Traffic 
Ops."""
+ArgsType.api_version.__doc__ = """The version number of the API to use."""
 
 def pytest_addoption(parser: pytest.Parser) -> None:
-    """Passing in Traffic Ops arguments [Username, Password, Url and Hostname] 
from command line.
-    :param parser: Parser to parse command line arguments
-    """
-    parser.addoption(
-        "--to-user", action="store", help="User name for Traffic Ops Session."
-    )
-    parser.addoption(
-        "--to-password", action="store", help="Password for Traffic Ops 
Session."
-    )
-    parser.addoption(
-        "--to-url", action="store", help="Traffic Ops URL."
-    )
+       """
+       Parses the Traffic Ops arguments from command line.
+       :param parser: Parser to parse command line arguments.
+       """
+       parser.addoption(
+               "--to-user", action="store", help="User name for Traffic Ops 
Session."
+       )
+       parser.addoption(
+               "--to-password", action="store", help="Password for Traffic Ops 
Session."
+       )
+       parser.addoption(
+               "--to-url", action="store", help="Traffic Ops URL."
+       )
+       parser.addoption(
+               "--config",
+               help="Path to configuration file.",
+               default=os.path.join(os.path.dirname(__file__), "to_data.json")
+       )
+       parser.addoption(
+               "--prerequisites",
+               help="Path to prerequisites file.",
+               default=os.path.join(os.path.dirname(__file__), 
"prerequisite_data.json")
+       )
+
+def coalesce_config(
+       arg: object | None,
+       file_key: str,
+       file_contents: dict[str, object | None] | None,
+       env_key: str
+) -> Optional[str]:
+       """
+       Coalesces configuration retrieved from different sources into a single
+       string.
+
+       This will raise a ValueError if the type of the configuration value in 
the
+       parsed configuration file is not a string.
+
+       In order of descending precedence this checks the command-line argument
+       value, the configuration file value, and then the environment variable
+       value.
+
+       :param arg: The command-line argument value.
+       :param file_key: The key under which to look in the parsed JSON 
configuration file data.
+       :param file_contents: The parsed JSON configuration file (if one was 
used).
+       :param env_key: The environment variable name to look for a value if 
one wasn't provided elsewhere.
+       :returns: The coalesced configuration value, or 'None' if no value 
could be determined.
+       """
+       if isinstance(arg, str):
+               return arg
+
+       if file_contents:
+               file_value = file_contents.get(file_key)
+               if isinstance(file_value, str):
+                       return file_value
+               if file_value is not None:
+                       raise ValueError(f"incorrect value; want: 'str', got: 
'{type(file_value)}'")
+
+       return os.environ.get(env_key)
 
+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)
+       (APIVersion(major=5, minor=270), 420)
+       >>> parse_to_url("trafficops.example.test")
+       (APIVersion(major=4, minor=0), 443)
+       """
+       parsed = urlparse(raw)
+       if not parsed.netloc:
+               raise ValueError("missing network location (hostname & optional 
port)")
+
+       if parsed.scheme and parsed.scheme.lower() != "https":
+               raise ValueError("invalid scheme; must use HTTPS")
+
+       port = 443
+       if ":" in parsed.netloc:
+               port_str = parsed.netloc.split(":")[-1]
+               try:
+                       port = int(port_str)
+               except ValueError as e:
+                       raise ValueError(f"invalid port number: {port_str}") 
from e
+
+       api_version = APIVersion(4, 0)
+       if parsed.path and parsed.path != "/":
+               ver_str = parsed.path.lstrip("/api/").split("/", 1)[0]
+               if not ver_str:
+                       raise ValueError(f"invalid API path: {parsed.path} 
(should be e.g. '/api/4.0')")
+               api_version = APIVersion.from_string(ver_str)
+       else:
+               logging.warning("using default API version: %s", api_version)
+
+       return (api_version, port)
 
 @pytest.fixture(name="to_args")
 def to_data(pytestconfig: pytest.Config) -> ArgsType:
-    """PyTest fixture to store Traffic ops arguments passed from command line.
-    :param pytestconfig: Session-scoped fixture that returns the session's 
pytest.Config object
-    :returns args: Return Traffic Ops arguments
-    """
-    with open(os.path.join(os.path.dirname(__file__), "to_data.json"),
-            encoding="utf-8", mode="r") as session_file:
-        session_data = json.load(session_file)
-    to_user = pytestconfig.getoption("--to-user")
-    to_password = pytestconfig.getoption("--to-password")
-    to_url = pytestconfig.getoption("--to-url")
-    api_version = urlparse(
-        session_data.get("url")).path.strip("/").split("/")[1]
-
-    if not all([to_user, to_password, to_url]):
-        logger.info(
-            "Traffic Ops session data were not passed from Command line Args.")
-        args = ArgsType(user=session_data.get("user"), 
password=session_data.get(
-            "password"), url=session_data.get("url"), 
port=session_data.get("port"),
-            api_version=api_version)
-
-    else:
-        args = ArgsType(user=to_user, password=to_password, url=to_url,
-                        port=session_data.get("port"), api_version=api_version)
-        logger.info("Parsed Traffic ops session data from args %s", args)
-    return args
+       """
+       PyTest fixture to store Traffic ops arguments passed from command line.
+       :param pytestconfig: Session-scoped fixture that returns the session's 
pytest.Config object.
+       :returns: Configuration for connecting to Traffic Ops.
+       """
+       session_data: JSONData = None
+       cfg_path = pytestconfig.getoption("--config")
+       if isinstance(cfg_path, str):
+               try:
+                       with open(cfg_path, encoding="utf-8", mode="r") as 
session_file:
+                               session_data = json.load(session_file)
+               except (FileNotFoundError, PermissionError) as read_err:
+                       raise ValueError(f"could not read configuration file at 
'{cfg_path}'") from read_err
+
+       if session_data is not None and not isinstance(session_data, dict):
+               raise ValueError(
+                       f"invalid configuration file; expected top-level 
object, got: {type(session_data)}"
+               )
+
+       to_user = coalesce_config(pytestconfig.getoption("--to-user"), "user", 
session_data, "TO_USER")
+       if not to_user:
+               raise ValueError(
+                       "Traffic Ops password is not configured - use 
'--to-password', the config file, or an "
+                       "environment variable to do so"
+               )
+
+       to_password = coalesce_config(
+               pytestconfig.getoption("--to-password"),
+               "password",
+               session_data,
+               "TO_PASSWORD"
+       )
+
+       if not to_password:
+               raise ValueError(
+                       "Traffic Ops password is not configured - use 
'--to-password', the config file, or an "
+                       "environment variable to do so"
+               )
 
+       to_url = coalesce_config(pytestconfig.getoption("--to-url"), "url", 
session_data, "TO_USER")
+       if not to_url:
+               raise ValueError(
+                       "Traffic Ops URL is not configured - use '--to-url', 
the config file, or an "
+                       "environment variable to do so"
+               )
+
+       try:
+               api_version, port = parse_to_url(to_url)
+       except ValueError as e:
+               raise ValueError("invalid Traffic Ops URL") from e
+
+       return ArgsType(
+               to_user,
+               to_password,
+               to_url,
+               port,
+               api_version
+       )
 
 @pytest.fixture(name="to_session")
 def to_login(to_args: ArgsType) -> TOSession:
-    """PyTest Fixture to create a Traffic Ops session from Traffic Ops 
Arguments
-    passed as command line arguments in to_args fixture in conftest.
-    :param to_args: Fixture to get Traffic ops session arguments
-    :returns to_session: Return Traffic ops session
-    """
-    # Create a Traffic Ops V4 session and login
-    to_url = urlparse(to_args.url)
-    to_host = to_url.hostname
-    try:
-        to_session = TOSession(host_ip=to_host, host_port=to_args.port,
-                               api_version=to_args.api_version, ssl=True, 
verify_cert=False)
-        logger.info("Established Traffic Ops Session.")
-    except OperationError as error:
-        logger.debug("%s", error, exc_info=True, stack_info=True)
-        logger.error(
-            "Failure in Traffic Ops session creation. Reason: %s", error)
-        sys.exit(-1)
-
-    # Login To TO_API
-    to_session.login(to_args.user, to_args.password)
-    logger.info("Successfully logged into Traffic Ops.")
-    return to_session
+       """
+       PyTest Fixture to create a Traffic Ops session from Traffic Ops 
Arguments
+       passed as command line arguments in to_args fixture in conftest.
+
+       :param to_args: Fixture to get Traffic ops session arguments.
+       :returns: An authenticated Traffic Ops session.
+       """
+       # Create a Traffic Ops V4 session and login
+       to_url = urlparse(to_args.url)
+       to_host = to_url.hostname
+       try:
+               to_session = TOSession(
+                       host_ip=to_host,
+                       host_port=to_args.port,
+                       api_version=str(to_args.api_version),
+                       ssl=True,
+                       verify_cert=False
+               )
+               logger.info("Established Traffic Ops Session.")
+       except OperationError as error:
+               logger.debug("%s", error, exc_info=True, stack_info=True)
+               logger.error("Failure in Traffic Ops session creation. Reason: 
%s", error)
+               sys.exit(-1)
+
+       # Login To TO_API
+       to_session.login(to_args.user, to_args.password)
+       logger.info("Successfully logged into Traffic Ops.")
+       return to_session
 
 
 @pytest.fixture()
-def cdn_post_data(to_session: TOSession, cdn_prereq_data:
-                  object) -> Optional[list[dict[str, str] | 
requests.Response]]:
-    """PyTest Fixture to create POST data for cdns endpoint.
-    :param to_session: Fixture to get Traffic ops session 
-    :param get_cdn_data: Fixture to get cdn data from a prereq file
-    :returns prerequisite_data: Returns sample Post data and actual api 
response
-    """
-
-    # Return new post data and post response from cdns POST request
-    cdn_prereq_data["name"] = cdn_prereq_data["name"][:4]+str(randint(0, 1000))
-    cdn_prereq_data["domainName"] = cdn_prereq_data["domainName"][:5] + \
-        str(randint(0, 1000))
-    logger.info("New cdn data to hit POST method %s", cdn_prereq_data)
-    # Hitting cdns POST methed
-    response = to_session.create_cdn(data=cdn_prereq_data)
-    prerequisite_data = None
-    try:
-        cdn_response = response[0]
-        prerequisite_data = [cdn_prereq_data, cdn_response]
-    except IndexError:
-        logger.error("No CDN response data from cdns POST request.")
-    return prerequisite_data
+def cdn_post_data(to_session: TOSession, cdn_prereq_data: list[JSONData]) -> 
dict[str, object]:
+       """
+       PyTest Fixture to create POST data for cdns endpoint.
+
+       :param to_session: Fixture to get Traffic Ops session.
+       :param get_cdn_data: Fixture to get CDN data from a prerequisites file.
+       :returns: Sample POST data and the actual API response.
+       """
+
+       try:
+               cdn = cdn_prereq_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)}'")
+
+       # Return new post data and post response from cdns POST request
+       randstr = str(randint(0, 1000))
+       try:
+               name = cdn["name"]
+               if not isinstance(name, str):
+                       raise TypeError(f"name must be str, not '{type(name)}'")
+               cdn["name"] = name[:4] + randstr
+               domain_name = cdn["domainName"]
+               if not isinstance(domain_name, str):
+                       raise TypeError(f"domainName must be str, not 
'{type(domain_name)}")
+               cdn["domainName"] = domain_name[:5] + randstr
+       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", cdn_prereq_data)
+       # 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)
diff --git a/traffic_ops/testing/api_contract/v4/prerequisite_data.json 
b/traffic_ops/testing/api_contract/v4/prerequisite_data.json
index 4939bbe284..2acf4b7801 100644
--- a/traffic_ops/testing/api_contract/v4/prerequisite_data.json
+++ b/traffic_ops/testing/api_contract/v4/prerequisite_data.json
@@ -1,9 +1,9 @@
 {
-    "cdns": {
-        "name": "test",
-        "domainName": "quest",
-        "dnssecEnabled": false,
-        "id": null,
-        "lastUpdated": null
-    }
+       "cdns": [{
+               "name": "test",
+               "domainName": "quest",
+               "dnssecEnabled": false,
+               "id": null,
+               "lastUpdated": null
+       }]
 }
diff --git a/traffic_ops/testing/api_contract/v4/test_cdns.py 
b/traffic_ops/testing/api_contract/v4/test_cdns.py
index 9b7e4d2491..e146ab579e 100644
--- a/traffic_ops/testing/api_contract/v4/test_cdns.py
+++ b/traffic_ops/testing/api_contract/v4/test_cdns.py
@@ -12,68 +12,112 @@
 # limitations under the License.
 #
 
-"""Api Contract Test Case for cdns endpoint."""
+"""API Contract Test Case for cdns endpoint."""
 import json
 import logging
-import os
+
 import pytest
 import requests
+
 from trafficops.tosession import TOSession
 
 # Create and configure logger
 logger = logging.getLogger()
 
+primitive = bool | int | float | str | None
 
 @pytest.fixture(name="cdn_prereq_data")
-def get_cdn_prereq_data() -> object:
-    """PyTest Fixture to store prereq data for cdns endpoint.
-    :returns cdn_data: Returns prerequisite data for cdns endpoint
-    """
-    # Response keys for cdns endpoint
-    with open(os.path.join(os.path.dirname(__file__), 
"prerequisite_data.json"),
-              encoding="utf-8", mode="r") as prereq_file:
-        data = json.load(prereq_file)
-    cdn_data = data["cdns"]
-    return cdn_data
-
-
-def test_cdn_contract(to_session: TOSession, cdn_prereq_data: object,
-                      cdn_post_data: list[dict[str, str] | requests.Response]) 
-> None:
-    """Test step to validate keys, values and data types from cdns endpoint 
response.
-    :param to_session: Fixture to get Traffic ops session 
-    :param get_cdn_data: Fixture to get cdn data from a prereq file
-    :param cdn_prereq: Fixture to get sample cdn data and actual cdn response
-    """
-    # validate CDN keys from cdns get response
-    logger.info("Accessing Cdn endpoint through Traffic ops session.")
-    cdn_name = cdn_post_data[0]["name"]
-    cdn_get_response = to_session.get_cdns(
-        query_params={"name": cdn_name})
-    try:
-        cdn_data = cdn_get_response[0]
-        cdn_keys = list(cdn_data[0].keys())
-        logger.info(
-            "CDN Keys from cdns endpoint response %s", cdn_keys)
-        # validate cdn values from prereq data in cdns get response.
-        prereq_values = [cdn_post_data[0]["name"], cdn_post_data[0]
-                         ["domainName"], cdn_post_data[0]["dnssecEnabled"]]
-        get_values = [cdn_data[0]["name"], cdn_data[0]
-                      ["domainName"], cdn_data[0]["dnssecEnabled"]]
-        # validate data types for values from cdn get json response.
-        for (prereq_value, get_value) in zip(prereq_values, get_values):
-            assert isinstance(prereq_value, type(get_value))
-        assert cdn_keys.sort() == list(cdn_prereq_data.keys()).sort()
-        assert get_values == prereq_values
-    except IndexError:
-        logger.error("No CDN data from cdns get request")
-        pytest.fail("Response from get request is empty, Failing test_get_cdn")
-    finally:
-        # Delete CDN after test execution to avoid redundancy.
-        try:
-            cdn_response = cdn_post_data[1]
-            cdn_id = cdn_response["id"]
-            to_session.delete_cdn_by_id(cdn_id=cdn_id)
-        except IndexError:
-            logger.error("CDN wasn't created")
-            pytest.fail(
-                "Response from delete request is empty, Failing test_get_cdn")
+def get_cdn_prereq_data(
+       pytestconfig: pytest.Config
+) -> list[dict[str, object] | list[object] | primitive]:
+       """
+       PyTest Fixture to store POST request body data for cdns endpoint.
+
+       :returns: Prerequisite data for cdns endpoint.
+       """
+       prereq_path = pytestconfig.getoption("prerequisites")
+       if not isinstance(prereq_path, str):
+               # unlike the configuration file, this must be present
+               raise ValueError("prereqisites path not configured")
+
+       # Response keys for cdns endpoint
+       data: dict[
+               str,
+               list[dict[str, object] | list[object] | primitive] |\
+                       dict[object, object] |\
+                       primitive
+               ] |\
+       primitive = None
+       with open(prereq_path, encoding="utf-8", mode="r") as prereq_file:
+               data = json.load(prereq_file)
+       if not isinstance(data, dict):
+               raise TypeError(f"prerequisite data must be an object, not 
'{type(data)}'")
+
+       cdn_data = data["cdns"]
+       if not isinstance(cdn_data, list):
+               raise TypeError(f"cdns data must be a list, not 
'{type(cdn_data)}'")
+
+       return cdn_data
+
+
+def test_cdn_contract(
+       to_session: TOSession,
+       cdn_prereq_data: list[dict[str, object] | list[object] | primitive],
+       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 get_cdn_data: Fixture to get CDN data from a prerequisites file.
+       :param cdn_prereq: 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 = cdn_prereq_data[0]
+       if not isinstance(cdn, dict):
+               raise TypeError("malformed cdn in prerequisite data; not an 
object")
+
+       cdn_name = cdn.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,
+               requests.Response
+       ] = to_session.get_cdns(query_params={"name": cdn_name})
+       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")
+               cdn_keys = set(first_cdn.keys())
+
+               logger.info("CDN Keys from cdns endpoint response %s", cdn_keys)
+               # 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"]]
+               # validate data types for values from cdn get json response.
+               for (prereq_value, get_value) in zip(prereq_values, get_values):
+                       assert isinstance(prereq_value, type(get_value))
+               assert cdn_keys == set(cdn_post_data.keys())
+               assert get_values == prereq_values
+       except IndexError:
+               logger.error("Either prerequisite data or API response was 
malformed")
+               pytest.fail("Either prerequisite data or API response was 
malformed")
+       finally:
+               # Delete CDN after test execution to avoid redundancy.
+               try:
+                       cdn_id = cdn_post_data["id"]
+                       to_session.delete_cdn_by_id(cdn_id=cdn_id)
+               except IndexError:
+                       logger.error("CDN returned by Traffic Ops is missing an 
'id' property")
+                       pytest.fail("Response from delete request is empty, 
Failing test_get_cdn")

Reply via email to