~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/webapp/publisher/test_publisher.py

  • Committer: William Grant
  • Date: 2009-12-08 03:50:24 UTC
  • mfrom: (1294.2.143 ui-the-third)
  • Revision ID: grantw@unimelb.edu.au-20091208035024-wjx8zp54gth15ph8
Merge ui-the-third. This is another UI revamp.

The header is now thin! Thin! The yellow bar is gone. The tabs are gone.
Breadcrumbs are here. Routes is replaced (with an object publishing
mechanism). Views are less repetitive. etc.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from nose.tools import assert_equal, raises
 
2
 
 
3
from ivle.webapp.publisher import (INF, InsufficientPathSegments, NoPath,
 
4
                                   NotFound, RouteConflict, Publisher, ROOT)
 
5
 
 
6
class Root(object):
 
7
    def __init__(self):
 
8
        self.subjects = {}
 
9
        self.users = {}
 
10
 
 
11
    def add_subject(self, subject):
 
12
        self.subjects[subject.name] = subject
 
13
 
 
14
    def add_user(self, user):
 
15
        self.users[user.login] = user
 
16
 
 
17
class User(object):
 
18
    def __init__(self, login):
 
19
        self.login = login
 
20
 
 
21
class Subject(object):
 
22
    def __init__(self, name, code):
 
23
        self.name = name
 
24
        self.code = code
 
25
        self.offerings = {}
 
26
 
 
27
    def add_offering(self, offering):
 
28
        assert self.name == offering.subject.name
 
29
        self.offerings[(offering.year, offering.semester)] = offering
 
30
 
 
31
class Offering(object):
 
32
    def __init__(self, subject, year, semester):
 
33
        self.subject = subject
 
34
        self.year = year
 
35
        self.semester = semester
 
36
        self.projects = {}
 
37
 
 
38
    def add_project(self, project):
 
39
        assert project.offering is self
 
40
        self.projects[project.name] = project
 
41
 
 
42
class OfferingFiles(object):
 
43
    def __init__(self, offering):
 
44
        self.offering = offering
 
45
 
 
46
class OfferingFile(object):
 
47
    def __init__(self, offeringfiles, path):
 
48
        self.offering = offeringfiles.offering
 
49
        self.path = path
 
50
 
 
51
class Project(object):
 
52
    def __init__(self, offering, name):
 
53
        self.offering = offering
 
54
        self.name = name
 
55
 
 
56
 
 
57
class View(object):
 
58
    def __init__(self, context):
 
59
        self.context = context
 
60
 
 
61
class RootIndex(View):
 
62
    pass
 
63
 
 
64
class UserServeView(View):
 
65
    pass
 
66
 
 
67
class SubjectIndex(View):
 
68
    pass
 
69
 
 
70
class SubjectEdit(View):
 
71
    pass
 
72
 
 
73
class OfferingIndex(View):
 
74
    pass
 
75
 
 
76
class OfferingEdit(View):
 
77
    pass
 
78
 
 
79
class OfferingAPIIndex(View):
 
80
    pass
 
81
 
 
82
class OfferingFilesIndex(View):
 
83
    pass
 
84
 
 
85
class OfferingFileIndex(View):
 
86
    pass
 
87
 
 
88
class ProjectIndex(View):
 
89
    pass
 
90
 
 
91
class OfferingProjects(View):
 
92
    pass
 
93
 
 
94
class OfferingAddProject(View):
 
95
    pass
 
96
 
 
97
def root_to_subject_or_user(root, name):
 
98
    if name.startswith('~'):
 
99
        return root.users.get(name[1:])
 
100
    return root.subjects.get(name)
 
101
 
 
102
def subject_to_offering(subject, year, semester):
 
103
    return subject.offerings.get((int(year), int(semester)))
 
104
 
 
105
def offering_to_files(offering):
 
106
    return OfferingFiles(offering)
 
107
 
 
108
def offering_files_to_file(offeringfiles, *path):
 
109
    return OfferingFile(offeringfiles, path)
 
110
 
 
111
def offering_to_project(offering, name):
 
112
    return offering.projects.get(name)
 
113
 
 
114
def subject_url(subject):
 
115
    return (ROOT, subject.name)
 
116
 
 
117
def offering_url(offering):
 
118
    return (offering.subject, (str(offering.year), str(offering.semester)))
 
