mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-10-09 01:44:03 -07:00
Add latest updates from FTD Ansible downstream repository. (#53638)
* Add latest updates from FTD Ansible downstream repository. - add a better implementation of the upsert operation; - add API version lookup functionality; - add filter which remove duplicated references from the list of references; - fix minor bugs. * fix issues outlined by ansibot * fix argument name for _check_enum_method
This commit is contained in:
parent
71216cace5
commit
2176b53a55
15 changed files with 882 additions and 298 deletions
|
@ -16,7 +16,7 @@
|
|||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from ansible.module_utils.network.ftd.common import equal_objects
|
||||
from ansible.module_utils.network.ftd.common import equal_objects, delete_ref_duplicates
|
||||
|
||||
|
||||
# simple objects
|
||||
|
@ -246,3 +246,129 @@ def test_equal_objects_return_true_with_equal_nested_list_of_object_references()
|
|||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_equal_objects_return_true_with_reference_list_containing_duplicates():
|
||||
assert equal_objects(
|
||||
{
|
||||
'name': 'foo',
|
||||
'config': {
|
||||
'version': '1',
|
||||
'ports': [{
|
||||
'name': 'oldPortName',
|
||||
'type': 'port',
|
||||
'id': '123'
|
||||
}, {
|
||||
'name': 'oldPortName',
|
||||
'type': 'port',
|
||||
'id': '123'
|
||||
}, {
|
||||
'name': 'oldPortName2',
|
||||
'type': 'port',
|
||||
'id': '234'
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'foo',
|
||||
'config': {
|
||||
'version': '1',
|
||||
'ports': [{
|
||||
'name': 'newPortName',
|
||||
'type': 'port',
|
||||
'id': '123'
|
||||
}, {
|
||||
'name': 'newPortName2',
|
||||
'type': 'port',
|
||||
'id': '234',
|
||||
'extraField': 'foo'
|
||||
}]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_delete_ref_duplicates_with_none():
|
||||
assert delete_ref_duplicates(None) is None
|
||||
|
||||
|
||||
def test_delete_ref_duplicates_with_empty_dict():
|
||||
assert {} == delete_ref_duplicates({})
|
||||
|
||||
|
||||
def test_delete_ref_duplicates_with_simple_object():
|
||||
data = {
|
||||
'id': '123',
|
||||
'name': 'foo',
|
||||
'type': 'bar',
|
||||
'values': ['a', 'b']
|
||||
}
|
||||
assert data == delete_ref_duplicates(data)
|
||||
|
||||
|
||||
def test_delete_ref_duplicates_with_object_containing_refs():
|
||||
data = {
|
||||
'id': '123',
|
||||
'name': 'foo',
|
||||
'type': 'bar',
|
||||
'refs': [
|
||||
{'id': '123', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'foo'}
|
||||
]
|
||||
}
|
||||
assert data == delete_ref_duplicates(data)
|
||||
|
||||
|
||||
def test_delete_ref_duplicates_with_object_containing_duplicate_refs():
|
||||
data = {
|
||||
'id': '123',
|
||||
'name': 'foo',
|
||||
'type': 'bar',
|
||||
'refs': [
|
||||
{'id': '123', 'type': 'baz'},
|
||||
{'id': '123', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'foo'}
|
||||
]
|
||||
}
|
||||
assert {
|
||||
'id': '123',
|
||||
'name': 'foo',
|
||||
'type': 'bar',
|
||||
'refs': [
|
||||
{'id': '123', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'foo'}
|
||||
]
|
||||
} == delete_ref_duplicates(data)
|
||||
|
||||
|
||||
def test_delete_ref_duplicates_with_object_containing_duplicate_refs_in_nested_object():
|
||||
data = {
|
||||
'id': '123',
|
||||
'name': 'foo',
|
||||
'type': 'bar',
|
||||
'children': {
|
||||
'refs': [
|
||||
{'id': '123', 'type': 'baz'},
|
||||
{'id': '123', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'foo'}
|
||||
]
|
||||
}
|
||||
}
|
||||
assert {
|
||||
'id': '123',
|
||||
'name': 'foo',
|
||||
'type': 'bar',
|
||||
'children': {
|
||||
'refs': [
|
||||
{'id': '123', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'baz'},
|
||||
{'id': '234', 'type': 'foo'}
|
||||
]
|
||||
}
|
||||
} == delete_ref_duplicates(data)
|
||||
|
|
|
@ -80,12 +80,11 @@ class TestBaseConfigurationResource(object):
|
|||
# we need evaluate it.
|
||||
assert [objects[1]] == list(resource.get_objects_by_filter(
|
||||
'test',
|
||||
{ParamName.FILTERS: {'type': 1, 'foo': {'bar': 'buz'}}}))
|
||||
{ParamName.FILTERS: {'name': 'obj2', 'type': 1, 'foo': {'bar': 'buz'}}}))
|
||||
|
||||
send_request_mock.assert_has_calls(
|
||||
[
|
||||
mock.call('/object/', 'get', {}, {},
|
||||
{QueryParams.FILTER: "foo:{'bar': 'buz'};type:1", 'limit': 10, 'offset': 0})
|
||||
mock.call('/object/', 'get', {}, {}, {QueryParams.FILTER: 'name:obj2', 'limit': 10, 'offset': 0})
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -111,8 +110,7 @@ class TestBaseConfigurationResource(object):
|
|||
{ParamName.FILTERS: {'type': 'foo'}}))
|
||||
send_request_mock.assert_has_calls(
|
||||
[
|
||||
mock.call('/object/', 'get', {}, {},
|
||||
{QueryParams.FILTER: "type:foo", 'limit': 10, 'offset': 0})
|
||||
mock.call('/object/', 'get', {}, {}, {'limit': 10, 'offset': 0})
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -136,10 +134,8 @@ class TestBaseConfigurationResource(object):
|
|||
assert [{'name': 'obj1', 'type': 'foo'}, {'name': 'obj3', 'type': 'foo'}] == resp
|
||||
send_request_mock.assert_has_calls(
|
||||
[
|
||||
mock.call('/object/', 'get', {}, {},
|
||||
{QueryParams.FILTER: "type:foo", 'limit': 2, 'offset': 0}),
|
||||
mock.call('/object/', 'get', {}, {},
|
||||
{QueryParams.FILTER: "type:foo", 'limit': 2, 'offset': 2})
|
||||
mock.call('/object/', 'get', {}, {}, {'limit': 2, 'offset': 0}),
|
||||
mock.call('/object/', 'get', {}, {}, {'limit': 2, 'offset': 2})
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -542,29 +538,14 @@ class TestOperationCheckerClass(unittest.TestCase):
|
|||
operation_name, params, operation_spec
|
||||
)
|
||||
|
||||
@patch.object(OperationChecker, "is_add_operation")
|
||||
@patch.object(OperationChecker, "is_edit_operation")
|
||||
@patch.object(OperationChecker, "is_get_list_operation")
|
||||
def test_is_upsert_operation_supported_operation(self, is_add_mock, is_edit_mock, is_get_list_mock):
|
||||
operations_spec = {
|
||||
'add': 1,
|
||||
'edit': 1,
|
||||
'getList': 1
|
||||
}
|
||||
is_add_mock.side_effect = [1, 0, 0]
|
||||
is_edit_mock.side_effect = [1, 0, 0]
|
||||
is_get_list_mock.side_effect = [1, 0, 0]
|
||||
def test_is_upsert_operation_supported_operation(self):
|
||||
get_list_op_spec = {OperationField.METHOD: HTTPMethod.GET, OperationField.RETURN_MULTIPLE_ITEMS: True}
|
||||
add_op_spec = {OperationField.METHOD: HTTPMethod.POST}
|
||||
edit_op_spec = {OperationField.METHOD: HTTPMethod.PUT}
|
||||
|
||||
assert self._checker.is_upsert_operation_supported(operations_spec)
|
||||
|
||||
is_add_mock.side_effect = [1, 0, 0]
|
||||
is_edit_mock.side_effect = [0, 1, 0]
|
||||
is_get_list_mock.side_effect = [0, 0, 0]
|
||||
|
||||
assert not self._checker.is_upsert_operation_supported(operations_spec)
|
||||
|
||||
is_add_mock.side_effect = [1, 0, 0]
|
||||
is_edit_mock.side_effect = [0, 0, 0]
|
||||
is_get_list_mock.side_effect = [1, 0, 0]
|
||||
|
||||
assert not self._checker.is_upsert_operation_supported(operations_spec)
|
||||
assert self._checker.is_upsert_operation_supported({'getList': get_list_op_spec, 'edit': edit_op_spec})
|
||||
assert self._checker.is_upsert_operation_supported(
|
||||
{'add': add_op_spec, 'getList': get_list_op_spec, 'edit': edit_op_spec})
|
||||
assert not self._checker.is_upsert_operation_supported({'getList': get_list_op_spec})
|
||||
assert not self._checker.is_upsert_operation_supported({'edit': edit_op_spec})
|
||||
assert not self._checker.is_upsert_operation_supported({'getList': get_list_op_spec, 'add': add_op_spec})
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -45,17 +45,20 @@ base = {
|
|||
},
|
||||
'paths': {
|
||||
"/object/networks": {
|
||||
"get": {"tags": ["NetworkObject"], "operationId": "getNetworkObjectList",
|
||||
"responses": {"200": {"description": "", "schema": {"type": "object",
|
||||
"title": "NetworkObjectList",
|
||||
"properties": {"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/NetworkObjectWrapper"}},
|
||||
"paging": {
|
||||
"$ref": "#/definitions/Paging"}},
|
||||
"required": ["items",
|
||||
"paging"]}}},
|
||||
"get": {"tags": ["NetworkObject"],
|
||||
"operationId": "getNetworkObjectList",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"schema": {"type": "object",
|
||||
"title": "NetworkObjectList",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/NetworkObjectWrapper"}},
|
||||
"paging": {
|
||||
"$ref": "#/definitions/Paging"}},
|
||||
"required": ["items", "paging"]}}},
|
||||
"parameters": [
|
||||
{"name": "offset", "in": "query", "required": False, "type": "integer"},
|
||||
{"name": "limit", "in": "query", "required": False, "type": "integer"},
|
||||
|
@ -141,7 +144,8 @@ class TestFdmSwaggerParser(unittest.TestCase):
|
|||
}
|
||||
}
|
||||
},
|
||||
'returnMultipleItems': True
|
||||
'returnMultipleItems': True,
|
||||
"tags": ["NetworkObject"]
|
||||
},
|
||||
'addNetworkObject': {
|
||||
'method': HTTPMethod.POST,
|
||||
|
@ -149,7 +153,8 @@ class TestFdmSwaggerParser(unittest.TestCase):
|
|||
'modelName': 'NetworkObject',
|
||||
'parameters': {'path': {},
|
||||
'query': {}},
|
||||
'returnMultipleItems': False
|
||||
'returnMultipleItems': False,
|
||||
"tags": ["NetworkObject"]
|
||||
},
|
||||
'getNetworkObject': {
|
||||
'method': HTTPMethod.GET,
|
||||
|
@ -164,7 +169,8 @@ class TestFdmSwaggerParser(unittest.TestCase):
|
|||
},
|
||||
'query': {}
|
||||
},
|
||||
'returnMultipleItems': False
|
||||
'returnMultipleItems': False,
|
||||
"tags": ["NetworkObject"]
|
||||
},
|
||||
'editNetworkObject': {
|
||||
'method': HTTPMethod.PUT,
|
||||
|
@ -179,7 +185,8 @@ class TestFdmSwaggerParser(unittest.TestCase):
|
|||
},
|
||||
'query': {}
|
||||
},
|
||||
'returnMultipleItems': False
|
||||
'returnMultipleItems': False,
|
||||
"tags": ["NetworkObject"]
|
||||
},
|
||||
'deleteNetworkObject': {
|
||||
'method': HTTPMethod.DELETE,
|
||||
|
@ -194,7 +201,8 @@ class TestFdmSwaggerParser(unittest.TestCase):
|
|||
},
|
||||
'query': {}
|
||||
},
|
||||
'returnMultipleItems': False
|
||||
'returnMultipleItems': False,
|
||||
"tags": ["NetworkObject"]
|
||||
}
|
||||
}
|
||||
assert sorted(['NetworkObject', 'NetworkObjectWrapper']) == sorted(self.fdm_data['models'].keys())
|
||||
|
@ -302,7 +310,8 @@ class TestFdmSwaggerParser(unittest.TestCase):
|
|||
'method': HTTPMethod.GET,
|
||||
'url': '/v2/path1',
|
||||
'modelName': 'Model1',
|
||||
'returnMultipleItems': True
|
||||
'returnMultipleItems': True,
|
||||
'tags': []
|
||||
},
|
||||
'addSomeModel': {
|
||||
'method': HTTPMethod.POST,
|
||||
|
@ -312,13 +321,15 @@ class TestFdmSwaggerParser(unittest.TestCase):
|
|||
'path': {},
|
||||
'query': {}
|
||||
},
|
||||
'returnMultipleItems': False
|
||||
'returnMultipleItems': False,
|
||||
'tags': []
|
||||
},
|
||||
'getSomeModel': {
|
||||
'method': HTTPMethod.GET,
|
||||
'url': '/v2/path2/{id}',
|
||||
'modelName': 'Model3',
|
||||
'returnMultipleItems': False
|
||||
'returnMultipleItems': False,
|
||||
'tags': []
|
||||
},
|
||||
'editSomeModel': {
|
||||
'method': HTTPMethod.PUT,
|
||||
|
@ -328,19 +339,22 @@ class TestFdmSwaggerParser(unittest.TestCase):
|
|||
'path': {},
|
||||
'query': {}
|
||||
},
|
||||
'returnMultipleItems': False
|
||||
'returnMultipleItems': False,
|
||||
'tags': []
|
||||
},
|
||||
'deleteModel3': {
|
||||
'method': HTTPMethod.DELETE,
|
||||
'url': '/v2/path2/{id}',
|
||||
'modelName': 'Model3',
|
||||
'returnMultipleItems': False
|
||||
'returnMultipleItems': False,
|
||||
'tags': []
|
||||
},
|
||||
'deleteNoneModel': {
|
||||
'method': HTTPMethod.DELETE,
|
||||
'url': '/v2/path3',
|
||||
'modelName': None,
|
||||
'returnMultipleItems': False
|
||||
'returnMultipleItems': False,
|
||||
'tags': []
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -350,7 +364,7 @@ class TestFdmSwaggerParser(unittest.TestCase):
|
|||
assert {
|
||||
'Model1': {
|
||||
'getSomeModelList': expected_operations['getSomeModelList'],
|
||||
'editSomeModel': expected_operations['editSomeModel']
|
||||
'editSomeModel': expected_operations['editSomeModel'],
|
||||
},
|
||||
'Model2': {
|
||||
'addSomeModel': expected_operations['addSomeModel']
|
||||
|
|
|
@ -413,6 +413,29 @@ class TestFdmSwaggerValidator(unittest.TestCase):
|
|||
]
|
||||
}) == sort_validator_rez(rez)
|
||||
|
||||
data = {
|
||||
'objId': "123",
|
||||
'parentId': "1",
|
||||
'someParam': None,
|
||||
'p_integer': None
|
||||
}
|
||||
valid, rez = getattr(validator, method)('getNetwork', data)
|
||||
assert not valid
|
||||
assert sort_validator_rez({
|
||||
'invalid_type': [
|
||||
{
|
||||
'path': 'someParam',
|
||||
'expected_type': 'string',
|
||||
'actually_value': None
|
||||
},
|
||||
{
|
||||
'path': 'p_integer',
|
||||
'expected_type': 'integer',
|
||||
'actually_value': None
|
||||
}
|
||||
]
|
||||
}) == sort_validator_rez(rez)
|
||||
|
||||
def test_validate_path_params_method_with_empty_data(self):
|
||||
self.validate_url_data_with_empty_data(method='validate_path_params', parameters_type='path')
|
||||
|
||||
|
@ -593,6 +616,16 @@ class TestFdmSwaggerValidator(unittest.TestCase):
|
|||
assert valid
|
||||
assert rez is None
|
||||
|
||||
def test_pass_only_required_fields_with_none_values(self):
|
||||
data = {
|
||||
'subType': 'NETWORK',
|
||||
'type': 'networkobject',
|
||||
'value': None
|
||||
}
|
||||
valid, rez = FdmSwaggerValidator(mock_data).validate_data('getNetworkObjectList', data)
|
||||
assert not valid
|
||||
assert {'required': ['value']} == rez
|
||||
|
||||
def test_pass_no_data_with_no_required_fields(self):
|
||||
spec = copy.deepcopy(mock_data)
|
||||
del spec['models']['NetworkObject']['required']
|
||||
|
@ -725,6 +758,17 @@ class TestFdmSwaggerValidator(unittest.TestCase):
|
|||
assert valid
|
||||
assert rez is None
|
||||
|
||||
valid_data = {
|
||||
"f_string": None,
|
||||
"f_number": None,
|
||||
"f_boolean": None,
|
||||
"f_integer": None
|
||||
}
|
||||
|
||||
valid, rez = FdmSwaggerValidator(local_mock_data).validate_data('getdata', valid_data)
|
||||
assert valid
|
||||
assert rez is None
|
||||
|
||||
def test_invalid_simple_types(self):
|
||||
local_mock_data = {
|
||||
'models': {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import json
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from ansible.module_utils.network.ftd.fdm_swagger_client import FdmSwaggerValidator, FdmSwaggerParser
|
||||
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
TEST_DATA_FOLDER = os.path.join(DIR_PATH, 'test_data')
|
||||
|
||||
|
||||
class TestFdmSwagger(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.init_mock_data()
|
||||
|
||||
def init_mock_data(self):
|
||||
with open(os.path.join(TEST_DATA_FOLDER, 'ngfw_with_ex.json'), 'rb') as f:
|
||||
self.base_data = json.loads(f.read().decode('utf-8'))
|
||||
|
||||
def test_with_all_data(self):
|
||||
fdm_data = FdmSwaggerParser().parse_spec(self.base_data)
|
||||
validator = FdmSwaggerValidator(fdm_data)
|
||||
models = fdm_data['models']
|
||||
operations = fdm_data['operations']
|
||||
|
||||
invalid = set({})
|
||||
for operation in operations:
|
||||
model_name = operations[operation]['modelName']
|
||||
method = operations[operation]['method']
|
||||
if method != 'get' and model_name in models:
|
||||
if 'example' in models[model_name]:
|
||||
example = models[model_name]['example']
|
||||
try:
|
||||
valid, rez = validator.validate_data(operation, example)
|
||||
assert valid
|
||||
except Exception:
|
||||
invalid.add(model_name)
|
||||
assert invalid == set(['TCPPortObject',
|
||||
'UDPPortObject',
|
||||
'ICMPv4PortObject',
|
||||
'ICMPv6PortObject',
|
||||
'StandardAccessList',
|
||||
'ExtendedAccessList',
|
||||
'ASPathList',
|
||||
'RouteMap',
|
||||
'StandardCommunityList',
|
||||
'ExpandedCommunityList',
|
||||
'IPV4PrefixList',
|
||||
'IPV6PrefixList',
|
||||
'PolicyList',
|
||||
'SyslogServer',
|
||||
'HAConfiguration',
|
||||
'TestIdentitySource'])
|
||||
|
||||
def test_parse_all_data(self):
|
||||
self.fdm_data = FdmSwaggerParser().parse_spec(self.base_data)
|
||||
operations = self.fdm_data['operations']
|
||||
without_model_name = []
|
||||
expected_operations_counter = 0
|
||||
for key in self.base_data['paths']:
|
||||
operation = self.base_data['paths'][key]
|
||||
for dummy in operation:
|
||||
expected_operations_counter += 1
|
||||
|
||||
for key in operations:
|
||||
operation = operations[key]
|
||||
if not operation['modelName']:
|
||||
without_model_name.append(operation['url'])
|
||||
|
||||
if operation['modelName'] == '_File' and 'download' not in operation['url']:
|
||||
self.fail('File type can be defined for download operation only')
|
||||
|
||||
assert sorted(['/api/fdm/v2/operational/deploy/{objId}', '/api/fdm/v2/action/upgrade']) == sorted(
|
||||
without_model_name)
|
||||
assert sorted(self.fdm_data['model_operations'][None].keys()) == sorted(['deleteDeployment', 'startUpgrade'])
|
||||
assert expected_operations_counter == len(operations)
|
|
@ -27,7 +27,8 @@ from units.compat import mock
|
|||
|
||||
from ansible.module_utils.network.ftd.common import FtdServerError, HTTPMethod, ResponseParams, FtdConfigurationError
|
||||
from ansible.module_utils.network.ftd.configuration import DUPLICATE_NAME_ERROR_MESSAGE, UNPROCESSABLE_ENTITY_STATUS, \
|
||||
MULTIPLE_DUPLICATES_FOUND_ERROR, BaseConfigurationResource, FtdInvalidOperationNameError, QueryParams
|
||||
MULTIPLE_DUPLICATES_FOUND_ERROR, BaseConfigurationResource, FtdInvalidOperationNameError, QueryParams, \
|
||||
ADD_OPERATION_NOT_SUPPORTED_ERROR, ParamName
|
||||
from ansible.module_utils.network.ftd.fdm_swagger_client import ValidationError
|
||||
|
||||
ADD_RESPONSE = {'status': 'Object added'}
|
||||
|
@ -39,8 +40,8 @@ ARBITRARY_RESPONSE = {'status': 'Arbitrary request sent'}
|
|||
|
||||
class TestUpsertOperationUnitTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
conn = mock.MagicMock()
|
||||
self._resource = BaseConfigurationResource(conn)
|
||||
self._conn = mock.MagicMock()
|
||||
self._resource = BaseConfigurationResource(self._conn)
|
||||
|
||||
def test_get_operation_name(self):
|
||||
operation_a = mock.MagicMock()
|
||||
|
@ -59,11 +60,7 @@ class TestUpsertOperationUnitTests(unittest.TestCase):
|
|||
|
||||
assert operation_a == self._resource._get_operation_name(checker_wrapper(operation_a), operations)
|
||||
assert operation_b == self._resource._get_operation_name(checker_wrapper(operation_b), operations)
|
||||
|
||||
self.assertRaises(
|
||||
FtdConfigurationError,
|
||||
self._resource._get_operation_name, checker_wrapper(None), operations
|
||||
)
|
||||
assert self._resource._get_operation_name(checker_wrapper(None), operations) is None
|
||||
|
||||
@mock.patch.object(BaseConfigurationResource, "_get_operation_name")
|
||||
@mock.patch.object(BaseConfigurationResource, "add_object")
|
||||
|
@ -79,6 +76,19 @@ class TestUpsertOperationUnitTests(unittest.TestCase):
|
|||
model_operations)
|
||||
add_object_mock.assert_called_once_with(add_op_name, params)
|
||||
|
||||
@mock.patch.object(BaseConfigurationResource, "_get_operation_name")
|
||||
@mock.patch.object(BaseConfigurationResource, "add_object")
|
||||
def test_add_upserted_object_with_no_add_operation(self, add_object_mock, get_operation_mock):
|
||||
model_operations = mock.MagicMock()
|
||||
get_operation_mock.return_value = None
|
||||
|
||||
with pytest.raises(FtdConfigurationError) as exc_info:
|
||||
self._resource._add_upserted_object(model_operations, mock.MagicMock())
|
||||
assert ADD_OPERATION_NOT_SUPPORTED_ERROR in str(exc_info.value)
|
||||
|
||||
get_operation_mock.assert_called_once_with(self._resource._operation_checker.is_add_operation, model_operations)
|
||||
add_object_mock.assert_not_called()
|
||||
|
||||
@mock.patch.object(BaseConfigurationResource, "_get_operation_name")
|
||||
@mock.patch.object(BaseConfigurationResource, "edit_object")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration.copy_identity_properties")
|
||||
|
@ -112,139 +122,175 @@ class TestUpsertOperationUnitTests(unittest.TestCase):
|
|||
params
|
||||
)
|
||||
|
||||
@mock.patch.object(BaseConfigurationResource, "get_operation_specs_by_model_name")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration.OperationChecker.is_upsert_operation_supported")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration._extract_model_from_upsert_operation")
|
||||
def test_is_upsert_operation_supported(self, extract_model_mock, is_upsert_supported_mock, get_operation_spec_mock):
|
||||
op_name = mock.MagicMock()
|
||||
|
||||
result = self._resource.is_upsert_operation_supported(op_name)
|
||||
|
||||
assert result == is_upsert_supported_mock.return_value
|
||||
extract_model_mock.assert_called_once_with(op_name)
|
||||
get_operation_spec_mock.assert_called_once_with(extract_model_mock.return_value)
|
||||
is_upsert_supported_mock.assert_called_once_with(get_operation_spec_mock.return_value)
|
||||
|
||||
@mock.patch.object(BaseConfigurationResource, "is_upsert_operation_supported")
|
||||
@mock.patch.object(BaseConfigurationResource, "get_operation_specs_by_model_name")
|
||||
@mock.patch.object(BaseConfigurationResource, "_find_object_matching_params")
|
||||
@mock.patch.object(BaseConfigurationResource, "_add_upserted_object")
|
||||
@mock.patch.object(BaseConfigurationResource, "_edit_upserted_object")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration._extract_model_from_upsert_operation")
|
||||
def test_upsert_object_succesfully_added(self, extract_model_mock, edit_mock, add_mock, get_operation_mock,
|
||||
is_upsert_supported_mock):
|
||||
op_name = mock.MagicMock()
|
||||
def test_upsert_object_successfully_added(self, edit_mock, add_mock, find_object, get_operation_mock,
|
||||
is_upsert_supported_mock):
|
||||
params = mock.MagicMock()
|
||||
|
||||
is_upsert_supported_mock.return_value = True
|
||||
find_object.return_value = None
|
||||
|
||||
result = self._resource.upsert_object(op_name, params)
|
||||
result = self._resource.upsert_object('upsertFoo', params)
|
||||
|
||||
assert result == add_mock.return_value
|
||||
is_upsert_supported_mock.assert_called_once_with(op_name)
|
||||
extract_model_mock.assert_called_once_with(op_name)
|
||||
get_operation_mock.assert_called_once_with(extract_model_mock.return_value)
|
||||
self._conn.get_model_spec.assert_called_once_with('Foo')
|
||||
is_upsert_supported_mock.assert_called_once_with(get_operation_mock.return_value)
|
||||
get_operation_mock.assert_called_once_with('Foo')
|
||||
find_object.assert_called_once_with('Foo', params)
|
||||
add_mock.assert_called_once_with(get_operation_mock.return_value, params)
|
||||
edit_mock.assert_not_called()
|
||||
|
||||
@mock.patch.object(BaseConfigurationResource, "is_upsert_operation_supported")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration.equal_objects")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration.OperationChecker.is_upsert_operation_supported")
|
||||
@mock.patch.object(BaseConfigurationResource, "get_operation_specs_by_model_name")
|
||||
@mock.patch.object(BaseConfigurationResource, "_find_object_matching_params")
|
||||
@mock.patch.object(BaseConfigurationResource, "_add_upserted_object")
|
||||
@mock.patch.object(BaseConfigurationResource, "_edit_upserted_object")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration._extract_model_from_upsert_operation")
|
||||
def test_upsert_object_succesfully_edited(self, extract_model_mock, edit_mock, add_mock, get_operation_mock,
|
||||
is_upsert_supported_mock):
|
||||
op_name = mock.MagicMock()
|
||||
def test_upsert_object_successfully_edited(self, edit_mock, add_mock, find_object, get_operation_mock,
|
||||
is_upsert_supported_mock, equal_objects_mock):
|
||||
params = mock.MagicMock()
|
||||
existing_obj = mock.MagicMock()
|
||||
|
||||
is_upsert_supported_mock.return_value = True
|
||||
error = FtdConfigurationError("Obj duplication error")
|
||||
error.obj = mock.MagicMock()
|
||||
find_object.return_value = existing_obj
|
||||
equal_objects_mock.return_value = False
|
||||
|
||||
add_mock.side_effect = error
|
||||
|
||||
result = self._resource.upsert_object(op_name, params)
|
||||
result = self._resource.upsert_object('upsertFoo', params)
|
||||
|
||||
assert result == edit_mock.return_value
|
||||
is_upsert_supported_mock.assert_called_once_with(op_name)
|
||||
extract_model_mock.assert_called_once_with(op_name)
|
||||
get_operation_mock.assert_called_once_with(extract_model_mock.return_value)
|
||||
add_mock.assert_called_once_with(get_operation_mock.return_value, params)
|
||||
edit_mock.assert_called_once_with(get_operation_mock.return_value, error.obj, params)
|
||||
self._conn.get_model_spec.assert_called_once_with('Foo')
|
||||
get_operation_mock.assert_called_once_with('Foo')
|
||||
is_upsert_supported_mock.assert_called_once_with(get_operation_mock.return_value)
|
||||
add_mock.assert_not_called()
|
||||
equal_objects_mock.assert_called_once_with(existing_obj, params[ParamName.DATA])
|
||||
edit_mock.assert_called_once_with(get_operation_mock.return_value, existing_obj, params)
|
||||
|
||||
@mock.patch.object(BaseConfigurationResource, "is_upsert_operation_supported")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration.equal_objects")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration.OperationChecker.is_upsert_operation_supported")
|
||||
@mock.patch.object(BaseConfigurationResource, "get_operation_specs_by_model_name")
|
||||
@mock.patch.object(BaseConfigurationResource, "_find_object_matching_params")
|
||||
@mock.patch.object(BaseConfigurationResource, "_add_upserted_object")
|
||||
@mock.patch.object(BaseConfigurationResource, "_edit_upserted_object")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration._extract_model_from_upsert_operation")
|
||||
def test_upsert_object_not_supported(self, extract_model_mock, edit_mock, add_mock, get_operation_mock,
|
||||
def test_upsert_object_returned_without_modifications(self, edit_mock, add_mock, find_object, get_operation_mock,
|
||||
is_upsert_supported_mock, equal_objects_mock):
|
||||
params = mock.MagicMock()
|
||||
existing_obj = mock.MagicMock()
|
||||
|
||||
is_upsert_supported_mock.return_value = True
|
||||
find_object.return_value = existing_obj
|
||||
equal_objects_mock.return_value = True
|
||||
|
||||
result = self._resource.upsert_object('upsertFoo', params)
|
||||
|
||||
assert result == existing_obj
|
||||
self._conn.get_model_spec.assert_called_once_with('Foo')
|
||||
get_operation_mock.assert_called_once_with('Foo')
|
||||
is_upsert_supported_mock.assert_called_once_with(get_operation_mock.return_value)
|
||||
add_mock.assert_not_called()
|
||||
equal_objects_mock.assert_called_once_with(existing_obj, params[ParamName.DATA])
|
||||
edit_mock.assert_not_called()
|
||||
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration.OperationChecker.is_upsert_operation_supported")
|
||||
@mock.patch.object(BaseConfigurationResource, "get_operation_specs_by_model_name")
|
||||
@mock.patch.object(BaseConfigurationResource, "_find_object_matching_params")
|
||||
@mock.patch.object(BaseConfigurationResource, "_add_upserted_object")
|
||||
@mock.patch.object(BaseConfigurationResource, "_edit_upserted_object")
|
||||
def test_upsert_object_not_supported(self, edit_mock, add_mock, find_object, get_operation_mock,
|
||||
is_upsert_supported_mock):
|
||||
op_name = mock.MagicMock()
|
||||
params = mock.MagicMock()
|
||||
|
||||
is_upsert_supported_mock.return_value = False
|
||||
|
||||
self.assertRaises(
|
||||
FtdInvalidOperationNameError,
|
||||
self._resource.upsert_object, op_name, params
|
||||
self._resource.upsert_object, 'upsertFoo', params
|
||||
)
|
||||
|
||||
is_upsert_supported_mock.assert_called_once_with(op_name)
|
||||
extract_model_mock.assert_not_called()
|
||||
get_operation_mock.assert_not_called()
|
||||
self._conn.get_model_spec.assert_called_once_with('Foo')
|
||||
get_operation_mock.assert_called_once_with('Foo')
|
||||
is_upsert_supported_mock.assert_called_once_with(get_operation_mock.return_value)
|
||||
find_object.assert_not_called()
|
||||
add_mock.assert_not_called()
|
||||
edit_mock.assert_not_called()
|
||||
|
||||
@mock.patch.object(BaseConfigurationResource, "is_upsert_operation_supported")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration.OperationChecker.is_upsert_operation_supported")
|
||||
@mock.patch.object(BaseConfigurationResource, "get_operation_specs_by_model_name")
|
||||
@mock.patch.object(BaseConfigurationResource, "_find_object_matching_params")
|
||||
@mock.patch.object(BaseConfigurationResource, "_add_upserted_object")
|
||||
@mock.patch.object(BaseConfigurationResource, "_edit_upserted_object")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration._extract_model_from_upsert_operation")
|
||||
def test_upsert_object_neither_added_nor_edited(self, extract_model_mock, edit_mock, add_mock, get_operation_mock,
|
||||
def test_upsert_object_when_model_not_supported(self, edit_mock, add_mock, find_object, get_operation_mock,
|
||||
is_upsert_supported_mock):
|
||||
op_name = mock.MagicMock()
|
||||
params = mock.MagicMock()
|
||||
self._conn.get_model_spec.return_value = None
|
||||
|
||||
self.assertRaises(
|
||||
FtdInvalidOperationNameError,
|
||||
self._resource.upsert_object, 'upsertNonExisting', params
|
||||
)
|
||||
|
||||
self._conn.get_model_spec.assert_called_once_with('NonExisting')
|
||||
get_operation_mock.assert_not_called()
|
||||
is_upsert_supported_mock.assert_not_called()
|
||||
find_object.assert_not_called()
|
||||
add_mock.assert_not_called()
|
||||
edit_mock.assert_not_called()
|
||||
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration.equal_objects")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration.OperationChecker.is_upsert_operation_supported")
|
||||
@mock.patch.object(BaseConfigurationResource, "get_operation_specs_by_model_name")
|
||||
@mock.patch.object(BaseConfigurationResource, "_find_object_matching_params")
|
||||
@mock.patch.object(BaseConfigurationResource, "_add_upserted_object")
|
||||
@mock.patch.object(BaseConfigurationResource, "_edit_upserted_object")
|
||||
def test_upsert_object_with_fatal_error_during_edit(self, edit_mock, add_mock, find_object, get_operation_mock,
|
||||
is_upsert_supported_mock, equal_objects_mock):
|
||||
params = mock.MagicMock()
|
||||
existing_obj = mock.MagicMock()
|
||||
|
||||
is_upsert_supported_mock.return_value = True
|
||||
error = FtdConfigurationError("Obj duplication error")
|
||||
error.obj = mock.MagicMock()
|
||||
|
||||
add_mock.side_effect = error
|
||||
find_object.return_value = existing_obj
|
||||
equal_objects_mock.return_value = False
|
||||
edit_mock.side_effect = FtdConfigurationError("Some object edit error")
|
||||
|
||||
self.assertRaises(
|
||||
FtdConfigurationError,
|
||||
self._resource.upsert_object, op_name, params
|
||||
self._resource.upsert_object, 'upsertFoo', params
|
||||
)
|
||||
|
||||
is_upsert_supported_mock.assert_called_once_with(op_name)
|
||||
extract_model_mock.assert_called_once_with(op_name)
|
||||
get_operation_mock.assert_called_once_with(extract_model_mock.return_value)
|
||||
add_mock.assert_called_once_with(get_operation_mock.return_value, params)
|
||||
edit_mock.assert_called_once_with(get_operation_mock.return_value, error.obj, params)
|
||||
is_upsert_supported_mock.assert_called_once_with(get_operation_mock.return_value)
|
||||
self._conn.get_model_spec.assert_called_once_with('Foo')
|
||||
get_operation_mock.assert_called_once_with('Foo')
|
||||
find_object.assert_called_once_with('Foo', params)
|
||||
add_mock.assert_not_called()
|
||||
edit_mock.assert_called_once_with(get_operation_mock.return_value, existing_obj, params)
|
||||
|
||||
@mock.patch.object(BaseConfigurationResource, "is_upsert_operation_supported")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration.OperationChecker.is_upsert_operation_supported")
|
||||
@mock.patch.object(BaseConfigurationResource, "get_operation_specs_by_model_name")
|
||||
@mock.patch.object(BaseConfigurationResource, "_find_object_matching_params")
|
||||
@mock.patch.object(BaseConfigurationResource, "_add_upserted_object")
|
||||
@mock.patch.object(BaseConfigurationResource, "_edit_upserted_object")
|
||||
@mock.patch("ansible.module_utils.network.ftd.configuration._extract_model_from_upsert_operation")
|
||||
def test_upsert_object_with_fatal_error_during_add(self, extract_model_mock, edit_mock, add_mock,
|
||||
get_operation_mock, is_upsert_supported_mock):
|
||||
op_name = mock.MagicMock()
|
||||
def test_upsert_object_with_fatal_error_during_add(self, edit_mock, add_mock, find_object, get_operation_mock,
|
||||
is_upsert_supported_mock):
|
||||
params = mock.MagicMock()
|
||||
|
||||
is_upsert_supported_mock.return_value = True
|
||||
find_object.return_value = None
|
||||
|
||||
error = FtdConfigurationError("Obj duplication error")
|
||||
add_mock.side_effect = error
|
||||
|
||||
self.assertRaises(
|
||||
FtdConfigurationError,
|
||||
self._resource.upsert_object, op_name, params
|
||||
self._resource.upsert_object, 'upsertFoo', params
|
||||
)
|
||||
|
||||
is_upsert_supported_mock.assert_called_once_with(op_name)
|
||||
extract_model_mock.assert_called_once_with(op_name)
|
||||
get_operation_mock.assert_called_once_with(extract_model_mock.return_value)
|
||||
is_upsert_supported_mock.assert_called_once_with(get_operation_mock.return_value)
|
||||
self._conn.get_model_spec.assert_called_once_with('Foo')
|
||||
get_operation_mock.assert_called_once_with('Foo')
|
||||
find_object.assert_called_once_with('Foo', params)
|
||||
add_mock.assert_called_once_with(get_operation_mock.return_value, params)
|
||||
edit_mock.assert_not_called()
|
||||
|
||||
|
@ -289,13 +335,28 @@ class TestUpsertOperationFunctionalTests(object):
|
|||
def get_operation_spec(name):
|
||||
return operations[name]
|
||||
|
||||
def request_handler(url_path=None, http_method=None, body_params=None, path_params=None, query_params=None):
|
||||
if http_method == HTTPMethod.POST:
|
||||
assert url_path == url
|
||||
assert body_params == params['data']
|
||||
assert query_params == {}
|
||||
assert path_params == params['path_params']
|
||||
return {
|
||||
ResponseParams.SUCCESS: True,
|
||||
ResponseParams.RESPONSE: ADD_RESPONSE
|
||||
}
|
||||
elif http_method == HTTPMethod.GET:
|
||||
return {
|
||||
ResponseParams.SUCCESS: True,
|
||||
ResponseParams.RESPONSE: {'items': []}
|
||||
}
|
||||
else:
|
||||
assert False
|
||||
|
||||
connection_mock.get_operation_spec = get_operation_spec
|
||||
|
||||
connection_mock.get_operation_specs_by_model_name.return_value = operations
|
||||
connection_mock.send_request.return_value = {
|
||||
ResponseParams.SUCCESS: True,
|
||||
ResponseParams.RESPONSE: ADD_RESPONSE
|
||||
}
|
||||
connection_mock.send_request = request_handler
|
||||
params = {
|
||||
'operation': 'upsertObject',
|
||||
'data': {'id': '123', 'name': 'testObject', 'type': 'object'},
|
||||
|
@ -305,13 +366,62 @@ class TestUpsertOperationFunctionalTests(object):
|
|||
|
||||
result = self._resource_execute_operation(params, connection=connection_mock)
|
||||
|
||||
connection_mock.send_request.assert_called_once_with(url_path=url,
|
||||
http_method=HTTPMethod.POST,
|
||||
path_params=params['path_params'],
|
||||
query_params={},
|
||||
body_params=params['data'])
|
||||
assert ADD_RESPONSE == result
|
||||
|
||||
def test_module_should_fail_when_no_model(self, connection_mock):
|
||||
connection_mock.get_model_spec.return_value = None
|
||||
params = {
|
||||
'operation': 'upsertObject',
|
||||
'data': {'id': '123', 'name': 'testObject', 'type': 'object'},
|
||||
'path_params': {'objId': '123'},
|
||||
'register_as': 'test_var'
|
||||
}
|
||||
|
||||
with pytest.raises(FtdInvalidOperationNameError) as exc_info:
|
||||
self._resource_execute_operation(params, connection=connection_mock)
|
||||
assert 'upsertObject' == exc_info.value.operation_name
|
||||
|
||||
def test_module_should_fail_when_no_add_operation_and_no_object(self, connection_mock):
|
||||
url = '/test'
|
||||
|
||||
operations = {
|
||||
'getObjectList': {
|
||||
'method': HTTPMethod.GET,
|
||||
'url': url,
|
||||
'modelName': 'Object',
|
||||
'returnMultipleItems': True},
|
||||
'editObject': {
|
||||
'method': HTTPMethod.PUT,
|
||||
'modelName': 'Object',
|
||||
'url': '/test/{objId}'},
|
||||
'otherObjectOperation': {
|
||||
'method': HTTPMethod.GET,
|
||||
'modelName': 'Object',
|
||||
'url': '/test/{objId}',
|
||||
'returnMultipleItems': False
|
||||
}}
|
||||
|
||||
def get_operation_spec(name):
|
||||
return operations[name]
|
||||
|
||||
connection_mock.get_operation_spec = get_operation_spec
|
||||
|
||||
connection_mock.get_operation_specs_by_model_name.return_value = operations
|
||||
connection_mock.send_request.return_value = {
|
||||
ResponseParams.SUCCESS: True,
|
||||
ResponseParams.RESPONSE: {'items': []}
|
||||
}
|
||||
params = {
|
||||
'operation': 'upsertObject',
|
||||
'data': {'id': '123', 'name': 'testObject', 'type': 'object'},
|
||||
'path_params': {'objId': '123'},
|
||||
'register_as': 'test_var'
|
||||
}
|
||||
|
||||
with pytest.raises(FtdConfigurationError) as exc_info:
|
||||
self._resource_execute_operation(params, connection=connection_mock)
|
||||
assert ADD_OPERATION_NOT_SUPPORTED_ERROR in str(exc_info.value)
|
||||
|
||||
# test when object exists but with different fields(except id)
|
||||
def test_module_should_update_object_when_upsert_operation_and_object_exists(self, connection_mock):
|
||||
url = '/test'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
# Copyright (c) 2018-2019 Cisco and/or its affiliates.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
|
@ -105,10 +105,10 @@ class TestFtdConfiguration(object):
|
|||
|
||||
def test_module_should_run_successful(self, resource_mock):
|
||||
operation_name = 'test name'
|
||||
resource_mock.return_value = 'ok'
|
||||
resource_mock.return_value = {'result': 'ok'}
|
||||
|
||||
result = self._run_module({'operation': operation_name})
|
||||
assert result['response'] == 'ok'
|
||||
assert result['response'] == {'result': 'ok'}
|
||||
|
||||
def _run_module(self, module_args):
|
||||
set_module_args(module_args)
|
||||
|
|
|
@ -1,31 +1,13 @@
|
|||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import pytest
|
||||
|
||||
from ansible.module_utils import basic
|
||||
from ansible.module_utils.network.ftd.common import HTTPMethod
|
||||
from ansible.module_utils.network.ftd.fdm_swagger_client import OperationField
|
||||
from ansible.modules.network.ftd import ftd_file_upload
|
||||
from units.modules.utils import set_module_args, exit_json, fail_json, AnsibleFailJson, AnsibleExitJson
|
||||
|
||||
from ansible.modules.network.ftd import ftd_file_upload
|
||||
from ansible.module_utils.network.ftd.fdm_swagger_client import OperationField
|
||||
from ansible.module_utils.network.ftd.common import HTTPMethod
|
||||
|
||||
|
||||
class TestFtdFileUpload(object):
|
||||
module = ftd_file_upload
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
|
@ -21,20 +22,20 @@ import json
|
|||
from ansible.module_utils.six.moves.urllib.error import HTTPError
|
||||
from units.compat import mock
|
||||
from units.compat import unittest
|
||||
from units.compat.builtins import BUILTINS
|
||||
from units.compat.mock import mock_open, patch
|
||||
|
||||
from ansible.errors import AnsibleConnectionFailure
|
||||
from ansible.module_utils.connection import ConnectionError
|
||||
from ansible.module_utils.network.ftd.common import HTTPMethod, ResponseParams
|
||||
from ansible.module_utils.network.ftd.fdm_swagger_client import SpecProp, FdmSwaggerParser
|
||||
from ansible.module_utils.six import BytesIO, StringIO
|
||||
from ansible.plugins.httpapi.ftd import HttpApi
|
||||
from ansible.module_utils.six import BytesIO, PY3, StringIO
|
||||
from ansible.plugins.httpapi.ftd import HttpApi, BASE_HEADERS, TOKEN_PATH_TEMPLATE, DEFAULT_API_VERSIONS
|
||||
|
||||
EXPECTED_BASE_HEADERS = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if PY3:
|
||||
BUILTINS_NAME = 'builtins'
|
||||
else:
|
||||
BUILTINS_NAME = '__builtin__'
|
||||
|
||||
|
||||
class FakeFtdHttpApiPlugin(HttpApi):
|
||||
|
@ -48,6 +49,9 @@ class FakeFtdHttpApiPlugin(HttpApi):
|
|||
def get_option(self, var):
|
||||
return self.hostvars[var]
|
||||
|
||||
def set_option(self, var, val):
|
||||
self.hostvars[var] = val
|
||||
|
||||
|
||||
class TestFtdHttpApi(unittest.TestCase):
|
||||
|
||||
|
@ -84,7 +88,7 @@ class TestFtdHttpApi(unittest.TestCase):
|
|||
expected_body = json.dumps({'grant_type': 'refresh_token', 'refresh_token': 'REFRESH_TOKEN'})
|
||||
self.connection_mock.send.assert_called_once_with(mock.ANY, expected_body, headers=mock.ANY, method=mock.ANY)
|
||||
|
||||
def test_login_should_use_host_variable_when_set(self):
|
||||
def test_login_should_use_env_variable_when_set(self):
|
||||
temp_token_path = self.ftd_plugin.hostvars['token_path']
|
||||
self.ftd_plugin.hostvars['token_path'] = '/testFakeLoginUrl'
|
||||
self.connection_mock.send.return_value = self._connection_response(
|
||||
|
@ -146,7 +150,7 @@ class TestFtdHttpApi(unittest.TestCase):
|
|||
assert {ResponseParams.SUCCESS: True, ResponseParams.STATUS_CODE: 200,
|
||||
ResponseParams.RESPONSE: exp_resp} == resp
|
||||
self.connection_mock.send.assert_called_once_with('/test/123?at=0', '{"name": "foo"}', method=HTTPMethod.PUT,
|
||||
headers=EXPECTED_BASE_HEADERS)
|
||||
headers=BASE_HEADERS)
|
||||
|
||||
def test_send_request_should_return_empty_dict_when_no_response_data(self):
|
||||
self.connection_mock.send.return_value = self._connection_response(None)
|
||||
|
@ -155,7 +159,7 @@ class TestFtdHttpApi(unittest.TestCase):
|
|||
|
||||
assert {ResponseParams.SUCCESS: True, ResponseParams.STATUS_CODE: 200, ResponseParams.RESPONSE: {}} == resp
|
||||
self.connection_mock.send.assert_called_once_with('/test', None, method=HTTPMethod.GET,
|
||||
headers=EXPECTED_BASE_HEADERS)
|
||||
headers=BASE_HEADERS)
|
||||
|
||||
def test_send_request_should_return_error_info_when_http_error_raises(self):
|
||||
self.connection_mock.send.side_effect = HTTPError('http://testhost.com', 500, '', {},
|
||||
|
@ -198,7 +202,7 @@ class TestFtdHttpApi(unittest.TestCase):
|
|||
self.connection_mock.send.return_value = self._connection_response('File content')
|
||||
|
||||
open_mock = mock_open()
|
||||
with patch('%s.open' % BUILTINS, open_mock):
|
||||
with patch('%s.open' % BUILTINS_NAME, open_mock):
|
||||
self.ftd_plugin.download_file('/files/1', '/tmp/test.txt')
|
||||
|
||||
open_mock.assert_called_once_with('/tmp/test.txt', 'wb')
|
||||
|
@ -213,7 +217,7 @@ class TestFtdHttpApi(unittest.TestCase):
|
|||
self.connection_mock.send.return_value = response, response_data
|
||||
|
||||
open_mock = mock_open()
|
||||
with patch('%s.open' % BUILTINS, open_mock):
|
||||
with patch('%s.open' % BUILTINS_NAME, open_mock):
|
||||
self.ftd_plugin.download_file('/files/1', '/tmp/')
|
||||
|
||||
open_mock.assert_called_once_with('/tmp/%s' % filename, 'wb')
|
||||
|
@ -226,11 +230,11 @@ class TestFtdHttpApi(unittest.TestCase):
|
|||
self.connection_mock.send.return_value = self._connection_response({'id': '123'})
|
||||
|
||||
open_mock = mock_open()
|
||||
with patch('%s.open' % BUILTINS, open_mock):
|
||||
with patch('%s.open' % BUILTINS_NAME, open_mock):
|
||||
resp = self.ftd_plugin.upload_file('/tmp/test.txt', '/files')
|
||||
|
||||
assert {'id': '123'} == resp
|
||||
exp_headers = dict(EXPECTED_BASE_HEADERS)
|
||||
exp_headers = dict(BASE_HEADERS)
|
||||
exp_headers['Content-Length'] = len('--Encoded data--')
|
||||
exp_headers['Content-Type'] = 'multipart/form-data'
|
||||
self.connection_mock.send.assert_called_once_with('/files', data='--Encoded data--',
|
||||
|
@ -244,7 +248,7 @@ class TestFtdHttpApi(unittest.TestCase):
|
|||
self.connection_mock.send.return_value = self._connection_response('invalidJsonResponse')
|
||||
|
||||
open_mock = mock_open()
|
||||
with patch('%s.open' % BUILTINS, open_mock):
|
||||
with patch('%s.open' % BUILTINS_NAME, open_mock):
|
||||
with self.assertRaises(ConnectionError) as res:
|
||||
self.ftd_plugin.upload_file('/tmp/test.txt', '/files')
|
||||
|
||||
|
@ -271,7 +275,7 @@ class TestFtdHttpApi(unittest.TestCase):
|
|||
assert self.ftd_plugin.get_model_spec('NonExistingTestModel') is None
|
||||
|
||||
@patch.object(FdmSwaggerParser, 'parse_spec')
|
||||
def test_get_model_spec(self, parse_spec_mock):
|
||||
def test_get_operation_spec_by_model_name(self, parse_spec_mock):
|
||||
self.connection_mock.send.return_value = self._connection_response(None)
|
||||
operation1 = {'modelName': 'TestModel'}
|
||||
op_model_name_is_none = {'modelName': None}
|
||||
|
@ -315,3 +319,96 @@ class TestFtdHttpApi(unittest.TestCase):
|
|||
response_text = json.dumps(response) if type(response) is dict else response
|
||||
response_data = BytesIO(response_text.encode() if response_text else ''.encode())
|
||||
return response_mock, response_data
|
||||
|
||||
def test_get_list_of_supported_api_versions_with_failed_http_request(self):
|
||||
error_msg = "Invalid Credentials"
|
||||
fp = mock.MagicMock()
|
||||
fp.read.return_value = '{{"error-msg": "{0}"}}'.format(error_msg)
|
||||
send_mock = mock.MagicMock(side_effect=HTTPError('url', 400, 'msg', 'hdrs', fp))
|
||||
with mock.patch.object(self.ftd_plugin.connection, 'send', send_mock):
|
||||
with self.assertRaises(ConnectionError) as res:
|
||||
self.ftd_plugin._get_supported_api_versions()
|
||||
|
||||
assert error_msg in str(res.exception)
|
||||
|
||||
def test_get_list_of_supported_api_versions_with_buggy_response(self):
|
||||
error_msg = "Non JSON value"
|
||||
http_response_mock = mock.MagicMock()
|
||||
http_response_mock.getvalue.return_value = error_msg
|
||||
|
||||
send_mock = mock.MagicMock(return_value=(None, http_response_mock))
|
||||
|
||||
with mock.patch.object(self.ftd_plugin.connection, 'send', send_mock):
|
||||
with self.assertRaises(ConnectionError) as res:
|
||||
self.ftd_plugin._get_supported_api_versions()
|
||||
assert error_msg in str(res.exception)
|
||||
|
||||
def test_get_list_of_supported_api_versions_with_positive_response(self):
|
||||
http_response_mock = mock.MagicMock()
|
||||
http_response_mock.getvalue.return_value = '{"supportedVersions": ["v1"]}'
|
||||
|
||||
send_mock = mock.MagicMock(return_value=(None, http_response_mock))
|
||||
with mock.patch.object(self.ftd_plugin.connection, 'send', send_mock):
|
||||
supported_versions = self.ftd_plugin._get_supported_api_versions()
|
||||
assert supported_versions == ['v1']
|
||||
|
||||
@patch('ansible.plugins.httpapi.ftd.HttpApi._get_api_token_path', mock.MagicMock(return_value=None))
|
||||
@patch('ansible.plugins.httpapi.ftd.HttpApi._get_known_token_paths')
|
||||
def test_lookup_login_url_with_empty_response(self, get_known_token_paths_mock):
|
||||
payload = mock.MagicMock()
|
||||
get_known_token_paths_mock.return_value = []
|
||||
self.assertRaises(
|
||||
ConnectionError,
|
||||
self.ftd_plugin._lookup_login_url,
|
||||
payload
|
||||
)
|
||||
|
||||
@patch('ansible.plugins.httpapi.ftd.HttpApi._get_known_token_paths')
|
||||
@patch('ansible.plugins.httpapi.ftd.HttpApi._send_login_request')
|
||||
def test_lookup_login_url_with_failed_request(self, api_request_mock, get_known_token_paths_mock):
|
||||
payload = mock.MagicMock()
|
||||
url = mock.MagicMock()
|
||||
get_known_token_paths_mock.return_value = [url]
|
||||
api_request_mock.side_effect = ConnectionError('Error message')
|
||||
with mock.patch.object(self.ftd_plugin.connection, 'queue_message') as display_mock:
|
||||
self.assertRaises(
|
||||
ConnectionError,
|
||||
self.ftd_plugin._lookup_login_url,
|
||||
payload
|
||||
)
|
||||
assert display_mock.called
|
||||
|
||||
@patch('ansible.plugins.httpapi.ftd.HttpApi._get_api_token_path', mock.MagicMock(return_value=None))
|
||||
@patch('ansible.plugins.httpapi.ftd.HttpApi._get_known_token_paths')
|
||||
@patch('ansible.plugins.httpapi.ftd.HttpApi._send_login_request')
|
||||
@patch('ansible.plugins.httpapi.ftd.HttpApi._set_api_token_path')
|
||||
def test_lookup_login_url_with_positive_result(self, set_api_token_mock, api_request_mock,
|
||||
get_known_token_paths_mock):
|
||||
payload = mock.MagicMock()
|
||||
url = mock.MagicMock()
|
||||
get_known_token_paths_mock.return_value = [url]
|
||||
response_mock = mock.MagicMock()
|
||||
api_request_mock.return_value = response_mock
|
||||
|
||||
resp = self.ftd_plugin._lookup_login_url(payload)
|
||||
|
||||
set_api_token_mock.assert_called_once_with(url)
|
||||
assert resp == response_mock
|
||||
|
||||
@patch('ansible.plugins.httpapi.ftd.HttpApi._get_supported_api_versions')
|
||||
def test_get_known_token_paths_with_positive_response(self, get_list_of_supported_api_versions_mock):
|
||||
test_versions = ['v1', 'v2']
|
||||
get_list_of_supported_api_versions_mock.return_value = test_versions
|
||||
result = self.ftd_plugin._get_known_token_paths()
|
||||
assert result == [TOKEN_PATH_TEMPLATE.format(version) for version in test_versions]
|
||||
|
||||
@patch('ansible.plugins.httpapi.ftd.HttpApi._get_supported_api_versions')
|
||||
def test_get_known_token_paths_with_failed_api_call(self, get_list_of_supported_api_versions_mock):
|
||||
get_list_of_supported_api_versions_mock.side_effect = ConnectionError('test error message')
|
||||
result = self.ftd_plugin._get_known_token_paths()
|
||||
assert result == [TOKEN_PATH_TEMPLATE.format(version) for version in DEFAULT_API_VERSIONS]
|
||||
|
||||
def test_set_api_token_path(self):
|
||||
url = mock.MagicMock()
|
||||
self.ftd_plugin._set_api_token_path(url)
|
||||
assert self.ftd_plugin._get_api_token_path() == url
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue