~jose/charms/precise/juju-gui/add-blank-defaults

« back to all changes in this revision

Viewing changes to server/guiserver/tests/bundles/test_views.py

  • Committer: Nicola Larosa
  • Date: 2013-07-31 15:45:55 UTC
  • mto: (60.27.1 integrate-builtin-server-2)
  • mto: This revision was merged to the branch mainline in revision 75.
  • Revision ID: nicola.larosa@canonical.com-20130731154555-aekmw8ur2dxutawi
Fix and add tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# This file is part of the Juju GUI, which lets users view and manage Juju
2
 
# environments within a graphical interface (https://launchpad.net/juju-gui).
3
 
# Copyright (C) 2013 Canonical Ltd.
4
 
#
5
 
# This program is free software: you can redistribute it and/or modify it under
6
 
# the terms of the GNU Affero General Public License version 3, as published by
7
 
# the Free Software Foundation.
8
 
#
9
 
# This program is distributed in the hope that it will be useful, but WITHOUT
10
 
# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
11
 
# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 
# Affero General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU Affero General Public License
15
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 
 
17
 
"""Tests for the bundle deployment views."""
18
 
 
19
 
import mock
20
 
from tornado import concurrent
21
 
from tornado.testing import(
22
 
    AsyncTestCase,
23
 
    ExpectLog,
24
 
    gen_test,
25
 
    LogTrapTestCase,
26
 
)
27
 
 
28
 
from guiserver.bundles import views
29
 
from guiserver.tests import helpers
30
 
 
31
 
 
32
 
class ViewsTestMixin(object):
33
 
    """Base helpers and common tests for all the view tests.
34
 
 
35
 
    Subclasses must define a get_view() method returning the view function to
36
 
    be tested. Subclasses can also override the invalid_params and
37
 
    invalid_params_error attributes, used to test the view in the case the
38
 
    passed parameters are not valid.
39
 
    """
40
 
 
41
 
    invalid_params = {'No-such': 'parameter'}
42
 
    invalid_params_error = 'invalid request: invalid data parameters'
43
 
 
44
 
    def setUp(self):
45
 
        super(ViewsTestMixin, self).setUp()
46
 
        self.view = self.get_view()
47
 
        self.deployer = mock.Mock()
48
 
 
49
 
    def make_future(self, result):
50
 
        """Create and return a Future containing the given result."""
51
 
        future = concurrent.Future()
52
 
        future.set_result(result)
53
 
        return future
54
 
 
55
 
    @gen_test
56
 
    def test_not_authenticated(self):
57
 
        # An error response is returned if the user is not authenticated.
58
 
        request = self.make_view_request(is_authenticated=False)
59
 
        expected_log = 'deployer: unauthorized access: no user logged in'
60
 
        with ExpectLog('', expected_log, required=True):
61
 
            response = yield self.view(request, self.deployer)
62
 
        expected_response = {
63
 
            'Response': {},
64
 
            'Error': 'unauthorized access: no user logged in',
65
 
        }
66
 
        self.assertEqual(expected_response, response)
67
 
        # The Deployer methods have not been called.
68
 
        self.assertEqual(0, len(self.deployer.mock_calls))
69
 
 
70
 
    @gen_test
71
 
    def test_invalid_parameters(self):
72
 
        # An error response is returned if the parameters in the request are
73
 
        # not valid.
74
 
        request = self.make_view_request(params=self.invalid_params)
75
 
        expected_log = 'deployer: {}'.format(self.invalid_params_error)
76
 
        with ExpectLog('', expected_log, required=True):
77
 
            response = yield self.view(request, self.deployer)
78
 
        expected_response = {
79
 
            'Response': {},
80
 
            'Error': self.invalid_params_error,
81
 
        }
82
 
        self.assertEqual(expected_response, response)
83
 
        # The Deployer methods have not been called.
84
 
        self.assertEqual(0, len(self.deployer.mock_calls))
85
 
 
86
 
 
87
 
class TestImportBundle(
88
 
        ViewsTestMixin, helpers.BundlesTestMixin, LogTrapTestCase,
89
 
        AsyncTestCase):
90
 
 
91
 
    def get_view(self):
92
 
        return views.import_bundle
93
 
 
94
 
    @gen_test