119
 
 
120
def offering_files_url(offeringfiles):
 
121
    return (offeringfiles.offering, '+files')
 
122
 
 
123
def project_url(project):
 
124
    return (project.offering, ('+projects', project.name))
 
125
 
 
126
class BaseTest(object):
 
127
    def setUp(self):
 
128
        r = Root()
 
129
        self.r = r
 
130
 
 
131
        # A user would be nice.
 
132
        r.add_user(User('jsmith'))
 
133
 
 
134
        # Give us some subjects...
 
135
        r.add_subject(Subject('info1', '600151'))
 
136
        r.add_subject(Subject('info2', '600152'))
 
137
        r.add_subject(Subject('info3', '600251'))
 
138
 
 
139
        # ... and some offerings.
 
140
        r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
 
141
                                         2008, 1))
 
142
        r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
 
143
                                         2008, 2))
 
144
        r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
 
145
                                         2009, 1))
 
146
        r.subjects['info2'].add_offering(Offering(self.r.subjects['info2'],
 
147
                                         2008, 2))
 
148
        r.subjects['info2'].add_offering(Offering(self.r.subjects['info2'],
 
149
                                         2009, 1))
 
150
        r.subjects['info3'].add_offering(Offering(self.r.subjects['info3'],
 
151
                                         2009, 1))
 
152
 
 
153
        # A normal project...
 
154
        r.subjects['info1'].offerings[(2009, 1)].add_project(
 
155
            Project(r.subjects['info1'].offerings[(2009, 1)], 'p1')
 
156
            )
 
157
 
 
158
        # And one conflicting with a deep view, just to be nasty.
 
159
        r.subjects['info1'].offerings[(2009, 1)].add_project(
 
160
            Project(r.subjects['info1'].offerings[(2009, 1)], '+new')
 
161
            )
 
162
 
 
163
class TestResolution(BaseTest):
 
164
    def setUp(self):
 
165
        super(TestResolution, self).setUp()
 
166
        self.rtr = Publisher(root=self.r, viewset='browser')
 
167
        self.rtr.add_set_switch('api', 'api')
 
168
        self.rtr.add_forward(Root, None, root_to_subject_or_user, 1)
 
169
        self.rtr.add_forward(Subject, None, subject_to_offering, 2)
 
170
        self.rtr.add_forward(Offering, '+files', offering_to_files, 0)
 
171
        self.rtr.add_forward(OfferingFiles, None, offering_files_to_file, INF)
 
172
        self.rtr.add_forward(Offering, '+projects', offering_to_project, 1)
 
173
        self.rtr.add_view(User, None, UserServeView, viewset='browser')
 
174
        self.rtr.add_view(Subject, '+index', SubjectIndex, viewset='browser')
 
175
        self.rtr.add_view(Subject, '+edit', SubjectEdit, viewset='browser')
 
176
        self.rtr.add_view(Offering, '+index', OfferingIndex, viewset='browser')
 
177
        self.rtr.add_view(Offering, '+index', OfferingAPIIndex, viewset='api')
 
178
        self.rtr.add_view(OfferingFiles, '+index', OfferingFilesIndex,
 
179
                          viewset='browser')
 
180
        self.rtr.add_view(OfferingFile, '+index', OfferingFileIndex,
 
181
                          viewset='browser')
 
182
        self.rtr.add_view(Project, '+index', ProjectIndex, viewset='browser')
 
183
        self.rtr.add_view(Offering, ('+projects', '+new'), OfferingAddProject,
 
184
                          viewset='browser')
 
185
        self.rtr.add_view(Offering, ('+projects', '+index'), OfferingProjects,
 
186
                          viewset='browser')
 
187
 
 
188
    def testOneRoute(self):
 
189
        assert_equal(self.rtr.resolve('/info1'),
 
190
                     (self.r.subjects['info1'], SubjectIndex, ())
 
191
                     )
 
192
        assert_equal(self.rtr.resolve('/info3'),
 
193
                     (self.r.subjects['info3'], SubjectIndex, ())
 
194
                     )
 
195
 
 
196
    def testTwoRoutes(self):
 
197
        assert_equal(self.rtr.resolve('/info1/2009/1'),
 
198
             (self.r.subjects['info1'].offerings[(2009, 1)], OfferingIndex, ())
 
199
             )
 
