Skip to content

Commit

Permalink
Add support for nested comparison (#741)
Browse files Browse the repository at this point in the history
* Add support for nested comparison

* Add a reasonable debug message
  • Loading branch information
anshikg authored Apr 29, 2021
1 parent 5435570 commit 90161ca
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 29 deletions.
23 changes: 23 additions & 0 deletions src/rpdk/core/contract/resource_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,29 @@ def generate_invalid_update_example(self, create_model):
example = override_properties(self.invalid_strategy.example(), overrides)
return {**create_model, **example}

def compare(self, inputs, outputs):
assertion_error_message = (
"All properties specified in the request MUST "
"be present in the model returned, and they MUST"
" match exactly, with the exception of properties"
" defined as writeOnlyProperties in the resource schema"
)
try:
for key in inputs:
if isinstance(inputs[key], dict):
self.compare(inputs[key], outputs[key])
elif isinstance(inputs[key], list):
assert len(inputs[key]) == len(outputs[key])
self.compare_list(inputs[key], outputs[key])
else:
assert inputs[key] == outputs[key], assertion_error_message
except KeyError as e:
raise AssertionError(assertion_error_message) from e

def compare_list(self, inputs, outputs):
for index in range(len(inputs)): # pylint: disable=C0200
self.compare(inputs[index], outputs[index])

@staticmethod
def key_error_safe_traverse(resource_model, write_only_property):
try:
Expand Down
33 changes: 4 additions & 29 deletions src/rpdk/core/contract/suite/handler_commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,32 +163,7 @@ def test_input_equals_output(resource_client, input_model, output_model):
pruned_output_model = prune_properties_if_not_exist_in_path(
pruned_output_model, pruned_input_model, resource_client.create_only_paths
)

assertion_error_message = (
"All properties specified in the request MUST "
"be present in the model returned, and they MUST"
" match exactly, with the exception of properties"
" defined as writeOnlyProperties in the resource schema"
)
# only comparing properties in input model to those in output model and
# ignoring extraneous properties that maybe present in output model.
try:
for key in pruned_input_model:
if key in resource_client.properties_without_insertion_order:
assert test_unordered_list_match(
pruned_input_model[key], pruned_output_model[key]
)
else:
assert (
pruned_input_model[key] == pruned_output_model[key]
), assertion_error_message
except KeyError as e:
raise AssertionError(assertion_error_message) from e


def test_unordered_list_match(inputs, outputs):
assert len(inputs) == len(outputs)
try:
assert all(input in outputs for input in inputs)
except KeyError as exception:
raise AssertionError("lists do not match") from exception
resource_client.compare(
pruned_input_model,
pruned_output_model,
)
75 changes: 75 additions & 0 deletions tests/contract/test_resource_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,32 @@
"writeOnlyProperties": ["/properties/d"],
}

SCHEMA_WITH_NESTED_PROPERTIES = {
"properties": {
"a": {"type": "string"},
"g": {"type": "number"},
"b": {"$ref": "#/definitions/c"},
"f": {
"type": "array",
"items": {"$ref": "#/definitions/c"},
},
"h": {
"type": "array",
"insertionOrder": "false",
"items": {"$ref": "#/definitions/c"},
},
},
"definitions": {
"c": {
"type": "object",
"properties": {"d": {"type": "integer"}, "e": {"type": "integer"}},
}
},
"readOnlyProperties": ["/properties/a"],
"primaryIdentifier": ["/properties/a"],
"writeOnlyProperties": ["/properties/g"],
}

SCHEMA_WITH_COMPOSITE_KEY = {
"properties": {
"a": {"type": "number"},
Expand Down Expand Up @@ -1223,3 +1249,52 @@ def test_generate_update_example_with_composite_key(
created_resource
)
assert updated_resource == {"a": 1, "c": 2, "d": 3}


def test_compare_should_pass(resource_client):
resource_client._update_schema(SCHEMA_WITH_NESTED_PROPERTIES)
inputs = {"b": {"d": 1}, "f": [{"d": 1}], "h": [{"d": 1}, {"d": 2}]}

outputs = {
"b": {"d": 1, "e": 3},
"f": [{"d": 1, "e": 2}],
"h": [{"d": 1, "e": 3}, {"d": 2}],
}
resource_client.compare(inputs, outputs)


def test_compare_should_throw_exception(resource_client):
resource_client._update_schema(SCHEMA_WITH_NESTED_PROPERTIES)
inputs = {"b": {"d": 1}, "f": [{"d": 1}], "h": [{"d": 1}], "z": 1}

outputs = {
"b": {"d": 1, "e": 2},
"f": [{"d": 1}],
"h": [{"d": 1}],
}
try:
resource_client.compare(inputs, outputs)
except AssertionError:
logging.debug("This test expects Assertion Exception to be thrown")


def test_compare_should_throw_key_error(resource_client):
resource_client._update_schema(SCHEMA_WITH_NESTED_PROPERTIES)
inputs = {"b": {"d": 1}, "f": [{"d": 1}], "h": [{"d": 1}]}

outputs = {"b": {"d": 1, "e": 2}, "f": [{"d": 1, "e": 2}, {"d": 2, "e": 3}]}
try:
resource_client.compare(inputs, outputs)
except AssertionError:
logging.debug("This test expects Assertion Exception to be thrown")


def test_compare_ordered_list_throws_assertion_exception(resource_client):
resource_client._update_schema(SCHEMA_WITH_NESTED_PROPERTIES)
inputs = {"b": {"d": 1}, "f": [{"d": 1}], "h": [{"d": 1}]}

outputs = {"b": {"d": 1, "e": 2}, "f": [{"e": 2}, {"d": 2, "e": 3}]}
try:
resource_client.compare(inputs, outputs)
except AssertionError:
logging.debug("This test expects Assertion Exception to be thrown")

0 comments on commit 90161ca

Please sign in to comment.