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")