200
        assert_equal(self.rtr.resolve('/info2/2008/2'),
 
201
             (self.r.subjects['info2'].offerings[(2008, 2)], OfferingIndex, ())
 
202
             )
 
203
 
 
204
    def testNamedRoute(self):
 
205
        assert_equal(type(self.rtr.resolve('/info1/2009/1/+files')[0]),
 
206
                     OfferingFiles
 
207
                    )
 
208
        assert_equal(self.rtr.resolve('/info1/2009/1/+files')[0].offering,
 
209
                     self.r.subjects['info1'].offerings[(2009, 1)]
 
210
                    )
 
211
 
 
212
    def testNonDefaultView(self):
 
213
        assert_equal(self.rtr.resolve('/info1/+edit'),
 
214
                     (self.r.subjects['info1'], SubjectEdit, ())
 
215
                     )
 
216
 
 
217
    def testDefaultView(self):
 
218
        assert_equal(self.rtr.resolve('/info1'),
 
219
                     (self.r.subjects['info1'], SubjectIndex, ())
 
220
                     )
 
221
 
 
222
    def testViewWithSubpath(self):
 
223
        assert_equal(self.rtr.resolve('/info1/+edit/foo/bar'),
 
224
                     (self.r.subjects['info1'], SubjectEdit, ('foo', 'bar'))
 
225
                     )
 
226
 
 
227
    def testNoDefaultView(self):
 
228
        try:
 
229
            self.rtr.default = 'not+index'
 
230
            self.rtr.resolve('/info1')
 
231
        except NotFound, e:
 
232
            assert_equal(e.args, (self.r.subjects['info1'], '+index', ()))
 
233
        except:
 
234
            raise
 
235
        else:
 
236
            raise AssertionError('did not raise NotFound')
 
237
        finally:
 
238
            self.rtr.default = '+index'
 
239
 
 
240
    def testMissingView(self):
 
241
        try:
 
242
            self.rtr.resolve('/info1/+foo')
 
243
        except NotFound, e:
 
244
            assert_equal(e.args, (self.r.subjects['info1'], '+foo', ()))
 
245
        except:
 
246
            raise
 
247
        else:
 
248
            raise AssertionError('did not raise NotFound')
 
249
 
 
250
    def testViewSetSeparation(self):
 
251
        try:
 
252
            self.rtr.resolve('/api/info1/+edit')
 
253
        except NotFound, e:
 
254
            assert_equal(e.args, (self.r.subjects['info1'], '+edit', ()))
 
255
        except:
 
256
            raise
 
257
        else:
 
258
            raise AssertionError('did not raise NotFound')
 
259
 
 
260
    def testRouteReturningNone(self):
 
261
        try:
 
262
            self.rtr.resolve('/info9/+index')
 
263
        except NotFound, e:
 
264
            assert_equal(e.args, (self.r, 'info9', ('+index',)))
 
265
        except:
 
266
            raise
 
267
        else:
 
268
            raise AssertionError('did not raise NotFound')
 
269
 
 
270
    def testRouteWithInfinitelyManyArguments(self):
 
271
        o, v, sp = self.rtr.resolve('/info1/2009/1/+files/foo/bar/baz')
 
272
 
 
273
        assert_equal(type(o), OfferingFile)
 
274
        assert_equal(o.path, ('foo', 'bar', 'baz'))
 
275
        assert_equal(o.offering, self.r.subjects['info1'].offerings[(2009, 1)])
 
276
        assert_equal(v, OfferingFileIndex)
 
277
        assert_equal(sp, ())
 
278
 
 
279
    def testMissingRoute(self):
 
280
        try:
 
281
            self.rtr.resolve('/info1/2009/1/+foo')
 
282
        except NotFound, e:
 
283
            assert_equal(e.args, (
 
284
                self.r.subjects['info1'].offerings[(2009, 1)],
 
285
                '+foo',
 
286
                ()
 
287
                ))
 
288
        except:
 
289
            raise
 
290
        else:
 
291
            raise AssertionError('did not raise NotFound')
 
292
 
 
293
    def testAlternateViewSetWithDefault(self):
 
294
        assert_equal(self.rtr.resolve('/info1/2009/1'),
 
295
             (self.r.subjects['info1'].offerings[(2009, 1)], OfferingIndex, ())
 
296
             )
 
297
 
 
298
        assert_equal(self.rtr.resolve('/api/info1/2009/1'),
 
299
          (self.r.subjects['info1'].offerings[(2009, 1)], OfferingAPIIndex, ())
 
300
          )
 
