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
class OfferingWorksheets(View):
100
class OfferingWorksheetMarks(View):
103
class OfferingWorksheetCSVMarks(View):
106
def root_to_subject_or_user(root, name):
107
if name.startswith('~'):
108
return root.users.get(name[1:])
109
return root.subjects.get(name)
111
def subject_to_offering(subject, year, semester):
112
return subject.offerings.get((int(year), int(semester)))
114
def offering_to_files(offering):
115
return OfferingFiles(offering)
117
def offering_files_to_file(offeringfiles, *path):
118
return OfferingFile(offeringfiles, path)
120
def offering_to_project(offering, name):
121
return offering.projects.get(name)
123
def subject_url(subject):
124
return (ROOT, subject.name)
126
def offering_url(offering):
127
return (offering.subject, (str(offering.year), str(offering.semester)))
129
def offering_files_url(offeringfiles):
130
return (offeringfiles.offering, '+files')
132
def project_url(project):
133
return (project.offering, ('+projects', project.name))
135
class BaseTest(object):
140
# A user would be nice.
141
r.add_user(User('jsmith'))
143
# Give us some subjects...
144
r.add_subject(Subject('info1', '600151'))
145
r.add_subject(Subject('info2', '600152'))
146
r.add_subject(Subject('info3', '600251'))
148
# ... and some offerings.
149
r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
151
r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
153
r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
155
r.subjects['info2'].add_offering(Offering(self.r.subjects['info2'],
157
r.subjects['info2'].add_offering(Offering(self.r.subjects['info2'],
159
r.subjects['info3'].add_offering(Offering(self.r.subjects['info3'],
162
# A normal project...
163
r.subjects['info1'].offerings[(2009, 1)].add_project(
164
Project(r.subjects['info1'].offerings[(2009, 1)], 'p1')
167
# And one conflicting with a deep view, just to be nasty.
168
r.subjects['info1'].offerings[(2009, 1)].add_project(
169
Project(r.subjects['info1'].offerings[(2009, 1)], '+new')
172
class TestResolution(BaseTest):
174
super(TestResolution, self).setUp()
175
self.rtr = Publisher(root=self.r, viewset='browser')
176
self.rtr.add_set_switch('api', 'api')
177
self.rtr.add_forward(Root, None, root_to_subject_or_user, 1)
178
self.rtr.add_forward(Subject, None, subject_to_offering, 2)
179
self.rtr.add_forward(Offering, '+files', offering_to_files, 0)
180
self.rtr.add_forward(OfferingFiles, None, offering_files_to_file, INF)
181
self.rtr.add_forward(Offering, '+projects', offering_to_project, 1)
182
self.rtr.add_view(User, None, UserServeView, viewset='browser')
183
self.rtr.add_view(Subject, '+index', SubjectIndex, viewset='browser')
184
self.rtr.add_view(Subject, '+edit', SubjectEdit, viewset='browser')
185
self.rtr.add_view(Offering, '+index', OfferingIndex, viewset='browser')
186
self.rtr.add_view(Offering, '+index', OfferingAPIIndex, viewset='api')
187
self.rtr.add_view(OfferingFiles, '+index', OfferingFilesIndex,
189
self.rtr.add_view(OfferingFile, '+index', OfferingFileIndex,
191
self.rtr.add_view(Project, '+index', ProjectIndex, viewset='browser')
192
self.rtr.add_view(Offering, ('+projects', '+new'), OfferingAddProject,
194
self.rtr.add_view(Offering, ('+projects', '+index'), OfferingProjects,
196
self.rtr.add_view(Offering, ('+worksheets', '+index'),
197
OfferingWorksheets, viewset='browser')
198
self.rtr.add_view(Offering, ('+worksheets', '+marks', '+index'),
199
OfferingWorksheetMarks, viewset='browser')
200
self.rtr.add_view(Offering, ('+worksheets', '+marks', 'marks.csv'),
201
OfferingWorksheetCSVMarks, viewset='browser')
203
def testOneRoute(self):
204
assert_equal(self.rtr.resolve('/info1'),
205
(self.r.subjects['info1'], SubjectIndex, ())
207
assert_equal(self.rtr.resolve('/info3'),
208
(self.r.subjects['info3'], SubjectIndex, ())
211
def testTwoRoutes(self):
212
assert_equal(self.rtr.resolve('/info1/2009/1'),
213
(self.r.subjects['info1'].offerings[(2009, 1)], OfferingIndex, ())
215
assert_equal(self.rtr.resolve('/info2/2008/2'),
216
(self.r.subjects['info2'].offerings[(2008, 2)], OfferingIndex, ())
219
def testNamedRoute(self):
220
assert_equal(type(self.rtr.resolve('/info1/2009/1/+files')[0]),
223
assert_equal(self.rtr.resolve('/info1/2009/1/+files')[0].offering,
224
self.r.subjects['info1'].offerings[(2009, 1)]
227
def testNonDefaultView(self):
228
assert_equal(self.rtr.resolve('/info1/+edit'),
229
(self.r.subjects['info1'], SubjectEdit, ())
232
def testDefaultView(self):
233
assert_equal(self.rtr.resolve('/info1'),
234
(self.r.subjects['info1'], SubjectIndex, ())
237
def testViewWithSubpath(self):
238
assert_equal(self.rtr.resolve('/info1/+edit/foo/bar'),
239
(self.r.subjects['info1'], SubjectEdit, ('foo', 'bar'))
242
def testNoDefaultView(self):
244
self.rtr.default = 'not+index'
245
self.rtr.resolve('/info1')
247
assert_equal(e.args, (self.r.subjects['info1'], '+index', ()))
251
raise AssertionError('did not raise NotFound')
253
self.rtr.default = '+index'
255
def testMissingView(self):
257
self.rtr.resolve('/info1/+foo')
259
assert_equal(e.args, (self.r.subjects['info1'], '+foo', ()))
263
raise AssertionError('did not raise NotFound')
265
def testViewSetSeparation(self):
267
self.rtr.resolve('/api/info1/+edit')
269
assert_equal(e.args, (self.r.subjects['info1'], '+edit', ()))
273
raise AssertionError('did not raise NotFound')
275
def testRouteReturningNone(self):
277
self.rtr.resolve('/info9/+index')
279
assert_equal(e.args, (self.r, 'info9', ('+index',)))
283
raise AssertionError('did not raise NotFound')
285
def testRouteWithInfinitelyManyArguments(self):
286
o, v, sp = self.rtr.resolve('/info1/2009/1/+files/foo/bar/baz')
288
assert_equal(type(o), OfferingFile)
289
assert_equal(o.path, ('foo', 'bar', 'baz'))
290
assert_equal(o.offering, self.r.subjects['info1'].offerings[(2009, 1)])
291
assert_equal(v, OfferingFileIndex)
294
def testMissingRoute(self):
296
self.rtr.resolve('/info1/2009/1/+foo')
298
assert_equal(e.args, (
299
self.r.subjects['info1'].offerings[(2009, 1)],
306
raise AssertionError('did not raise NotFound')
308
def testAlternateViewSetWithDefault(self):
309
assert_equal(self.rtr.resolve('/info1/2009/1'),
310
(self.r.subjects['info1'].offerings[(2009, 1)], OfferingIndex, ())
313
assert_equal(self.rtr.resolve('/api/info1/2009/1'),
314
(self.r.subjects['info1'].offerings[(2009, 1)], OfferingAPIIndex, ())
317
def testDeepView(self):
318
assert_equal(self.rtr.resolve('/info1/2009/1/+projects/+new'),
319
(self.r.subjects['info1'].offerings[(2009, 1)],
320
OfferingAddProject, ())
323
def testDefaultDeepView(self):
324
assert_equal(self.rtr.resolve('/info1/2009/1/+projects'),
325
(self.r.subjects['info1'].offerings[(2009, 1)],
326
OfferingProjects, ())
329
def testAnotherDefaultDeepView(self):
330
assert_equal(self.rtr.resolve('/info1/2009/1/+worksheets'),
331
(self.r.subjects['info1'].offerings[(2009, 1)],
332
OfferingWorksheets, ())
335
def testReallyDeepView(self):
337
self.rtr.resolve('/info1/2009/1/+worksheets/+marks/marks.csv'),
338
(self.r.subjects['info1'].offerings[(2009, 1)],
339
OfferingWorksheetCSVMarks, ())
342
def testDefaultReallyDeepView(self):
343
assert_equal(self.rtr.resolve('/info1/2009/1/+worksheets/+marks'),
344
(self.r.subjects['info1'].offerings[(2009, 1)],
345
OfferingWorksheetMarks, ())
348
def testNamedRouteWithDeepView(self):
349
assert_equal(self.rtr.resolve('/info1/2009/1/+projects/p1'),
350
(self.r.subjects['info1'].offerings[(2009, 1)].projects['p1'],
354
def testNullPathView(self):
355
"""Verify that views can be placed immediately under an object.
357
There are some cases in which it is useful for a view with a
358
subpath to exist immediately under an object, with no name.
360
assert_equal(self.rtr.resolve('/~jsmith/foo/bar'),
361
(self.r.users['jsmith'], UserServeView, ('foo', 'bar')))
363
def testTrailingSlashResolvesToDefaultView(self):
365
self.rtr.resolve('/info1/2009/1/'),
366
(self.r.subjects['info1'].offerings[(2009, 1)],
370
def testTrailingSlashResolvesToDeepDefaultView(self):
372
self.rtr.resolve('/info1/2009/1/+worksheets/+marks/'),
373
(self.r.subjects['info1'].offerings[(2009, 1)],
374
OfferingWorksheetMarks, ())
377
def testSubpathIndicatesTrailingSlash(self):
379
self.rtr.resolve('/info1/2009/1/+index/'),
380
(self.r.subjects['info1'].offerings[(2009, 1)],
381
OfferingIndex, ('',))
384
class TestGeneration(BaseTest):
386
super(TestGeneration, self).setUp()
387
self.rtr = Publisher(root=self.r, viewset='browser')
388
self.rtr.add_set_switch('api', 'api')
389
self.rtr.add_reverse(Subject, subject_url)
390
self.rtr.add_reverse(Offering, offering_url)
391
self.rtr.add_reverse(OfferingFiles, offering_files_url)
392
self.rtr.add_reverse(Project, project_url)
393
self.rtr.add_view(Subject, '+index', SubjectIndex, viewset='browser')
394
self.rtr.add_view(Subject, '+edit', SubjectEdit, viewset='browser')
395
self.rtr.add_view(Offering, '+index', OfferingIndex, viewset='browser')
396
self.rtr.add_view(Offering, '+index', OfferingAPIIndex, viewset='api')
397
self.rtr.add_view(Project, '+index', ProjectIndex, viewset='browser')
398
self.rtr.add_view(Offering, ('+projects', '+new'), OfferingAddProject,
400
self.rtr.add_view(Offering, ('+projects', '+index'), OfferingProjects,
403
def testOneLevel(self):
404
assert_equal(self.rtr.generate(self.r.subjects['info1']), '/info1')
406
def testTwoLevel(self):
408
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)]),
412
self.rtr.generate(self.r.subjects['info2'].offerings[(2008, 2)]),
416
def testNamedRoute(self):
417
assert_equal(self.rtr.generate(
418
OfferingFiles(self.r.subjects['info1'].offerings[(2009, 1)])),
419
'/info1/2009/1/+files'
423
assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit),
427
def testDefaultView(self):
429
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
435
def testViewWithSubpath(self):
436
assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit,
438
'/info1/+edit/foo/bar'
441
def testViewWithStringSubpath(self):
442
assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit,
444
'/info1/+edit/foo/bar'
447
def testAlternateViewSetWithDefault(self):
449
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
455
def testDeepView(self):
458
self.r.subjects['info1'].offerings[(2009, 1)],
461
'/info1/2009/1/+projects/+new'
464
def testDefaultDeepView(self):
467
self.r.subjects['info1'].offerings[(2009, 1)],
470
'/info1/2009/1/+projects'
473
def testDefaultDeepViewWithSubpath(self):
476
self.r.subjects['info1'].offerings[(2009, 1)],
477
OfferingProjects, ('foo', 'bar')
479
'/info1/2009/1/+projects/+index/foo/bar'
482
def testNamedRouteWithDeepView(self):
485
self.r.subjects['info1'].offerings[(2009, 1)].projects['p1'],
488
'/info1/2009/1/+projects/p1'
492
assert_equal(self.rtr.generate(self.r), '/')
495
class TestErrors(BaseTest):
497
super(TestErrors, self).setUp()
498
self.rtr = Publisher(root=self.r)
499
self.rtr.add_forward(Root, None, root_to_subject_or_user, 1)
500
self.rtr.add_forward(Subject, '+foo', lambda s: s.name + 'foo', 0)
501
self.rtr.add_forward(Subject, None, subject_to_offering, 2)
502
self.rtr.add_reverse(Subject, subject_url)
503
self.rtr.add_reverse(Offering, offering_url)
504
self.rtr.add_view(Offering, '+index', OfferingIndex)
505
self.rtr.add_view(Offering, '+index', OfferingAPIIndex, viewset='api')
506
self.rtr.add_set_switch('rest', 'rest')
508
@raises(RouteConflict)
509
def testForwardConflict(self):
510
self.rtr.add_forward(Subject, '+foo', object(), 2)
512
@raises(RouteConflict)
513
def testReverseConflict(self):
514
self.rtr.add_reverse(Subject, object())
516
@raises(RouteConflict)
517
def testViewConflict(self):
518
self.rtr.add_view(Offering, '+index', object())
520
@raises(RouteConflict)
521
def testSetSwitchForwardConflict(self):
522
self.rtr.add_set_switch('rest', 'foo')
524
@raises(RouteConflict)
525
def testSetSwitchReverseConflict(self):
526
self.rtr.add_set_switch('bar', 'rest')
529
def testNoPath(self):
530
self.rtr.generate(object())
533
def testNoSetSwitch(self):
534
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
538
def testUnregisteredView(self):
539
self.rtr.generate(self.r.subjects['info1'], SubjectIndex)
542
def testNotFound(self):
543
self.rtr.resolve('/bar')
545
@raises(InsufficientPathSegments)
546
def testInsufficientPathSegments(self):
547
self.rtr.resolve('/info1/foo')