1
from nose.tools import assert_equal, raises
3
from ivle.webapp.publisher import (INF, InsufficientPathSegments, NoPath,
4
NotFound, RouteConflict, Publisher, ROOT)
11
def add_subject(self, subject):
12
self.subjects[subject.name] = subject
14
def add_user(self, user):
15
self.users[user.login] = user
18
def __init__(self, login):
21
class Subject(object):
22
def __init__(self, name, code):
27
def add_offering(self, offering):
28
assert self.name == offering.subject.name
29
self.offerings[(offering.year, offering.semester)] = offering
31
class Offering(object):
32
def __init__(self, subject, year, semester):
33
self.subject = subject
35
self.semester = semester
38
def add_project(self, project):
39
assert project.offering is self
40
self.projects[project.name] = project
42
class OfferingFiles(object):
43
def __init__(self, offering):
44
self.offering = offering
46
class OfferingFile(object):
47
def __init__(self, offeringfiles, path):
48
self.offering = offeringfiles.offering
51
class Project(object):
52
def __init__(self, offering, name):
53
self.offering = offering
58
def __init__(self, context):
59
self.context = context
61
class RootIndex(View):
64
class UserServeView(View):
67
class SubjectIndex(View):
70
class SubjectEdit(View):
73
class OfferingIndex(View):
76
class OfferingEdit(View):
79
class OfferingAPIIndex(View):
82
class OfferingFilesIndex(View):
85
class OfferingFileIndex(View):
88
class ProjectIndex(View):
91
class OfferingProjects(View):
94
class OfferingAddProject(View):
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)
102
def subject_to_offering(subject, year, semester):
103
return subject.offerings.get((int(year), int(semester)))
105
def offering_to_files(offering):
106
return OfferingFiles(offering)
108
def offering_files_to_file(offeringfiles, *path):
109
return OfferingFile(offeringfiles, path)
111
def offering_to_project(offering, name):
112
return offering.projects.get(name)
114
def subject_url(subject):
115
return (ROOT, subject.name)
117
def offering_url(offering):
118
return (offering.subject, (str(offering.year), str(offering.semester)))
120
def offering_files_url(offeringfiles):
121
return (offeringfiles.offering, '+files')
123
def project_url(project):
124
return (project.offering, ('+projects', project.name))
126
class BaseTest(object):
131
# A user would be nice.
132
r.add_user(User('jsmith'))
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'))
139
# ... and some offerings.
140
r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
142
r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
144
r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
146
r.subjects['info2'].add_offering(Offering(self.r.subjects['info2'],
148
r.subjects['info2'].add_offering(Offering(self.r.subjects['info2'],
150
r.subjects['info3'].add_offering(Offering(self.r.subjects['info3'],
153
# A normal project...
154
r.subjects['info1'].offerings[(2009, 1)].add_project(
155
Project(r.subjects['info1'].offerings[(2009, 1)], 'p1')
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')
163
class TestResolution(BaseTest):
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,
180
self.rtr.add_view(OfferingFile, '+index', OfferingFileIndex,
182
self.rtr.add_view(Project, '+index', ProjectIndex, viewset='browser')
183
self.rtr.add_view(Offering, ('+projects', '+new'), OfferingAddProject,
185
self.rtr.add_view(Offering, ('+projects', '+index'), OfferingProjects,
188
def testOneRoute(self):
189
assert_equal(self.rtr.resolve('/info1'),
190
(self.r.subjects['info1'], SubjectIndex, ())
192
assert_equal(self.rtr.resolve('/info3'),
193
(self.r.subjects['info3'], SubjectIndex, ())
196
def testTwoRoutes(self):
197
assert_equal(self.rtr.resolve('/info1/2009/1'),
198
(self.r.subjects['info1'].offerings[(2009, 1)], OfferingIndex, ())
200
assert_equal(self.rtr.resolve('/info2/2008/2'),
201
(self.r.subjects['info2'].offerings[(2008, 2)], OfferingIndex, ())
204
def testNamedRoute(self):
205
assert_equal(type(self.rtr.resolve('/info1/2009/1/+files')[0]),
208
assert_equal(self.rtr.resolve('/info1/2009/1/+files')[0].offering,
209
self.r.subjects['info1'].offerings[(2009, 1)]
212
def testNonDefaultView(self):
213
assert_equal(self.rtr.resolve('/info1/+edit'),
214
(self.r.subjects['info1'], SubjectEdit, ())
217
def testDefaultView(self):
218
assert_equal(self.rtr.resolve('/info1'),
219
(self.r.subjects['info1'], SubjectIndex, ())
222
def testViewWithSubpath(self):
223
assert_equal(self.rtr.resolve('/info1/+edit/foo/bar'),
224
(self.r.subjects['info1'], SubjectEdit, ('foo', 'bar'))
227
def testNoDefaultView(self):
229
self.rtr.default = 'not+index'
230
self.rtr.resolve('/info1')
232
assert_equal(e.args, (self.r.subjects['info1'], '+index', ()))
236
raise AssertionError('did not raise NotFound')
238
self.rtr.default = '+index'
240
def testMissingView(self):
242
self.rtr.resolve('/info1/+foo')
244
assert_equal(e.args, (self.r.subjects['info1'], '+foo', ()))
248
raise AssertionError('did not raise NotFound')
250
def testViewSetSeparation(self):
252
self.rtr.resolve('/api/info1/+edit')
254
assert_equal(e.args, (self.r.subjects['info1'], '+edit', ()))
258
raise AssertionError('did not raise NotFound')
260
def testRouteReturningNone(self):
262
self.rtr.resolve('/info9/+index')
264
assert_equal(e.args, (self.r, 'info9', ('+index',)))
268
raise AssertionError('did not raise NotFound')
270
def testRouteWithInfinitelyManyArguments(self):
271
o, v, sp = self.rtr.resolve('/info1/2009/1/+files/foo/bar/baz')
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)
279
def testMissingRoute(self):
281
self.rtr.resolve('/info1/2009/1/+foo')
283
assert_equal(e.args, (
284
self.r.subjects['info1'].offerings[(2009, 1)],
291
raise AssertionError('did not raise NotFound')
293
def testAlternateViewSetWithDefault(self):
294
assert_equal(self.rtr.resolve('/info1/2009/1'),
295
(self.r.subjects['info1'].offerings[(2009, 1)], OfferingIndex, ())
298
assert_equal(self.rtr.resolve('/api/info1/2009/1'),
299
(self.r.subjects['info1'].offerings[(2009, 1)], OfferingAPIIndex, ())
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, ())
308
def testDefaultDeepView(self):
309
assert_equal(self.rtr.resolve('/info1/2009/1/+projects'),
310
(self.r.subjects['info1'].offerings[(2009, 1)],
311
OfferingProjects, ())
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'],
320
def testNullPathView(self):
321
"""Verify that views can be placed immediately under an object.
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.
326
assert_equal(self.rtr.resolve('/~jsmith/foo/bar'),
327
(self.r.users['jsmith'], UserServeView, ('foo', 'bar')))
330
class TestGeneration(BaseTest):
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,
346
self.rtr.add_view(Offering, ('+projects', '+index'), OfferingProjects,
349
def testOneLevel(self):
350
assert_equal(self.rtr.generate(self.r.subjects['info1']), '/info1')
352
def testTwoLevel(self):
354
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)]),
358
self.rtr.generate(self.r.subjects['info2'].offerings[(2008, 2)]),
362
def testNamedRoute(self):
363
assert_equal(self.rtr.generate(
364
OfferingFiles(self.r.subjects['info1'].offerings[(2009, 1)])),
365
'/info1/2009/1/+files'
369
assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit),
373
def testDefaultView(self):
375
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
381
def testViewWithSubpath(self):
382
assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit,
384
'/info1/+edit/foo/bar'
387
def testViewWithStringSubpath(self):
388
assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit,
390
'/info1/+edit/foo/bar'
393
def testAlternateViewSetWithDefault(self):
395
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
401
def testDeepView(self):
404
self.r.subjects['info1'].offerings[(2009, 1)],
407
'/info1/2009/1/+projects/+new'
410
def testDefaultDeepView(self):
413
self.r.subjects['info1'].offerings[(2009, 1)],
416
'/info1/2009/1/+projects'
419
def testDefaultDeepViewWithSubpath(self):
422
self.r.subjects['info1'].offerings[(2009, 1)],
423
OfferingProjects, ('foo', 'bar')
425
'/info1/2009/1/+projects/+index/foo/bar'
428
def testNamedRouteWithDeepView(self):
431
self.r.subjects['info1'].offerings[(2009, 1)].projects['p1'],
434
'/info1/2009/1/+projects/p1'
438
assert_equal(self.rtr.generate(self.r), '/')
441
class TestErrors(BaseTest):
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')
454
@raises(RouteConflict)
455
def testForwardConflict(self):
456
self.rtr.add_forward(Subject, '+foo', object(), 2)
458
@raises(RouteConflict)
459
def testReverseConflict(self):
460
self.rtr.add_reverse(Subject, object())
462
@raises(RouteConflict)
463
def testViewConflict(self):
464
self.rtr.add_view(Offering, '+index', object())
466
@raises(RouteConflict)
467
def testSetSwitchForwardConflict(self):
468
self.rtr.add_set_switch('rest', 'foo')
470
@raises(RouteConflict)
471
def testSetSwitchReverseConflict(self):
472
self.rtr.add_set_switch('bar', 'rest')
475
def testNoPath(self):
476
self.rtr.generate(object())
479
def testNoSetSwitch(self):
480
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
484
def testUnregisteredView(self):
485
self.rtr.generate(self.r.subjects['info1'], SubjectIndex)
488
def testNotFound(self):
489
self.rtr.resolve('/bar')
491
@raises(InsufficientPathSegments)
492
def testInsufficientPathSegments(self):
493
self.rtr.resolve('/info1/foo')