Merge lp:~gmb/maas/fix-bug-1394746 into lp:~maas-committers/maas/trunk

Proposed by Graham Binns
Status: Merged
Merged at revision: 3392
Proposed branch: lp:~gmb/maas/fix-bug-1394746
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 713 lines (+138/-61)
16 files modified
src/maasserver/api/boot_resources.py (+2/-2)
src/maasserver/api/boot_source_selections.py (+3/-3)
src/maasserver/api/boot_sources.py (+3/-3)
src/maasserver/api/license_keys.py (+3/-3)
src/maasserver/api/maas.py (+2/-2)
src/maasserver/api/networks.py (+6/-6)
src/maasserver/api/node_group_interfaces.py (+3/-3)
src/maasserver/api/node_groups.py (+7/-7)
src/maasserver/api/nodes.py (+11/-13)
src/maasserver/api/ssh_keys.py (+2/-2)
src/maasserver/api/ssl_keys.py (+2/-2)
src/maasserver/api/tags.py (+5/-7)
src/maasserver/api/utils.py (+7/-5)
src/maasserver/api/zones.py (+3/-3)
src/maasserver/exceptions.py (+22/-0)
src/maasserver/tests/test_exceptions.py (+57/-0)
To merge this branch: bzr merge lp:~gmb/maas/fix-bug-1394746
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+243016@code.launchpad.net

Commit message

Add a MAASAPIValidationError, which is a nice way of putting Django validation errors into something that behaves in a MAASException-like way.

All the callsites that used ValidationError in maasserver.api.* have been updated to use the new exception class.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Looks good, but make_http_response() needs a test or two.

review: Approve
Revision history for this message
Graham Binns (gmb) wrote :

On 27 November 2014 at 11:17, Gavin Panella <email address hidden>
wrote:

> Looks good, but make_http_response() needs a test or two.
>

