~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/testing/tests/test_yuixhr.py

  • Committer: Gary Poster
  • Date: 2011-09-20 22:33:07 UTC
  • mto: This revision was merged to the branch mainline in revision 14015.
  • Revision ID: gary.poster@canonical.com-20110920223307-zt1kr1px2ixjg9mn
Add yui xhr integration test support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2011 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Tests for the lp.testing.yuixhr."""
 
5
 
 
6
__metaclass__ = type
 
7
 
 
8
 
 
9
import re
 
10
import os
 
11
from shutil import rmtree
 
12
import simplejson
 
13
import sys
 
14
import tempfile
 
15
import types
 
16
 
 
17
from storm.exceptions import DisconnectionError
 
18
from testtools.testcase import ExpectedException
 
19
import transaction
 
20
from zope.component import getUtility
 
21
from zope.interface.verify import verifyObject
 
22
from zope.publisher.interfaces import NotFound
 
23
from zope.publisher.interfaces.browser import IBrowserPublisher
 
24
from zope.publisher.interfaces.http import IResult
 
25
from zope.security.proxy import removeSecurityProxy
 
26
 
 
27
from canonical.config import config
 
28
from canonical.launchpad.webapp.interfaces import ILaunchpadRoot
 
29
from canonical.testing.layers import LaunchpadFunctionalLayer
 
30
 
 
31
from lp.registry.interfaces.product import IProductSet
 
32
from lp.testing import (
 
33
    TestCase,
 
34
    login,
 
35
    ANONYMOUS,
 
36
    )
 
37
from lp.testing.views import create_view
 
38
from lp.testing.yuixhr import setup
 
39
from lp.testing.tests import test_yuixhr_fixture
 
40
from lp.testing.publication import test_traverse
 
41
 
 
42
TEST_MODULE_NAME = '_lp_.tests'
 
43
 
 
44
 
 
45
def create_traversed_view(*args, **kwargs):
 
46
    login(ANONYMOUS)
 
47
    root = getUtility(ILaunchpadRoot)
 
48
    view = create_view(root, '+yuitest', *args, **kwargs)
 
49
    view.names = kwargs['path_info'].split('/')[2:]
 
50
    return view
 
51
 
 
52
 
 
53
class TestYUITestFixtureController(TestCase):
 
54
    layer = LaunchpadFunctionalLayer
 
55
 
 
56
    def test_provides_browserpublisher(self):
 
57
        root = getUtility(ILaunchpadRoot)
 
58
        view = create_view(root, '+yuitest')
 
59
        self.assertTrue(view, verifyObject(IBrowserPublisher, view))
 
60
 
 
61
    def test_traverse_stores_the_path(self):
 
62
        login(ANONYMOUS)
 
63
        object, view, request = test_traverse(
 
64
            'http://launchpad.dev/+yuitest/'
 
65
            'lib/lp/testing/tests/test_yuixhr_fixture.js')
 
66
        self.assertEquals(
 
67
            'lib/lp/testing/tests/test_yuixhr_fixture.js',
 
68
            removeSecurityProxy(view).traversed_path)
 
69
 
 
70
    def test_request_is_js(self):
 
71
        view = create_traversed_view(
 
72
            path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture.js')
 
73
        view.initialize()
 
74
        self.assertEquals(view.JAVASCRIPT, view.action)
 
75
 
 
76
    def test_request_is_html(self):
 
77
        view = create_traversed_view(
 
78
            path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture')
 
79
        view.initialize()
 
80
        self.assertEquals(view.HTML, view.action)
 
81
 
 
82
    def test_request_is_setup(self):
 
83
        view = create_traversed_view(
 
84
            path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture',
 
85
            form={'action': 'setup', 'fixtures': 'base_line'},
 
86
            method='POST')
 
87
        view.initialize()
 
88
        self.assertEquals(view.SETUP, view.action)
 
89
        self.assertEquals(['base_line'], view.fixtures)
 
90
 
 
91
    def test_request_is_teardown(self):
 
92
        view = create_traversed_view(
 
93
            path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture',
 
94
            form={'action': 'teardown', 'fixtures': 'base_line'},
 
95
            method='POST')
 
96
        view.initialize()
 
97
        self.assertEquals(view.TEARDOWN, view.action)
 
98
        self.assertEquals(['base_line'], view.fixtures)
 
99
 
 
100
    def test_page(self):
 
101
        view = create_traversed_view(
 
102
            path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture')
 
103
        view.initialize()
 
104
        content = view.page()
 
105
        self.assertTrue(content.startswith('<!DOCTYPE HTML'))
 
106
        self.assertTextMatchesExpressionIgnoreWhitespace(
 
107
            re.escape(
 
108
                'src="/+yuitest/lp/testing/tests/test_yuixhr_fixture.js"'),
 
109
            content)
 
110
 
 
111
    def test_render_javascript(self):
 
112
        top_dir = tempfile.mkdtemp()
 
113
        js_dir = os.path.join(top_dir, 'lib')
 
114
        os.mkdir(js_dir)
 
115
        true_root = config.root
 
116
        self.addCleanup(setattr, config, 'root', true_root)
 
117
        self.addCleanup(rmtree, top_dir)
 
118
        open(os.path.join(js_dir, 'foo.py'), 'w').close()
 
119
        js_file = open(os.path.join(js_dir, 'foo.js'), 'w')
 
120
        js_file.write('// javascript')
 
121
        js_file.close()
 
122
        config.root = top_dir
 
123
        view = create_traversed_view(path_info='/+yuitest/foo.js')
 
124
        content = view()
 
125
        self.assertEqual('// javascript', content.read())
 
126
        self.assertEqual(
 
127
            'text/javascript',
 
128
            view.request.response.getHeader('Content-Type'))
 
129
 
 
130
    def test_javascript_must_have_a_py_fixture(self):
 
131
        js_dir = tempfile.mkdtemp()
 
132
        true_root = config.root
 
133
        self.addCleanup(setattr, config, 'root', true_root)
 
134
        self.addCleanup(rmtree, js_dir)
 
135
        open(os.path.join(js_dir, 'foo.js'), 'w').close()
 
136
        config.root = js_dir
 
137
        view = create_traversed_view(path_info='/+yuitest/foo.js')
 
138
        with ExpectedException(NotFound, '.*'):
 
139
            view()
 
140
 
 
141
    def test_missing_javascript_raises_NotFound(self):
 
142
        js_dir = tempfile.mkdtemp()
 
143
        true_root = config.root
 
144
        self.addCleanup(setattr, config, 'root', true_root)
 
145
        self.addCleanup(rmtree, js_dir)
 
146
        open(os.path.join(js_dir, 'foo.py'), 'w').close()
 
147
        config.root = js_dir
 
148
        view = create_traversed_view(path_info='/+yuitest/foo')
 
149
        with ExpectedException(NotFound, '.*'):
 
150
            view()
 
151
 
 
152
    def test_render_html(self):
 
153
        view = create_traversed_view(
 
154
            path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture')
 
155
        content = view()
 
156
        self.assertTrue(content.startswith('<!DOCTYPE HTML'))
 
157
        self.assertEqual(
 
158
            'text/html',
 
159
            view.request.response.getHeader('Content-Type'))
 
160
 
 
161
    def test_get_fixtures(self):
 
162
        view = create_traversed_view(
 
163
            path_info='/+yuitest/lp/testing/tests/'
 
164
                      'test_yuixhr_fixture',
 
165
            form={'action': 'setup', 'fixtures': 'baseline'},
 
166
            method='POST')
 
167
        view.initialize()
 
168
        self.assertEquals(
 
169
            test_yuixhr_fixture._fixtures_, view.get_fixtures())
 
170
 
 
171
    def make_example_setup_function_module(self):
 
172
        module = types.ModuleType(TEST_MODULE_NAME)
 
173
        sys.modules[TEST_MODULE_NAME] = module
 
174
        self.addCleanup(lambda: sys.modules.pop(TEST_MODULE_NAME))
 
175
 
 
176
        def baseline(request, data):
 
177
            data['hi'] = 'world'
 
178
            data['called'] = ['baseline']
 
179
        baseline.__module__ = TEST_MODULE_NAME
 
180
        module.baseline = baseline
 
181
        return module
 
182
 
 
183
    def test_setup_decorator(self):
 
184
        module = self.make_example_setup_function_module()
 
185
        fixture = setup(module.baseline)
 
186
        self.assertTrue('_fixtures_' in module.__dict__)
 
187
        self.assertTrue('baseline' in module._fixtures_)
 
188
        self.assertEquals(fixture, module._fixtures_['baseline'])
 
189
        self.assertTrue(getattr(fixture, 'add_cleanup', None) is not None)
 
190
        self.assertTrue(getattr(fixture, 'teardown', None) is not None)
 
191
        self.assertTrue(getattr(fixture, 'extend', None) is not None)
 
192
 
 
193
    def test_do_setup(self):
 
194
        view = create_traversed_view(
 
195
            path_info='/+yuitest/lp/testing/tests/'
 
196
                      'test_yuixhr_fixture',
 
197
            form={'action': 'setup', 'fixtures': 'baseline'},
 
198
            method='POST')
 
199
        content = view()
 
200
        self.assertEqual({'hello': 'world'}, simplejson.loads(content))
 
201
        self.assertEqual(
 
202
            'application/json',
 
203
            view.request.response.getHeader('Content-Type'))
 
204
 
 
205
    def test_do_setup_data_returns_object_summaries(self):
 
206
        view = create_traversed_view(
 
207
            path_info='/+yuitest/lp/testing/tests/'
 
208
                      'test_yuixhr_fixture',
 
209
            form={'action': 'setup', 'fixtures': 'make_product'},
 
210
            method='POST')
 
211
        data = simplejson.loads(view())
 
212
        # The licenses is just an example.
 
213
        self.assertEqual(['GNU GPL v2'], data['product']['licenses'])
 
214
 
 
215
    def test_do_setup_data_object_summaries_are_redacted_if_necessary(self):
 
216
        view = create_traversed_view(
 
217
            path_info='/+yuitest/lp/testing/tests/'
 
218
                      'test_yuixhr_fixture',
 
219
            form={'action': 'setup', 'fixtures': 'make_product'},
 
220
            method='POST')
 
221
        data = simplejson.loads(view())
 
222
        self.assertEqual(
 
223
            'tag:launchpad.net:2008:redacted',
 
224
            data['product']['project_reviewed'])
 
225
 
 
226
    def test_do_setup_unproxied_data_object_summaries_are_redacted(self):
 
227
        view = create_traversed_view(
 
228
            path_info='/+yuitest/lp/testing/tests/'
 
229
                      'test_yuixhr_fixture',
 
230
            form={'action': 'setup', 'fixtures': 'naughty_make_product'},
 
231
            method='POST')
 
232
        data = simplejson.loads(view())
 
233
        self.assertEqual(
 
234
            'tag:launchpad.net:2008:redacted',
 
235
            data['product']['project_reviewed'])
 
236
 
 
237
    def test_do_setup_data_object_summaries_not_redacted_if_possible(self):
 
238
        view = create_traversed_view(
 
239
            path_info='/+yuitest/lp/testing/tests/'
 
240
                      'test_yuixhr_fixture',
 
241
            form={'action': 'setup', 'fixtures': 'make_product_loggedin'},
 
242
            method='POST')
 
243
        data = simplejson.loads(view())
 
244
        self.assertEqual(
 
245
            False,
 
246
            data['product']['project_reviewed'])
 
247
 
 
248
    def test_add_cleanup_decorator(self):
 
249
        fixture = setup(self.make_example_setup_function_module().baseline)
 
250
        result = []
 
251
 
 
252
        def my_teardown(request, data):
 
253
            result.append('foo')
 
254
        self.assertEquals(fixture, fixture.add_cleanup(my_teardown))
 
255
        fixture.teardown(None, None)
 
256
        self.assertEquals(['foo'], result)
 
257
 
 
258
    def test_add_cleanup_decorator_twice(self):
 
259
        fixture = setup(self.make_example_setup_function_module().baseline)
 
260
        result = []
 
261
 
 
262
        def my_teardown(request, data):
 
263
            result.append('foo')
 
264
 
 
265
        def my_other_teardown(request, data):
 
266
            result.append('bar')
 
267
        self.assertEquals(fixture, fixture.add_cleanup(my_teardown))
 
268
        self.assertEquals(fixture, fixture.add_cleanup(my_other_teardown))
 
269
        fixture.teardown(None, None)
 
270
        self.assertEquals(['bar', 'foo'], result)
 
271
 
 
272
    def test_do_teardown(self):
 
273
        del test_yuixhr_fixture._received[:]
 
274
        view = create_traversed_view(
 
275
            path_info='/+yuitest/lp/testing/tests/'
 
276
                      'test_yuixhr_fixture',
 
277
            form={'action': 'teardown', 'fixtures': 'baseline',
 
278
                  'data': simplejson.dumps({'bonjour': 'monde'})},
 
279
            method='POST')
 
280
        view.request.response.setResult(view())
 
281
        # The teardowns are called *before* the result is iterated.
 
282
        self.assertEqual(1, len(test_yuixhr_fixture._received))
 
283
        self.assertEqual(
 
284
            ('baseline', view.request, {'bonjour': 'monde'}),
 
285
            test_yuixhr_fixture._received[0])
 
286
        result = view.request.response.consumeBodyIter()
 
287
        self.assertProvides(result, IResult)
 
288
        self.assertEqual('\n', ''.join(result))
 
289
        self.assertEqual(
 
290
            '1',
 
291
            view.request.response.getHeader('Content-Length'))
 
292
        del test_yuixhr_fixture._received[:] # Cleanup
 
293
 
 
294
    def test_do_teardown_resets_database_only_after_request_completes(self):
 
295
        view = create_traversed_view(
 
296
            path_info='/+yuitest/lp/testing/tests/'
 
297
                      'test_yuixhr_fixture',
 
298
            form={'action': 'setup', 'fixtures': 'make_product'},
 
299
            method='POST')
 
300
        data = view()
 
301
        # Committing the transaction makes sure that we are not just seeing
 
302
        # the effect of an abort, below.
 
303
        transaction.commit()
 
304
        name = simplejson.loads(data)['product']['name']
 
305
        products = getUtility(IProductSet)
 
306
        # The new product exists after the setup.
 
307
        self.assertFalse(products.getByName(name) is None)
 
308
        view = create_traversed_view(
 
309
            path_info='/+yuitest/lp/testing/tests/'
 
310
                      'test_yuixhr_fixture',
 
311
            form={'action': 'teardown', 'fixtures': 'make_product',
 
312
                  'data': data},
 
313
            method='POST')
 
314
        view.request.response.setResult(view())
 
315
        # The product still exists after the teardown has been called.
 
316
        self.assertFalse(products.getByName(name) is None)
 
317
        # Iterating over the result causes the database to be reset.
 
318
        ''.join(view.request.response.consumeBodyIter())
 
319
        # The database is disconnected now.
 
320
        self.assertRaises(
 
321
            DisconnectionError,
 
322
            products.getByName, name)
 
323
        # If we abort the transaction...
 
324
        transaction.abort()
 
325
        # ...we see that the product is gone: the database has been reset.
 
326
        self.assertTrue(products.getByName(name) is None)
 
327
 
 
328
    def test_do_teardown_multiple(self):
 
329
        # Teardown should call fixtures in reverse order.
 
330
        del test_yuixhr_fixture._received[:]
 
331
        view = create_traversed_view(
 
332
            path_info='/+yuitest/lp/testing/tests/'
 
333
                      'test_yuixhr_fixture',
 
334
            form={'action': 'teardown', 'fixtures': 'baseline,second',
 
335
                  'data': simplejson.dumps({'bonjour': 'monde'})},
 
336
            method='POST')
 
337
        view()
 
338
        self.assertEqual(
 
339
            'second', test_yuixhr_fixture._received[0][0])
 
340
        self.assertEqual(
 
341
            'baseline', test_yuixhr_fixture._received[1][0])
 
342
        del test_yuixhr_fixture._received[:]
 
343
 
 
344
    def test_extend_decorator_setup(self):
 
345
        module = self.make_example_setup_function_module()
 
346
        original_fixture = setup(module.baseline)
 
347
        second_fixture = self.make_extend_fixture(
 
348
            original_fixture, 'second')
 
349
        data = {}
 
350
        second_fixture(None, data)
 
351
        self.assertEqual(['baseline', 'second'], data['called'])
 
352
        data = {}
 
353
        original_fixture(None, data)
 
354
        self.assertEqual(['baseline'], data['called'])
 
355
 
 
356
    def test_extend_decorator_can_be_composed(self):
 
357
        module = self.make_example_setup_function_module()
 
358
        original_fixture = setup(module.baseline)
 
359
        second_fixture = self.make_extend_fixture(
 
360
            original_fixture, 'second')
 
361
        third_fixture = self.make_extend_fixture(
 
362
            second_fixture, 'third')
 
363
        data = {}
 
364
        third_fixture(None, data)
 
365
        self.assertEqual(['baseline', 'second', 'third'], data['called'])
 
366
 
 
367
    def make_extend_fixture(self, original_fixture, name):
 
368
        f = lambda request, data: data['called'].append(name)
 
369
        f.__module__ == TEST_MODULE_NAME
 
370
        return original_fixture.extend(f)
 
371
 
 
372
    def test_extend_calls_teardown_in_reverse_order(self):
 
373
        module = self.make_example_setup_function_module()
 
374
        original_fixture = setup(module.baseline)
 
375
        second_fixture = self.make_extend_fixture(
 
376
            original_fixture, 'second')
 
377
        third_fixture = self.make_extend_fixture(
 
378
            second_fixture, 'third')
 
379
        called = []
 
380
        original_fixture.add_cleanup(
 
381
            lambda request, data: called.append('original'))
 
382
        second_fixture.add_cleanup(
 
383
            lambda request, data: called.append('second'))
 
384
        third_fixture.add_cleanup(
 
385
            lambda request, data: called.append('third'))
 
386
        third_fixture.teardown(None, dict())
 
387
        self.assertEquals(['third', 'second', 'original'], called)
 
388
 
 
389
        del called[:]
 
390
        original_fixture.teardown(None, dict())
 
391
        self.assertEquals(['original'], called)