301
 
 
302
    def testDeepView(self):
 
303
        assert_equal(self.rtr.resolve('/info1/2009/1/+projects/+new'),
 
304
             (self.r.subjects['info1'].offerings[(2009, 1)],
 
305
              OfferingAddProject, ())
 
306
             )
 
307
 
 
308
    def testDefaultDeepView(self):
 
309
        assert_equal(self.rtr.resolve('/info1/2009/1/+projects'),
 
310
             (self.r.subjects['info1'].offerings[(2009, 1)],
 
311
              OfferingProjects, ())
 
312
             )
 
313
 
 
314
    def testNamedRouteWithDeepView(self):
 
315
        assert_equal(self.rtr.resolve('/info1/2009/1/+projects/p1'),
 
316
             (self.r.subjects['info1'].offerings[(2009, 1)].projects['p1'],
 
317
              ProjectIndex, ())
 
318
             )
 
319
 
 
320
    def testNullPathView(self):
 
321
        """Verify that views can be placed immediately under an object.
 
322
 
 
323
        There are some cases in which it is useful for a view with a
 
324
        subpath to exist immediately under an object, with no name.
 
325
        """
 
326
        assert_equal(self.rtr.resolve('/~jsmith/foo/bar'),
 
327
             (self.r.users['jsmith'], UserServeView, ('foo', 'bar')))
 
328
 
 
329
 
 
330
class TestGeneration(BaseTest):
 
331
    def setUp(self):
 
332
        super(TestGeneration, self).setUp()
 
333
        self.rtr = Publisher(root=self.r, viewset='browser')
 
334
        self.rtr.add_set_switch('api', 'api')
 
335
        self.rtr.add_reverse(Subject, subject_url)
 
336
        self.rtr.add_reverse(Offering, offering_url)
 
337
        self.rtr.add_reverse(OfferingFiles, offering_files_url)
 
338
        self.rtr.add_reverse(Project, project_url)
 
339
        self.rtr.add_view(Subject, '+index', SubjectIndex, viewset='browser')
 
340
        self.rtr.add_view(Subject, '+edit', SubjectEdit, viewset='browser')
 
341
        self.rtr.add_view(Offering, '+index', OfferingIndex, viewset='browser')
 
342
        self.rtr.add_view(Offering, '+index', OfferingAPIIndex, viewset='api')
 
343
        self.rtr.add_view(Project, '+index', ProjectIndex, viewset='browser')
 
344
        self.rtr.add_view(Offering, ('+projects', '+new'), OfferingAddProject,
 
345
                          viewset='browser')
 
346
        self.rtr.add_view(Offering, ('+projects', '+index'), OfferingProjects,
 
347
                          viewset='browser')
 
348
 
 
349
    def testOneLevel(self):
 
350
        assert_equal(self.rtr.generate(self.r.subjects['info1']), '/info1')
 
351
 
 
352
    def testTwoLevel(self):
 
353
        assert_equal(
 
354
            self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)]),
 
355
            '/info1/2009/1'
 
356
            )
 
357
        assert_equal(
 
358
            self.rtr.generate(self.r.subjects['info2'].offerings[(2008, 2)]),
 
359
            '/info2/2008/2'
 
360
            )
 
361
 
 
362
    def testNamedRoute(self):
 
363
        assert_equal(self.rtr.generate(
 
364
                OfferingFiles(self.r.subjects['info1'].offerings[(2009, 1)])),
 
365
                '/info1/2009/1/+files'
 
366
            )
 
367
 
 
368
    def testView(self):
 
369
        assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit),
 
370
                     '/info1/+edit'
 
371
                     )
 
372
 
 
373
    def testDefaultView(self):
 
374
        assert_equal(
 
375
            self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
 
376
                              OfferingIndex
 
377
                              ),
 
378
            '/info1/2009/1'
 
379
            )
 
380
 
 
381
    def testViewWithSubpath(self):
 
382
        assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit,
 
383
                                       ('foo', 'bar')),
 
384
                     '/info1/+edit/foo/bar'
 
385
                     )
 
386
 
 
387
    def testViewWithStringSubpath(self):
 
388
        assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit,
 
389
                                       'foo/bar'),
 
390
                     '/info1/+edit/foo/bar'
 
391
                     )
 
