Skip to content

Commit 49dca0b

Browse files
authored
adjusted summary report (#553)
1 parent c6ab432 commit 49dca0b

File tree

10 files changed

+117
-36
lines changed

10 files changed

+117
-36
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ repos:
1111
- id: isort
1212
# language_version: python3.6
1313
- repo: https://github.com/ambv/black
14-
rev: stable
14+
rev: 20.8b1
1515
hooks:
1616
- id: black
1717
- repo: https://github.com/pre-commit/pre-commit-hooks

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ To run the contract tests, use the `test` command.
4040
```bash
4141
cfn test
4242
cfn test -- -k contract_delete_update #to run a single test
43+
cfn test --tb=long # exhaustive, informative traceback formatting
4344
cfn test --enforce-timeout 60 #set the RL handler timeout to 60 seconds and CUD handler timeout to 120 seconds.
4445
cfn test --enforce-timeout 60 -- -k contract_delete_update # combine args
4546
```

src/rpdk/core/contract/resource_client.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ def assert_write_only_property_does_not_exist(self, resource_model):
204204
assert not any(
205205
self.key_error_safe_traverse(resource_model, write_only_property)
206206
for write_only_property in self.write_only_paths
207-
)
207+
), "The model MUST NOT return properties defined as \
208+
writeOnlyProperties in the resource schema"
208209

209210
@property
210211
def strategy(self):
@@ -378,22 +379,37 @@ def assert_time(self, start_time, end_time, action):
378379

379380
@staticmethod
380381
def assert_primary_identifier(primary_identifier_paths, resource_model):
381-
assert all(
382-
traverse(resource_model, fragment_list(primary_identifier, "properties"))[0]
383-
for primary_identifier in primary_identifier_paths
384-
)
382+
try:
383+
assert all(
384+
traverse(
385+
resource_model, fragment_list(primary_identifier, "properties")
386+
)[0]
387+
for primary_identifier in primary_identifier_paths
388+
), "Every returned model MUST include the primaryIdentifier"
389+
except KeyError as e:
390+
raise AssertionError(
391+
"Every returned model MUST include the primaryIdentifier"
392+
) from e
385393

386394
@staticmethod
387395
def is_primary_identifier_equal(
388396
primary_identifier_path, created_model, updated_model
389397
):
390-
return all(
391-
traverse(created_model, fragment_list(primary_identifier, "properties"))[0]
392-
== traverse(updated_model, fragment_list(primary_identifier, "properties"))[
393-
0
394-
]
395-
for primary_identifier in primary_identifier_path
396-
)
398+
try:
399+
return all(
400+
traverse(
401+
created_model, fragment_list(primary_identifier, "properties")
402+
)[0]
403+
== traverse(
404+
updated_model, fragment_list(primary_identifier, "properties")
405+
)[0]
406+
for primary_identifier in primary_identifier_path
407+
)
408+
except KeyError as e:
409+
raise AssertionError(
410+
"The primaryIdentifier returned in every progress event must\
411+
match the primaryIdentifier passed into the request"
412+
) from e
397413

