Merge "Skip non-base properties in patch method"
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import jsonpatch
|
import jsonpatch
|
||||||
|
import six
|
||||||
import warlock.model as warlock
|
import warlock.model as warlock
|
||||||
|
|
||||||
|
|
||||||
@@ -34,9 +35,10 @@ class SchemaBasedModel(warlock.Model):
|
|||||||
original = copy.deepcopy(self.__dict__['__original__'])
|
original = copy.deepcopy(self.__dict__['__original__'])
|
||||||
new = dict(self)
|
new = dict(self)
|
||||||
if self.__dict__['schema']:
|
if self.__dict__['schema']:
|
||||||
for prop in self.schema['properties']:
|
for (name, prop) in six.iteritems(self.schema['properties']):
|
||||||
if prop not in original and prop in new:
|
if (name not in original and name in new and
|
||||||
original[prop] = None
|
prop.get('is_base', True)):
|
||||||
|
original[name] = None
|
||||||
|
|
||||||
return jsonpatch.make_patch(original, dict(self)).to_string()
|
return jsonpatch.make_patch(original, dict(self)).to_string()
|
||||||
|
|
||||||
|
@@ -58,6 +58,7 @@ data_fixtures = {
|
|||||||
'required': ['url', 'metadata'],
|
'required': ['url', 'metadata'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'color': {'type': 'string', 'is_base': False},
|
||||||
},
|
},
|
||||||
'additionalProperties': {'type': 'string'}
|
'additionalProperties': {'type': 'string'}
|
||||||
},
|
},
|
||||||
@@ -125,6 +126,7 @@ data_fixtures = {
|
|||||||
'name': 'image-3',
|
'name': 'image-3',
|
||||||
'barney': 'rubble',
|
'barney': 'rubble',
|
||||||
'george': 'jetson',
|
'george': 'jetson',
|
||||||
|
'color': 'red',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'PATCH': (
|
'PATCH': (
|
||||||
@@ -367,7 +369,8 @@ schema_fixtures = {
|
|||||||
},
|
},
|
||||||
'required': ['url', 'metadata'],
|
'required': ['url', 'metadata'],
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
'color': {'type': 'string', 'is_base': False},
|
||||||
},
|
},
|
||||||
'additionalProperties': {'type': 'string'}
|
'additionalProperties': {'type': 'string'}
|
||||||
}
|
}
|
||||||
@@ -692,6 +695,38 @@ class TestController(testtools.TestCase):
|
|||||||
with testtools.ExpectedException(TypeError):
|
with testtools.ExpectedException(TypeError):
|
||||||
self.controller.update(image_id, **params)
|
self.controller.update(image_id, **params)
|
||||||
|
|
||||||
|
def test_update_add_custom_property(self):
|
||||||
|
image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
||||||
|
params = {'color': 'red'}
|
||||||
|
image = self.controller.update(image_id, **params)
|
||||||
|
expect_hdrs = {
|
||||||
|
'Content-Type': 'application/openstack-images-v2.1-json-patch',
|
||||||
|
}
|
||||||
|
expect_body = '[{"path": "/color", "value": "red", "op": "add"}]'
|
||||||
|
expect = [
|
||||||
|
('GET', '/v2/images/%s' % image_id, {}, None),
|
||||||
|
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
|
||||||
|
('GET', '/v2/images/%s' % image_id, {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(image_id, image.id)
|
||||||
|
|
||||||
|
def test_update_replace_custom_property(self):
|
||||||
|
image_id = 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3'
|
||||||
|
params = {'color': 'blue'}
|
||||||
|
image = self.controller.update(image_id, **params)
|
||||||
|
expect_hdrs = {
|
||||||
|
'Content-Type': 'application/openstack-images-v2.1-json-patch',
|
||||||
|
}
|
||||||
|
expect_body = '[{"path": "/color", "value": "blue", "op": "replace"}]'
|
||||||
|
expect = [
|
||||||
|
('GET', '/v2/images/%s' % image_id, {}, None),
|
||||||
|
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
|
||||||
|
('GET', '/v2/images/%s' % image_id, {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(image_id, image.id)
|
||||||
|
|
||||||
def test_location_ops_when_server_disabled_location_ops(self):
|
def test_location_ops_when_server_disabled_location_ops(self):
|
||||||
# Location operations should not be allowed if server has not
|
# Location operations should not be allowed if server has not
|
||||||
# enabled location related operations
|
# enabled location related operations
|
||||||
|
@@ -50,6 +50,7 @@ _SCHEMA = schemas.Schema({
|
|||||||
'properties': {
|
'properties': {
|
||||||
'name': {'type': 'string'},
|
'name': {'type': 'string'},
|
||||||
'color': {'type': 'string'},
|
'color': {'type': 'string'},
|
||||||
|
'shape': {'type': 'string', 'is_base': False},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -170,3 +171,28 @@ class TestSchemaBasedModel(testtools.TestCase):
|
|||||||
expected = '[{"path": "/color", "op": "remove"}]'
|
expected = '[{"path": "/color", "op": "remove"}]'
|
||||||
self.assertTrue(compare_json_patches(patch, expected))
|
self.assertTrue(compare_json_patches(patch, expected))
|
||||||
self.assertEqual(expected, patch)
|
self.assertEqual(expected, patch)
|
||||||
|
|
||||||
|
def test_patch_should_add_missing_custom_properties(self):
|
||||||
|
obj = {
|
||||||
|
'name': 'fred'
|
||||||
|
}
|
||||||
|
|
||||||
|
original = self.model(obj)
|
||||||
|
original['shape'] = 'circle'
|
||||||
|
|
||||||
|
patch = original.patch
|
||||||
|
expected = '[{"path": "/shape", "value": "circle", "op": "add"}]'
|
||||||
|
self.assertTrue(compare_json_patches(patch, expected))
|
||||||
|
|
||||||
|
def test_patch_should_replace_custom_properties(self):
|
||||||
|
obj = {
|
||||||
|
'name': 'fred',
|
||||||
|
'shape': 'circle'
|
||||||
|
}
|
||||||
|
|
||||||
|
original = self.model(obj)
|
||||||
|
original['shape'] = 'square'
|
||||||
|
|
||||||
|
patch = original.patch
|
||||||
|
expected = '[{"path": "/shape", "value": "square", "op": "replace"}]'
|
||||||
|
self.assertTrue(compare_json_patches(patch, expected))
|
||||||
|
Reference in New Issue
Block a user