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
1=== modified file 'src/maasserver/tests/test_forms.py'
2--- src/maasserver/tests/test_forms.py 2014-08-26 05:43:19 +0000
3+++ src/maasserver/tests/test_forms.py 2014-08-26 07:57:29 +0000
4@@ -18,7 +18,6 @@
5
6 from django.conf import settings
7 from django.core.exceptions import ValidationError
8-from maasserver.clusterrpc.power_parameters import get_power_type_choices
9 from maasserver.enum import (
10 NODE_STATUS,
11 NODEGROUP_STATUS,
12@@ -27,7 +26,6 @@
13 from maasserver.forms import (
14 AdminNodeForm,
15 AdminNodeWithMACAddressesForm,
16- BLANK_CHOICE,
17 ERROR_MESSAGE_STATIC_IPS_OUTSIDE_RANGE,
18 ERROR_MESSAGE_STATIC_RANGE_IN_USE,
19 get_node_create_form,
20@@ -37,7 +35,6 @@
21 MACAddressForm,
22 MAX_MESSAGES,
23 merge_error_messages,
24- NO_ARCHITECTURES_AVAILABLE,
25 NodeForm,
26 NodeGroupInterfaceForeignDHCPForm,
27 NodeGroupInterfaceForm,
28@@ -56,17 +53,9 @@
29 )
30 from maasserver.models.network import get_name_and_vlan_from_cluster_interface
31 from maasserver.models.staticipaddress import StaticIPAddress
32-from maasserver.testing.architecture import (
33- make_usable_architecture,
34- patch_usable_architectures,
35- )
36+from maasserver.testing.architecture import make_usable_architecture
37 from maasserver.testing.factory import factory
38 from maasserver.testing.orm import reload_object
39-from maasserver.testing.osystems import (
40- make_osystem_with_releases,
41- make_usable_osystem,
42- patch_usable_osystems,
43- )
44 from maasserver.testing.testcase import MAASServerTestCase
45 from maastesting.matchers import MockCalledOnceWith
46 from netaddr import IPNetwork
47@@ -202,318 +191,6 @@
48 AdminNodeWithMACAddressesForm, get_node_create_form(admin))
49
50
51-class TestNodeForm(MAASServerTestCase):
52-
53- def test_contains_limited_set_of_fields(self):
54- form = NodeForm()
55-
56- self.assertEqual(
57- [
58- 'hostname',
59- 'architecture',
60- 'osystem',
61- 'distro_series',
62- 'license_key',
63- 'disable_ipv4',
64- 'nodegroup',
65- ], list(form.fields))
66-
67- def test_changes_node(self):
68- node = factory.make_node()
69- hostname = factory.make_string()
70- patch_usable_architectures(self, [node.architecture])
71-
72- form = NodeForm(
73- data={
74- 'hostname': hostname,
75- 'architecture': make_usable_architecture(self),
76- },
77- instance=node)
78- form.save()
79-
80- self.assertEqual(hostname, node.hostname)
81-
82- def test_accepts_usable_architecture(self):
83- arch = make_usable_architecture(self)
84- form = NodeForm(data={
85- 'hostname': factory.make_name('host'),
86- 'architecture': arch,
87- })
88- self.assertTrue(form.is_valid(), form._errors)
89-
90- def test_rejects_unusable_architecture(self):
91- patch_usable_architectures(self)
92- form = NodeForm(data={
93- 'hostname': factory.make_name('host'),
94- 'architecture': factory.make_name('arch'),
95- })
96- self.assertFalse(form.is_valid())
97- self.assertItemsEqual(['architecture'], form._errors.keys())
98-
99- def test_starts_with_default_architecture(self):
100- arches = sorted([factory.make_name('arch') for _ in range(5)])
101- patch_usable_architectures(self, arches)
102- form = NodeForm()
103- self.assertEqual(
104- pick_default_architecture(arches),
105- form.fields['architecture'].initial)
106-
107- def test_adds_blank_default_when_no_arches_available(self):
108- patch_usable_architectures(self, [])
109- form = NodeForm()
110- self.assertEqual(
111- [BLANK_CHOICE],
112- form.fields['architecture'].choices)
113-
114- def test_adds_error_when_no_arches_available(self):
115- patch_usable_architectures(self, [])
116- form = NodeForm()
117- self.assertFalse(form.is_valid())
118- self.assertEqual(
119- [NO_ARCHITECTURES_AVAILABLE],
120- form.errors['architecture'])
121-
122- def test_accepts_osystem(self):
123- osystem = make_usable_osystem(self)
124- form = NodeForm(data={
125- 'hostname': factory.make_name('host'),
126- 'architecture': make_usable_architecture(self),
127- 'osystem': osystem.name,
128- })
129- self.assertTrue(form.is_valid(), form._errors)
130-
131- def test_rejects_invalid_osystem(self):
132- patch_usable_osystems(self)
133- form = NodeForm(data={
134- 'hostname': factory.make_name('host'),
135- 'architecture': make_usable_architecture(self),
136- 'osystem': factory.make_name('os'),
137- })
138- self.assertFalse(form.is_valid())
139- self.assertItemsEqual(['osystem'], form._errors.keys())
140-
141- def test_starts_with_default_osystem(self):
142- osystems = [make_osystem_with_releases(self) for _ in range(5)]
143- patch_usable_osystems(self, osystems)
144- form = NodeForm()
145- self.assertEqual(
146- '',
147- form.fields['osystem'].initial)
148-
149- def test_accepts_osystem_distro_series(self):
150- osystem = make_usable_osystem(self)
151- release = osystem.get_default_release()
152- form = NodeForm(data={
153- 'hostname': factory.make_name('host'),
154- 'architecture': make_usable_architecture(self),
155- 'osystem': osystem.name,
156- 'distro_series': '%s/%s' % (osystem.name, release),
157- })
158- self.assertTrue(form.is_valid(), form._errors)
159-
160- def test_rejects_invalid_osystem_distro_series(self):
161- osystem = make_usable_osystem(self)
162- release = factory.make_name('release')
163- form = NodeForm(data={
164- 'hostname': factory.make_name('host'),
165- 'architecture': make_usable_architecture(self),
166- 'osystem': osystem.name,
167- 'distro_series': '%s/%s' % (osystem.name, release),
168- })
169- self.assertFalse(form.is_valid())
170- self.assertItemsEqual(['distro_series'], form._errors.keys())
171-
172- def test_starts_with_default_distro_series(self):
173- osystems = [make_osystem_with_releases(self) for _ in range(5)]
174- patch_usable_osystems(self, osystems)
175- form = NodeForm()
176- self.assertEqual(
177- '',
178- form.fields['distro_series'].initial)
179-
180- def test_rejects_mismatch_osystem_distro_series(self):
181- osystem = make_usable_osystem(self)
182- release = osystem.get_default_release()
183- invalid = factory.make_name('invalid_os')
184- form = NodeForm(data={
185- 'hostname': factory.make_name('host'),
186- 'architecture': make_usable_architecture(self),
187- 'osystem': osystem.name,
188- 'distro_series': '%s/%s' % (invalid, release),
189- })
190- self.assertFalse(form.is_valid())
191- self.assertItemsEqual(['distro_series'], form._errors.keys())
192-
193- def test_rejects_missing_license_key(self):
194- osystem = make_usable_osystem(self)
195- release = osystem.get_default_release()
196- self.patch(osystem, 'requires_license_key').return_value = True
197- mock_validate = self.patch(osystem, 'validate_license_key')
198- mock_validate.return_value = True
199- form = NodeForm(data={
200- 'hostname': factory.make_name('host'),
201- 'architecture': make_usable_architecture(self),
202- 'osystem': osystem.name,
203- 'distro_series': '%s/%s*' % (osystem.name, release),
204- })
205- self.assertFalse(form.is_valid())
206- self.assertItemsEqual(['license_key'], form._errors.keys())
207-
208- def test_calls_validate_license_key(self):
209- osystem = make_usable_osystem(self)
210- release = osystem.get_default_release()
211- self.patch(osystem, 'requires_license_key').return_value = True
212- mock_validate = self.patch(osystem, 'validate_license_key')
213- mock_validate.return_value = True
214- form = NodeForm(data={
215- 'hostname': factory.make_name('host'),
216- 'architecture': make_usable_architecture(self),
217- 'osystem': osystem.name,
218- 'distro_series': '%s/%s*' % (osystem.name, release),
219- 'license_key': factory.make_string(),
220- })
221- self.assertTrue(form.is_valid())
222- mock_validate.assert_called_once()
223-
224- def test_rejects_duplicate_fqdn_with_unmanaged_dns_on_one_nodegroup(self):
225- # If a host with a given hostname exists on a managed nodegroup,
226- # new nodes on unmanaged nodegroups with hostnames that match
227- # that FQDN will be rejected.
228- nodegroup = factory.make_node_group(
229- status=NODEGROUP_STATUS.ACCEPTED,
230- management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
231- node = factory.make_node(
232- hostname=factory.make_name("hostname"), nodegroup=nodegroup)
233- other_nodegroup = factory.make_node_group()
234- form = NodeForm(data={
235- 'nodegroup': other_nodegroup,
236- 'hostname': node.fqdn,
237- 'architecture': make_usable_architecture(self),
238- })
239- form.instance.nodegroup = other_nodegroup
240- self.assertFalse(form.is_valid())
241-
242- def test_rejects_duplicate_fqdn_on_same_nodegroup(self):
243- # If a node with a given FQDN exists on a managed nodegroup, new
244- # nodes on that nodegroup with duplicate FQDNs will be rejected.
245- nodegroup = factory.make_node_group(
246- status=NODEGROUP_STATUS.ACCEPTED,
247- management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
248- node = factory.make_node(
249- hostname=factory.make_name("hostname"), nodegroup=nodegroup)
250- form = NodeForm(data={
251- 'nodegroup': nodegroup,
252- 'hostname': node.fqdn,
253- 'architecture': make_usable_architecture(self),
254- })
255- form.instance.nodegroup = nodegroup
256- self.assertFalse(form.is_valid())
257-
258-
259-class TestAdminNodeForm(MAASServerTestCase):
260-
261- def test_AdminNodeForm_contains_limited_set_of_fields(self):
262- node = factory.make_node()
263- form = AdminNodeForm(instance=node)
264-
265- self.assertEqual(
266- [
267- 'hostname',
268- 'architecture',
269- 'osystem',
270- 'distro_series',
271- 'license_key',
272- 'disable_ipv4',
273- 'power_type',
274- 'power_parameters',
275- 'cpu_count',
276- 'memory',
277- 'storage',
278- 'zone',
279- ],
280- list(form.fields))
281-
282- def test_AdminNodeForm_initialises_zone(self):
283- # The zone field uses "to_field_name", so that it can refer to a zone
284- # by name instead of by ID. A bug in Django breaks initialisation
285- # from an instance: the field tries to initialise the field using a
286- # zone's ID instead of its name, and ends up reverting to the default.
287- # The code must work around this bug.
288- zone = factory.make_zone()
289- node = factory.make_node(zone=zone)
290- # We'll create a form that makes a change, but not to the zone.
291- data = {'hostname': factory.make_name('host')}
292- form = AdminNodeForm(instance=node, data=data)
293- # The Django bug would stop the initial field value from being set,
294- # but the workaround ensures that it is initialised.
295- self.assertEqual(zone.name, form.initial['zone'])
296-
297- def test_AdminNodeForm_changes_node(self):
298- node = factory.make_node()
299- zone = factory.make_zone()
300- hostname = factory.make_string()
301- power_type = factory.pick_power_type()
302- form = AdminNodeForm(
303- data={
304- 'hostname': hostname,
305- 'power_type': power_type,
306- 'architecture': make_usable_architecture(self),
307- 'zone': zone.name,
308- },
309- instance=node)
310- form.save()
311-
312- node = reload_object(node)
313- self.assertEqual(
314- (node.hostname, node.power_type, node.zone),
315- (hostname, power_type, zone))
316-
317- def test_AdminNodeForm_populates_power_type_choices(self):
318- form = AdminNodeForm()
319- self.assertEqual(
320- [''] + [choice[0] for choice in get_power_type_choices()],
321- [choice[0] for choice in form.fields['power_type'].choices])
322-
323- def test_AdminNodeForm_populates_power_type_initial(self):
324- node = factory.make_node()
325- form = AdminNodeForm(instance=node)
326- self.assertEqual(node.power_type, form.fields['power_type'].initial)
327-
328- def test_AdminNodeForm_changes_node_with_skip_check(self):
329- node = factory.make_node()
330- hostname = factory.make_string()
331- power_type = factory.pick_power_type()
332- power_parameters_field = factory.make_string()
333- arch = make_usable_architecture(self)
334- form = AdminNodeForm(
335- data={
336- 'hostname': hostname,
337- 'architecture': arch,
338- 'power_type': power_type,
339- 'power_parameters_field': power_parameters_field,
340- 'power_parameters_skip_check': True,
341- },
342- instance=node)
343- form.save()
344-
345- self.assertEqual(
346- (hostname, power_type, {'field': power_parameters_field}),
347- (node.hostname, node.power_type, node.power_parameters))
348-
349- def test_AdminForm_does_not_permit_nodegroup_change(self):
350- # We had to make Node.nodegroup editable to get Django to
351- # validate it as non-blankable, but that doesn't mean that we
352- # actually want to allow people to edit it through API or UI.
353- old_nodegroup = factory.make_node_group()
354- node = factory.make_node(
355- nodegroup=old_nodegroup,
356- architecture=make_usable_architecture(self))
357- new_nodegroup = factory.make_node_group()
358- AdminNodeForm(data={'nodegroup': new_nodegroup}, instance=node).save()
359- # The form saved without error, but the nodegroup change was ignored.
360- self.assertEqual(old_nodegroup, node.nodegroup)
361-
362-
363 class TestMergeErrorMessages(MAASServerTestCase):
364
365 def test_merge_error_messages_returns_summary_message(self):
366
367=== added file 'src/maasserver/tests/test_forms_node.py'
368--- src/maasserver/tests/test_forms_node.py 1970-01-01 00:00:00 +0000
369+++ src/maasserver/tests/test_forms_node.py 2014-08-26 07:57:29 +0000
370@@ -0,0 +1,352 @@
371+# Copyright 2014 Canonical Ltd. This software is licensed under the
372+# GNU Affero General Public License version 3 (see the file LICENSE).
373+
374+"""Tests for node forms."""
375+
376+from __future__ import (
377+ absolute_import,
378+ print_function,
379+ unicode_literals,
380+ )
381+
382+str = None
383+
384+__metaclass__ = type
385+__all__ = []
386+
387+from maasserver.clusterrpc.power_parameters import get_power_type_choices
388+from maasserver.enum import (
389+ NODEGROUP_STATUS,
390+ NODEGROUPINTERFACE_MANAGEMENT,
391+ )
392+from maasserver.forms import (
393+ AdminNodeForm,
394+ BLANK_CHOICE,
395+ NO_ARCHITECTURES_AVAILABLE,
396+ NodeForm,
397+ pick_default_architecture,
398+ )
399+from maasserver.testing.architecture import (
400+ make_usable_architecture,
401+ patch_usable_architectures,
402+ )
403+from maasserver.testing.factory import factory
404+from maasserver.testing.orm import reload_object
405+from maasserver.testing.osystems import (
406+ make_osystem_with_releases,
407+ make_usable_osystem,
408+ patch_usable_osystems,
409+ )
410+from maasserver.testing.testcase import MAASServerTestCase
411+
412+
413+class TestNodeForm(MAASServerTestCase):
414+
415+ def test_contains_limited_set_of_fields(self):
416+ form = NodeForm()
417+
418+ self.assertEqual(
419+ [
420+ 'hostname',
421+ 'architecture',
422+ 'osystem',
423+ 'distro_series',
424+ 'license_key',
425+ 'disable_ipv4',
426+ 'nodegroup',
427+ ], list(form.fields))
428+
429+ def test_changes_node(self):
430+ node = factory.make_node()
431+ hostname = factory.make_string()
432+ patch_usable_architectures(self, [node.architecture])
433+
434+ form = NodeForm(
435+ data={
436+ 'hostname': hostname,
437+ 'architecture': make_usable_architecture(self),
438+ },
439+ instance=node)
440+ form.save()
441+
442+ self.assertEqual(hostname, node.hostname)
443+
444+ def test_accepts_usable_architecture(self):
445+ arch = make_usable_architecture(self)
446+ form = NodeForm(data={
447+ 'hostname': factory.make_name('host'),
448+ 'architecture': arch,
449+ })
450+ self.assertTrue(form.is_valid(), form._errors)
451+
452+ def test_rejects_unusable_architecture(self):
453+ patch_usable_architectures(self)
454+ form = NodeForm(data={
455+ 'hostname': factory.make_name('host'),
456+ 'architecture': factory.make_name('arch'),
457+ })
458+ self.assertFalse(form.is_valid())
459+ self.assertItemsEqual(['architecture'], form._errors.keys())
460+
461+ def test_starts_with_default_architecture(self):
462+ arches = sorted([factory.make_name('arch') for _ in range(5)])
463+ patch_usable_architectures(self, arches)
464+ form = NodeForm()
465+ self.assertEqual(
466+ pick_default_architecture(arches),
467+ form.fields['architecture'].initial)
468+
469+ def test_adds_blank_default_when_no_arches_available(self):
470+ patch_usable_architectures(self, [])
471+ form = NodeForm()
472+ self.assertEqual(
473+ [BLANK_CHOICE],
474+ form.fields['architecture'].choices)
475+
476+ def test_adds_error_when_no_arches_available(self):
477+ patch_usable_architectures(self, [])
478+ form = NodeForm()
479+ self.assertFalse(form.is_valid())
480+ self.assertEqual(
481+ [NO_ARCHITECTURES_AVAILABLE],
482+ form.errors['architecture'])
483+
484+ def test_accepts_osystem(self):
485+ osystem = make_usable_osystem(self)
486+ form = NodeForm(data={
487+ 'hostname': factory.make_name('host'),
488+ 'architecture': make_usable_architecture(self),
489+ 'osystem': osystem.name,
490+ })
491+ self.assertTrue(form.is_valid(), form._errors)
492+
493+ def test_rejects_invalid_osystem(self):
494+ patch_usable_osystems(self)
495+ form = NodeForm(data={
496+ 'hostname': factory.make_name('host'),
497+ 'architecture': make_usable_architecture(self),
498+ 'osystem': factory.make_name('os'),
499+ })
500+ self.assertFalse(form.is_valid())
501+ self.assertItemsEqual(['osystem'], form._errors.keys())
502+
503+ def test_starts_with_default_osystem(self):
504+ osystems = [make_osystem_with_releases(self) for _ in range(5)]
505+ patch_usable_osystems(self, osystems)
506+ form = NodeForm()
507+ self.assertEqual(
508+ '',
509+ form.fields['osystem'].initial)
510+
511+ def test_accepts_osystem_distro_series(self):
512+ osystem = make_usable_osystem(self)
513+ release = osystem.get_default_release()
514+ form = NodeForm(data={
515+ 'hostname': factory.make_name('host'),
516+ 'architecture': make_usable_architecture(self),
517+ 'osystem': osystem.name,
518+ 'distro_series': '%s/%s' % (osystem.name, release),
519+ })
520+ self.assertTrue(form.is_valid(), form._errors)
521+
522+ def test_rejects_invalid_osystem_distro_series(self):
523+ osystem = make_usable_osystem(self)
524+ release = factory.make_name('release')
525+ form = NodeForm(data={
526+ 'hostname': factory.make_name('host'),
527+ 'architecture': make_usable_architecture(self),
528+ 'osystem': osystem.name,
529+ 'distro_series': '%s/%s' % (osystem.name, release),
530+ })
531+ self.assertFalse(form.is_valid())
532+ self.assertItemsEqual(['distro_series'], form._errors.keys())
533+
534+ def test_starts_with_default_distro_series(self):
535+ osystems = [make_osystem_with_releases(self) for _ in range(5)]
536+ patch_usable_osystems(self, osystems)
537+ form = NodeForm()
538+ self.assertEqual(
539+ '',
540+ form.fields['distro_series'].initial)
541+
542+ def test_rejects_mismatch_osystem_distro_series(self):
543+ osystem = make_usable_osystem(self)
544+ release = osystem.get_default_release()
545+ invalid = factory.make_name('invalid_os')
546+ form = NodeForm(data={
547+ 'hostname': factory.make_name('host'),
548+ 'architecture': make_usable_architecture(self),
549+ 'osystem': osystem.name,
550+ 'distro_series': '%s/%s' % (invalid, release),
551+ })
552+ self.assertFalse(form.is_valid())
553+ self.assertItemsEqual(['distro_series'], form._errors.keys())
554+
555+ def test_rejects_missing_license_key(self):
556+ osystem = make_usable_osystem(self)
557+ release = osystem.get_default_release()
558+ self.patch(osystem, 'requires_license_key').return_value = True
559+ mock_validate = self.patch(osystem, 'validate_license_key')
560+ mock_validate.return_value = True
561+ form = NodeForm(data={
562+ 'hostname': factory.make_name('host'),
563+ 'architecture': make_usable_architecture(self),
564+ 'osystem': osystem.name,
565+ 'distro_series': '%s/%s*' % (osystem.name, release),
566+ })
567+ self.assertFalse(form.is_valid())
568+ self.assertItemsEqual(['license_key'], form._errors.keys())
569+
570+ def test_calls_validate_license_key(self):
571+ osystem = make_usable_osystem(self)
572+ release = osystem.get_default_release()
573+ self.patch(osystem, 'requires_license_key').return_value = True
574+ mock_validate = self.patch(osystem, 'validate_license_key')
575+ mock_validate.return_value = True
576+ form = NodeForm(data={
577+ 'hostname': factory.make_name('host'),
578+ 'architecture': make_usable_architecture(self),
579+ 'osystem': osystem.name,
580+ 'distro_series': '%s/%s*' % (osystem.name, release),
581+ 'license_key': factory.make_string(),
582+ })
583+ self.assertTrue(form.is_valid())
584+ mock_validate.assert_called_once()
585+
586+ def test_rejects_duplicate_fqdn_with_unmanaged_dns_on_one_nodegroup(self):
587+ # If a host with a given hostname exists on a managed nodegroup,
588+ # new nodes on unmanaged nodegroups with hostnames that match
589+ # that FQDN will be rejected.
590+ nodegroup = factory.make_node_group(
591+ status=NODEGROUP_STATUS.ACCEPTED,
592+ management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
593+ node = factory.make_node(
594+ hostname=factory.make_name("hostname"), nodegroup=nodegroup)
595+ other_nodegroup = factory.make_node_group()
596+ form = NodeForm(data={
597+ 'nodegroup': other_nodegroup,
598+ 'hostname': node.fqdn,
599+ 'architecture': make_usable_architecture(self),
600+ })
601+ form.instance.nodegroup = other_nodegroup
602+ self.assertFalse(form.is_valid())
603+
604+ def test_rejects_duplicate_fqdn_on_same_nodegroup(self):
605+ # If a node with a given FQDN exists on a managed nodegroup, new
606+ # nodes on that nodegroup with duplicate FQDNs will be rejected.
607+ nodegroup = factory.make_node_group(
608+ status=NODEGROUP_STATUS.ACCEPTED,
609+ management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
610+ node = factory.make_node(
611+ hostname=factory.make_name("hostname"), nodegroup=nodegroup)
612+ form = NodeForm(data={
613+ 'nodegroup': nodegroup,
614+ 'hostname': node.fqdn,
615+ 'architecture': make_usable_architecture(self),
616+ })
617+ form.instance.nodegroup = nodegroup
618+ self.assertFalse(form.is_valid())
619+
620+
621+class TestAdminNodeForm(MAASServerTestCase):
622+
623+ def test_AdminNodeForm_contains_limited_set_of_fields(self):
624+ node = factory.make_node()
625+ form = AdminNodeForm(instance=node)
626+
627+ self.assertEqual(
628+ [
629+ 'hostname',
630+ 'architecture',
631+ 'osystem',
632+ 'distro_series',
633+ 'license_key',
634+ 'disable_ipv4',
635+ 'power_type',
636+ 'power_parameters',
637+ 'cpu_count',
638+ 'memory',
639+ 'storage',
640+ 'zone',
641+ ],
642+ list(form.fields))
643+
644+ def test_AdminNodeForm_initialises_zone(self):
645+ # The zone field uses "to_field_name", so that it can refer to a zone
646+ # by name instead of by ID. A bug in Django breaks initialisation
647+ # from an instance: the field tries to initialise the field using a
648+ # zone's ID instead of its name, and ends up reverting to the default.
649+ # The code must work around this bug.
650+ zone = factory.make_zone()
651+ node = factory.make_node(zone=zone)
652+ # We'll create a form that makes a change, but not to the zone.
653+ data = {'hostname': factory.make_name('host')}
654+ form = AdminNodeForm(instance=node, data=data)
655+ # The Django bug would stop the initial field value from being set,
656+ # but the workaround ensures that it is initialised.
657+ self.assertEqual(zone.name, form.initial['zone'])
658+
659+ def test_AdminNodeForm_changes_node(self):
660+ node = factory.make_node()
661+ zone = factory.make_zone()
662+ hostname = factory.make_string()
663+ power_type = factory.pick_power_type()
664+ form = AdminNodeForm(
665+ data={
666+ 'hostname': hostname,
667+ 'power_type': power_type,
668+ 'architecture': make_usable_architecture(self),
669+ 'zone': zone.name,
670+ },
671+ instance=node)
672+ form.save()
673+
674+ node = reload_object(node)
675+ self.assertEqual(
676+ (node.hostname, node.power_type, node.zone),
677+ (hostname, power_type, zone))
678+
679+ def test_AdminNodeForm_populates_power_type_choices(self):
680+ form = AdminNodeForm()
681+ self.assertEqual(
682+ [''] + [choice[0] for choice in get_power_type_choices()],
683+ [choice[0] for choice in form.fields['power_type'].choices])
684+
685+ def test_AdminNodeForm_populates_power_type_initial(self):
686+ node = factory.make_node()
687+ form = AdminNodeForm(instance=node)
688+ self.assertEqual(node.power_type, form.fields['power_type'].initial)
689+
690+ def test_AdminNodeForm_changes_node_with_skip_check(self):
691+ node = factory.make_node()
692+ hostname = factory.make_string()
693+ power_type = factory.pick_power_type()
694+ power_parameters_field = factory.make_string()
695+ arch = make_usable_architecture(self)
696+ form = AdminNodeForm(
697+ data={
698+ 'hostname': hostname,
699+ 'architecture': arch,
700+ 'power_type': power_type,
701+ 'power_parameters_field': power_parameters_field,
702+ 'power_parameters_skip_check': True,
703+ },
704+ instance=node)
705+ form.save()
706+
707+ self.assertEqual(
708+ (hostname, power_type, {'field': power_parameters_field}),
709+ (node.hostname, node.power_type, node.power_parameters))
710+
711+ def test_AdminForm_does_not_permit_nodegroup_change(self):
712+ # We had to make Node.nodegroup editable to get Django to
713+ # validate it as non-blankable, but that doesn't mean that we
714+ # actually want to allow people to edit it through API or UI.
715+ old_nodegroup = factory.make_node_group()
716+ node = factory.make_node(
717+ nodegroup=old_nodegroup,
718+ architecture=make_usable_architecture(self))
719+ new_nodegroup = factory.make_node_group()
720+ AdminNodeForm(data={'nodegroup': new_nodegroup}, instance=node).save()
721+ # The form saved without error, but the nodegroup change was ignored.
722+ self.assertEqual(old_nodegroup, node.nodegroup)