Merge lp:~allenap/maas/has-status-code-matcher into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 5311
Proposed branch: lp:~allenap/maas/has-status-code-matcher
Merge into: lp:~maas-committers/maas/trunk
Prerequisite: lp:~allenap/maas/make-gecos-field
Diff against target: 382 lines (+81/-47)
6 files modified
src/maasserver/api/tests/test_api.py (+3/-2)
src/maasserver/api/tests/test_doc.py (+2/-1)
src/maasserver/api/tests/test_machines.py (+24/-23)
src/maasserver/api/tests/test_node.py (+3/-1)
src/maasserver/testing/api.py (+0/-20)
src/maasserver/testing/matchers.py (+49/-0)
To merge this branch: bzr merge lp:~allenap/maas/has-status-code-matcher
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+304503@code.launchpad.net

Commit message

New matcher HasStatusCode.

This matches a Django HttpResponse's status code, and provides a full dump of the response on mismatch.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Nice!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/tests/test_api.py'
2--- src/maasserver/api/tests/test_api.py 2016-08-09 21:59:28 +0000
3+++ src/maasserver/api/tests/test_api.py 2016-09-01 08:46:07 +0000
4@@ -32,6 +32,7 @@
5 APITransactionTestCase,
6 )
7 from maasserver.testing.factory import factory
8+from maasserver.testing.matchers import HasStatusCode
9 from maasserver.testing.testcase import MAASServerTestCase
10 from maasserver.testing.testclient import MAASSensibleOAuthClient
11 from maasserver.utils.converters import json_load_bytes
12@@ -325,14 +326,14 @@
13
14 def test_get_api_version(self):
15 response = self.client.get(reverse('api_version'))
16- self.assertResponseCode(http.client.OK, response)
17+ self.assertThat(response, HasStatusCode(http.client.OK))
18 self.assertIn('text/plain', response['Content-Type'])
19 self.assertEqual(b'2.0', response.content)
20
21 def test_old_api_request(self):
22 old_api_url = reverse('api_v1_error') + "maas/" + factory.make_string()
23 response = self.client.get(old_api_url)
24- self.assertResponseCode(http.client.GONE, response)
25+ self.assertThat(response, HasStatusCode(http.client.GONE))
26 self.assertIn('text/plain', response['Content-Type'])
27 self.assertEqual(
28 b'The 1.0 API is no longer available. Please use API version 2.0.',
29
30=== modified file 'src/maasserver/api/tests/test_doc.py'
31--- src/maasserver/api/tests/test_doc.py 2016-05-24 14:43:27 +0000
32+++ src/maasserver/api/tests/test_doc.py 2016-09-01 08:46:07 +0000
33@@ -40,6 +40,7 @@
34 from maasserver.testing.api import APITestCase
35 from maasserver.testing.config import RegionConfigurationFixture
36 from maasserver.testing.factory import factory
37+from maasserver.testing.matchers import HasStatusCode
38 from maastesting.matchers import (
39 IsCallable,
40 MockCalledOnceWith,
41@@ -138,7 +139,7 @@
42 def test_api_doc_accessibility(self):
43 self.patch(sys, "stderr", StringIO())
44 response = self.client.get(reverse('api-doc'))
45- self.assertResponseCode(http.client.OK, response)
46+ self.assertThat(response, HasStatusCode(http.client.OK))
47 # No error or warning are emitted by docutils.
48 self.assertEqual("", sys.stderr.getvalue())
49
50
51=== modified file 'src/maasserver/api/tests/test_machines.py'
52--- src/maasserver/api/tests/test_machines.py 2016-08-16 10:36:47 +0000
53+++ src/maasserver/api/tests/test_machines.py 2016-09-01 08:46:07 +0000
54@@ -44,6 +44,7 @@
55 RunningEventLoopFixture,
56 )
57 from maasserver.testing.factory import factory
58+from maasserver.testing.matchers import HasStatusCode
59 from maasserver.testing.testclient import MAASSensibleOAuthClient
60 from maasserver.utils import ignore_unused
61 from maasserver.utils.orm import reload_object
62@@ -711,7 +712,7 @@
63 'op': 'allocate',
64 'cpu_count': 2,
65 })
66- self.assertResponseCode(http.client.OK, response)
67+ self.assertThat(response, HasStatusCode(http.client.OK))
68 response_json = json.loads(
69 response.content.decode(settings.DEFAULT_CHARSET))
70 self.assertEqual(machine.system_id, response_json['system_id'])
71@@ -724,7 +725,7 @@
72 'op': 'allocate',
73 'cpu_count': '1.0',
74 })
75- self.assertResponseCode(http.client.OK, response)
76+ self.assertThat(response, HasStatusCode(http.client.OK))
77 response_json = json.loads(
78 response.content.decode(settings.DEFAULT_CHARSET))
79 self.assertEqual(machine.system_id, response_json['system_id'])
80@@ -737,7 +738,7 @@
81 'op': 'allocate',
82 'cpu_count': 'plenty',
83 })
84- self.assertResponseCode(http.client.BAD_REQUEST, response)
85+ self.assertThat(response, HasStatusCode(http.client.BAD_REQUEST))
86
87 def test_POST_allocate_allocates_machine_by_mem(self):
88 # Asking for enough memory acquires a machine with at least that.
89@@ -747,7 +748,7 @@
90 'op': 'allocate',
91 'mem': 1024,
92 })
93- self.assertResponseCode(http.client.OK, response)
94+ self.assertThat(response, HasStatusCode(http.client.OK))
95 response_json = json.loads(
96 response.content.decode(settings.DEFAULT_CHARSET))
97 self.assertEqual(machine.system_id, response_json['system_id'])
98@@ -760,7 +761,7 @@
99 'op': 'allocate',
100 'mem': 'bags',
101 })
102- self.assertResponseCode(http.client.BAD_REQUEST, response)
103+ self.assertThat(response, HasStatusCode(http.client.BAD_REQUEST))
104
105 def test_POST_allocate_allocates_machine_by_tags(self):
106 machine = factory.make_Node(
107@@ -772,7 +773,7 @@
108 'op': 'allocate',
109 'tags': ['fast', 'stable'],
110 })
111- self.assertResponseCode(http.client.OK, response)
112+ self.assertThat(response, HasStatusCode(http.client.OK))
113 response_json = json.loads(
114 response.content.decode(settings.DEFAULT_CHARSET))
115 self.assertItemsEqual(machine_tag_names, response_json['tag_names'])
116@@ -791,7 +792,7 @@
117 'op': 'allocate',
118 'not_tags': ['cute']
119 })
120- self.assertResponseCode(http.client.OK, response)
121+ self.assertThat(response, HasStatusCode(http.client.OK))
122 response_json = json.loads(
123 response.content.decode(settings.DEFAULT_CHARSET))
124 self.assertEqual(
125@@ -810,7 +811,7 @@
126 'op': 'allocate',
127 'zone': zone.name,
128 })
129- self.assertResponseCode(http.client.OK, response)
130+ self.assertThat(response, HasStatusCode(http.client.OK))
131 response_json = json.loads(
132 response.content.decode(settings.DEFAULT_CHARSET))
133 self.assertEqual(machine.system_id, response_json['system_id'])
134@@ -823,7 +824,7 @@
135 'op': 'allocate',
136 'zone': zone.name,
137 })
138- self.assertResponseCode(http.client.CONFLICT, response)
139+ self.assertThat(response, HasStatusCode(http.client.CONFLICT))
140
141 def test_POST_allocate_rejects_unknown_zone(self):
142 response = self.client.post(reverse('machines_handler'), {
143@@ -842,7 +843,7 @@
144 'op': 'allocate',
145 'tags': 'fast, stable',
146 })
147- self.assertResponseCode(http.client.OK, response)
148+ self.assertThat(response, HasStatusCode(http.client.OK))
149 response_json = json.loads(
150 response.content.decode(settings.DEFAULT_CHARSET))
151 self.assertItemsEqual(machine_tag_names, response_json['tag_names'])
152@@ -857,7 +858,7 @@
153 'op': 'allocate',
154 'tags': 'fast stable',
155 })
156- self.assertResponseCode(http.client.OK, response)
157+ self.assertThat(response, HasStatusCode(http.client.OK))
158 response_json = json.loads(
159 response.content.decode(settings.DEFAULT_CHARSET))
160 self.assertItemsEqual(machine_tag_names, response_json['tag_names'])
161@@ -872,7 +873,7 @@
162 'op': 'allocate',
163 'tags': 'fast, stable cute',
164 })
165- self.assertResponseCode(http.client.OK, response)
166+ self.assertThat(response, HasStatusCode(http.client.OK))
167 response_json = json.loads(
168 response.content.decode(settings.DEFAULT_CHARSET))
169 self.assertItemsEqual(machine_tag_names, response_json['tag_names'])
170@@ -887,7 +888,7 @@
171 'op': 'allocate',
172 'tags': ['fast, stable', 'cute'],
173 })
174- self.assertResponseCode(http.client.OK, response)
175+ self.assertThat(response, HasStatusCode(http.client.OK))
176 response_json = json.loads(
177 response.content.decode(settings.DEFAULT_CHARSET))
178 self.assertItemsEqual(machine_tag_names, response_json['tag_names'])
179@@ -905,7 +906,7 @@
180 'op': 'allocate',
181 'storage': 'needed:10(ssd)',
182 })
183- self.assertResponseCode(http.client.OK, response)
184+ self.assertThat(response, HasStatusCode(http.client.OK))
185 response_json = json.loads(
186 response.content.decode(settings.DEFAULT_CHARSET))
187 device_id = response_json['physicalblockdevice_set'][0]['id']
188@@ -929,7 +930,7 @@
189 'storage': 'needed:10(ssd)',
190 'verbose': 'true',
191 })
192- self.assertResponseCode(http.client.OK, response)
193+ self.assertThat(response, HasStatusCode(http.client.OK))
194 response_json = json.loads(
195 response.content.decode(settings.DEFAULT_CHARSET))
196 device_id = response_json['physicalblockdevice_set'][0]['id']
197@@ -953,7 +954,7 @@
198 'op': 'allocate',
199 'interfaces': 'needed:fabric=ubuntu',
200 })
201- self.assertResponseCode(http.client.OK, response)
202+ self.assertThat(response, HasStatusCode(http.client.OK))
203 response_json = json.loads(
204 response.content.decode(settings.DEFAULT_CHARSET))
205 self.expectThat(
206@@ -981,7 +982,7 @@
207 'verbose': 'true',
208 'dry_run': 'true',
209 })
210- self.assertResponseCode(http.client.OK, response)
211+ self.assertThat(response, HasStatusCode(http.client.OK))
212 response_json = json.loads(
213 response.content.decode(settings.DEFAULT_CHARSET))
214 self.expectThat(
215@@ -1015,7 +1016,7 @@
216 'interfaces': 'needed:fabric=ubuntu',
217 'verbose': 'true',
218 })
219- self.assertResponseCode(http.client.OK, response)
220+ self.assertThat(response, HasStatusCode(http.client.OK))
221 response_json = json.loads(
222 response.content.decode(settings.DEFAULT_CHARSET))
223 constraints = response_json['constraints_by_type']
224@@ -1044,7 +1045,7 @@
225 'op': 'allocate',
226 'tags': 'fast, cheap',
227 })
228- self.assertResponseCode(http.client.CONFLICT, response)
229+ self.assertThat(response, HasStatusCode(http.client.CONFLICT))
230
231 def test_POST_allocate_fails_with_unknown_tags(self):
232 # Asking for a tag that does not exist gives a specific error.
233@@ -1082,7 +1083,7 @@
234 'subnets': [subnets[pick].name],
235 })
236
237- self.assertResponseCode(http.client.OK, response)
238+ self.assertThat(response, HasStatusCode(http.client.OK))
239 response_json = json.loads(
240 response.content.decode(settings.DEFAULT_CHARSET))
241 self.assertEqual(
242@@ -1104,7 +1105,7 @@
243 'not_subnets': [subnet.name for subnet in subnets],
244 })
245
246- self.assertResponseCode(http.client.OK, response)
247+ self.assertThat(response, HasStatusCode(http.client.OK))
248 response_json = json.loads(
249 response.content.decode(settings.DEFAULT_CHARSET))
250 self.assertEqual(right_machine.system_id, response_json['system_id'])
251@@ -1143,7 +1144,7 @@
252 status=available_status, owner=None, with_boot_disk=True)
253 response = self.client.post(
254 reverse('machines_handler'), {'op': 'allocate'})
255- self.assertResponseCode(http.client.OK, response)
256+ self.assertThat(response, HasStatusCode(http.client.OK))
257 machine = Machine.objects.get(system_id=machine.system_id)
258 oauth_key = self.client.token.key
259 self.assertEqual(oauth_key, machine.token.key)
260@@ -2040,7 +2041,7 @@
261 response = self.client.get(
262 self.get_machine_uri(machine), {"op": "query_power_state"})
263
264- self.assertResponseCode(http.client.OK, response)
265+ self.assertThat(response, HasStatusCode(http.client.OK))
266 response = json.loads(
267 response.content.decode(settings.DEFAULT_CHARSET))
268 self.assertEqual({"state": random_state}, response)
269
270=== modified file 'src/maasserver/api/tests/test_node.py'
271--- src/maasserver/api/tests/test_node.py 2016-06-17 07:16:39 +0000
272+++ src/maasserver/api/tests/test_node.py 2016-09-01 08:46:07 +0000
273@@ -25,6 +25,7 @@
274 from maasserver.testing.api import APITestCase
275 from maasserver.testing.architecture import make_usable_architecture
276 from maasserver.testing.factory import factory
277+from maasserver.testing.matchers import HasStatusCode
278 from maasserver.testing.osystems import make_usable_osystem
279 from maasserver.testing.testcase import MAASServerTestCase
280 from maasserver.testing.testclient import MAASSensibleOAuthClient
281@@ -473,7 +474,8 @@
282 'stop').side_effect = PowerActionAlreadyInProgress(exc_text)
283 response = self.client.post(
284 self.get_node_uri(machine), {'op': 'power_off'})
285- self.assertResponseCode(http.client.SERVICE_UNAVAILABLE, response)
286+ self.assertThat(response, HasStatusCode(
287+ http.client.SERVICE_UNAVAILABLE))
288 self.assertIn(
289 exc_text, response.content.decode(settings.DEFAULT_CHARSET))
290
291
292=== modified file 'src/maasserver/testing/api.py'
293--- src/maasserver/testing/api.py 2016-05-24 22:05:45 +0000
294+++ src/maasserver/testing/api.py 2016-09-01 08:46:07 +0000
295@@ -30,10 +30,6 @@
296 MAASTestType,
297 )
298 from testscenarios import multiply_scenarios
299-from testtools.content import (
300- Content,
301- UTF8_TEXT,
302-)
303
304
305 def merge_scenarios(*scenario_lists):
306@@ -181,22 +177,6 @@
307 self.user.is_superuser = True
308 self.user.save()
309
310- def assertResponseCode(self, expected_code, response):
311- """Assert that `response` has the HTTP status `expected_code`.
312-
313- :param expected_code: An integer HTTP status code.
314- :param response: An instance of Django's `HttpResponse`.
315- """
316- if response.status_code != expected_code:
317- response_dump = response.serialize()
318- if response.charset.lower() not in {"utf-8", "utf_8", "utf8"}:
319- response_dump = response_dump.decode(response.charset)
320- response_dump = response_dump.encode("utf-8", "replace")
321- response_content = Content(UTF8_TEXT, lambda: [response_dump])
322- self.addDetail("Unexpected HTTP response", response_content)
323- self.fail("Expected HTTP %s, got %s" % (
324- expected_code, response.status_code))
325-
326 def client_log_in(self, as_admin=False):
327 self.fail(
328 "client_log_in is broken. Use become_admin() "
329
330=== added file 'src/maasserver/testing/matchers.py'
331--- src/maasserver/testing/matchers.py 1970-01-01 00:00:00 +0000
332+++ src/maasserver/testing/matchers.py 2016-09-01 08:46:07 +0000
333@@ -0,0 +1,49 @@
334+# Copyright 2016 Canonical Ltd. This software is licensed under the
335+# GNU Affero General Public License version 3 (see the file LICENSE).
336+
337+"""Custom matchers for testing in the region."""
338+
339+__all__ = [
340+ 'HasStatusCode',
341+]
342+
343+from testtools.content import (
344+ Content,
345+ UTF8_TEXT,
346+)
347+from testtools.matchers import (
348+ Matcher,
349+ Mismatch,
350+)
351+
352+
353+class HasStatusCode(Matcher):
354+ """Match if the given response has the expected HTTP status.
355+
356+ In case of a mismatch this assumes that the response contains a textual
357+ body. If it's not already encoded as UTF-8, it is recoded, replacing any
358+ problematic characters in the process, like surrogate escapes.
359+
360+ The response against which this matches is expected to be an instance of
361+ Django's `HttpResponse` though this is not explicitly tested.
362+ """
363+
364+ def __init__(self, status_code):
365+ super(HasStatusCode, self).__init__()
366+ self.status_code = status_code
367+
368+ def match(self, response):
369+ if response.status_code != self.status_code:
370+ response_dump = response.serialize()
371+ if response.charset.lower() not in {"utf-8", "utf_8", "utf8"}:
372+ response_dump = response_dump.decode(response.charset)
373+ response_dump = response_dump.encode("utf-8", "replace")
374+
375+ description = "Expected HTTP %s, got %s" % (
376+ self.status_code, response.status_code)
377+ details = {
378+ "Unexpected HTTP response": Content(
379+ UTF8_TEXT, lambda: [response_dump]),
380+ }
381+
382+ return Mismatch(description, details)