95
 
    def test_invalid_yaml(self):
96
 
        # An error response is returned if an invalid YAML encoded string is
97
 
        # passed.
98
 
        params = {'Name': 'bundle-name', 'YAML': 42}
99
 
        request = self.make_view_request(params=params)
100
 
        response = yield self.view(request, self.deployer)
101
 
        expected_response = {
102
 
            'Response': {},
103
 
            'Error': 'invalid request: invalid YAML contents: '
104
 
                     "'int' object has no attribute 'read'",
105
 
        }
106
 
        self.assertEqual(expected_response, response)
107
 
        # The Deployer methods have not been called.
108
 
        self.assertEqual(0, len(self.deployer.mock_calls))
109
 
 
110
 
    @gen_test
111
 
    def test_no_name_failure(self):
112
 
        # An error response is returned if the requested bundle name is not
113
 
        # provided and the YAML contents include multiple bundles
114
 
        params = {'YAML': 'bundle1: contents1\nbundle2: contents2'}
115
 
        request = self.make_view_request(params=params)
116
 
        response = yield self.view(request, self.deployer)
117
 
        expected_response = {
118
 
            'Response': {},
119
 
            'Error': 'invalid request: invalid data parameters: '
120
 
                     'no bundle name provided',
121
 
        }
122
 
        self.assertEqual(expected_response, response)
123
 
        # The Deployer methods have not been called.
124
 
        self.assertEqual(0, len(self.deployer.mock_calls))
125
 
 
126
 
    @gen_test
127
 
    def test_bundle_not_found(self):
128
 
        # An error response is returned if the requested bundle name is not
129
 
        # found in the bundle YAML contents.
130
 
        params = {'Name': 'no-such-bundle', 'YAML': 'mybundle: mycontents'}
131
 
        request = self.make_view_request(params=params)
132
 
        response = yield self.view(request, self.deployer)
133
 
        expected_response = {
134
 
            'Response': {},
135
 
            'Error': 'invalid request: bundle no-such-bundle not found',
136
 
        }
137
 
        self.assertEqual(expected_response, response)
138
 
        # The Deployer methods have not been called.
139
 
        self.assertEqual(0, len(self.deployer.mock_calls))
140
 
 
141
 
    @gen_test
142
 
    def test_invalid_bundle(self):
143
 
        # An error response is returned if the bundle is not well formed.
144
 
        params = {'Name': 'mybundle', 'YAML': 'mybundle: not valid'}
145
 
        request = self.make_view_request(params=params)
146
 
        response = yield self.view(request, self.deployer)
147
 
        expected_response = {
148
 
            'Response': {},
149
 
            'Error': 'invalid request: invalid bundle mybundle: '
150
 
                     'the bundle data is not well formed',
151
 
        }
152
 
        self.assertEqual(expected_response, response)
153
 
        # The Deployer methods have not been called.
154
 
        self.assertEqual(0, len(self.deployer.mock_calls))
155
 
 
156
 
    @gen_test
157
 
    def test_invalid_bundle_constraints(self):
158
 
        # An error response is returned if the bundle includes services with
159
 
        # unsupported constraints.
160
 
        params = {
161
 
            'Name': 'mybundle',
162
 
            'YAML': 'mybundle: {services: {django: {constraints: invalid=1}}}',
163
 
        }
164
 
        request = self.make_view_request(params=params)
165
 
        response = yield self.view(request, self.deployer)
166
 
        expected_response = {
167
 
            'Response': {},
168
 
            'Error': 'invalid request: invalid bundle mybundle: '
169
 
                     'unsupported constraints: invalid',
170
 
        }
171
 
        self.assertEqual(expected_response, response)
172
 
        # The Deployer methods have not been called.
173
 
        self.assertEqual(0, len(self.deployer.mock_calls))
174
 
 
175
 
    @gen_test
176
 
    def test_undeployable_bundle(self):
177
 
        # An error response is returned if the bundle cannot be imported in the
178
 
        # current Juju environment.
179
 
        params = {'Name': 'mybundle', 'YAML': 'mybundle: {services: {}}'}
180
 
        request = self.make_view_request(params=params)
181
 
        # Simulate an error returned by the Deployer validate method.
182
 
        self.deployer.validate.return_value = self.make_future('an error')
