~lutostag/ubuntu/trusty/maas/1.5.4

« back to all changes in this revision

Viewing changes to src/maasserver/views/tests/test_zones.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2014-02-15 12:08:23 UTC
  • mto: This revision was merged to the branch mainline in revision 48.
  • Revision ID: package-import@ubuntu.com-20140215120823-u7dkitfy0h8tbruh
Tags: upstream-1.5+bzr1948
ImportĀ upstreamĀ versionĀ 1.5+bzr1948

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2013-2014 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Test maasserver zones views."""
 
5
 
 
6
from __future__ import (
 
7
    absolute_import,
 
8
    print_function,
 
9
    unicode_literals,
 
10
    )
 
11
 
 
12
str = None
 
13
 
 
14
__metaclass__ = type
 
15
__all__ = []
 
16
 
 
17
 
 
18
import httplib
 
19
from urllib import urlencode
 
20
 
 
21
from django.core.exceptions import ValidationError
 
22
from django.core.urlresolvers import reverse
 
23
from lxml.html import fromstring
 
24
from maasserver.models import Zone
 
25
from maasserver.models.zone import DEFAULT_ZONE_NAME
 
26
from maasserver.testing import (
 
27
    extract_redirect,
 
28
    get_content_links,
 
29
    reload_object,
 
30
    )
 
31
from maasserver.testing.factory import factory
 
32
from maasserver.testing.testcase import MAASServerTestCase
 
33
from maasserver.views.zones import ZoneAdd
 
34
from testtools.matchers import (
 
35
    Contains,
 
36
    ContainsAll,
 
37
    Equals,
 
38
    MatchesAll,
 
39
    Not,
 
40
    )
 
41
 
 
42
 
 
43
class ZoneListingViewTest(MAASServerTestCase):
 
44
 
 
45
    def test_zone_list_link_present_on_homepage(self):
 
46
        self.client_log_in()
 
47
        response = self.client.get(reverse('index'))
 
48
        zone_list_link = reverse('zone-list')
 
49
        self.assertIn(
 
50
            zone_list_link,
 
51
            get_content_links(response, element='#main-nav'))
 
52
 
 
53
    def test_zone_list_displays_zone_details(self):
 
54
        # Zone listing displays the zone name and the zone description.
 
55
        self.client_log_in()
 
56
        [factory.make_zone() for i in range(3)]
 
57
        zones = Zone.objects.all()
 
58
        response = self.client.get(reverse('zone-list'))
 
59
        zone_names = [zone.name for zone in zones]
 
60
        truncated_zone_descriptions = [
 
61
            zone.description[:20] for zone in zones]
 
62
        self.assertThat(response.content, ContainsAll(zone_names))
 
63
        self.assertThat(
 
64
            response.content, ContainsAll(truncated_zone_descriptions))
 
65
 
 
66
    def test_zone_list_displays_sorted_list_of_zones(self):
 
67
        # Zones are alphabetically sorted on the zone list page.
 
68
        self.client_log_in()
 
69
        [factory.make_zone() for i in range(3)]
 
70
        zones = Zone.objects.all()
 
71
        sorted_zones = sorted(zones, key=lambda x: x.name.lower())
 
72
        response = self.client.get(reverse('zone-list'))
 
73
        zone_links = [
 
74
            reverse('zone-view', args=[zone.name])
 
75
            for zone in sorted_zones]
 
76
        self.assertEqual(
 
77
            zone_links,
 
78
            [link for link in get_content_links(response)
 
79
                if link.startswith('/zones/')])
 
80
 
 
81
    def test_zone_list_displays_links_to_zone_node(self):
 
82
        self.client_log_in()
 
83
        [factory.make_zone() for i in range(3)]
 
84
        zones = Zone.objects.all()
 
85
        sorted_zones = sorted(zones, key=lambda x: x.name.lower())
 
86
        response = self.client.get(reverse('zone-list'))
 
87
        zone_node_links = [
 
88
            reverse('node-list') + "?" +
 
89
            urlencode({'query': 'zone=%s' % zone.name})
 
90
            for zone in sorted_zones]
 
91
        self.assertEqual(
 
92
            zone_node_links,
 
93
            [link for link in get_content_links(response)
 
94
                if link.startswith('/nodes/')])
 
95
 
 
96
 
 
97
class ZoneListingViewTestNonAdmin(MAASServerTestCase):
 
98
 
 
99
    def test_zone_list_does_not_contain_edit_and_delete_links(self):
 
100
        self.client_log_in()
 
101
        zones = [factory.make_zone() for i in range(3)]
 
102
        response = self.client.get(reverse('zone-list'))
 
103
        zone_edit_links = [
 
104
            reverse('zone-edit', args=[zone.name]) for zone in zones]
 
105
        zone_delete_links = [
 
106
            reverse('zone-del', args=[zone.name]) for zone in zones]
 
107
        all_links = get_content_links(response)
 
108
        self.assertThat(
 
109
            all_links,
 
110
            MatchesAll(*[Not(Equals(link)) for link in zone_edit_links]))
 
111
        self.assertThat(
 
112
            all_links,
 
113
            MatchesAll(*[Not(Equals(link)) for link in zone_delete_links]))
 
114
 
 
115
    def test_zone_list_does_not_contain_add_link(self):
 
116
        self.client_log_in()
 
117
        response = self.client.get(reverse('zone-list'))
 
118
        add_link = reverse('zone-add')
 
119
        self.assertNotIn(add_link, get_content_links(response))
 
120
 
 
121
 
 
122
class ZoneListingViewTestAdmin(MAASServerTestCase):
 
123
 
 
124
    def test_zone_list_contains_edit_links(self):
 
125
        self.client_log_in(as_admin=True)
 
126
        zones = [factory.make_zone() for i in range(3)]
 
127
        default_zone = Zone.objects.get_default_zone()
 
128
        zone_edit_links = [
 
129
            reverse('zone-edit', args=[zone.name]) for zone in zones]
 
130
        zone_delete_links = [
 
131
            reverse('zone-del', args=[zone.name]) for zone in zones]
 
132
        zone_default_edit = reverse('zone-edit', args=[default_zone])
 
133
        zone_default_delete = reverse('zone-del', args=[default_zone])
 
134
 
 
135
        response = self.client.get(reverse('zone-list'))
 
136
        all_links = get_content_links(response)
 
137
 
 
138
        self.assertThat(all_links, ContainsAll(
 
139
            zone_edit_links + zone_delete_links))
 
140
        self.assertThat(all_links, Not(Contains(zone_default_edit)))
 
141
        self.assertThat(all_links, Not(Contains(zone_default_delete)))
 
142
 
 
143
    def test_zone_list_contains_add_link(self):
 
144
        self.client_log_in(as_admin=True)
 
145
        response = self.client.get(reverse('zone-list'))
 
146
        add_link = reverse('zone-add')
 
147
        self.assertIn(add_link, get_content_links(response))
 
148
 
 
149
 
 
150
class ZoneAddTestNonAdmin(MAASServerTestCase):
 
151
 
 
152
    def test_cannot_add_zone(self):
 
153
        self.client_log_in()
 
154
        name = factory.make_name('zone')
 
155
        response = self.client.post(reverse('zone-add'), {'name': name})
 
156
        # This returns an inappropriate response (302 FOUND, redirect to the
 
157
        # login page; should be 403 FORBIDDEN) but does not actually create the
 
158
        # zone, and that's the main thing.
 
159
        self.assertEqual(reverse('login'), extract_redirect(response))
 
160
        self.assertEqual([], list(Zone.objects.filter(name=name)))
 
161
 
 
162
 
 
163
class ZoneAddTestAdmin(MAASServerTestCase):
 
164
 
 
165
    def test_adds_zone(self):
 
166
        self.client_log_in(as_admin=True)
 
167
        definition = {
 
168
            'name': factory.make_name('zone'),
 
169
            'description': factory.getRandomString(),
 
170
        }
 
171
        response = self.client.post(reverse('zone-add'), definition)
 
172
        self.assertEqual(httplib.FOUND, response.status_code)
 
173
        zone = Zone.objects.get(name=definition['name'])
 
174
        self.assertEqual(definition['description'], zone.description)
 
175
        self.assertEqual(reverse('zone-list'), extract_redirect(response))
 
176
 
 
177
    def test_description_is_optional(self):
 
178
        self.client_log_in(as_admin=True)
 
179
        name = factory.make_name('zone')
 
180
        response = self.client.post(reverse('zone-add'), {'name': name})
 
181
        self.assertEqual(httplib.FOUND, response.status_code)
 
182
        zone = Zone.objects.get(name=name)
 
183
        self.assertEqual('', zone.description)
 
184
 
 
185
    def test_get_success_url_returns_valid_url(self):
 
186
        self.client_log_in(as_admin=True)
 
187
        url = ZoneAdd().get_success_url()
 
188
        self.assertIn("/zones", url)
 
189
 
 
190
 
 
191
class ZoneDetailViewTest(MAASServerTestCase):
 
192
 
 
193
    def test_zone_detail_displays_zone_detail(self):
 
194
        # The Zone detail view displays the zone name and the zone
 
195
        # description.
 
196
        self.client_log_in()
 
197
        zone = factory.make_zone()
 
198
        response = self.client.get(reverse('zone-view', args=[zone.name]))
 
199
        self.assertThat(response.content, Contains(zone.name))
 
200
        self.assertThat(
 
201
            response.content, Contains(zone.description))
 
202
 
 
203
    def test_zone_detail_displays_node_count(self):
 
204
        self.client_log_in()
 
205
        zone = factory.make_zone()
 
206
        node = factory.make_node()
 
207
        node.zone = zone
 
208
        response = self.client.get(reverse('zone-view', args=[zone.name]))
 
209
        document = fromstring(response.content)
 
210
        count_text = document.get_element_by_id("#nodecount").text_content()
 
211
        self.assertThat(
 
212
            count_text, Contains(unicode(zone.node_set.count())))
 
213
 
 
214
    def test_zone_detail_links_to_node_list(self):
 
215
        self.client_log_in()
 
216
        zone = factory.make_zone()
 
217
        node = factory.make_node()
 
218
        node.zone = zone
 
219
        response = self.client.get(reverse('zone-view', args=[zone.name]))
 
220
        zone_node_link = (
 
221
            reverse('node-list') + "?" +
 
222
            urlencode({'query': 'zone=%s' % zone.name}))
 
223
        all_links = get_content_links(response)
 
224
        self.assertIn(zone_node_link, all_links)
 
225
 
 
226
 
 
227
class ZoneDetailViewNonAdmin(MAASServerTestCase):
 
228
 
 
229
    def test_zone_detail_does_not_contain_edit_link(self):
 
230
        self.client_log_in()
 
231
        zone = factory.make_zone()
 
232
        response = self.client.get(reverse('zone-view', args=[zone.name]))
 
233
        zone_edit_link = reverse('zone-edit', args=[zone.name])
 
234
        self.assertNotIn(zone_edit_link, get_content_links(response))
 
235
 
 
236
    def test_zone_detail_does_not_contain_delete_link(self):
 
237
        self.client_log_in()
 
238
        zone = factory.make_zone()
 
239
        response = self.client.get(reverse('zone-view', args=[zone.name]))
 
240
        zone_delete_link = reverse('zone-del', args=[zone.name])
 
241
        self.assertNotIn(zone_delete_link, get_content_links(response))
 
242
 
 
243
 
 
244
class ZoneDetailViewAdmin(MAASServerTestCase):
 
245
 
 
246
    def test_zone_detail_contains_edit_link(self):
 
247
        self.client_log_in(as_admin=True)
 
248
        zone = factory.make_zone()
 
249
        response = self.client.get(reverse('zone-view', args=[zone.name]))
 
250
        zone_edit_link = reverse('zone-edit', args=[zone.name])
 
251
        self.assertIn(zone_edit_link, get_content_links(response))
 
252
 
 
253
    def test_zone_detail_contains_delete_link(self):
 
254
        self.client_log_in(as_admin=True)
 
255
        zone = factory.make_zone()
 
256
        response = self.client.get(reverse('zone-view', args=[zone.name]))
 
257
        zone_delete_link = reverse('zone-del', args=[zone.name])
 
258
        self.assertIn(zone_delete_link, get_content_links(response))
 
259
 
 
260
    def test_zone_detail_for_default_zone_does_not_contain_delete_link(self):
 
261
        self.client_log_in(as_admin=True)
 
262
        response = self.client.get(
 
263
            reverse('zone-view', args=[DEFAULT_ZONE_NAME]))
 
264
        zone_delete_link = reverse('zone-del', args=[DEFAULT_ZONE_NAME])
 
265
        self.assertNotIn(zone_delete_link, get_content_links(response))
 
266
 
 
267
 
 
268
class ZoneEditNonAdminTest(MAASServerTestCase):
 
269
 
 
270
    def test_cannot_access_zone_edit(self):
 
271
        self.client_log_in()
 
272
        zone = factory.make_zone()
 
273
        response = self.client.post(reverse('zone-edit', args=[zone.name]))
 
274
        self.assertEqual(reverse('login'), extract_redirect(response))
 
275
 
 
276
 
 
277
class ZoneEditAdminTest(MAASServerTestCase):
 
278
 
 
279
    def test_zone_edit(self):
 
280
        self.client_log_in(as_admin=True)
 
281
        zone = factory.make_zone()
 
282
        new_name = factory.make_name('name')
 
283
        new_description = factory.make_name('description')
 
284
        response = self.client.post(
 
285
            reverse('zone-edit', args=[zone.name]),
 
286
            data={
 
287
                'name': new_name,
 
288
                'description': new_description,
 
289
            })
 
290
        self.assertEqual(
 
291
            reverse('zone-list'), extract_redirect(response),
 
292
            response.content)
 
293
        zone = reload_object(zone)
 
294
        self.assertEqual(
 
295
            (new_name, new_description),
 
296
            (zone.name, zone.description),
 
297
        )
 
298
 
 
299
 
 
300
class ZoneDeleteNonAdminTest(MAASServerTestCase):
 
301
 
 
302
    def test_cannot_delete(self):
 
303
        self.client_log_in()
 
304
        zone = factory.make_zone()
 
305
        response = self.client.post(reverse('zone-del', args=[zone.name]))
 
306
        self.assertEqual(reverse('login'), extract_redirect(response))
 
307
        self.assertIsNotNone(reload_object(zone))
 
308
 
 
309
 
 
310
class ZoneDeleteAdminTest(MAASServerTestCase):
 
311
 
 
312
    def test_deletes_zone(self):
 
313
        self.client_log_in(as_admin=True)
 
314
        zone = factory.make_zone()
 
315
        response = self.client.post(
 
316
            reverse('zone-del', args=[zone.name]),
 
317
            {'post': 'yes'})
 
318
        self.assertEqual(httplib.FOUND, response.status_code)
 
319
        self.assertIsNone(reload_object(zone))
 
320
 
 
321
    def test_rejects_deletion_of_default_zone(self):
 
322
        self.client_log_in(as_admin=True)
 
323
        try:
 
324
            self.client.post(
 
325
                reverse('zone-del', args=[DEFAULT_ZONE_NAME]),
 
326
                {'post': 'yes'})
 
327
        except ValidationError:
 
328
            # Right now, this generates an error because the deletion
 
329
            # is prevented in the model code and not at the form level.
 
330
            # This is not so bad because we make sure that the deletion link
 
331
            # for the default zone isn't shown anywhere.
 
332
            # If we move validation to the form level, this exception goes
 
333
            # away and we'll have to check the HTTP response for a validation
 
334
            # failure.
 
335
            pass
 
336
 
 
337
        # The default zone is still there.
 
338
        self.assertIsNotNone(Zone.objects.get_default_zone())
 
339
 
 
340
    def test_redirects_to_listing(self):
 
341
        self.client_log_in(as_admin=True)
 
342
        zone = factory.make_zone()
 
343
        response = self.client.post(
 
344
            reverse('zone-del', args=[zone.name]),
 
345
            {'post': 'yes'})
 
346
        self.assertEqual(reverse('zone-list'), extract_redirect(response))
 
347
 
 
348
    def test_does_not_delete_nodes(self):
 
349
        self.client_log_in(as_admin=True)
 
350
        zone = factory.make_zone()
 
351
        node = factory.make_node(zone=zone)
 
352
        response = self.client.post(
 
353
            reverse('zone-del', args=[zone.name]),
 
354
            {'post': 'yes'})
 
355
        self.assertEqual(httplib.FOUND, response.status_code)
 
356
        self.assertIsNone(reload_object(zone))
 
357
        node = reload_object(node)
 
358
        self.assertIsNotNone(node)
 
359
        self.assertEqual(Zone.objects.get_default_zone(), node.zone)