392
 
 
393
    def testAlternateViewSetWithDefault(self):
 
394
        assert_equal(
 
395
            self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
 
396
                              OfferingAPIIndex
 
397
                              ),
 
398
            '/api/info1/2009/1'
 
399
            )
 
400
 
 
401
    def testDeepView(self):
 
402
        assert_equal(
 
403
            self.rtr.generate(
 
404
                self.r.subjects['info1'].offerings[(2009, 1)],
 
405
                OfferingAddProject
 
406
                ),
 
407
        '/info1/2009/1/+projects/+new'
 
408
        )
 
409
 
 
410
    def testDefaultDeepView(self):
 
411
        assert_equal(
 
412
            self.rtr.generate(
 
413
                self.r.subjects['info1'].offerings[(2009, 1)],
 
414
                OfferingProjects
 
415
                ),
 
416
        '/info1/2009/1/+projects'
 
417
        )
 
418
 
 
419
    def testDefaultDeepViewWithSubpath(self):
 
420
        assert_equal(
 
421
            self.rtr.generate(
 
422
                self.r.subjects['info1'].offerings[(2009, 1)],
 
423
                OfferingProjects, ('foo', 'bar')
 
424
                ),
 
425
        '/info1/2009/1/+projects/+index/foo/bar'
 
426
        )
 
427
 
 
428
    def testNamedRouteWithDeepView(self):
 
429
        assert_equal(
 
430
            self.rtr.generate(
 
431
                self.r.subjects['info1'].offerings[(2009, 1)].projects['p1'],
 
432
                ProjectIndex
 
433
                ),
 
434
        '/info1/2009/1/+projects/p1'
 
435
        )
 
436
 
 
437
    def testRoot(self):
 
438
        assert_equal(self.rtr.generate(self.r), '/')
 
439
 
 
440
 
 
441
class TestErrors(BaseTest):
 
442
    def setUp(self):
 
443
        super(TestErrors, self).setUp()
 
444
        self.rtr = Publisher(root=self.r)
 
445
        self.rtr.add_forward(Root, None, root_to_subject_or_user, 1)
 
446
        self.rtr.add_forward(Subject, '+foo', lambda s: s.name + 'foo', 0)
 
447
        self.rtr.add_forward(Subject, None, subject_to_offering, 2)
 
448
        self.rtr.add_reverse(Subject, subject_url)
 
449
        self.rtr.add_reverse(Offering, offering_url)
 
450
        self.rtr.add_view(Offering, '+index', OfferingIndex)
 
451
        self.rtr.add_view(Offering, '+index', OfferingAPIIndex, viewset='api')
 
452
        self.rtr.add_set_switch('rest', 'rest')
 
453
 
 
454
    @raises(RouteConflict)
 
455
    def testForwardConflict(self):
 
456
        self.rtr.add_forward(Subject, '+foo', object(), 2)
 
457
 
 
458
    @raises(RouteConflict)
 
459
    def testReverseConflict(self):
 
460
        self.rtr.add_reverse(Subject, object())
 
461
 
 
462
    @raises(RouteConflict)
 
463
    def testViewConflict(self):
 
464
        self.rtr.add_view(Offering, '+index', object())
 
465
 
 
466
    @raises(RouteConflict)
 
467
    def testSetSwitchForwardConflict(self):
 
468
        self.rtr.add_set_switch('rest', 'foo')
 
469
 
 
470
    @raises(RouteConflict)
 
471
    def testSetSwitchReverseConflict(self):
 
472
        self.rtr.add_set_switch('bar', 'rest')
 
473
 
 
474
    @raises(NoPath)
 
475
    def testNoPath(self):
 
476
        self.rtr.generate(object())
 
477
 
 
478
    @raises(NoPath)
 
479
    def testNoSetSwitch(self):
 
480
        self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
 
481
                          OfferingAPIIndex)
 
482
 
 
483
    @raises(NoPath)
 
484
    def testUnregisteredView(self):
 
485
        self.rtr.generate(self.r.subjects['info1'], SubjectIndex)
 
486
 
 
487
    @raises(NotFound)
 
488
    def testNotFound(self):
 
489
        self.rtr.resolve('/bar')
 
490
 
 
491
    @raises(InsufficientPathSegments)
 
492
    def testInsufficientPathSegments(self):
 
493
        self.rtr.resolve('/info1/foo')