Merge lp:~jtv/maas/extract-formtests-node into lp:~maas-committers/maas/trunk

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: no longer in the source branch.
Merged at revision: 2816
Proposed branch: lp:~jtv/maas/extract-formtests-node
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 722 lines (+353/-324)
2 files modified
src/maasserver/tests/test_forms.py (+1/-324)
src/maasserver/tests/test_forms_node.py (+352/-0)
To merge this branch: bzr merge lp:~jtv/maas/extract-formtests-node
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+232170@code.launchpad.net

Commit message

Extract Node form tests into their own test module.

Description of the change

For self-approval.

Jeroen

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Looks OK. Self-approving.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/tests/test_forms.py'
--- src/maasserver/tests/test_forms.py 2014-08-26 05:43:19 +0000
+++ src/maasserver/tests/test_forms.py 2014-08-26 07:57:29 +0000
@@ -18,7 +18,6 @@
1818
19from django.conf import settings19from django.conf import settings
20from django.core.exceptions import ValidationError20from django.core.exceptions import ValidationError
21from maasserver.clusterrpc.power_parameters import get_power_type_choices
22from maasserver.enum import (21from maasserver.enum import (
23 NODE_STATUS,22 NODE_STATUS,
24 NODEGROUP_STATUS,23 NODEGROUP_STATUS,
@@ -27,7 +26,6 @@
27from maasserver.forms import (26from maasserver.forms import (
28 AdminNodeForm,27 AdminNodeForm,
29 AdminNodeWithMACAddressesForm,28 AdminNodeWithMACAddressesForm,
30 BLANK_CHOICE,
31 ERROR_MESSAGE_STATIC_IPS_OUTSIDE_RANGE,29 ERROR_MESSAGE_STATIC_IPS_OUTSIDE_RANGE,
32 ERROR_MESSAGE_STATIC_RANGE_IN_USE,30 ERROR_MESSAGE_STATIC_RANGE_IN_USE,
33 get_node_create_form,31 get_node_create_form,
@@ -37,7 +35,6 @@
37 MACAddressForm,35 MACAddressForm,
38 MAX_MESSAGES,36 MAX_MESSAGES,
39 merge_error_messages,37 merge_error_messages,
40 NO_ARCHITECTURES_AVAILABLE,
41 NodeForm,38 NodeForm,
42 NodeGroupInterfaceForeignDHCPForm,39 NodeGroupInterfaceForeignDHCPForm,
43 NodeGroupInterfaceForm,40 NodeGroupInterfaceForm,
@@ -56,17 +53,9 @@
56 )53 )
57from maasserver.models.network import get_name_and_vlan_from_cluster_interface54from maasserver.models.network import get_name_and_vlan_from_cluster_interface
58from maasserver.models.staticipaddress import StaticIPAddress55from maasserver.models.staticipaddress import StaticIPAddress
59from maasserver.testing.architecture import (56from maasserver.testing.architecture import make_usable_architecture
60 make_usable_architecture,
61 patch_usable_architectures,
62 )
63from maasserver.testing.factory import factory57from maasserver.testing.factory import factory
64from maasserver.testing.orm import reload_object58from maasserver.testing.orm import reload_object
65from maasserver.testing.osystems import (
66 make_osystem_with_releases,
67 make_usable_osystem,
68 patch_usable_osystems,
69 )
70from maasserver.testing.testcase import MAASServerTestCase59from maasserver.testing.testcase import MAASServerTestCase
71from maastesting.matchers import MockCalledOnceWith60from maastesting.matchers import MockCalledOnceWith
72from netaddr import IPNetwork61from netaddr import IPNetwork
@@ -202,318 +191,6 @@
202 AdminNodeWithMACAddressesForm, get_node_create_form(admin))191 AdminNodeWithMACAddressesForm, get_node_create_form(admin))
203192
204193
205class TestNodeForm(MAASServerTestCase):
206
207 def test_contains_limited_set_of_fields(self):
208 form = NodeForm()
209
210 self.assertEqual(
211 [
212 'hostname',
213 'architecture',
214 'osystem',
215 'distro_series',
216 'license_key',
217 'disable_ipv4',
218 'nodegroup',
219 ], list(form.fields))
220
221 def test_changes_node(self):
222 node = factory.make_node()
223 hostname = factory.make_string()
224 patch_usable_architectures(self, [node.architecture])
225
226 form = NodeForm(
227 data={
228 'hostname': hostname,
229 'architecture': make_usable_architecture(self),
230 },
231 instance=node)
232 form.save()
233
234 self.assertEqual(hostname, node.hostname)
235
236 def test_accepts_usable_architecture(self):
237 arch = make_usable_architecture(self)
238 form = NodeForm(data={
239 'hostname': factory.make_name('host'),
240 'architecture': arch,
241 })
242 self.assertTrue(form.is_valid(), form._errors)
243
244 def test_rejects_unusable_architecture(self):
245 patch_usable_architectures(self)
246 form = NodeForm(data={
247 'hostname': factory.make_name('host'),
248 'architecture': factory.make_name('arch'),
249 })
250 self.assertFalse(form.is_valid())
251 self.assertItemsEqual(['architecture'], form._errors.keys())
252
253 def test_starts_with_default_architecture(self):
254 arches = sorted([factory.make_name('arch') for _ in range(5)])
255 patch_usable_architectures(self, arches)
256 form = NodeForm()
257 self.assertEqual(
258 pick_default_architecture(arches),
259 form.fields['architecture'].initial)
260
261 def test_adds_blank_default_when_no_arches_available(self):
262 patch_usable_architectures(self, [])
263 form = NodeForm()
264 self.assertEqual(
265 [BLANK_CHOICE],
266 form.fields['architecture'].choices)
267
268 def test_adds_error_when_no_arches_available(self):
269 patch_usable_architectures(self, [])
270 form = NodeForm()
271 self.assertFalse(form.is_valid())
272 self.assertEqual(
273 [NO_ARCHITECTURES_AVAILABLE],
274 form.errors['architecture'])
275
276 def test_accepts_osystem(self):
277 osystem = make_usable_osystem(self)
278 form = NodeForm(data={
279 'hostname': factory.make_name('host'),
280 'architecture': make_usable_architecture(self),
281 'osystem': osystem.name,
282 })
283 self.assertTrue(form.is_valid(), form._errors)
284
285 def test_rejects_invalid_osystem(self):
286 patch_usable_osystems(self)
287 form = NodeForm(data={
288 'hostname': factory.make_name('host'),
289 'architecture': make_usable_architecture(self),
290 'osystem': factory.make_name('os'),
291 })
292 self.assertFalse(form.is_valid())
293 self.assertItemsEqual(['osystem'], form._errors.keys())
294
295 def test_starts_with_default_osystem(self):
296 osystems = [make_osystem_with_releases(self) for _ in range(5)]
297 patch_usable_osystems(self, osystems)
298 form = NodeForm()
299 self.assertEqual(
300 '',
301 form.fields['osystem'].initial)
302
303 def test_accepts_osystem_distro_series(self):
304 osystem = make_usable_osystem(self)
305 release = osystem.get_default_release()
306 form = NodeForm(data={
307 'hostname': factory.make_name('host'),
308 'architecture': make_usable_architecture(self),
309 'osystem': osystem.name,
310 'distro_series': '%s/%s' % (osystem.name, release),
311 })
312 self.assertTrue(form.is_valid(), form._errors)
313
314 def test_rejects_invalid_osystem_distro_series(self):
315 osystem = make_usable_osystem(self)
316 release = factory.make_name('release')
317 form = NodeForm(data={
318 'hostname': factory.make_name('host'),
319 'architecture': make_usable_architecture(self),
320 'osystem': osystem.name,
321 'distro_series': '%s/%s' % (osystem.name, release),
322 })
323 self.assertFalse(form.is_valid())
324 self.assertItemsEqual(['distro_series'], form._errors.keys())
325
326 def test_starts_with_default_distro_series(self):
327 osystems = [make_osystem_with_releases(self) for _ in range(5)]
328 patch_usable_osystems(self, osystems)
329 form = NodeForm()
330 self.assertEqual(
331 '',
332 form.fields['distro_series'].initial)
333
334 def test_rejects_mismatch_osystem_distro_series(self):
335 osystem = make_usable_osystem(self)
336 release = osystem.get_default_release()
337 invalid = factory.make_name('invalid_os')
338 form = NodeForm(data={
339 'hostname': factory.make_name('host'),
340 'architecture': make_usable_architecture(self),
341 'osystem': osystem.name,
342 'distro_series': '%s/%s' % (invalid, release),
343 })
344 self.assertFalse(form.is_valid())
345 self.assertItemsEqual(['distro_series'], form._errors.keys())
346
347 def test_rejects_missing_license_key(self):
348 osystem = make_usable_osystem(self)
349 release = osystem.get_default_release()
350 self.patch(osystem, 'requires_license_key').return_value = True
351 mock_validate = self.patch(osystem, 'validate_license_key')
352 mock_validate.return_value = True
353 form = NodeForm(data={
354 'hostname': factory.make_name('host'),
355 'architecture': make_usable_architecture(self),
356 'osystem': osystem.name,
357 'distro_series': '%s/%s*' % (osystem.name, release),
358 })
359 self.assertFalse(form.is_valid())
360 self.assertItemsEqual(['license_key'], form._errors.keys())
361
362 def test_calls_validate_license_key(self):
363 osystem = make_usable_osystem(self)
364 release = osystem.get_default_release()
365 self.patch(osystem, 'requires_license_key').return_value = True
366 mock_validate = self.patch(osystem, 'validate_license_key')
367 mock_validate.return_value = True
368 form = NodeForm(data={
369 'hostname': factory.make_name('host'),
370 'architecture': make_usable_architecture(self),
371 'osystem': osystem.name,
372 'distro_series': '%s/%s*' % (osystem.name, release),
373 'license_key': factory.make_string(),
374 })
375 self.assertTrue(form.is_valid())
376 mock_validate.assert_called_once()
377
378 def test_rejects_duplicate_fqdn_with_unmanaged_dns_on_one_nodegroup(self):
379 # If a host with a given hostname exists on a managed nodegroup,
380 # new nodes on unmanaged nodegroups with hostnames that match
381 # that FQDN will be rejected.
382 nodegroup = factory.make_node_group(
383 status=NODEGROUP_STATUS.ACCEPTED,
384 management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
385 node = factory.make_node(
386 hostname=factory.make_name("hostname"), nodegroup=nodegroup)
387 other_nodegroup = factory.make_node_group()
388 form = NodeForm(data={
389 'nodegroup': other_nodegroup,
390 'hostname': node.fqdn,
391 'architecture': make_usable_architecture(self),
392 })
393 form.instance.nodegroup = other_nodegroup
394 self.assertFalse(form.is_valid())
395
396 def test_rejects_duplicate_fqdn_on_same_nodegroup(self):
397 # If a node with a given FQDN exists on a managed nodegroup, new
398 # nodes on that nodegroup with duplicate FQDNs will be rejected.
399 nodegroup = factory.make_node_group(
400 status=NODEGROUP_STATUS.ACCEPTED,
401 management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
402 node = factory.make_node(
403 hostname=factory.make_name("hostname"), nodegroup=nodegroup)
404 form = NodeForm(data={
405 'nodegroup': nodegroup,
406 'hostname': node.fqdn,
407 'architecture': make_usable_architecture(self),
408 })
409 form.instance.nodegroup = nodegroup
410 self.assertFalse(form.is_valid())
411
412
413class TestAdminNodeForm(MAASServerTestCase):
414
415 def test_AdminNodeForm_contains_limited_set_of_fields(self):
416 node = factory.make_node()
417 form = AdminNodeForm(instance=node)
418
419 self.assertEqual(
420 [
421 'hostname',
422 'architecture',
423 'osystem',
424 'distro_series',
425 'license_key',
426 'disable_ipv4',
427 'power_type',
428 'power_parameters',
429 'cpu_count',
430 'memory',
431 'storage',
432 'zone',
433 ],
434 list(form.fields))
435
436 def test_AdminNodeForm_initialises_zone(self):
437 # The zone field uses "to_field_name", so that it can refer to a zone
438 # by name instead of by ID. A bug in Django breaks initialisation
439 # from an instance: the field tries to initialise the field using a
440 # zone's ID instead of its name, and ends up reverting to the default.
441 # The code must work around this bug.
442 zone = factory.make_zone()
443 node = factory.make_node(zone=zone)
444 # We'll create a form that makes a change, but not to the zone.
445 data = {'hostname': factory.make_name('host')}
446 form = AdminNodeForm(instance=node, data=data)
447 # The Django bug would stop the initial field value from being set,
448 # but the workaround ensures that it is initialised.
449 self.assertEqual(zone.name, form.initial['zone'])
450
451 def test_AdminNodeForm_changes_node(self):
452 node = factory.make_node()
453 zone = factory.make_zone()
454 hostname = factory.make_string()
455 power_type = factory.pick_power_type()
456 form = AdminNodeForm(
457 data={
458 'hostname': hostname,
459 'power_type': power_type,
460 'architecture': make_usable_architecture(self),
461 'zone': zone.name,
462 },
463 instance=node)
464 form.save()
465
466 node = reload_object(node)
467 self.assertEqual(
468 (node.hostname, node.power_type, node.zone),
469 (hostname, power_type, zone))
470
471 def test_AdminNodeForm_populates_power_type_choices(self):
472 form = AdminNodeForm()
473 self.assertEqual(
474 [''] + [choice[0] for choice in get_power_type_choices()],
475 [choice[0] for choice in form.fields['power_type'].choices])
476
477 def test_AdminNodeForm_populates_power_type_initial(self):
478 node = factory.make_node()
479 form = AdminNodeForm(instance=node)
480 self.assertEqual(node.power_type, form.fields['power_type'].initial)
481
482 def test_AdminNodeForm_changes_node_with_skip_check(self):
483 node = factory.make_node()
484 hostname = factory.make_string()
485 power_type = factory.pick_power_type()
486 power_parameters_field = factory.make_string()
487 arch = make_usable_architecture(self)
488 form = AdminNodeForm(
489 data={
490 'hostname': hostname,
491 'architecture': arch,
492 'power_type': power_type,
493 'power_parameters_field': power_parameters_field,
494 'power_parameters_skip_check': True,
495 },
496 instance=node)
497 form.save()
498
499 self.assertEqual(
500 (hostname, power_type, {'field': power_parameters_field}),
501 (node.hostname, node.power_type, node.power_parameters))
502
503 def test_AdminForm_does_not_permit_nodegroup_change(self):
504 # We had to make Node.nodegroup editable to get Django to
505 # validate it as non-blankable, but that doesn't mean that we
506 # actually want to allow people to edit it through API or UI.
507 old_nodegroup = factory.make_node_group()
508 node = factory.make_node(
509 nodegroup=old_nodegroup,
510 architecture=make_usable_architecture(self))
511 new_nodegroup = factory.make_node_group()
512 AdminNodeForm(data={'nodegroup': new_nodegroup}, instance=node).save()
513 # The form saved without error, but the nodegroup change was ignored.
514 self.assertEqual(old_nodegroup, node.nodegroup)
515
516
517class TestMergeErrorMessages(MAASServerTestCase):194class TestMergeErrorMessages(MAASServerTestCase):
518195
519 def test_merge_error_messages_returns_summary_message(self):196 def test_merge_error_messages_returns_summary_message(self):
520197
=== added file 'src/maasserver/tests/test_forms_node.py'
--- src/maasserver/tests/test_forms_node.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/tests/test_forms_node.py 2014-08-26 07:57:29 +0000
@@ -0,0 +1,352 @@
1# Copyright 2014 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for node forms."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = []
16
17from maasserver.clusterrpc.power_parameters import get_power_type_choices
18from maasserver.enum import (
19 NODEGROUP_STATUS,
20 NODEGROUPINTERFACE_MANAGEMENT,
21 )
22from maasserver.forms import (
23 AdminNodeForm,
24 BLANK_CHOICE,
25 NO_ARCHITECTURES_AVAILABLE,
26 NodeForm,
27 pick_default_architecture,
28 )
29from maasserver.testing.architecture import (
30 make_usable_architecture,
31 patch_usable_architectures,
32 )
33from maasserver.testing.factory import factory
34from maasserver.testing.orm import reload_object
35from maasserver.testing.osystems import (
36 make_osystem_with_releases,
37 make_usable_osystem,
38 patch_usable_osystems,
39 )
40from maasserver.testing.testcase import MAASServerTestCase
41
42
43class TestNodeForm(MAASServerTestCase):
44
45 def test_contains_limited_set_of_fields(self):
46 form = NodeForm()
47
48 self.assertEqual(
49 [
50 'hostname',
51 'architecture',
52 'osystem',
53 'distro_series',
54 'license_key',
55 'disable_ipv4',
56 'nodegroup',
57 ], list(form.fields))
58
59 def test_changes_node(self):
60 node = factory.make_node()
61 hostname = factory.make_string()
62 patch_usable_architectures(self, [node.architecture])
63
64 form = NodeForm(
65 data={
66 'hostname': hostname,
67 'architecture': make_usable_architecture(self),
68 },
69 instance=node)
70 form.save()
71
72 self.assertEqual(hostname, node.hostname)
73
74 def test_accepts_usable_architecture(self):
75 arch = make_usable_architecture(self)
76 form = NodeForm(data={
77 'hostname': factory.make_name('host'),
78 'architecture': arch,
79 })
80 self.assertTrue(form.is_valid(), form._errors)
81
82 def test_rejects_unusable_architecture(self):
83 patch_usable_architectures(self)
84 form = NodeForm(data={
85 'hostname': factory.make_name('host'),
86 'architecture': factory.make_name('arch'),
87 })
88 self.assertFalse(form.is_valid())
89 self.assertItemsEqual(['architecture'], form._errors.keys())
90
91 def test_starts_with_default_architecture(self):
92 arches = sorted([factory.make_name('arch') for _ in range(5)])
93 patch_usable_architectures(self, arches)
94 form = NodeForm()
95 self.assertEqual(
96 pick_default_architecture(arches),
97 form.fields['architecture'].initial)
98
99 def test_adds_blank_default_when_no_arches_available(self):
100 patch_usable_architectures(self, [])
101 form = NodeForm()
102 self.assertEqual(
103 [BLANK_CHOICE],
104 form.fields['architecture'].choices)
105
106 def test_adds_error_when_no_arches_available(self):
107 patch_usable_architectures(self, [])
108 form = NodeForm()
109 self.assertFalse(form.is_valid())
110 self.assertEqual(
111 [NO_ARCHITECTURES_AVAILABLE],
112 form.errors['architecture'])
113
114 def test_accepts_osystem(self):
115 osystem = make_usable_osystem(self)
116 form = NodeForm(data={
117 'hostname': factory.make_name('host'),
118 'architecture': make_usable_architecture(self),
119 'osystem': osystem.name,
120 })
121 self.assertTrue(form.is_valid(), form._errors)
122
123 def test_rejects_invalid_osystem(self):
124 patch_usable_osystems(self)
125 form = NodeForm(data={
126 'hostname': factory.make_name('host'),
127 'architecture': make_usable_architecture(self),
128 'osystem': factory.make_name('os'),
129 })
130 self.assertFalse(form.is_valid())
131 self.assertItemsEqual(['osystem'], form._errors.keys())
132
133 def test_starts_with_default_osystem(self):
134 osystems = [make_osystem_with_releases(self) for _ in range(5)]
135 patch_usable_osystems(self, osystems)
136 form = NodeForm()
137 self.assertEqual(
138 '',
139 form.fields['osystem'].initial)
140
141 def test_accepts_osystem_distro_series(self):
142 osystem = make_usable_osystem(self)
143 release = osystem.get_default_release()
144 form = NodeForm(data={
145 'hostname': factory.make_name('host'),
146 'architecture': make_usable_architecture(self),
147 'osystem': osystem.name,
148 'distro_series': '%s/%s' % (osystem.name, release),
149 })
150 self.assertTrue(form.is_valid(), form._errors)
151
152 def test_rejects_invalid_osystem_distro_series(self):
153 osystem = make_usable_osystem(self)
154 release = factory.make_name('release')
155 form = NodeForm(data={
156 'hostname': factory.make_name('host'),
157 'architecture': make_usable_architecture(self),
158 'osystem': osystem.name,
159 'distro_series': '%s/%s' % (osystem.name, release),
160 })
161 self.assertFalse(form.is_valid())
162 self.assertItemsEqual(['distro_series'], form._errors.keys())
163
164 def test_starts_with_default_distro_series(self):
165 osystems = [make_osystem_with_releases(self) for _ in range(5)]
166 patch_usable_osystems(self, osystems)
167 form = NodeForm()
168 self.assertEqual(
169 '',
170 form.fields['distro_series'].initial)
171
172 def test_rejects_mismatch_osystem_distro_series(self):
173 osystem = make_usable_osystem(self)
174 release = osystem.get_default_release()
175 invalid = factory.make_name('invalid_os')
176 form = NodeForm(data={
177 'hostname': factory.make_name('host'),
178 'architecture': make_usable_architecture(self),
179 'osystem': osystem.name,
180 'distro_series': '%s/%s' % (invalid, release),
181 })
182 self.assertFalse(form.is_valid())
183 self.assertItemsEqual(['distro_series'], form._errors.keys())
184
185 def test_rejects_missing_license_key(self):
186 osystem = make_usable_osystem(self)
187 release = osystem.get_default_release()
188 self.patch(osystem, 'requires_license_key').return_value = True
189 mock_validate = self.patch(osystem, 'validate_license_key')
190 mock_validate.return_value = True
191 form = NodeForm(data={
192 'hostname': factory.make_name('host'),
193 'architecture': make_usable_architecture(self),
194 'osystem': osystem.name,
195 'distro_series': '%s/%s*' % (osystem.name, release),
196 })
197 self.assertFalse(form.is_valid())
198 self.assertItemsEqual(['license_key'], form._errors.keys())
199
200 def test_calls_validate_license_key(self):
201 osystem = make_usable_osystem(self)
202 release = osystem.get_default_release()
203 self.patch(osystem, 'requires_license_key').return_value = True
204 mock_validate = self.patch(osystem, 'validate_license_key')
205 mock_validate.return_value = True
206 form = NodeForm(data={
207 'hostname': factory.make_name('host'),
208 'architecture': make_usable_architecture(self),
209 'osystem': osystem.name,
210 'distro_series': '%s/%s*' % (osystem.name, release),
211 'license_key': factory.make_string(),
212 })
213 self.assertTrue(form.is_valid())
214 mock_validate.assert_called_once()
215
216 def test_rejects_duplicate_fqdn_with_unmanaged_dns_on_one_nodegroup(self):
217 # If a host with a given hostname exists on a managed nodegroup,
218 # new nodes on unmanaged nodegroups with hostnames that match
219 # that FQDN will be rejected.
220 nodegroup = factory.make_node_group(
221 status=NODEGROUP_STATUS.ACCEPTED,
222 management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
223 node = factory.make_node(
224 hostname=factory.make_name("hostname"), nodegroup=nodegroup)
225 other_nodegroup = factory.make_node_group()
226 form = NodeForm(data={
227 'nodegroup': other_nodegroup,
228 'hostname': node.fqdn,
229 'architecture': make_usable_architecture(self),
230 })
231 form.instance.nodegroup = other_nodegroup
232 self.assertFalse(form.is_valid())
233
234 def test_rejects_duplicate_fqdn_on_same_nodegroup(self):
235 # If a node with a given FQDN exists on a managed nodegroup, new
236 # nodes on that nodegroup with duplicate FQDNs will be rejected.
237 nodegroup = factory.make_node_group(
238 status=NODEGROUP_STATUS.ACCEPTED,
239 management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
240 node = factory.make_node(
241 hostname=factory.make_name("hostname"), nodegroup=nodegroup)
242 form = NodeForm(data={
243 'nodegroup': nodegroup,
244 'hostname': node.fqdn,
245 'architecture': make_usable_architecture(self),
246 })
247 form.instance.nodegroup = nodegroup
248 self.assertFalse(form.is_valid())
249
250
251class TestAdminNodeForm(MAASServerTestCase):
252
253 def test_AdminNodeForm_contains_limited_set_of_fields(self):
254 node = factory.make_node()
255 form = AdminNodeForm(instance=node)
256
257 self.assertEqual(
258 [
259 'hostname',
260 'architecture',
261 'osystem',
262 'distro_series',
263 'license_key',
264 'disable_ipv4',
265 'power_type',
266 'power_parameters',
267 'cpu_count',
268 'memory',
269 'storage',
270 'zone',
271 ],
272 list(form.fields))
273
274 def test_AdminNodeForm_initialises_zone(self):
275 # The zone field uses "to_field_name", so that it can refer to a zone
276 # by name instead of by ID. A bug in Django breaks initialisation
277 # from an instance: the field tries to initialise the field using a
278 # zone's ID instead of its name, and ends up reverting to the default.
279 # The code must work around this bug.
280 zone = factory.make_zone()
281 node = factory.make_node(zone=zone)
282 # We'll create a form that makes a change, but not to the zone.
283 data = {'hostname': factory.make_name('host')}
284 form = AdminNodeForm(instance=node, data=data)
285 # The Django bug would stop the initial field value from being set,
286 # but the workaround ensures that it is initialised.
287 self.assertEqual(zone.name, form.initial['zone'])
288
289 def test_AdminNodeForm_changes_node(self):
290 node = factory.make_node()
291 zone = factory.make_zone()
292 hostname = factory.make_string()
293 power_type = factory.pick_power_type()
294 form = AdminNodeForm(
295 data={
296 'hostname': hostname,
297 'power_type': power_type,
298 'architecture': make_usable_architecture(self),
299 'zone': zone.name,
300 },
301 instance=node)
302 form.save()
303
304 node = reload_object(node)
305 self.assertEqual(
306 (node.hostname, node.power_type, node.zone),
307 (hostname, power_type, zone))
308
309 def test_AdminNodeForm_populates_power_type_choices(self):
310 form = AdminNodeForm()
311 self.assertEqual(
312 [''] + [choice[0] for choice in get_power_type_choices()],
313 [choice[0] for choice in form.fields['power_type'].choices])
314
315 def test_AdminNodeForm_populates_power_type_initial(self):
316 node = factory.make_node()
317 form = AdminNodeForm(instance=node)
318 self.assertEqual(node.power_type, form.fields['power_type'].initial)
319
320 def test_AdminNodeForm_changes_node_with_skip_check(self):
321 node = factory.make_node()
322 hostname = factory.make_string()
323 power_type = factory.pick_power_type()
324 power_parameters_field = factory.make_string()
325 arch = make_usable_architecture(self)
326 form = AdminNodeForm(
327 data={
328 'hostname': hostname,
329 'architecture': arch,
330 'power_type': power_type,
331 'power_parameters_field': power_parameters_field,
332 'power_parameters_skip_check': True,
333 },
334 instance=node)
335 form.save()
336
337 self.assertEqual(
338 (hostname, power_type, {'field': power_parameters_field}),
339 (node.hostname, node.power_type, node.power_parameters))
340
341 def test_AdminForm_does_not_permit_nodegroup_change(self):
342 # We had to make Node.nodegroup editable to get Django to
343 # validate it as non-blankable, but that doesn't mean that we
344 # actually want to allow people to edit it through API or UI.
345 old_nodegroup = factory.make_node_group()
346 node = factory.make_node(
347 nodegroup=old_nodegroup,
348 architecture=make_usable_architecture(self))
349 new_nodegroup = factory.make_node_group()
350 AdminNodeForm(data={'nodegroup': new_nodegroup}, instance=node).save()
351 # The form saved without error, but the nodegroup change was ignored.
352 self.assertEqual(old_nodegroup, node.nodegroup)