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

ocket8888 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 d4f22e7bba Api contract phys locations (#7461)
d4f22e7bba is described below

commit d4f22e7bba9231c69c9df4e267fbb90af3e18a5a
Author: Kashatlast2 <[email protected]>
AuthorDate: Mon May 15 13:37:43 2023 -0400

    Api contract phys locations (#7461)
    
    * Added divisions test case
    
    * resolve linting issues
    
    * trailing new lines removed
    
    * Added Regions test
    
    * Added phys_locations test case
    
    * Updated phys_locations test
    
    * Delete test_regions.py
    
    file already merged in from regions branch
    
    * Fixed conflicts - Update request_template.json
    
    * Added Api version 3.1 - Update tosession.py
    
    ---------
    
    Co-authored-by: yblanc545 <[email protected]>
---
 .../clients/python/trafficops/tosession.py         |  11 ++
 traffic_ops/testing/api_contract/v4/conftest.py    |  64 +++++++++
 .../testing/api_contract/v4/request_template.json  | 151 +++++++++++----------
 .../testing/api_contract/v4/response_template.json |  47 +++++++
 .../testing/api_contract/v4/test_phys_locations.py | 122 +++++++++++++++++
 5 files changed, 327 insertions(+), 68 deletions(-)

diff --git a/traffic_control/clients/python/trafficops/tosession.py 
b/traffic_control/clients/python/trafficops/tosession.py
index a38068ea17..14b17dbc9a 100644
--- a/traffic_control/clients/python/trafficops/tosession.py
+++ b/traffic_control/clients/python/trafficops/tosession.py
@@ -1395,6 +1395,17 @@ class TOSession(RestApiSession):
                :raises: Union[LoginError, OperationError]
                """
 
+       @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):
+               """
+               Create a Physical Location
+               :ref:`to-api-phys_locations`
+               :param data: The parameter data to use for Physical Location 
creation
+               :type data: Dict[str, Any]
+               :rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]]], 
requests.Response]
+               :raises: Union[LoginError, OperationError]
+               """
+
        @api_request('put', 'phys_locations/{physical_location_id:d}', ('3.0', 
'4.0', '4.1', '5.0'))
        def update_physical_location(self, physical_location_id=None, 
query_params=None):
                """
diff --git a/traffic_ops/testing/api_contract/v4/conftest.py 
b/traffic_ops/testing/api_contract/v4/conftest.py
index 36af044e7a..ab74e6ab87 100644
--- a/traffic_ops/testing/api_contract/v4/conftest.py
+++ b/traffic_ops/testing/api_contract/v4/conftest.py
@@ -817,3 +817,67 @@ def region_post_data(to_session: TOSession, 
request_template_data: list[JSONData
        except IndexError:
                logger.error("No Region response data from divisions POST 
request.")
        sys.exit(1)
+
[email protected]()
+def phys_locations_post_data(to_session: TOSession, request_template_data: 
list[JSONData]
+                         ) -> 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.
+       :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)}'")
+
+       # Return new post data and post response from phys_locations POST 
request
+       randstr = str(randint(0, 1000))
+       try:
+               name = phys_locations["name"]
+               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"]
+               if not isinstance(name, str):
+                       raise TypeError(f"shortName must be str, not 
'{type(shortName)}'")
+               phys_locations["shortName"] = shortName[: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
+
+       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)
+
diff --git a/traffic_ops/testing/api_contract/v4/request_template.json 
b/traffic_ops/testing/api_contract/v4/request_template.json
index 51dd169dd2..c67d746976 100644
--- a/traffic_ops/testing/api_contract/v4/request_template.json
+++ b/traffic_ops/testing/api_contract/v4/request_template.json
@@ -1,71 +1,86 @@
 {
-    "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": [
-                   {
+       "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"
-               }
-       ]
+               "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/response_template.json 
b/traffic_ops/testing/api_contract/v4/response_template.json
index 3955152040..72508ede94 100644
--- a/traffic_ops/testing/api_contract/v4/response_template.json
+++ b/traffic_ops/testing/api_contract/v4/response_template.json
@@ -217,5 +217,52 @@
                 "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_phys_locations.py 
b/traffic_ops/testing/api_contract/v4/test_phys_locations.py
new file mode 100644
index 0000000000..265c739162
--- /dev/null
+++ b/traffic_ops/testing/api_contract/v4/test_phys_locations.py
@@ -0,0 +1,122 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""API Contract Test Case for phys_locations endpoint."""
+import logging
+import pytest
+import requests
+
+from trafficops.tosession import TOSession
+
+# Create and configure logger
+logger = logging.getLogger()
+
+primitive = 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],
+       phys_locations_post_data: dict[str, object]
+) -> None:
+       """
+       Test step to validate keys, values and data types from phys_locations 
endpoint
+       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 
+       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")
+       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,
+               requests.Response
+       ] = to_session.get_physical_locations(query_params={"name": 
phys_location_name})
+       try:
+               phys_location_data = phys_location_get_response[0]
+               if not isinstance(phys_location_data, list):
+                       raise TypeError("malformed API response; 'response' 
property not an array")
+
+               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)
+               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"]]
+
+               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 get_values == prereq_values
+       except IndexError:
+               logger.error("Either prerequisite data or API response was 
malformed")
+               pytest.fail("API contract test failed for phys_locations 
endpoint: API response was malformed")
+       finally:
+               # Delete phys_location after test execution to avoid redundancy.
+               try:
+                       physical_location_id = phys_locations_post_data["id"]
+                       
to_session.delete_physical_location(physical_location_id=physical_location_id)
+               except IndexError:
+                       logger.error("phys_location returned by Traffic Ops is 
missing an 'id' property")
+                       pytest.fail("Response from delete request is empty, 
Failing test_get_phys_location")

Reply via email to