1
from nose.tools import assert_equal, raises
3
from ivle.webapp.publisher import (INF, InsufficientPathSegments, NoPath,
4
NotFound, RouteConflict, Publisher, ROOT)
10
def add_subject(self, subject):
11
self.subjects[subject.name] = subject
13
class Subject(object):
14
def __init__(self, name, code):
19
def add_offering(self, offering):
20
assert self.name == offering.subject.name
21
self.offerings[(offering.year, offering.semester)] = offering
23
class Offering(object):
24
def __init__(self, subject, year, semester):
25
self.subject = subject
27
self.semester = semester
30
def add_project(self, project):
31
assert project.offering is self
32
self.projects[project.name] = project
34
class OfferingFiles(object):
35
def __init__(self, offering):
36
self.offering = offering
38
class OfferingFile(object):
39
def __init__(self, offeringfiles, path):
40
self.offering = offeringfiles.offering
43
class Project(object):
44
def __init__(self, offering, name):
45
self.offering = offering
50
def __init__(self, context):
51
self.context = context
53
class RootIndex(View):
56
class SubjectIndex(View):
59
class SubjectEdit(View):
62
class OfferingIndex(View):
65
class OfferingEdit(View):
68
class OfferingAPIIndex(View):
71
class OfferingFilesIndex(View):
74
class OfferingFileIndex(View):
77
class ProjectIndex(View):
80
class OfferingProjects(View):
83
class OfferingAddProject(View):
86
def root_to_subject(root, name):
87
return root.subjects.get(name)
89
def subject_to_offering(subject, year, semester):
90
return subject.offerings.get((int(year), int(semester)))
92
def offering_to_files(offering):
93
return OfferingFiles(offering)
95
def offering_files_to_file(offeringfiles, *path):
96
return OfferingFile(offeringfiles, path)
98
def offering_to_project(offering, name):
99
return offering.projects.get(name)
101
def subject_url(subject):
102
return (ROOT, subject.name)
104
def offering_url(offering):
105
return (offering.subject, (str(offering.year), str(offering.semester)))
107
def offering_files_url(offeringfiles):
108
return (offeringfiles.offering, '+files')
110
def project_url(project):
111
return (project.offering, ('+projects', project.name))
113
class BaseTest(object):
118
# Give us some subjects...
119
r.add_subject(Subject('info1', '600151'))
120
r.add_subject(Subject('info2', '600152'))
121
r.add_subject(Subject('info3', '600251'))
123
# ... and some offerings.
124
r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
126
r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
128
r.subjects['info1'].add_offering(Offering(self.r.subjects['info1'],
130
r.subjects['info2'].add_offering(Offering(self.r.subjects['info2'],
132
r.subjects['info2'].add_offering(Offering(self.r.subjects['info2'],
134
r.subjects['info3'].add_offering(Offering(self.r.subjects['info3'],
137
# A normal project...
138
r.subjects['info1'].offerings[(2009, 1)].add_project(
139
Project(r.subjects['info1'].offerings[(2009, 1)], 'p1')
142
# And one conflicting with a deep view, just to be nasty.
143
r.subjects['info1'].offerings[(2009, 1)].add_project(
144
Project(r.subjects['info1'].offerings[(2009, 1)], '+new')
147
class TestResolution(BaseTest):
149
super(TestResolution, self).setUp()
150
self.rtr = Publisher(root=self.r, viewset='browser')
151
self.rtr.add_set_switch('api', 'api')
152
self.rtr.add_forward(Root, None, root_to_subject, 1)
153
self.rtr.add_forward(Subject, None, subject_to_offering, 2)
154
self.rtr.add_forward(Offering, '+files', offering_to_files, 0)
155
self.rtr.add_forward(OfferingFiles, None, offering_files_to_file, INF)
156
self.rtr.add_forward(Offering, '+projects', offering_to_project, 1)
157
self.rtr.add_view(Subject, '+index', SubjectIndex, viewset='browser')
158
self.rtr.add_view(Subject, '+edit', SubjectEdit, viewset='browser')
159
self.rtr.add_view(Offering, '+index', OfferingIndex, viewset='browser')
160
self.rtr.add_view(Offering, '+index', OfferingAPIIndex, viewset='api')
161
self.rtr.add_view(OfferingFiles, '+index', OfferingFilesIndex,
163
self.rtr.add_view(OfferingFile, '+index', OfferingFileIndex,
165
self.rtr.add_view(Project, '+index', ProjectIndex, viewset='browser')
166
self.rtr.add_view(Offering, ('+projects', '+new'), OfferingAddProject,
168
self.rtr.add_view(Offering, ('+projects', '+index'), OfferingProjects,
171
def testOneRoute(self):
172
assert_equal(self.rtr.resolve('/info1'),
173
(self.r.subjects['info1'], SubjectIndex, ())
175
assert_equal(self.rtr.resolve('/info3'),
176
(self.r.subjects['info3'], SubjectIndex, ())
179
def testTwoRoutes(self):
180
assert_equal(self.rtr.resolve('/info1/2009/1'),
181
(self.r.subjects['info1'].offerings[(2009, 1)], OfferingIndex, ())
183
assert_equal(self.rtr.resolve('/info2/2008/2'),
184
(self.r.subjects['info2'].offerings[(2008, 2)], OfferingIndex, ())
187
def testNamedRoute(self):
188
assert_equal(type(self.rtr.resolve('/info1/2009/1/+files')[0]),
191
assert_equal(self.rtr.resolve('/info1/2009/1/+files')[0].offering,
192
self.r.subjects['info1'].offerings[(2009, 1)]
195
def testNonDefaultView(self):
196
assert_equal(self.rtr.resolve('/info1/+edit'),
197
(self.r.subjects['info1'], SubjectEdit, ())
200
def testDefaultView(self):
201
assert_equal(self.rtr.resolve('/info1'),
202
(self.r.subjects['info1'], SubjectIndex, ())
205
def testViewWithSubpath(self):
206
assert_equal(self.rtr.resolve('/info1/+edit/foo/bar'),
207
(self.r.subjects['info1'], SubjectEdit, ('foo', 'bar'))
210
def testNoDefaultView(self):
212
self.rtr.default = 'not+index'
213
self.rtr.resolve('/info1')
215
assert_equal(e.args, (self.r.subjects['info1'], '+index', ()))
219
raise AssertionError('did not raise NotFound')
221
self.rtr.default = '+index'
223
def testMissingView(self):
225
self.rtr.resolve('/info1/+foo')
227
assert_equal(e.args, (self.r.subjects['info1'], '+foo', ()))
231
raise AssertionError('did not raise NotFound')
233
def testViewSetSeparation(self):
235
self.rtr.resolve('/api/info1/+edit')
237
assert_equal(e.args, (self.r.subjects['info1'], '+edit', ()))
241
raise AssertionError('did not raise NotFound')
243
def testRouteReturningNone(self):
245
self.rtr.resolve('/info9/+index')
247
assert_equal(e.args, (self.r, 'info9', ('+index',)))
251
raise AssertionError('did not raise NotFound')
253
def testRouteWithInfinitelyManyArguments(self):
254
o, v, sp = self.rtr.resolve('/info1/2009/1/+files/foo/bar/baz')
256
assert_equal(type(o), OfferingFile)
257
assert_equal(o.path, ('foo', 'bar', 'baz'))
258
assert_equal(o.offering, self.r.subjects['info1'].offerings[(2009, 1)])
259
assert_equal(v, OfferingFileIndex)
262
def testMissingRoute(self):
264
self.rtr.resolve('/info1/2009/1/+foo')
266
assert_equal(e.args, (
267
self.r.subjects['info1'].offerings[(2009, 1)],
274
raise AssertionError('did not raise NotFound')
276
def testAlternateViewSetWithDefault(self):
277
assert_equal(self.rtr.resolve('/info1/2009/1'),
278
(self.r.subjects['info1'].offerings[(2009, 1)], OfferingIndex, ())
281
assert_equal(self.rtr.resolve('/api/info1/2009/1'),
282
(self.r.subjects['info1'].offerings[(2009, 1)], OfferingAPIIndex, ())
285
def testDeepView(self):
286
assert_equal(self.rtr.resolve('/info1/2009/1/+projects/+new'),
287
(self.r.subjects['info1'].offerings[(2009, 1)],
288
OfferingAddProject, ())
291
def testDefaultDeepView(self):
292
assert_equal(self.rtr.resolve('/info1/2009/1/+projects'),
293
(self.r.subjects['info1'].offerings[(2009, 1)],
294
OfferingProjects, ())
297
def testNamedRouteWithDeepView(self):
298
assert_equal(self.rtr.resolve('/info1/2009/1/+projects/p1'),
299
(self.r.subjects['info1'].offerings[(2009, 1)].projects['p1'],
304
class TestGeneration(BaseTest):
306
super(TestGeneration, self).setUp()
307
self.rtr = Publisher(root=self.r, viewset='browser')
308
self.rtr.add_set_switch('api', 'api')
309
self.rtr.add_reverse(Subject, subject_url)
310
self.rtr.add_reverse(Offering, offering_url)
311
self.rtr.add_reverse(OfferingFiles, offering_files_url)
312
self.rtr.add_reverse(Project, project_url)
313
self.rtr.add_view(Subject, '+index', SubjectIndex, viewset='browser')
314
self.rtr.add_view(Subject, '+edit', SubjectEdit, viewset='browser')
315
self.rtr.add_view(Offering, '+index', OfferingIndex, viewset='browser')
316
self.rtr.add_view(Offering, '+index', OfferingAPIIndex, viewset='api')
317
self.rtr.add_view(Project, '+index', ProjectIndex, viewset='browser')
318
self.rtr.add_view(Offering, ('+projects', '+new'), OfferingAddProject,
320
self.rtr.add_view(Offering, ('+projects', '+index'), OfferingProjects,
323
def testOneLevel(self):
324
assert_equal(self.rtr.generate(self.r.subjects['info1']), '/info1')
326
def testTwoLevel(self):
328
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)]),
332
self.rtr.generate(self.r.subjects['info2'].offerings[(2008, 2)]),
336
def testNamedRoute(self):
337
assert_equal(self.rtr.generate(
338
OfferingFiles(self.r.subjects['info1'].offerings[(2009, 1)])),
339
'/info1/2009/1/+files'
343
assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit),
347
def testDefaultView(self):
349
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
355
def testViewWithSubpath(self):
356
assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit,
358
'/info1/+edit/foo/bar'
361
def testViewWithStringSubpath(self):
362
assert_equal(self.rtr.generate(self.r.subjects['info1'], SubjectEdit,
364
'/info1/+edit/foo/bar'
367
def testAlternateViewSetWithDefault(self):
369
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
375
def testDeepView(self):
378
self.r.subjects['info1'].offerings[(2009, 1)],
381
'/info1/2009/1/+projects/+new'
384
def testDefaultDeepView(self):
387
self.r.subjects['info1'].offerings[(2009, 1)],
390
'/info1/2009/1/+projects'
393
def testDefaultDeepViewWithSubpath(self):
396
self.r.subjects['info1'].offerings[(2009, 1)],
397
OfferingProjects, ('foo', 'bar')
399
'/info1/2009/1/+projects/+index/foo/bar'
402
def testNamedRouteWithDeepView(self):
405
self.r.subjects['info1'].offerings[(2009, 1)].projects['p1'],
408
'/info1/2009/1/+projects/p1'
412
assert_equal(self.rtr.generate(self.r), '/')
415
class TestErrors(BaseTest):
417
super(TestErrors, self).setUp()
418
self.rtr = Publisher(root=self.r)
419
self.rtr.add_forward(Root, None, root_to_subject, 1)
420
self.rtr.add_forward(Subject, '+foo', lambda s: s.name + 'foo', 0)
421
self.rtr.add_forward(Subject, None, subject_to_offering, 2)
422
self.rtr.add_reverse(Subject, subject_url)
423
self.rtr.add_reverse(Offering, offering_url)
424
self.rtr.add_view(Offering, '+index', OfferingIndex)
425
self.rtr.add_view(Offering, '+index', OfferingAPIIndex, viewset='api')
426
self.rtr.add_set_switch('rest', 'rest')
428
@raises(RouteConflict)
429
def testForwardConflict(self):
430
self.rtr.add_forward(Subject, '+foo', object(), 2)
432
@raises(RouteConflict)
433
def testReverseConflict(self):
434
self.rtr.add_reverse(Subject, object())
436
@raises(RouteConflict)
437
def testViewConflict(self):
438
self.rtr.add_view(Offering, '+index', object())
440
@raises(RouteConflict)
441
def testSetSwitchForwardConflict(self):
442
self.rtr.add_set_switch('rest', 'foo')
444
@raises(RouteConflict)
445
def testSetSwitchReverseConflict(self):
446
self.rtr.add_set_switch('bar', 'rest')
449
def testNoPath(self):
450
self.rtr.generate(object())
453
def testNoSetSwitch(self):
454
self.rtr.generate(self.r.subjects['info1'].offerings[(2009, 1)],
458
def testUnregisteredView(self):
459
self.rtr.generate(self.r.subjects['info1'], SubjectIndex)
462
def testNotFound(self):
463
self.rtr.resolve('/bar')
465
@raises(InsufficientPathSegments)
466
def testInsufficientPathSegments(self):
467
self.rtr.resolve('/info1/foo')