183
 
        # Execute the view.
184
 
        response = yield self.view(request, self.deployer)
185
 
        expected_response = {
186
 
            'Response': {},
187
 
            'Error': 'invalid request: an error',
188
 
        }
189
 
        self.assertEqual(expected_response, response)
190
 
        # The Deployer validate method has been called.
191
 
        self.deployer.validate.assert_called_once_with(
192
 
            request.user, 'mybundle', {'services': {}})
193
 
 
194
 
    @gen_test
195
 
    def test_success(self):
196
 
        # The response includes the deployment identifier.
197
 
        params = {'Name': 'mybundle', 'YAML': 'mybundle: {services: {}}'}
198
 
        request = self.make_view_request(params=params)
199
 
        # Set up the Deployer mock.
200
 
        self.deployer.validate.return_value = self.make_future(None)
201
 
        self.deployer.import_bundle.return_value = 42
202
 
        # Execute the view.
203
 
        response = yield self.view(request, self.deployer)
204
 
        expected_response = {'Response': {'DeploymentId': 42}}
205
 
        self.assertEqual(expected_response, response)
206
 
        # Ensure the Deployer methods have been correctly called.
207
 
        args = (request.user, 'mybundle', {'services': {}})
208
 
        self.deployer.validate.assert_called_once_with(*args)
209
 
        args = (request.user, 'mybundle', {'services': {}}, None)
210
 
        self.deployer.import_bundle.assert_called_once_with(*args)
211
 
 
212
 
    @gen_test
213
 
    def test_logging(self):
214
 
        # The beginning of the bundle import process is properly logged.
215
 
        params = {'Name': 'mybundle', 'YAML': 'mybundle: {services: {}}'}
216
 
        request = self.make_view_request(params=params)
217
 
        # Set up the Deployer mock.
218
 
        self.deployer.validate.return_value = self.make_future(None)
219
 
        self.deployer.import_bundle.return_value = 42
220
 
        # Execute the view.
221
 
        expected_log = "import_bundle: scheduling 'mybundle' deployment"
222
 
        with ExpectLog('', expected_log, required=True):
223
 
            yield self.view(request, self.deployer)
224
 
 
225
 
    # The following tests exercise views._validate_import_params directly.
226
 
    def test_no_name_success(self):
227
 
        # The process succeeds if the bundle name is not provided but the
228
 
        # YAML contents include just one bundle.
229
 
        params = {'YAML': 'mybundle: {services: {}}'}
230
 
        results = views._validate_import_params(params)
231
 
        expected = ('mybundle', {'services': {}}, None)
232
 
        self.assertEqual(expected, results)
233
 
 
234
 
    def test_id_provided(self):
235
 
        params = {'YAML': 'mybundle: {services: {}}',
236
 
                  'BundleID': '~jorge/wiki/3/smallwiki'}
237
 
        results = views._validate_import_params(params)
238
 
        expected = ('mybundle', {'services': {}}, '~jorge/wiki/3/smallwiki')
239
 
        self.assertEqual(expected, results)
240
 
 
241
 
    def test_id_and_name_provided(self):
242
 
        params = {'YAML': 'mybundle: {services: {}}',
243
 
                  'Name': 'mybundle',
244
 
                  'BundleID': '~jorge/wiki/3/smallwiki'}
245
 
        results = views._validate_import_params(params)
246
 
        expected = ('mybundle', {'services': {}}, '~jorge/wiki/3/smallwiki')
247
 
        self.assertEqual(expected, results)
248
 
 
249
 
    @gen_test
250
 
    def test_id_passed_to_deployer(self):
251
 
        params = {'YAML': 'mybundle: {services: {}}',
252
 
                  'Name': 'mybundle',
253
 
                  'BundleID': '~jorge/wiki/3/smallwiki'}
254
 
        request = self.make_view_request(params=params)
255
 
        # Set up the Deployer mock.
256
 
        self.deployer.validate.return_value = self.make_future(None)
257
 
        self.deployer.import_bundle.return_value = 42
258
 
        # Execute the view.
259
 
        yield self.view(request, self.deployer)
260
 
        # Ensure the Deployer methods have been correctly called.
261
 
        args = (request.user, 'mybundle', {'services': {}})
