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:
parent
4207c74472
commit
07178e0347
@ -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,
|
||||
},
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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():
|
||||
|
Loading…
Reference in New Issue
Block a user