|
1 |
| -# python-common |
| 1 | +# Shipchain Common Python Library |
| 2 | + |
2 | 3 | A PyPI package containing shared code for ShipChain's Python/Django projects
|
| 4 | + |
| 5 | + |
| 6 | +## Pytest Fixtures |
| 7 | + |
| 8 | +When shipchain-common is installed, a pytest plugin named `json_asserter` is automatically registered. This plugin is |
| 9 | + designed for writing concise pytest cases that make json_asserter about responses from a Django Rest Framework API. Most |
| 10 | + of the functionality is tailored to the `application/vnd.api+json` response type, but should still be usable for |
| 11 | + plain `application/json` responses. |
| 12 | + |
| 13 | +### json_asserter Fixture |
| 14 | + |
| 15 | +The `json_asserter` fixture exposes several methods for testing specific HTTP Status codes as well as a class for |
| 16 | + building consistent entity references that must be found within the responses. |
| 17 | + |
| 18 | +#### Usage with application/vnd.api+json |
| 19 | + |
| 20 | +This is the default when utilizing the `json_asserter`. If the response does not conform to the |
| 21 | +[JSON Api standard](https://jsonapi.org/), the assertions will fail. |
| 22 | + |
| 23 | +##### Asserting Error Responses |
| 24 | + |
| 25 | +To assert that a given response must have an error status, there are several 400-level response methods. With the |
| 26 | + exception of the HTTP_400 method, each of these include the default error message for ease of use. |
| 27 | + |
| 28 | +The following will assert that the response status was 403 and that the default error message ("You do not have |
| 29 | + permission to perform this action") is present. |
| 30 | + |
| 31 | +```python |
| 32 | +response = api_client.get(self.detail_url) |
| 33 | +json_asserter.HTTP_403(response) |
| 34 | +``` |
| 35 | + |
| 36 | +If a different error message should exist, or when checking the error of a 400 response, the specific error may |
| 37 | + be provided as an argument. |
| 38 | + |
| 39 | +```python |
| 40 | +response = api_client.get(self.detail_url) |
| 41 | +json_asserter.HTTP_400(response, error='Specific error message that should be in the respose') |
| 42 | +``` |
| 43 | + |
| 44 | +##### Asserting Successful Responses |
| 45 | + |
| 46 | +To assert that a given response must have status 200, call the HTTP_200 method with only the Response object: |
| 47 | + |
| 48 | +```python |
| 49 | +response = api_client.get(self.detail_url) |
| 50 | +json_asserter.HTTP_200(response) |
| 51 | +``` |
| 52 | + |
| 53 | +While this is valid, it is **very strongly** recommended to include additional details about the data present in the |
| 54 | + response. There are two ways to provide the data; however only one way can be used at a time in a given invocation. |
| 55 | + |
| 56 | +###### Simple Usage |
| 57 | + |
| 58 | +For simple responses, the easiest way to specify required data in the responses is by directly specifying the |
| 59 | + Resource Type `resource`, the Resource Identifier `pk`, as well as any specific Attributes of the resource |
| 60 | + `attributes`. |
| 61 | + |
| 62 | +```python |
| 63 | +response = api_client.get(self.detail_url) |
| 64 | +json_asserter.HTTP_200(response, |
| 65 | + resource='User', |
| 66 | + pk='4b56399d-3155-4fe5-ba4a-9718289a78b7', |
| 67 | + attributes={'username': 'example_user'}) |
| 68 | +``` |
| 69 | + |
| 70 | +This will throw an assertion if the response is not for the resource type `User` with id |
| 71 | +`4b56399d-3155-4fe5-ba4a-9718289a78b7` and with _at least_ the attribute username `example_user`. If the response |
| 72 | + includes _additional_ attributes that are not listed in the call to the json_asserter method, they are ignored. The |
| 73 | + methods check partial objects and do not require that every attribute in the response must be defined in the |
| 74 | + assertion. |
| 75 | + |
| 76 | +It is also possible to assert only on the resource type and id without providing attributes. This is useful if you |
| 77 | + are testing a response that generates content for the fields that may not be known prior to obtaining the response. |
| 78 | + Additionally, providing only the attributes and not the type and id will check only that an object in the response |
| 79 | + has those attributes, regardless of resource type or id. |
| 80 | + |
| 81 | +###### Advanced Usage |
| 82 | + |
| 83 | +For responses where the associated Relationship and any extra Included resources are important, those can be included |
| 84 | + in the assertion. |
| 85 | + |
| 86 | +```python |
| 87 | +response = api_client.get(self.detail_url) |
| 88 | +json_asserter.HTTP_200(response, |
| 89 | + entity_refs=json_asserter.EntityRef( |
| 90 | + resource='User', |
| 91 | + pk='4b56399d-3155-4fe5-ba4a-9718289a78b7', |
| 92 | + attributes={'username': 'example_user'}, |
| 93 | + relationships={ |
| 94 | + 'manager': json_asserter.EntityRef( |
| 95 | + resource='User', |
| 96 | + pk='88e38305-9775-4b34-95d0-4e935bb7156c')}), |
| 97 | + included=json_asserter.EntityRef( |
| 98 | + resource='User', |
| 99 | + pk='88e38305-9775-4b34-95d0-4e935bb7156c', |
| 100 | + attributes={'username': 'manager_user'})) |
| 101 | +``` |
| 102 | + |
| 103 | +This requires the same original record in the response, but now also requires that there be _at least_ one relationship |
| 104 | + named `manager` with the associated User and that User must be present (with at least the one attribute) in the |
| 105 | + `included` property of the response. |
| 106 | + |
| 107 | +The above example utilizes the `EntityRef` exposed via the `json_asserter` fixture. This is a reference to a single |
| 108 | + entity defined by a combination of: ResourceType, ResourceID, Attributes, and Relationships. When providing the |
| 109 | + `entity_refs` argument to an assertion, you cannot provide any of the following arguments to the assertion directly: |
| 110 | + `resource`, `pk`, `attributes`, or `relationships`. |
| 111 | + |
| 112 | +When providing `included` json_asserter, you can provide either a single EntityRef or a list of EntityRef instances. If |
| 113 | + a list is provided, _all_ referenced entities must be present in the `included` property of the response. As they do |
| 114 | + for the simple usage above, The same assertion rules apply here regarding providing a combination of `resource`, |
| 115 | + `pk`, and `attributes`. |
| 116 | + |
| 117 | +The `entity_refs` parameter can be a list of EntityRef instances as well. However, this is only valid for List |
| 118 | + responses. If a list of entity_refs is provided for a non-list response, an assertion will occur. To assert that a |
| 119 | + response is a list, the parameter `is_list=True` must be provided. You can provide either a single EntityRef or a |
| 120 | + list of EntityRef instances. If a list is provided, _all_ referenced entities must be present in the list of |
| 121 | + returned data. |
| 122 | + |
| 123 | +#### Usage with application/json |
| 124 | + |
| 125 | +Support is included for making assertions on plain JSON responses with `json_asserter`. To ignore the JSON API specific |
| 126 | + assertions, you must provide the `vnd=False` parameter. Only the `attributes` parameter is valid as there are no |
| 127 | + relationships or included properties in a plain json response. |
| 128 | + |
| 129 | +Given this response: |
| 130 | + |
| 131 | +```json |
| 132 | +{ |
| 133 | + "id": "07b374c3-ed9b-4811-901a-d0c5d746f16a", |
| 134 | + "name": "example 1", |
| 135 | + "field_1": 1, |
| 136 | + "owner": { |
| 137 | + "username": "user1" |
| 138 | + } |
| 139 | +} |
| 140 | +``` |
| 141 | + |
| 142 | +Asserting the top level attributes as well as nested attributes is possible using the following call: |
| 143 | + |
| 144 | +```python |
| 145 | +response = api_client.get(self.detail_url) |
| 146 | +json_asserter.HTTP_200(response, |
| 147 | + vnd=False, |
| 148 | + attributes={ |
| 149 | + 'id': '07b374c3-ed9b-4811-901a-d0c5d746f16a', |
| 150 | + 'owner': { |
| 151 | + 'username': 'user1' |
| 152 | + } |
| 153 | + }) |
| 154 | +``` |
| 155 | + |
| 156 | +For a list response: |
| 157 | + |
| 158 | +```json |
| 159 | +[{ |
| 160 | + "username": "user1", |
| 161 | + "is_active": False |
| 162 | +}, |
| 163 | +{ |
| 164 | + "username": "user2", |
| 165 | + "is_active": False |
| 166 | +}, |
| 167 | +{ |
| 168 | + "username": "user3", |
| 169 | + "is_active": False |
| 170 | +}] |
| 171 | +``` |
| 172 | + |
| 173 | +It is possible to assert that one or many sets of attributes exist in the response: |
| 174 | +```python |
| 175 | +response = api_client.get(self.detail_url) |
| 176 | +json_asserter.HTTP_200(response, |
| 177 | + vnd=False, |
| 178 | + is_list=True, |
| 179 | + attributes=[{ |
| 180 | + "username": "user1", |
| 181 | + "is_active": False |
| 182 | + }, { |
| 183 | + "username": "user3", |
| 184 | + "is_active": False |
| 185 | + }]) |
| 186 | +``` |
0 commit comments