Aw, darn, really? (Yes)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/boot_resources.py'
2--- src/maasserver/api/boot_resources.py 2014-09-04 20:01:15 +0000
3+++ src/maasserver/api/boot_resources.py 2014-11-27 12:10:49 +0000
4@@ -21,7 +21,6 @@
5 import httplib
6 import os
7
8-from django.core.exceptions import ValidationError
9 from django.core.files.uploadedfile import SimpleUploadedFile
10 from django.core.urlresolvers import reverse
11 from django.http import HttpResponse
12@@ -40,6 +39,7 @@
13 from maasserver.exceptions import (
14 MAASAPIBadRequest,
15 MAASAPIForbidden,
16+ MAASAPIValidationError,
17 )
18 from maasserver.forms import (
19 BootResourceForm,
20@@ -205,7 +205,7 @@
21 else:
22 form = BootResourceNoContentForm(data=data)
23 if not form.is_valid():
24- raise ValidationError(form.errors)
25+ raise MAASAPIValidationError(form.errors)
26 resource = form.save()
27
28 # If an upload contained the full file, then we can have the clusters
29
30=== modified file 'src/maasserver/api/boot_source_selections.py'
31--- src/maasserver/api/boot_source_selections.py 2014-10-23 15:26:37 +0000
32+++ src/maasserver/api/boot_source_selections.py 2014-11-27 12:10:49 +0000
33@@ -18,9 +18,9 @@
34 ]
35
36
37-from django.core.exceptions import ValidationError
38 from django.shortcuts import get_object_or_404
39 from maasserver.api.support import OperationsHandler
40+from maasserver.exceptions import MAASAPIValidationError
41 from maasserver.forms import BootSourceSelectionForm
42 from maasserver.models import (
43 BootSource,
44@@ -72,7 +72,7 @@
45 if form.is_valid():
46 return form.save()
47 else:
48- raise ValidationError(form.errors)
49+ raise MAASAPIValidationError(form.errors)
50
51 def delete(self, request, boot_source_id, id):
52 """Delete a specific boot source."""
53@@ -166,7 +166,7 @@
54 if form.is_valid():
55 return form.save()
56 else:
57- raise ValidationError(form.errors)
58+ raise MAASAPIValidationError(form.errors)
59
60
61 class BootSourceSelectionsBackwardHandler(BootSourceSelectionsHandler):
62
63=== modified file 'src/maasserver/api/boot_sources.py'
64--- src/maasserver/api/boot_sources.py 2014-09-04 14:51:02 +0000
65+++ src/maasserver/api/boot_sources.py 2014-11-27 12:10:49 +0000
66@@ -20,11 +20,11 @@
67 from base64 import b64encode
68 import httplib
69
70-from django.core.exceptions import ValidationError
71 from django.core.urlresolvers import reverse
72 from django.http import HttpResponse
73 from django.shortcuts import get_object_or_404
74 from maasserver.api.support import OperationsHandler
75+from maasserver.exceptions import MAASAPIValidationError
76 from maasserver.forms import BootSourceForm
77 from maasserver.models import BootSource
78 from piston.emitters import JSONEmitter
79@@ -94,7 +94,7 @@
80 if form.is_valid():
81 return form.save()
82 else:
83- raise ValidationError(form.errors)
84+ raise MAASAPIValidationError(form.errors)
85
86 def delete(self, request, id):
87 """Delete a specific boot source."""
88@@ -176,7 +176,7 @@
89 stream, mimetype='application/json; charset=utf-8',
90 status=httplib.CREATED)
91 else:
92- raise ValidationError(form.errors)
93+ raise MAASAPIValidationError(form.errors)
94
95
96 class BootSourcesBackwardHandler(BootSourcesHandler):
97
98=== modified file 'src/maasserver/api/license_keys.py'
99--- src/maasserver/api/license_keys.py 2014-08-16 16:47:28 +0000
100+++ src/maasserver/api/license_keys.py 2014-11-27 12:10:49 +0000
101@@ -18,9 +18,9 @@
102 ]
103
104
105-from django.core.exceptions import ValidationError
106 from django.shortcuts import get_object_or_404
107 from maasserver.api.support import OperationsHandler
108+from maasserver.exceptions import MAASAPIValidationError
109 from maasserver.forms import LicenseKeyForm
110 from maasserver.models import LicenseKey
111 from maasserver.utils.orm import get_one
112@@ -52,7 +52,7 @@
113 data = {}
114 form = LicenseKeyForm(data=data)
115 if not form.is_valid():
116- raise ValidationError(form.errors)
117+ raise MAASAPIValidationError(form.errors)
118 return form.save()
119
120 @classmethod
121@@ -89,7 +89,7 @@
122 data = {}
123 form = LicenseKeyForm(instance=license_key, data=data)
124 if not form.is_valid():
125- raise ValidationError(form.errors)
126+ raise MAASAPIValidationError(form.errors)
127 return form.save()
128
129 def delete(self, request, osystem, distro_series):
130
131=== modified file 'src/maasserver/api/maas.py'
132--- src/maasserver/api/maas.py 2014-08-18 12:39:48 +0000
133+++ src/maasserver/api/maas.py 2014-11-27 12:10:49 +0000
134@@ -17,7 +17,6 @@
135 ]
136
137
138-from django.core.exceptions import ValidationError
139 from django.http import HttpResponse
140 from formencode import validators
141 from maasserver.api.support import (
142@@ -25,6 +24,7 @@
143 OperationsHandler,
144 )
145 from maasserver.api.utils import get_mandatory_param
146+from maasserver.exceptions import MAASAPIValidationError
147 from maasserver.forms_settings import (
148 get_config_doc,
149 get_config_form,
150@@ -56,7 +56,7 @@
151 value = get_mandatory_param(request.data, 'value')
152 form = get_config_form(name, {name: value})
153 if not form.is_valid():
154- raise ValidationError(form.errors)
155+ raise MAASAPIValidationError(form.errors)
156 form.save()
157 return rc.ALL_OK
158
159
160=== modified file 'src/maasserver/api/networks.py'
161--- src/maasserver/api/networks.py 2014-09-17 03:14:08 +0000
162+++ src/maasserver/api/networks.py 2014-11-27 12:10:49 +0000
163@@ -18,7 +18,6 @@
164 ]
165
166
167-from django.core.exceptions import ValidationError
168 from django.shortcuts import get_object_or_404
169 from maasserver.api.support import (
170 admin_method,
171@@ -26,6 +25,7 @@
172 OperationsHandler,
173 )
174 from maasserver.enum import NODE_PERMISSION
175+from maasserver.exceptions import MAASAPIValidationError
176 from maasserver.forms import (
177 NetworkConnectMACsForm,
178 NetworkDisconnectMACsForm,
179@@ -77,7 +77,7 @@
180 instance=network, data=request.data,
181 delete_macs_if_not_present=False)
182 if not form.is_valid():
183- raise ValidationError(form.errors)
184+ raise MAASAPIValidationError(form.errors)
185 return form.save()
186
187 @admin_method
188@@ -107,7 +107,7 @@
189 network = get_object_or_404(Network, name=name)
190 form = NetworkConnectMACsForm(network=network, data=request.data)
191 if not form.is_valid():
192- raise ValidationError(form.errors)
193+ raise MAASAPIValidationError(form.errors)
194 form.save()
195
196 @admin_method
197@@ -123,7 +123,7 @@
198 network = get_object_or_404(Network, name=name)
199 form = NetworkDisconnectMACsForm(network=network, data=request.data)
200 if not form.is_valid():
201- raise ValidationError(form.errors)
202+ raise MAASAPIValidationError(form.errors)
203 form.save()
204
205 @operation(idempotent=True)
206@@ -165,7 +165,7 @@
207 """
208 form = NetworksListingForm(data=request.GET)
209 if not form.is_valid():
210- raise ValidationError(form.errors)
211+ raise MAASAPIValidationError(form.errors)
212 return form.filter_networks(Network.objects.all())
213
214 @admin_method
215@@ -186,7 +186,7 @@
216 """
217 form = NetworkForm(request.data)
218 if not form.is_valid():
219- raise ValidationError(form.errors)
220+ raise MAASAPIValidationError(form.errors)
221 return form.save()
222
223 @classmethod
224
225=== modified file 'src/maasserver/api/node_group_interfaces.py'
226--- src/maasserver/api/node_group_interfaces.py 2014-11-24 18:23:26 +0000
227+++ src/maasserver/api/node_group_interfaces.py 2014-11-27 12:10:49 +0000
228@@ -18,13 +18,13 @@
229 ]
230
231
232-from django.core.exceptions import ValidationError
233 from django.shortcuts import get_object_or_404
234 from maasserver.api.node_groups import check_nodegroup_access
235 from maasserver.api.support import (
236 operation,
237 OperationsHandler,
238 )
239+from maasserver.exceptions import MAASAPIValidationError
240 from maasserver.forms import NodeGroupInterfaceForm
241 from maasserver.models import (
242 NodeGroup,
243@@ -107,7 +107,7 @@
244 if form.is_valid():
245 return form.save()
246 else:
247- raise ValidationError(form.errors)
248+ raise MAASAPIValidationError(form.errors)
249
250 @classmethod
251 def resource_uri(cls, nodegroup=None):
252@@ -189,7 +189,7 @@
253 if form.is_valid():
254 return form.save()
255 else:
256- raise ValidationError(form.errors)
257+ raise MAASAPIValidationError(form.errors)
258
259 def delete(self, request, uuid, name):
260 """Delete a specific NodeGroupInterface.
261
262=== modified file 'src/maasserver/api/node_groups.py'
263--- src/maasserver/api/node_groups.py 2014-11-24 17:55:55 +0000
264+++ src/maasserver/api/node_groups.py 2014-11-27 12:10:49 +0000
265@@ -21,10 +21,7 @@
266 import httplib
267
268 import bson
269-from django.core.exceptions import (
270- PermissionDenied,
271- ValidationError,
272- )
273+from django.core.exceptions import PermissionDenied
274 from django.http import HttpResponse
275 from django.shortcuts import get_object_or_404
276 from formencode import validators
277@@ -43,7 +40,10 @@
278 from maasserver.clusterrpc.power_parameters import (
279 get_all_power_types_from_clusters,
280 )
281-from maasserver.exceptions import Unauthorized
282+from maasserver.exceptions import (
283+ MAASAPIValidationError,
284+ Unauthorized,
285+ )
286 from maasserver.forms import (
287 DownloadProgressForm,
288 NodeGroupEdit,
289@@ -212,7 +212,7 @@
290 if form.is_valid():
291 return form.save()
292 else:
293- raise ValidationError(form.errors)
294+ raise MAASAPIValidationError(form.errors)
295
296 @admin_method
297 @operation(idempotent=False)
298@@ -356,7 +356,7 @@
299
300 form = DownloadProgressForm(data=request.data, instance=download)
301 if not form.is_valid():
302- raise ValidationError(form.errors)
303+ raise MAASAPIValidationError(form.errors)
304 form.save()
305
306 return HttpResponse(status=httplib.OK)
307
308=== modified file 'src/maasserver/api/nodes.py'
309--- src/maasserver/api/nodes.py 2014-11-24 18:23:26 +0000
310+++ src/maasserver/api/nodes.py 2014-11-27 12:10:49 +0000
311@@ -22,10 +22,7 @@
312 import bson
313 import crochet
314 from django.conf import settings
315-from django.core.exceptions import (
316- PermissionDenied,
317- ValidationError,
318- )
319+from django.core.exceptions import PermissionDenied
320 from django.http import HttpResponse
321 from django.shortcuts import get_object_or_404
322 from maasserver import locks
323@@ -50,6 +47,7 @@
324 )
325 from maasserver.exceptions import (
326 MAASAPIBadRequest,
327+ MAASAPIValidationError,
328 NodesNotAvailable,
329 NodeStateViolation,
330 PowerProblem,
331@@ -246,7 +244,7 @@
332 if form.is_valid():
333 return form.save()
334 else:
335- raise ValidationError(form.errors)
336+ raise MAASAPIValidationError(form.errors)
337
338 def delete(self, request, system_id):
339 """Delete a specific Node.
340@@ -341,7 +339,7 @@
341 if form.is_valid():
342 form.save()
343 else:
344- raise ValidationError(form.errors)
345+ raise MAASAPIValidationError(form.errors)
346
347 try:
348 node.start(request.user, user_data=user_data)
349@@ -396,7 +394,7 @@
350 node = form.save(allow_redirect=False)
351 return node
352 else:
353- raise ValidationError(form.errors)
354+ raise MAASAPIValidationError(form.errors)
355
356 @operation(idempotent=True)
357 def details(self, request, system_id):
358@@ -622,7 +620,7 @@
359 if given_arch and '/' in given_arch:
360 if given_subarch:
361 # Architecture with a '/' and a subarchitecture: error.
362- raise ValidationError('Subarchitecture cannot be specified twice.')
363+ raise MAASAPIValidationError('Subarchitecture cannot be specified twice.')
364 # Architecture with a '/' in it: use normally.
365 elif given_arch:
366 if given_subarch:
367@@ -644,7 +642,7 @@
368 # We insist on this to protect command-line API users who
369 # are manually enlisting nodes. You can't use the origin's
370 # IP address to indicate in which nodegroup the new node belongs.
371- raise ValidationError(
372+ raise MAASAPIValidationError(
373 "'autodetect_nodegroup' must be specified if 'nodegroup' "
374 "parameter missing")
375 nodegroup = find_nodegroup(request)
376@@ -660,7 +658,7 @@
377 maaslog.info("%s: Enlisted new node", node.hostname)
378 return node
379 else:
380- raise ValidationError(form.errors)
381+ raise MAASAPIValidationError(form.errors)
382
383
384 class AnonNodesHandler(AnonymousOperationsHandler):
385@@ -940,7 +938,7 @@
386 invalid_macs = [
387 mac for mac in match_macs if MAC_RE.match(mac) is None]
388 if len(invalid_macs) != 0:
389- raise ValidationError(
390+ raise MAASAPIValidationError(
391 "Invalid MAC address(es): %s" % ", ".join(invalid_macs))
392
393 # Fetch nodes and apply filters.
394@@ -1040,7 +1038,7 @@
395 request.user.username, request.data)
396
397 if not form.is_valid():
398- raise ValidationError(form.errors)
399+ raise MAASAPIValidationError(form.errors)
400
401 # This lock prevents a node we've picked as available from
402 # becoming unavailable before our transaction commits.
403@@ -1085,7 +1083,7 @@
404 }
405 form = BulkNodeActionForm(request.user, data=data)
406 if not form.is_valid():
407- raise ValidationError(form.errors)
408+ raise MAASAPIValidationError(form.errors)
409 form.save()
410
411 @admin_method
412
413=== modified file 'src/maasserver/api/ssh_keys.py'
414--- src/maasserver/api/ssh_keys.py 2014-11-20 23:47:49 +0000
415+++ src/maasserver/api/ssh_keys.py 2014-11-27 12:10:49 +0000
416@@ -19,13 +19,13 @@
417
418 import httplib
419
420-from django.core.exceptions import ValidationError
421 from django.http import HttpResponse
422 from django.shortcuts import get_object_or_404
423 from maasserver.api.support import (
424 operation,
425 OperationsHandler,
426 )
427+from maasserver.exceptions import MAASAPIValidationError
428 from maasserver.forms import SSHKeyForm
429 from maasserver.models import SSHKey
430 from piston.emitters import JSONEmitter
431@@ -64,7 +64,7 @@
432 stream, mimetype='application/json; charset=utf-8',
433 status=httplib.CREATED)
434 else:
435- raise ValidationError(form.errors)
436+ raise MAASAPIValidationError(form.errors)
437
438 @classmethod
439 def resource_uri(cls, *args, **kwargs):
440
441=== modified file 'src/maasserver/api/ssl_keys.py'
442--- src/maasserver/api/ssl_keys.py 2014-11-21 21:23:39 +0000
443+++ src/maasserver/api/ssl_keys.py 2014-11-27 12:10:49 +0000
444@@ -19,13 +19,13 @@
445
446 import httplib
447
448-from django.core.exceptions import ValidationError
449 from django.http import HttpResponse
450 from django.shortcuts import get_object_or_404
451 from maasserver.api.support import (
452 operation,
453 OperationsHandler,
454 )
455+from maasserver.exceptions import MAASAPIValidationError
456 from maasserver.forms import SSLKeyForm
457 from maasserver.models import SSLKey
458 from piston.emitters import JSONEmitter
459@@ -64,7 +64,7 @@
460 stream, mimetype='application/json; charset=utf-8',
461 status=httplib.CREATED)
462 else:
463- raise ValidationError(form.errors)
464+ raise MAASAPIValidationError(form.errors)
465
466 @classmethod
467 def resource_uri(cls, *args, **kwargs):
468
469=== modified file 'src/maasserver/api/tags.py'
470--- src/maasserver/api/tags.py 2014-11-21 21:23:39 +0000
471+++ src/maasserver/api/tags.py 2014-11-27 12:10:49 +0000
472@@ -20,10 +20,7 @@
473
474 import httplib
475
476-from django.core.exceptions import (
477- PermissionDenied,
478- ValidationError,
479- )
480+from django.core.exceptions import PermissionDenied
481 from django.db.utils import DatabaseError
482 from django.http import HttpResponse
483 from maasserver.api.node_groups import check_nodegroup_access
484@@ -33,6 +30,7 @@
485 )
486 from maasserver.api.utils import get_list_from_dict_or_multidict
487 from maasserver.enum import NODE_PERMISSION
488+from maasserver.exceptions import MAASAPIValidationError
489 from maasserver.forms import TagForm
490 from maasserver.models import (
491 Node,
492@@ -89,10 +87,10 @@
493 new_tag.save()
494 form.save_m2m()
495 except DatabaseError as e:
496- raise ValidationError(e)
497+ raise MAASAPIValidationError(e)
498 return new_tag
499 else:
500- raise ValidationError(form.errors)
501+ raise MAASAPIValidationError(form.errors)
502
503 def delete(self, request, name):
504 """Delete a specific Tag.
505@@ -223,7 +221,7 @@
506 if form.is_valid():
507 return form.save()
508 else:
509- raise ValidationError(form.errors)
510+ raise MAASAPIValidationError(form.errors)
511
512 @operation(idempotent=True)
513 def list(self, request):
514
515=== modified file 'src/maasserver/api/utils.py'
516--- src/maasserver/api/utils.py 2014-08-16 05:43:33 +0000
517+++ src/maasserver/api/utils.py 2014-11-27 12:10:49 +0000
518@@ -23,10 +23,12 @@
519 'get_overridden_query_dict',
520 ]
521
522-from django.core.exceptions import ValidationError
523 from django.http import QueryDict
524 from formencode.validators import Invalid
525-from maasserver.exceptions import Unauthorized
526+from maasserver.exceptions import (
527+ MAASAPIValidationError,
528+ Unauthorized,
529+ )
530 from piston.models import Token
531
532
533@@ -69,12 +71,12 @@
534 """
535 value = data.get(key, None)
536 if value is None:
537- raise ValidationError("No provided %s!" % key)
538+ raise MAASAPIValidationError("No provided %s!" % key)
539 if validator is not None:
540 try:
541 return validator.to_python(value)
542 except Invalid as e:
543- raise ValidationError("Invalid %s: %s" % (key, e.msg))
544+ raise MAASAPIValidationError("Invalid %s: %s" % (key, e.msg))
545 else:
546 return value
547
548@@ -103,7 +105,7 @@
549 try:
550 return validator.to_python(value)
551 except Invalid as e:
552- raise ValidationError("Invalid %s: %s" % (key, e.msg))
553+ raise MAASAPIValidationError("Invalid %s: %s" % (key, e.msg))
554 else:
555 return value
556
557
558=== modified file 'src/maasserver/api/zones.py'
559--- src/maasserver/api/zones.py 2014-11-24 17:55:55 +0000
560+++ src/maasserver/api/zones.py 2014-11-27 12:10:49 +0000
561@@ -17,12 +17,12 @@
562 'ZonesHandler',
563 ]
564
565-from django.core.exceptions import ValidationError
566 from django.shortcuts import get_object_or_404
567 from maasserver.api.support import (
568 admin_method,
569 OperationsHandler,
570 )
571+from maasserver.exceptions import MAASAPIValidationError
572 from maasserver.forms import ZoneForm
573 from maasserver.models import Zone
574 from maasserver.utils.orm import get_one
575@@ -63,7 +63,7 @@
576 zone = get_object_or_404(Zone, name=name)
577 form = ZoneForm(instance=zone, data=request.data)
578 if not form.is_valid():
579- raise ValidationError(form.errors)
580+ raise MAASAPIValidationError(form.errors)
581 return form.save()
582
583 @admin_method
584@@ -110,7 +110,7 @@
585 if form.is_valid():
586 return form.save()
587 else:
588- raise ValidationError(form.errors)
589+ raise MAASAPIValidationError(form.errors)
590
591 def read(self, request):
592 """List zones.
593
594=== modified file 'src/maasserver/exceptions.py'
595--- src/maasserver/exceptions.py 2014-11-25 10:18:03 +0000
596+++ src/maasserver/exceptions.py 2014-11-27 12:10:49 +0000
597@@ -31,10 +31,12 @@
598
599 import httplib
600
601+from django.core.exceptions import ValidationError
602 from django.http import (
603 HttpResponse,
604 HttpResponseRedirect,
605 )
606+import simplejson as json
607
608
609 class MAASException(Exception):
610@@ -78,6 +80,26 @@
611 api_error = httplib.FORBIDDEN
612
613
614+class MAASAPIValidationError(MAASAPIBadRequest, ValidationError):
615+ """A validation error raised during a MAAS API request."""
616+
617+ def make_http_response(self):
618+ """Create an :class:`HttpResponse` representing this exception."""
619+ mimetype = b"application/json"
620+ if hasattr(self, 'error_dict'):
621+ messages = json.dumps(self.message_dict)
622+ elif len(self.messages) == 1:
623+ messages = self.messages[0]
624+ mimetype = b"text/plain"
625+ else:
626+ messages = json.dumps(self.messages)
627+
628+ encoding = b'utf-8'
629+ return HttpResponse(
630+ status=self.api_error, content=messages,
631+ mimetype=b"%s; charset=%s" % (mimetype, encoding))
632+
633+
634 class Unauthorized(MAASAPIException):
635 """HTTP error 401: Unauthorized. Login required."""
636 api_error = httplib.UNAUTHORIZED
637
638=== modified file 'src/maasserver/tests/test_exceptions.py'
639--- src/maasserver/tests/test_exceptions.py 2014-07-18 17:05:57 +0000
640+++ src/maasserver/tests/test_exceptions.py 2014-11-27 12:10:49 +0000
641@@ -18,11 +18,14 @@
642
643 from maasserver.exceptions import (
644 MAASAPIBadRequest,
645+ MAASAPIValidationError,
646 Redirect,
647 )
648 from maasserver.testing import extract_redirect
649 from maastesting.factory import factory
650 from maastesting.testcase import MAASTestCase
651+import simplejson as json
652+from testtools.matchers import Equals
653
654
655 class TestExceptions(MAASTestCase):
656@@ -40,3 +43,57 @@
657 exception = Redirect(target)
658 response = exception.make_http_response()
659 self.assertEqual(target, extract_redirect(response))
660+
661+
662+class TestMAASAPIValidationError(MAASTestCase):
663+ """Tests for the `MAASAPIValidationError` exception class."""
664+
665+ def test_returns_http_response(self):
666+ error = factory.make_string()
667+ exception = MAASAPIValidationError(error)
668+ response = exception.make_http_response()
669+ self.assertEqual(
670+ (httplib.BAD_REQUEST, error),
671+ (response.status_code, response.content))
672+
673+ def test_returns_textual_response_if_message_is_a_string(self):
674+ error = factory.make_string()
675+ exception = MAASAPIValidationError(error)
676+ response = exception.make_http_response()
677+ self.assertEqual(
678+ "text/plain; charset=utf-8", response.get("Content-Type"))
679+
680+ def test_returns_json_response_if_message_is_a_list(self):
681+ errors = [
682+ factory.make_string(),
683+ factory.make_string(),
684+ ]
685+ exception = MAASAPIValidationError(errors)
686+ response = exception.make_http_response()
687+ self.expectThat(
688+ response.get("Content-Type"),
689+ Equals("application/json; charset=utf-8"))
690+ self.expectThat(response.content, Equals(json.dumps(errors)))
691+
692+ def test_if_message_is_single_item_list_returns_only_first_message(self):
693+ errors = [
694+ factory.make_string(),
695+ ]
696+ exception = MAASAPIValidationError(errors)
697+ response = exception.make_http_response()
698+ self.expectThat(
699+ response.get("Content-Type"),
700+ Equals("text/plain; charset=utf-8"))
701+ self.expectThat(response.content, Equals(errors[0]))
702+
703+ def test_returns_json_response_if_message_is_a_dict(self):
704+ errors = {
705+ 'error_1': [factory.make_string()],
706+ 'error_2': [factory.make_string()],
707+ }
708+ exception = MAASAPIValidationError(errors)
709+ response = exception.make_http_response()
710+ self.expectThat(
711+ response.get("Content-Type"),
712+ Equals("application/json; charset=utf-8"))
713+ self.expectThat(response.content, Equals(json.dumps(errors)))