Fail if request includes unexpected parameter

This change modifies the behaviour of requests so that they now fail
with a 400 error if the request includes a parameter that is not defined
in its schema. Previously the behaviour was to ignore anything not
expected. This should help to prevent users being mislead by responses,
for example a GET request with filters that were not applied because the
query was incorrect.

Changing this behaviour also addresses a difference between the project
and the API-WG guidelines[1].

The JSON schema validation keyword additionalProperties is used to
ensure that undefined properties cause the request to fail.

GET requests that don't accept any form of query now have a schema so
that the validation can generate a failure if a query is specified.

[1] https://specs.openstack.org/openstack/api-wg/guidelines/http.html

Change-Id: I61b0d9445334f93d787a9330f11fa01e4e3503d4
This commit is contained in:
git-harry 2017-02-02 11:57:41 +00:00
parent 4207c74472
commit 07178e0347
4 changed files with 139 additions and 131 deletions

View File

@ -1,5 +1,6 @@
DefinitionVariablesSource = {
"type": "object",
"additionalProperties": False,
"patternProperties": {
"^.+": {
"anyOf": [
@ -37,6 +38,7 @@ DefinitionsHost = {
"device_type",
],
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -89,6 +91,7 @@ DefinitionsHost = {
DefinitionsHostId = {
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -141,6 +144,7 @@ DefinitionsCell = {
"region_id",
],
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -170,6 +174,7 @@ DefinitionsCell = {
DefinitionsCellId = {
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -200,6 +205,7 @@ DefinitionsCellId = {
DefinitionsLabel = {
"type": "object",
"additionalProperties": False,
"properties": {
"labels": {
"type": "array",
@ -212,6 +218,7 @@ DefinitionsLabel = {
DefinitionsError = {
"type": "object",
"additionalProperties": False,
"properties": {
"fields": {
"type": "string",
@ -231,6 +238,7 @@ DefinitionsRegion = {
"name",
],
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -264,6 +272,7 @@ DefinitionsRegion = {
DefinitionsRegionId = {
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -298,6 +307,7 @@ DefinitionsRegionId = {
DefinitionUser = {
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -331,6 +341,7 @@ DefinitionUser = {
DefinitionProject = {
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -355,6 +366,7 @@ DefinitionNetwork = {
"netmask",
],
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -398,6 +410,7 @@ DefinitionNetwork = {
DefinitionNetworkId = {
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -447,6 +460,7 @@ DefinitionNetworkInterface = {
"ip_address",
],
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -507,6 +521,7 @@ DefinitionNetworkInterface = {
DefinitionNetworkInterfaceId = {
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -571,6 +586,7 @@ DefinitionNetworkDevice = {
"ip_address",
],
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -629,6 +645,7 @@ DefinitionNetworkDevice = {
DefinitionNetworkDeviceId = {
"type": "object",
"additionalProperties": False,
"properties": {
"created_at": {
"type": "string",
@ -685,9 +702,17 @@ DefinitionNetworkDeviceId = {
},
}
DefinitionNoParams = {
"type": "object",
"properties": {},
"maxProperties": 0,
"additionalProperties": False,
}
validators = {
("ansible_inventory", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"region_id": {
"default": None,
@ -712,6 +737,7 @@ validators = {
"json": DefinitionsLabel,
},
("hosts_labels", "GET"): {
"args": DefinitionNoParams,
},
("hosts_labels", "DELETE"): {
"json": DefinitionsLabel,
@ -720,6 +746,7 @@ validators = {
},
("hosts_id", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"resolved-values": {
"default": True,
@ -730,6 +757,7 @@ validators = {
},
("hosts_id", "PUT"): {
"json": {
"additionalProperties": False,
"properties": {
"active": {
"type": "boolean",
@ -752,6 +780,7 @@ validators = {
},
("hosts_id_variables", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"resolved-values": {
"default": True,
@ -762,6 +791,7 @@ validators = {
},
("regions", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"name": {
"type": "string",
@ -796,6 +826,7 @@ validators = {
"json": DefinitionVariablesSource,
},
("regions_id_variables", "GET"): {
"args": DefinitionNoParams,
},
("regions_id_variables", "DELETE"): {
"json": DefinitionVariablesSource,
@ -805,6 +836,7 @@ validators = {
},
("hosts", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"name": {
"type": "string",
@ -855,9 +887,11 @@ validators = {
("cells_id", "DELETE"): {
},
("cells_id", "GET"): {
"args": DefinitionNoParams,
},
("cells_id", "PUT"): {
"json": {
"additionalProperties": False,
"properties": {
"note": {
"type": "string",
@ -873,6 +907,7 @@ validators = {
},
("cells", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"region_id": {
"type": "string",
@ -907,9 +942,11 @@ validators = {
("regions_id", "DELETE"): {
},
("regions_id", "GET"): {
"args": DefinitionNoParams,
},
("regions_id", "PUT"): {
"json": {
"additionalProperties": False,
"properties": {
"name": {
"type": "string",
@ -924,12 +961,14 @@ validators = {
"json": DefinitionVariablesSource,
},
("cells_id_variables", "GET"): {
"args": DefinitionNoParams,
},
("cells_id_variables", "DELETE"): {
"json": DefinitionVariablesSource,
},
("projects", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"id": {
"default": None,
@ -961,9 +1000,11 @@ validators = {
("projects_id", "DELETE"): {
},
("projects_id", "GET"): {
"args": DefinitionNoParams,
},
("users", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"id": {
"default": None,
@ -995,9 +1036,11 @@ validators = {
("users_id", "DELETE"): {
},
("users_id", "GET"): {
"args": DefinitionNoParams,
},
("network_devices", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"id": {
"type": "integer",
@ -1045,6 +1088,7 @@ validators = {
},
("network_devices_id", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"resolved-values": {
"default": True,
@ -1057,6 +1101,7 @@ validators = {
"json": DefinitionVariablesSource
},
("network_devices_id_variables", "GET"): {
"args": DefinitionNoParams,
},
("network_devices_id_variables", "DELETE"): {
"json": DefinitionVariablesSource
@ -1064,9 +1109,11 @@ validators = {
("networks_id", "DELETE"): {
},
("networks_id", "GET"): {
"args": DefinitionNoParams,
},
("networks_id", "PUT"): {
"json": {
"additionalProperties": False,
"properties": {
"name": {
"type": "string",
@ -1091,6 +1138,7 @@ validators = {
},
("network_devices_id", "PUT"): {
"json": {
"additionalProperties": False,
"properties": {
"ip_address": {
"type": "string",
@ -1120,12 +1168,14 @@ validators = {
"json": DefinitionsLabel,
},
("network_devices_labels", "GET"): {
"args": DefinitionNoParams,
},
("network_devices_labels", "PUT"): {
"json": DefinitionsLabel,
},
("network_interfaces", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"id": {
"type": "integer",
@ -1163,9 +1213,11 @@ validators = {
("network_interfaces_id", "DELETE"): {
},
("network_interfaces_id", "GET"): {
"args": DefinitionNoParams,
},
("network_interfaces_id", "PUT"): {
"json": {
"additionalProperties": False,
"properties": {
"name": {
"type": "string",
@ -1199,6 +1251,7 @@ validators = {
},
("networks", "GET"): {
"args": {
"additionalProperties": False,
"properties": {
"id": {
"type": "integer",
@ -1242,6 +1295,7 @@ validators = {
"json": DefinitionVariablesSource
},
("networks_id_variables", "GET"): {
"args": DefinitionNoParams,
},
("networks_id_variables", "DELETE"): {
"json": DefinitionVariablesSource
@ -1257,6 +1311,7 @@ filters = {
"headers": None,
"schema": {
"type": "object",
"additionalProperties": False,
"patternProperties": {
"^.+": {
"anyOf": [
@ -1304,6 +1359,7 @@ filters = {
"headers": None,
"schema": {
"type": "object",
"additionalProperties": False,
"properties": {
"variables": DefinitionVariablesSource,
},
@ -1327,6 +1383,7 @@ filters = {
"headers": None,
"schema": {
"type": "object",
"additionalProperties": False,
"properties": {
"variables": DefinitionVariablesSource,
},
@ -1565,6 +1622,7 @@ filters = {
"headers": None,
"schema": {
"type": "object",
"additionalProperties": False,
"properties": {
"variables": DefinitionVariablesSource,
},
@ -1588,6 +1646,7 @@ filters = {
"headers": None,
"schema": {
"type": "object",
"additionalProperties": False,
"properties": {
"variables": DefinitionVariablesSource,
},
@ -1699,6 +1758,7 @@ filters = {
"headers": None,
"schema": {
"type": "object",
"additionalProperties": False,
"properties": {
"variables": DefinitionVariablesSource,
},
@ -1722,6 +1782,7 @@ filters = {
"headers": None,
"schema": {
"type": "object",
"additionalProperties": False,
"properties": {
"variables": DefinitionVariablesSource,
},
@ -2114,6 +2175,7 @@ filters = {
"headers": None,
"schema": {
"type": "object",
"additionalProperties": False,
"properties": {
"variables": DefinitionVariablesSource,
},
@ -2137,6 +2199,7 @@ filters = {
"headers": None,
"schema": {
"type": "object",
"additionalProperties": False,
"properties": {
"variables": DefinitionVariablesSource,
},
@ -2271,6 +2334,7 @@ filters = {
"headers": None,
"schema": {
"type": "object",
"additionalProperties": False,
"properties": {
"variables": DefinitionVariablesSource,
},
@ -2294,6 +2358,7 @@ filters = {
"headers": None,
"schema": {
"type": "object",
"additionalProperties": False,
"properties": {
"variables": DefinitionVariablesSource,
},

View File

@ -212,8 +212,12 @@ def request_validate(view):
value = getattr(request, location, MultiDict())
validator = FlaskValidatorAdaptor(schema)
result = validator.validate(value)
kwargs[data_type[location]] = result
LOG.info("Validated request %s: %s" % (location, result))
if schema.get("maxProperties") == 0:
continue
else:
kwargs[data_type[location]] = result
context = request.environ['context']
return view(*args, context=context, **kwargs)

View File

@ -124,8 +124,8 @@ class APIV1CellsIDTest(APIV1Test):
def test_put_cells_by_id_invalid_property(self, mock_cell):
data = {'foo': 'isinvalid'}
resp = self.put('v1/cells/1', data=data)
self.assertEqual(200, resp.status_code)
mock_cell.assert_called_once_with(mock.ANY, '1', {})
self.assertEqual(400, resp.status_code)
mock_cell.assert_not_called()
@mock.patch.object(dbapi, 'cells_update')
def test_update_cell(self, mock_cell):
@ -154,12 +154,9 @@ class APIV1CellsTest(APIV1Test):
@mock.patch.object(dbapi, 'cells_get_all')
def test_get_cells_invalid_property(self, mock_cells):
mock_cells.return_value = fake_resources.CELL_LIST
resp = self.get('v1/cells?foo=isaninvalidproperty')
self.assertEqual(len(resp.json), len(fake_resources.CELL_LIST))
mock_cells.assert_called_once_with(
mock.ANY, {}, {'limit': 30, 'marker': None},
)
self.assertEqual(400, resp.status_code)
mock_cells.assert_not_called()
@mock.patch.object(dbapi, 'cells_get_all')
def test_get_cells_with_name_filters(self, mock_cells):
@ -228,13 +225,10 @@ class APIV1CellsTest(APIV1Test):
@mock.patch.object(dbapi, 'cells_create')
def test_create_cell_with_invalid_property(self, mock_cell):
mock_cell.return_value = fake_resources.CELL1
data = {'name': 'cell1', 'region_id': 1, 'foo': 'invalidproperty'}
resp = self.post('v1/cells', data=data)
self.assertEqual(200, resp.status_code)
mock_cell.assert_called_once_with(
mock.ANY, {'name': 'cell1', 'region_id': 1, 'project_id': None}
)
self.assertEqual(400, resp.status_code)
mock_cell.assert_not_called()
class APIV1CellsVariablesTest(APIV1Test):
@ -312,8 +306,8 @@ class APIV1RegionsIDTest(APIV1Test):
def test_put_regions_by_id_invalid_property(self, mock_region):
data = {'foo': 'isinvalid'}
resp = self.put('v1/regions/1', data=data)
self.assertEqual(200, resp.status_code)
mock_region.assert_called_once_with(mock.ANY, '1', {})
self.assertEqual(400, resp.status_code)
mock_region.assert_not_called()
@mock.patch.object(dbapi, 'regions_update')
def test_update_region(self, mock_region):
@ -339,12 +333,9 @@ class APIV1RegionsTest(APIV1Test):
@mock.patch.object(dbapi, 'regions_get_all')
def test_regions_get_all_by_invalid_property_name(self, mock_regions):
mock_regions.return_value = fake_resources.REGIONS_LIST
resp = self.get('v1/regions?foo=invalidpropertyname')
self.assertEqual(len(resp.json), len(fake_resources.REGIONS_LIST))
mock_regions.assert_called_once_with(
mock.ANY, {}, {'limit': 30, 'marker': None},
)
self.assertEqual(400, resp.status_code)
mock_regions.assert_not_called()
@mock.patch.object(dbapi, 'regions_get_by_name')
def test_regions_get_by_name_filters(self, mock_regions):
@ -380,14 +371,10 @@ class APIV1RegionsTest(APIV1Test):
@mock.patch.object(dbapi, 'regions_create')
def test_post_region_with_invalid_property_name(self, mock_region):
mock_region.return_value = fake_resources.REGION1
data = {'name': 'region1', 'foo': 'invalidpropertyname'}
resp = self.post('v1/regions', data=data)
self.assertEqual(200, resp.status_code)
mock_region.assert_called_once_with(
mock.ANY,
{'project_id': None, 'name': 'region1'}
)
self.assertEqual(400, resp.status_code)
mock_region.assert_not_called()
@mock.patch.object(dbapi, 'regions_create')
def test_create_region_returns_region_obj(self, mock_region):
@ -465,19 +452,15 @@ class APIV1HostsIDTest(APIV1Test):
@mock.patch.object(dbapi, 'hosts_get_by_id')
def test_get_hosts_by_id_invalid_property_name(self, mock_hosts):
mock_hosts.return_value = fake_resources.HOST1
resp = self.get('/v1/hosts/1?foo=invalidproperty')
self.assertEqual(resp.json["name"], fake_resources.HOST1.name)
mock_hosts.assert_called_once_with(mock.ANY, "1")
self.assertEqual(400, resp.status_code)
mock_hosts.assert_not_called()
@mock.patch.object(dbapi, 'hosts_update')
def test_put_hosts_by_id_invalid_property_name(self, mock_hosts):
mock_hosts.return_value = fake_resources.HOST1
resp = self.put('/v1/hosts/1', data={'foo': 'invalidproperty'})
self.assertEqual(resp.json["name"], fake_resources.HOST1.name)
mock_hosts.assert_called_once_with(
mock.ANY, "1", {}
)
self.assertEqual(400, resp.status_code)
mock_hosts.assert_not_called()
@mock.patch.object(dbapi, 'hosts_get_by_id')
def test_get_hosts_by_bad_id_is_404(self, mock_hosts):
@ -538,12 +521,9 @@ class APIV1HostsLabelsTest(APIV1Test):
@mock.patch.object(dbapi, 'hosts_labels_update')
def test_put_hosts_labels_invalid_property_name(self, mock_host):
req_data = {"labels": ["a", "b"], "foo": ["should", "be", "removed"]}
resp_data = {"labels": ["a", "b"]}
mock_host.return_value = fake_resources.HOST4
resp = self.put('v1/hosts/1/labels', data=req_data)
self.assertEqual(200, resp.status_code)
self.assertEqual(resp.json, resp_data)
mock_host.assert_called_once_with(mock.ANY, '1', resp_data)
self.assertEqual(400, resp.status_code)
mock_host.assert_not_called()
@mock.patch.object(dbapi, 'hosts_labels_update')
def test_put_hosts_labels_validate_type(self, mock_host):
@ -577,12 +557,9 @@ class APIV1HostsTest(APIV1Test):
@mock.patch.object(dbapi, 'hosts_get_all')
def test_get_hosts_invalid_property_name(self, fake_hosts):
fake_hosts.return_value = fake_resources.HOSTS_LIST_R1
resp = self.get('/v1/hosts?foo=invalidproperty')
self.assertEqual(len(resp.json), 2)
fake_hosts.assert_called_once_with(
mock.ANY, {}, {'limit': 30, 'marker': None},
)
self.assertEqual(400, resp.status_code)
fake_hosts.assert_not_called()
@mock.patch.object(dbapi, 'hosts_get_all')
def test_get_host_by_non_existing_region_raises404(self, fake_hosts):
@ -665,21 +642,13 @@ class APIV1HostsTest(APIV1Test):
@mock.patch.object(dbapi, 'hosts_create')
def test_create_host_invalid_property_name(self, mock_host):
return_value = {'name': 'www.host1.com', 'region_id': 1,
'ip_address': '10.0.0.1', 'id': 1, 'variables': {},
'device_type': 'server', 'active': True}
mock_host.return_value = return_value
data = {'name': 'www.host1.com', 'region_id': 1, 'foo': 'invalidprop',
'ip_address': '10.0.0.1', 'device_type': 'server'}
db_json = data.copy()
db_json['project_id'] = None
del db_json['foo']
resp = self.post('v1/hosts', data=data)
self.assertEqual(200, resp.status_code)
self.assertEqual(return_value, resp.json)
mock_host.assert_called_once_with(mock.ANY, db_json)
self.assertEqual(400, resp.status_code)
mock_host.assert_not_called()
class APIV1HostsVariablesTest(APIV1Test):
@ -692,18 +661,9 @@ class APIV1HostsVariablesTest(APIV1Test):
@mock.patch.object(dbapi, 'hosts_get_by_id')
def test_host_get_variables_invalid_property_name(self, mock_host):
mock_host.return_value = fake_resources.HOST1
resp = self.get('v1/hosts/1/variables?foo=isnotreal')
expected = {
"variables": {
"r_var": "somevar",
"key1": "value1",
"key2": "value2",
}
}
self.assertEqual(expected, resp.json)
self.assertEqual(resp.status_code, 200)
mock_host.assert_called_once_with(mock.ANY, "1")
self.assertEqual(400, resp.status_code)
mock_host.assert_not_called()
@mock.patch.object(dbapi, 'hosts_get_by_id')
def test_host_get_resolved_variables(self, mock_host):
@ -778,8 +738,8 @@ class APIV1ProjectsTest(APIV1Test):
def test_project_post_invalid_property(self, mock_projects):
data = {'foo': 'isinvalidproperty'}
resp = self.post('v1/projects', data=data)
self.assertEqual(resp.status_code, 200)
mock_projects.assert_called_once_with(mock.ANY, {})
self.assertEqual(400, resp.status_code)
mock_projects.assert_not_called()
@mock.patch.object(dbapi, 'projects_get_all')
def test_projects_get_no_admin_fails(self, mock_project):
@ -804,21 +764,15 @@ class APIV1UsersTest(APIV1Test):
@mock.patch.object(dbapi, 'users_create')
@mock.patch.object(dbapi, 'projects_get_by_id')
def test_create_users_invalid_property(self, mock_project, mock_user):
mock_project.return_value = {'id': project_id1, 'name': 'project1'}
return_value = {'username': 'user1', 'is_admin': False, 'id': 1,
'api_key': 'xxxx'}
mock_user.return_value = return_value
data = {
'username': 'user1',
'is_admin': False,
'foo': 'isinvalidproperty'
}
resp = self.post('v1/users', data=data)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.json['id'], 1)
db_json = {'username': 'user1', 'is_admin': False, 'api_key': mock.ANY,
'project_id': None}
mock_user.assert_called_once_with(mock.ANY, db_json)
self.assertEqual(400, resp.status_code)
mock_project.assert_not_called()
mock_user.assert_not_called()
@mock.patch.object(dbapi, 'users_get_all')
def test_users_get_all(self, mock_user):
@ -867,12 +821,9 @@ class APIV1NetworksTest(APIV1Test):
@mock.patch.object(dbapi, 'networks_get_all')
def test_get_networks_invalid_property(self, fake_networks):
fake_networks.return_value = fake_resources.NETWORKS_LIST2
resp = self.get('/v1/networks?foo=invalid')
self.assertEqual(len(resp.json), 3)
fake_networks.assert_called_once_with(
mock.ANY, {}, {'limit': 30, 'marker': None},
)
self.assertEqual(400, resp.status_code)
fake_networks.assert_not_called()
@mock.patch.object(dbapi, 'networks_create')
def test_create_networks_with_valid_data(self, mock_network):
@ -893,13 +844,12 @@ class APIV1NetworksTest(APIV1Test):
@mock.patch.object(dbapi, 'networks_create')
def test_create_networks_with_invalid_property(self, mock_network):
mock_network.return_value = None
data = {'name': 'some network', 'region_id': 1,
'cidr': '10.10.1.0/24', 'gateway': '192.168.1.1',
'netmask': '255.255.255.0', 'foo': 'isinvalid'}
resp = self.post('/v1/networks', data=data)
self.assertEqual(200, resp.status_code)
mock_network.assert_called_once()
self.assertEqual(400, resp.status_code)
mock_network.assert_not_called()
class APIV1NetworksIDTest(APIV1Test):
@ -931,11 +881,10 @@ class APIV1NetworksIDTest(APIV1Test):
@mock.patch.object(dbapi, 'networks_update')
def test_update_network_invalid_property(self, mock_network):
mock_network.return_value = fake_resources.NETWORK1
payload = {"foo": "isinvalid"}
resp = self.put('v1/networks/1', data=payload)
self.assertEqual(resp.status_code, 200)
mock_network.assert_called_once_with(mock.ANY, '1', {})
self.assertEqual(400, resp.status_code)
mock_network.assert_not_called()
@mock.patch.object(dbapi, 'networks_delete')
def test_delete_network(self, mock_network):
@ -993,11 +942,9 @@ class APIV1NetworksVariablesTest(APIV1Test):
class APIV1NetworkDevicesIDTest(APIV1Test):
@mock.patch.object(dbapi, 'network_devices_get_by_id')
def test_get_network_devices_by_id_invalid_property(self, fake_device):
fake_device.return_value = fake_resources.NETWORK_DEVICE1
resp = self.get('/v1/network-devices/1?foo=isaninvalidproperty')
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.json['name'], 'NetDevices1')
fake_device.assert_called_once_with(mock.ANY, '1')
self.assertEqual(400, resp.status_code)
fake_device.assert_not_called()
@mock.patch.object(dbapi, 'network_devices_get_by_id')
def test_get_network_devices_by_id(self, fake_device):
@ -1021,11 +968,10 @@ class APIV1NetworkDevicesIDTest(APIV1Test):
@mock.patch.object(dbapi, 'network_devices_update')
def test_put_network_device_invalid_property(self, fake_device):
fake_device.return_value = fake_resources.NETWORK_DEVICE1
payload = {"foo": "isinvalid"}
resp = self.put('v1/network-devices/1', data=payload)
self.assertEqual(resp.status_code, 200)
fake_device.assert_called_once_with(mock.ANY, '1', {})
self.assertEqual(400, resp.status_code)
fake_device.assert_not_called()
@mock.patch.object(dbapi, 'network_devices_get_by_id')
def test_get_network_devices_get_by_id(self, mock_devices):
@ -1062,12 +1008,9 @@ class APIV1NetworkDevicesTest(APIV1Test):
@mock.patch.object(dbapi, 'network_devices_get_all')
def test_get_network_devices_invalid_property(self, fake_devices):
fake_devices.return_value = fake_resources.NETWORK_DEVICE_LIST2
resp = self.get('/v1/network-devices?foo=isaninvalidproperty')
self.assertEqual(len(resp.json), 2)
fake_devices.assert_called_once_with(
mock.ANY, {}, {'limit': 30, 'marker': None},
)
self.assertEqual(400, resp.status_code)
fake_devices.assert_not_called()
@mock.patch.object(dbapi, 'network_devices_get_all')
def test_get_network_devices(self, fake_devices):
@ -1107,13 +1050,12 @@ class APIV1NetworkDevicesTest(APIV1Test):
@mock.patch.object(dbapi, 'network_devices_create')
def test_create_netdevices_with_invalid_property(self, mock_devices):
mock_devices.return_value = None
data = {'name': 'NewNetDevice1', 'region_id': 1,
'device_type': 'Sample', 'ip_address': '0.0.0.0',
'foo': 'isinvalid'}
resp = self.post('/v1/network-devices', data=data)
self.assertEqual(200, resp.status_code)
mock_devices.assert_called_once()
self.assertEqual(400, resp.status_code)
mock_devices.assert_not_called()
class APIV1NetworkDevicesLabelsTest(APIV1Test):
@ -1127,11 +1069,10 @@ class APIV1NetworkDevicesLabelsTest(APIV1Test):
@mock.patch.object(dbapi, 'network_devices_labels_update')
def test_network_devices_labels_update_invalid_property(self, fake_device):
fake_device.return_value = fake_resources.NETWORK_DEVICE1
payload = {"foo": "isinvalid"}
resp = self.put('v1/network-devices/1/labels', data=payload)
self.assertEqual(resp.status_code, 200)
fake_device.assert_called_once_with(mock.ANY, '1', {})
self.assertEqual(400, resp.status_code)
fake_device.assert_not_called()
@mock.patch.object(dbapi, 'network_devices_labels_delete')
def test_network_devices_delete_labels(self, mock_network_device):
@ -1220,7 +1161,8 @@ class APIV1NetworkInterfacesTest(APIV1Test):
@mock.patch.object(dbapi, 'network_interfaces_get_all')
def test_get_network_interfaces_by_device_id(self, fake_interfaces):
fake_interfaces.return_value = fake_resources.NETWORK_INTERFACE_LIST1
resp = self.get('/v1/network-interfaces?name=NetInterface&device_id=1')
resp = self.get('/v1/network-interfaces?device_id=1')
self.assertEqual(200, resp.status_code)
network_interface_resp = fake_resources.NETWORK_INTERFACE1
self.assertEqual(resp.json[0]["name"], network_interface_resp.name)
self.assertEqual(
@ -1249,13 +1191,12 @@ class APIV1NetworkInterfacesTest(APIV1Test):
@mock.patch.object(dbapi, 'network_interfaces_create')
def test_network_interfaces_create_invalid_property(self, fake_interfaces):
fake_interfaces.return_value = None
data = {'name': 'NewNetInterface', 'device_id': 1,
'ip_address': '0.0.0.0', 'interface_type': 'Sample',
'foo': 'isinvalid'}
resp = self.post('/v1/network-interfaces', data=data)
self.assertEqual(200, resp.status_code)
fake_interfaces.assert_called_once()
self.assertEqual(400, resp.status_code)
fake_interfaces.assert_not_called()
@mock.patch.object(dbapi, 'network_interfaces_get_all')
def test_get_network_interfaces(self, fake_interfaces):
@ -1269,13 +1210,9 @@ class APIV1NetworkInterfacesTest(APIV1Test):
@mock.patch.object(dbapi, 'network_interfaces_get_all')
def test_get_network_interfaces_invalid_property(self, fake_interfaces):
fake_interfaces.return_value = fake_resources.NETWORK_INTERFACE_LIST2
resp = self.get('/v1/network-interfaces?foo=invalid')
self.assertEqual(200, resp.status_code)
self.assertEqual(len(resp.json), 2)
fake_interfaces.assert_called_once_with(
mock.ANY, {}, {'limit': 30, 'marker': None},
)
self.assertEqual(400, resp.status_code)
fake_interfaces.assert_not_called()
class APIV1NetworkInterfacesIDTest(APIV1Test):
@ -1310,13 +1247,11 @@ class APIV1NetworkInterfacesIDTest(APIV1Test):
@mock.patch.object(dbapi, 'network_interfaces_update')
def test_network_interfaces_update_invalid_property(self, fake_interfaces):
fake_interfaces.return_value = fake_resources.NETWORK_INTERFACE1
payload = {'foo': 'invalid'}
resp = self.put('/v1/network-interfaces/1', data=payload)
self.assertEqual(200, resp.status_code)
self.assertNotIn('foo', resp.json)
fake_interfaces.assert_called_once_with(mock.ANY, '1', {})
self.assertEqual(400, resp.status_code)
fake_interfaces.assert_not_called()
@mock.patch.object(dbapi, 'network_interfaces_delete')
def test_network_interfaces_delete(self, fake_interfaces):

View File

@ -10,8 +10,10 @@ VALIDATORS = {
('ansible_inventory', 'GET'),
('cells', 'GET'),
('cells', 'POST'),
('cells_id', 'GET'),
('cells_id', 'PUT'),
('cells_id_variables', 'DELETE'),
('cells_id_variables', 'GET'),
('cells_id_variables', 'PUT'),
('hosts', 'GET'),
('hosts', 'POST'),
@ -21,54 +23,52 @@ VALIDATORS = {
('hosts_id_variables', 'GET'),
('hosts_id_variables', 'PUT'),
('hosts_labels', 'DELETE'),
('hosts_labels', 'GET'),
('hosts_labels', 'PUT'),
('network_devices', 'GET'),
('network_devices', 'POST'),
('network_devices_id', 'GET'),
('network_devices_id', 'PUT'),
('network_devices_id_variables', 'DELETE'),
('network_devices_id_variables', 'GET'),
('network_devices_id_variables', 'PUT'),
('network_devices_labels', 'GET'),
('network_devices_labels', 'PUT'),
('network_devices_labels', 'DELETE'),
('network_interfaces', 'GET'),
('network_interfaces', 'POST'),
("network_interfaces_id", "GET"),
('network_interfaces_id', 'PUT'),
('networks', 'GET'),
('networks', 'POST'),
("networks_id", "GET"),
('networks_id', 'PUT'),
('networks_id_variables', 'DELETE'),
("networks_id_variables", "GET"),
('networks_id_variables', 'PUT'),
('projects', 'GET'),
('projects', 'POST'),
("projects_id", "GET"),
('regions', 'GET'),
('regions', 'POST'),
("regions_id", "GET"),
('regions_id', 'PUT'),
('regions_id_variables', 'DELETE'),
("regions_id_variables", "GET"),
('regions_id_variables', 'PUT'),
('users', 'GET'),
('users', 'POST'),
("users_id", "GET"),
],
"without_schema": [
('cells_id', 'DELETE'),
('cells_id', 'GET'),
('cells_id_variables', 'GET'),
('hosts_id', 'DELETE'),
('hosts_labels', 'GET'),
('network_devices_id', 'DELETE'),
('network_devices_id_variables', 'GET'),
('network_devices_labels', 'GET'),
("network_interfaces_id", "DELETE"),
("network_interfaces_id", "GET"),
("networks_id", "DELETE"),
("networks_id", "GET"),
("networks_id_variables", "GET"),
("projects_id", "DELETE"),
("projects_id", "GET"),
("users_id", "DELETE"),
("users_id", "GET"),
("regions_id", "DELETE"),
("regions_id", "GET"),
("regions_id_variables", "GET"),
]
}
@ -105,6 +105,8 @@ def generate_schema_validation_functions(cls):
self.assertIs(
jsonschema.Draft4Validator.check_schema(schema), None
)
if 'type' not in schema or schema['type'] == 'object':
self.assertFalse(schema['additionalProperties'])
name = '_'.join(('validator', endpoint, method))
setattr(cls, 'test_valid_schema_{}'.format(name), test)
@ -133,6 +135,8 @@ def generate_schema_validation_functions(cls):
self.assertIs(
jsonschema.Draft4Validator.check_schema(schema), None
)
if 'type' not in schema or schema['type'] == 'object':
self.assertFalse(schema['additionalProperties'])
setattr(cls, 'test_valid_schema_{}'.format(name), test)
for (endpoint, method), responses in filters.items():