262
 
        self.deployer.validate.assert_called_once_with(*args)
263
 
        args = (request.user, 'mybundle', {'services': {}},
264
 
                '~jorge/wiki/3/smallwiki')
265
 
        self.deployer.import_bundle.assert_called_once_with(*args)
266
 
 
267
 
 
268
 
class TestWatch(
269
 
        ViewsTestMixin, helpers.BundlesTestMixin, LogTrapTestCase,
270
 
        AsyncTestCase):
271
 
 
272
 
    def get_view(self):
273
 
        return views.watch
274
 
 
275
 
    @gen_test
276
 
    def test_deployment_not_found(self):
277
 
        # An error response is returned if the deployment identifier is not
278
 
        # valid.
279
 
        request = self.make_view_request(params={'DeploymentId': 42})
280
 
        # Set up the Deployer mock.
281
 
        self.deployer.watch.return_value = None
282
 
        # Execute the view.
283
 
        response = yield self.view(request, self.deployer)
284
 
        expected_response = {
285
 
            'Response': {},
286
 
            'Error': 'invalid request: deployment not found',
287
 
        }
288
 
        self.assertEqual(expected_response, response)
289
 
        # Ensure the Deployer methods have been correctly called.
290
 
        self.deployer.watch.assert_called_once_with(42)
291
 
 
292
 
    @gen_test
293
 
    def test_success(self):
294
 
        # The response includes the watcher identifier.
295
 
        request = self.make_view_request(params={'DeploymentId': 42})
296
 
        # Set up the Deployer mock.
297
 
        self.deployer.watch.return_value = 47
298
 
        # Execute the view.
299
 
        response = yield self.view(request, self.deployer)
300
 
        expected_response = {'Response': {'WatcherId': 47}}
301
 
        self.assertEqual(expected_response, response)
302
 
        # Ensure the Deployer methods have been correctly called.
303
 
        self.deployer.watch.assert_called_once_with(42)
304
 
 
305
 
    @gen_test
306
 
    def test_logging(self):
307
 
        # The beginning of the bundle watch process is properly logged.
308
 
        request = self.make_view_request(params={'DeploymentId': 42})
309
 
        # Set up the Deployer mock.
310
 
        self.deployer.watch.return_value = 47
311
 
        # Execute the view.
312
 
        expected_log = 'watch: deployment 42 being observed by watcher 47'
313
 
        with ExpectLog('', expected_log, required=True):
314
 
            yield self.view(request, self.deployer)
315
 
 
316
 
 
317
 
class TestNext(
318
 
        ViewsTestMixin, helpers.BundlesTestMixin, LogTrapTestCase,
319
 
        AsyncTestCase):
320
 
 
321
 
    def get_view(self):
322
 
        return views.next
323
 
 
324
 
    @gen_test
325
 
    def test_invalid_watcher_identifier(self):
326
 
        # An error response is returned if the watcher identifier is not valid.
327
 
        request = self.make_view_request(params={'WatcherId': 42})
328
 
        # Set up the Deployer mock.
329
 
        self.deployer.next.return_value = self.make_future(None)
330
 
        # Execute the view.
331
 
        response = yield self.view(request, self.deployer)
332
 
        expected_response = {
333
 
            'Response': {},
334
 
            'Error': 'invalid request: invalid watcher identifier',
335
 
        }
336
 
        self.assertEqual(expected_response, response)
337
 
        # Ensure the Deployer methods have been correctly called.
338
 
        self.deployer.next.assert_called_once_with(42)
339
 
 
340
 
    @gen_test
341
 
    def test_success(self):
342
 
        # The response includes the deployment changes.
343
 
        request = self.make_view_request(params={'WatcherId': 42})
344
 
        # Set up the Deployer mock.
345
 
        changes = ['change1', 'change2']
346
 
        self.deployer.next.return_value = self.make_future(changes)
347
 
        # Execute the view.
348
 
        response = yield self.view(request, self.deployer)
349
 
        expected_response = {'Response': {'Changes': changes}}
350
 
        self.assertEqual(expected_response, response)
351
 
        # Ensure the Deployer methods have been correctly called.
352
 
        self.deployer.next.assert_called_once_with(42)
353
 
 
354
 
    @gen_test
355
 
    def test_logging(self):