398414
def _make_payload(self, action, current_model, previous_model=None, **kwargs):
399415
return self.make_request(

src/rpdk/core/contract/suite/contract_asserts.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import logging
22
from functools import wraps
33
from inspect import Parameter, signature
4-
from unittest.test.testmock.support import is_instance
54

65
import pytest
76

@@ -85,7 +84,11 @@ def response_does_not_contain_write_only_properties(resource_client, response):
8584
def response_contains_resource_model_equal_current_model(
8685
response, current_resource_model
8786
):
88-
assert response["resourceModel"] == current_resource_model
87+
assert (
88+
response["resourceModel"] == current_resource_model
89+
), "All properties specified in the request MUST be present in the model \
90+
returned, and they MUST match exactly, with the exception of properties\
91+
defined as writeOnlyProperties in the resource schema"
8992

9093

9194
@decorate()
@@ -95,7 +98,9 @@ def response_contains_resource_model_equal_updated_model(
9598
assert response["resourceModel"] == {
9699
**current_resource_model,
97100
**update_resource_model,
98-
}
101+
}, "All properties specified in the update request MUST be present in the \
102+
model returned, and they MUST match exactly, with the exception of \
103+
properties defined as writeOnlyProperties in the resource schema"
99104

100105

101106
@decorate()
@@ -109,11 +114,12 @@ def response_contains_primary_identifier(resource_client, response):
109114
def response_contains_unchanged_primary_identifier(
110115
resource_client, response, current_resource_model
111116
):
112-
resource_client.is_primary_identifier_equal(
117+
assert resource_client.is_primary_identifier_equal(
113118
resource_client.primary_identifier_paths,
114119
current_resource_model,
115120
response["resourceModel"],
116-
)
121+
), "PrimaryIdentifier returned in every progress event must match \
122+
the primaryIdentifier passed into the request"
117123

118124

119125
@decorate(after=False)
@@ -128,7 +134,7 @@ def decorator_wrapper(func: object):
128134
def wrapper(*args, **kwargs):
129135
response_error = func(*args, **kwargs)
130136
if response_error is not None:
131-
if is_instance(error_code, HandlerErrorCode):
137+
if isinstance(error_code, HandlerErrorCode):
132138
error_code_tuple = (error_code,)
133139
assert response_error in error_code_tuple, msg
134140
return response_error

src/rpdk/core/contract/suite/handler_commons.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ def test_create_success(resource_client, current_resource_model):
2929

3030
@failed_event(
3131
error_code=HandlerErrorCode.AlreadyExists,
32-
msg="creating the same resource should not be possible",
32+
msg="A create handler MUST NOT create multiple resources given\
33+
the same idempotency token",
3334
)
3435
def test_create_failure_if_repeat_writeable_id(resource_client, current_resource_model):
3536
LOG.debug(
@@ -59,8 +60,15 @@ def test_read_success(resource_client, current_resource_model):
5960
return response
6061

6162

62-
@failed_event(error_code=HandlerErrorCode.NotFound)
63-
def test_read_failure_not_found(resource_client, current_resource_model):
63+
@failed_event(
64+
error_code=HandlerErrorCode.NotFound,
65+
msg="A read handler MUST return FAILED with a NotFound error code\
66+
if the resource does not exist",
67+
)
68+
def test_read_failure_not_found(
69+
resource_client,
70+
current_resource_model,
71+
):
6472
primay_identifier_only_model = create_model_with_properties_in_path(
6573
current_resource_model,
6674
resource_client.primary_identifier_paths,
@@ -115,7 +123,11 @@ def test_update_success(resource_client, update_resource_model, current_resource
115123
return response
116124

117125

118-
@failed_event(error_code=HandlerErrorCode.NotFound)
126+
@failed_event(
127+
error_code=HandlerErrorCode.NotFound,
128+
msg="An update handler MUST return FAILED with a NotFound error code\
129+
if the resource did not exist prior to the update request",
130+
)
119131
def test_update_failure_not_found(resource_client, current_resource_model):
120132
update_model = resource_client.generate_update_example(current_resource_model)
121133
_status, _response, error_code = resource_client.call_and_assert(
@@ -135,7 +147,11 @@ def test_delete_success(resource_client, current_resource_model):
135147
return response
136148

137149

138-
@failed_event(error_code=HandlerErrorCode.NotFound)
150+
@failed_event(
151+
error_code=HandlerErrorCode.NotFound,
152+
msg="A delete hander MUST return FAILED with a NotFound error code\
153+
if the resource did not exist prior to the delete request",
154+
)
139155
def test_delete_failure_not_found(resource_client, current_resource_model):
140156
primay_identifier_only_model = create_model_with_properties_in_path(
141157
current_resource_model,

src/rpdk/core/contract/suite/handler_delete.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,20 @@ def deleted_resource(resource_client):
4242
_status, response, _error = resource_client.call_and_assert(
4343
Action.DELETE, OperationStatus.SUCCESS, primay_identifier_only_model
4444
)
45-
assert "resourceModel" not in response
45+
assert (
46+
"resourceModel" not in response
47+
), "The deletion handler's response object MUST NOT contain a model"
4648
yield model, request
4749
finally:
4850
status, response = resource_client.call(Action.DELETE, pruned_model)
4951

5052
# a failed status is allowed if the error code is NotFound
5153
if status == OperationStatus.FAILED:
5254
error_code = resource_client.assert_failed(status, response)
53-
assert error_code == HandlerErrorCode.NotFound
55+
assert (
56+
error_code == HandlerErrorCode.NotFound
57+
), "A delete hander MUST return FAILED with a NotFound error code\
58+
if the resource did not exist prior to the delete request"
5459
else:
5560
resource_client.assert_success(status, response)
5661

@@ -70,7 +75,10 @@ def contract_delete_list(resource_client, deleted_resource):
7075
# remove the model from the list, however.
7176

7277
deleted_model, _request = deleted_resource
73-
assert not test_model_in_list(resource_client, deleted_model)
78+
assert not test_model_in_list(
79+
resource_client, deleted_model
80+
), "A list operation MUST NOT return the primaryIdentifier \
81+
of any deleted resource instance"
7482

7583

7684
@pytest.mark.delete
@@ -99,7 +107,11 @@ def contract_delete_create(resource_client, deleted_resource):
99107
response["resourceModel"], resource_client.read_only_paths
100108
)
101109

102-
assert deleted_model == response["resourceModel"]
110+
assert (
111+
deleted_model == response["resourceModel"]
112+
), "Once a delete operation successfully completes, a subsequent\
113+
create request with the same primaryIdentifier or additionalIdentifiers\
114+
MUST NOT return FAILED with an AlreadyExists error code"
103115
resource_client.call_and_assert(
104116
Action.DELETE, OperationStatus.SUCCESS, created_response["resourceModel"]
105117
)

src/rpdk/core/contract/suite/handler_update.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ def contract_update_read_success(updated_resource, resource_client):
5151
_create_request, _created_model, _update_request, updated_model = updated_resource
5252
assert resource_client.is_primary_identifier_equal(
5353
resource_client.primary_identifier_paths, _created_model, updated_model
54-
)
54+
), "The primaryIdentifier returned must match\
55+
the primaryIdentifier passed into the request"
5556
test_read_success(resource_client, updated_model)
5657

5758

@@ -63,5 +64,8 @@ def contract_update_list_success(updated_resource, resource_client):
6364
_create_request, _created_model, _update_request, updated_model = updated_resource
6465
assert resource_client.is_primary_identifier_equal(
6566
resource_client.primary_identifier_paths, _created_model, updated_model
66-
)
67-
assert test_model_in_list(resource_client, updated_model)
67+
), "The primaryIdentifier returned must match\
68+
the primaryIdentifier passed into the request"
69+
assert test_model_in_list(
70+
resource_client, updated_model
71+
), "A list handler MUST always return an updated model"

src/rpdk/core/contract/suite/handler_update_invalid.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@
1111

1212

1313
@pytest.mark.update
14-
@failed_event(error_code=(HandlerErrorCode.NotUpdatable, HandlerErrorCode.NotFound))
14+
@failed_event(
15+
error_code=(HandlerErrorCode.NotUpdatable, HandlerErrorCode.NotFound),
16+
msg="An update hander MUST return FAILED with a NotUpdatable error code \
17+
if the difference between the previous state and the properties included \
18+
in the request contains a property defined in the createOnlyProperties \
19+
in the resource schema",
20+
)
1521
def contract_update_create_only_property(resource_client):
1622

1723
if resource_client.create_only_paths:
@@ -27,7 +33,10 @@ def contract_update_create_only_property(resource_client):
2733
_status, response, _error_code = resource_client.call_and_assert(
2834
Action.UPDATE, OperationStatus.FAILED, update_request, created_model
2935
)
30-
assert response["message"]
36+
assert response[
37+
"message"
38+
], "The progress event MUST return an error message\
39+
when the status is failed"
3140
finally:
3241
primay_identifier_only_model = create_model_with_properties_in_path(
3342
created_model,
@@ -43,13 +52,17 @@ def contract_update_create_only_property(resource_client):
4352
@pytest.mark.update
4453
@failed_event(
4554
error_code=HandlerErrorCode.NotFound,
46-
msg="cannot update a resource which does not exist",
55+
msg="An update handler MUST return FAILED with a NotFound error code\
56+
if the resource did not exist prior to the update request",
4757
)
4858
def contract_update_non_existent_resource(resource_client):
4959
create_request = resource_client.generate_invalid_create_example()
5060
update_request = resource_client.generate_update_example(create_request)
5161
_status, response, _error = resource_client.call_and_assert(
5262
Action.UPDATE, OperationStatus.FAILED, update_request, create_request
5363
)
54-
assert response["message"]
64+
assert response[
65+
"message"
66+
], "The progress event MUST return an error message\
67+
when the status is failed"
5568
return _error

src/rpdk/core/data/pytest-contract.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
[pytest]
2+
log_cli = True
3+
log_cli_level = INFO
24
addopts =
35
--pyargs "rpdk.core.contract.suite"
46
--verbose
57
--show-capture no
8+
--tb=short
69
python_files = handler_*.py
710
python_functions = contract_*
811
markers =

tests/contract/test_resource_client.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,7 @@ def test_assert_primary_identifier_success(resource_client):
804804

805805

806806
def test_assert_primary_identifier_fail(resource_client):
807-
with pytest.raises(KeyError):
807+
with pytest.raises(AssertionError):
808808
resource_client._update_schema(SCHEMA)
809809
resource_client.assert_primary_identifier(
810810
resource_client.primary_identifier_paths, {"a": 1, "b": 2}
@@ -829,6 +829,16 @@ def test_is_primary_identifier_equal_fail(resource_client):
829829
)
830830

831831

832+
def test_is_primary_identifier_equal_fail_key(resource_client):
833+
with pytest.raises(AssertionError):
834+
resource_client._update_schema(SCHEMA)
835+
resource_client.is_primary_identifier_equal(
836+
resource_client.primary_identifier_paths,
837+
{"a": 1, "b": 2},
838+
{"a": 1, "b": 2},
839+
)
840+
841+
832842
def test_assert_write_only_property_does_not_exist(resource_client):
833843
schema = {
834844
"a": {"type": "number", "const": 1},

0 commit comments

Comments
 (0)