356
 
        # The watcher next request is properly logged.
357
 
        request = self.make_view_request(params={'WatcherId': 42})
358
 
        # Set up the Deployer mock.
359
 
        changes = ['change1', 'change2']
360
 
        self.deployer.next.return_value = self.make_future(changes)
361
 
        # Execute the view.
362
 
        expected_request_log = 'next: requested changes for watcher 42'
363
 
        expected_response_log = 'next: returning changes for watcher 42'
364
 
        with ExpectLog('', expected_request_log, required=True):
365
 
            with ExpectLog('', expected_response_log, required=True):
366
 
                yield self.view(request, self.deployer)
367
 
 
368
 
 
369
 
class TestCancel(
370
 
        ViewsTestMixin, helpers.BundlesTestMixin, LogTrapTestCase,
371
 
        AsyncTestCase):
372
 
 
373
 
    def get_view(self):
374
 
        return views.cancel
375
 
 
376
 
    @gen_test
377
 
    def test_invalid_deployment(self):
378
 
        # An error response is returned if the deployment identifier is not
379
 
        # valid.
380
 
        request = self.make_view_request(params={'DeploymentId': 42})
381
 
        # Set up the Deployer mock.
382
 
        self.deployer.cancel.return_value = 'bad wolf'
383
 
        # Execute the view.
384
 
        response = yield self.view(request, self.deployer)
385
 
        expected_response = {
386
 
            'Response': {},
387
 
            'Error': 'invalid request: bad wolf',
388
 
        }
389
 
        self.assertEqual(expected_response, response)
390
 
        # Ensure the Deployer methods have been correctly called.
391
 
        self.deployer.cancel.assert_called_once_with(42)
392
 
 
393
 
    @gen_test
394
 
    def test_success(self):
395
 
        # An empty response is returned if everything is ok.
396
 
        request = self.make_view_request(params={'DeploymentId': 42})
397
 
        # Set up the Deployer mock.
398
 
        self.deployer.cancel.return_value = None
399
 
        # Execute the view.
400
 
        response = yield self.view(request, self.deployer)
401
 
        self.assertEqual({'Response': {}}, response)
402
 
        # Ensure the Deployer methods have been correctly called.
403
 
        self.deployer.cancel.assert_called_once_with(42)
404
 
 
405
 
    @gen_test
406
 
    def test_logging(self):
407
 
        # The bundle cancellation is properly logged.
408
 
        request = self.make_view_request(params={'DeploymentId': 42})
409
 
        # Set up the Deployer mock.
410
 
        self.deployer.cancel.return_value = None
411
 
        # Execute the view.
412
 
        expected_log = 'cancel: deployment 42 cancelled'
413
 
        with ExpectLog('', expected_log, required=True):
414
 
            yield self.view(request, self.deployer)
415
 
 
416
 
 
417
 
class TestStatus(
418
 
        ViewsTestMixin, helpers.BundlesTestMixin, LogTrapTestCase,
419
 
        AsyncTestCase):
420
 
 
421
 
    invalid_params_error = 'invalid request: invalid data parameters: No-such'
422
 
 
423
 
    def get_view(self):
424
 
        return views.status
425
 
 
426
 
    @gen_test
427
 
    def test_success(self):
428
 
        # The response includes the watcher identifier.
429
 
        request = self.make_view_request()
430
 
        # Set up the Deployer mock.
431
 
        last_changes = ['change1', 'change2']
432
 
        self.deployer.status.return_value = last_changes
433
 
        # Execute the view.
434
 
        response = yield self.view(request, self.deployer)
435
 
        expected_response = {'Response': {'LastChanges': last_changes}}
436
 
        self.assertEqual(expected_response, response)
437
 
        # Ensure the Deployer methods have been correctly called.
438
 
        self.deployer.status.assert_called_once_with()
439
 
 
440
 
    @gen_test
441
 
    def test_logging(self):
442
 
        # The status request is properly logged.
443
 
        request = self.make_view_request()
444
 
        # Set up the Deployer mock.
445
 
        self.deployer.status.return_value = []
446
 
        # Execute the view.
447
 
        expected_log = 'status: returning last changes'
448
 
        with ExpectLog('', expected_log, required=True):
449
 
            yield self.view(request, self.deployer)