Michael Merickel
2018-10-15 81576ee51564c49d5ff3c1c07f214f22a8438231
commit | author | age
c73eb9 1 from collections import deque
77a146 2 import unittest
e16ab0 3 from pyramid import testing
77a146 4
f5b9e9 5 from pyramid.compat import (
bc37a5 6     PY2,
f5b9e9 7     text_,
CM 8     bytes_,
9     native_,
3c2f95 10     )
MR 11 from pyramid.security import (
12     AuthenticationAPIMixin,
13     AuthorizationAPIMixin,
f5b9e9 14     )
e6c2d2 15
11644e 16 class TestRequest(unittest.TestCase):
968209 17     def setUp(self):
e16ab0 18         self.config = testing.setUp()
968209 19
CM 20     def tearDown(self):
f426e5 21         testing.tearDown()
51c305 22
11644e 23     def _getTargetClass(self):
b60bdb 24         from pyramid.request import Request
11644e 25         return Request
02ce7d 26
TS 27     def _makeOne(self, environ=None):
28         if environ is None:
29             environ = {}
30         return self._getTargetClass()(environ)
2d5424 31
c51896 32     def _registerResourceURL(self):
CM 33         from pyramid.interfaces import IResourceURL
4eafaa 34         from zope.interface import Interface
c51896 35         class DummyResourceURL(object):
4eafaa 36             def __init__(self, context, request):
c51896 37                 self.physical_path = '/context/'
CM 38                 self.virtual_path = '/context/'
4eafaa 39         self.config.registry.registerAdapter(
c51896 40             DummyResourceURL, (Interface, Interface),
CM 41             IResourceURL)
02ce7d 42
TS 43     def test_class_conforms_to_IRequest(self):
44         from zope.interface.verify import verifyClass
45         from pyramid.interfaces import IRequest
46         verifyClass(IRequest, self._getTargetClass())
47
48     def test_instance_conforms_to_IRequest(self):
49         from zope.interface.verify import verifyObject
50         from pyramid.interfaces import IRequest
51         verifyObject(IRequest, self._makeOne())
4eafaa 52
c60c0e 53     def test_ResponseClass_is_pyramid_Response(self):
CM 54         from pyramid.response import Response
55         cls = self._getTargetClass()
56         self.assertEqual(cls.ResponseClass, Response)
57
3c2f95 58     def test_implements_security_apis(self):
MR 59         apis = (AuthenticationAPIMixin, AuthorizationAPIMixin)
60         r = self._makeOne()
61         self.assertTrue(isinstance(r, apis))
62
2d5424 63     def test_charset_defaults_to_utf8(self):
CM 64         r = self._makeOne({'PATH_INFO':'/'})
2d7993 65         self.assertEqual(r.charset, 'UTF-8')
77a146 66
81d3b5 67     def test_exception_defaults_to_None(self):
CM 68         r = self._makeOne({'PATH_INFO':'/'})
69         self.assertEqual(r.exception, None)
51c305 70
CR 71     def test_matchdict_defaults_to_None(self):
72         r = self._makeOne({'PATH_INFO':'/'})
73         self.assertEqual(r.matchdict, None)
74
75     def test_matched_route_defaults_to_None(self):
76         r = self._makeOne({'PATH_INFO':'/'})
77         self.assertEqual(r.matched_route, None)
81d3b5 78
77a146 79     def test_params_decoded_from_utf_8_by_default(self):
CM 80         environ = {
81             'PATH_INFO':'/',
82             'QUERY_STRING':'la=La%20Pe%C3%B1a'
83             }
84         request = self._makeOne(environ)
2d7993 85         request.charset = None
55fb96 86         self.assertEqual(request.GET['la'], text_(b'La Pe\xf1a'))
588c64 87
14dc81 88     def test_tmpl_context(self):
CM 89         from pyramid.request import TemplateContext
02ce7d 90         inst = self._makeOne()
14dc81 91         result = inst.tmpl_context
CM 92         self.assertEqual(result.__class__, TemplateContext)
51c305 93
968209 94     def test_session_configured(self):
CM 95         from pyramid.interfaces import ISessionFactory
02ce7d 96         inst = self._makeOne()
968209 97         def factory(request):
CM 98             return 'orangejuice'
99         self.config.registry.registerUtility(factory, ISessionFactory)
100         inst.registry = self.config.registry
101         self.assertEqual(inst.session, 'orangejuice')
102         self.assertEqual(inst.__dict__['session'], 'orangejuice')
103
104     def test_session_not_configured(self):
02ce7d 105         inst = self._makeOne()
968209 106         inst.registry = self.config.registry
147567 107         self.assertRaises(AttributeError, getattr, inst, 'session')
968209 108
e49bea 109     def test_setattr_and_getattr_dotnotation(self):
02ce7d 110         inst = self._makeOne()
e49bea 111         inst.foo = 1
CM 112         self.assertEqual(inst.foo, 1)
113
114     def test_setattr_and_getattr(self):
bca03f 115         environ = {}
CM 116         inst = self._makeOne(environ)
e49bea 117         setattr(inst, 'bar', 1)
CM 118         self.assertEqual(getattr(inst, 'bar'), 1)
bca03f 119         self.assertEqual(environ, {}) # make sure we're not using adhoc attrs
11644e 120
844e98 121     def test_add_response_callback(self):
02ce7d 122         inst = self._makeOne()
37f3ed 123         self.assertEqual(len(inst.response_callbacks), 0)
844e98 124         def callback(request, response):
CM 125             """ """
126         inst.add_response_callback(callback)
c73eb9 127         self.assertEqual(list(inst.response_callbacks), [callback])
844e98 128         inst.add_response_callback(callback)
c73eb9 129         self.assertEqual(list(inst.response_callbacks), [callback, callback])
844e98 130
CM 131     def test__process_response_callbacks(self):
02ce7d 132         inst = self._makeOne()
844e98 133         def callback1(request, response):
CM 134             request.called1 = True
135             response.called1 = True
136         def callback2(request, response):
137             request.called2  = True
138             response.called2 = True
c73eb9 139         inst.add_response_callback(callback1)
DV 140         inst.add_response_callback(callback2)
844e98 141         response = DummyResponse()
CM 142         inst._process_response_callbacks(response)
143         self.assertEqual(inst.called1, True)
144         self.assertEqual(inst.called2, True)
145         self.assertEqual(response.called1, True)
146         self.assertEqual(response.called2, True)
c73eb9 147         self.assertEqual(len(inst.response_callbacks), 0)
844e98 148
63e6e1 149     def test__process_response_callback_adding_response_callback(self):
DV 150         """
151         When a response callback adds another callback, that new callback should still be called.
152
153         See https://github.com/Pylons/pyramid/pull/1373
154         """
155         inst = self._makeOne()
156         def callback1(request, response):
157             request.called1 = True
158             response.called1 = True
159             request.add_response_callback(callback2)
160         def callback2(request, response):
161             request.called2  = True
162             response.called2 = True
163         inst.add_response_callback(callback1)
164         response = DummyResponse()
165         inst._process_response_callbacks(response)
166         self.assertEqual(inst.called1, True)
167         self.assertEqual(inst.called2, True)
168         self.assertEqual(response.called1, True)
169         self.assertEqual(response.called2, True)
c73eb9 170         self.assertEqual(len(inst.response_callbacks), 0)
63e6e1 171
ad6a67 172     def test_add_finished_callback(self):
02ce7d 173         inst = self._makeOne()
37f3ed 174         self.assertEqual(len(inst.finished_callbacks), 0)
ad6a67 175         def callback(request):
CM 176             """ """
177         inst.add_finished_callback(callback)
c73eb9 178         self.assertEqual(list(inst.finished_callbacks), [callback])
ad6a67 179         inst.add_finished_callback(callback)
c73eb9 180         self.assertEqual(list(inst.finished_callbacks), [callback, callback])
ad6a67 181
CM 182     def test__process_finished_callbacks(self):
02ce7d 183         inst = self._makeOne()
ad6a67 184         def callback1(request):
CM 185             request.called1 = True
186         def callback2(request):
187             request.called2  = True
c73eb9 188         inst.add_finished_callback(callback1)
DV 189         inst.add_finished_callback(callback2)
ad6a67 190         inst._process_finished_callbacks()
CM 191         self.assertEqual(inst.called1, True)
192         self.assertEqual(inst.called2, True)
c73eb9 193         self.assertEqual(len(inst.finished_callbacks), 0)
ad6a67 194
92c3e5 195     def test_resource_url(self):
c51896 196         self._registerResourceURL()
CM 197         environ = {
198             'PATH_INFO':'/',
199             'SERVER_NAME':'example.com',
200             'SERVER_PORT':'80',
201             'wsgi.url_scheme':'http',
202             }
203         inst = self._makeOne(environ)
4eafaa 204         root = DummyContext()
92c3e5 205         result = inst.resource_url(root)
4eafaa 206         self.assertEqual(result, 'http://example.com/context/')
CM 207
208     def test_route_url(self):
209         environ = {
210             'PATH_INFO':'/',
211             'SERVER_NAME':'example.com',
212             'SERVER_PORT':'5432',
213             'QUERY_STRING':'la=La%20Pe%C3%B1a',
214             'wsgi.url_scheme':'http',
215             }
216         from pyramid.interfaces import IRoutesMapper
217         inst = self._makeOne(environ)
218         mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3'))
219         self.config.registry.registerUtility(mapper, IRoutesMapper)
220         result = inst.route_url('flub', 'extra1', 'extra2',
221                                 a=1, b=2, c=3, _query={'a':1},
e6c2d2 222                                 _anchor=text_("foo"))
4eafaa 223         self.assertEqual(result,
CM 224                          'http://example.com:5432/1/2/3/extra1/extra2?a=1#foo')
225
2c9d14 226     def test_route_path(self):
CM 227         environ = {
228             'PATH_INFO':'/',
229             'SERVER_NAME':'example.com',
230             'SERVER_PORT':'5432',
231             'QUERY_STRING':'la=La%20Pe%C3%B1a',
232             'wsgi.url_scheme':'http',
233             }
234         from pyramid.interfaces import IRoutesMapper
235         inst = self._makeOne(environ)
236         mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3'))
237         self.config.registry.registerUtility(mapper, IRoutesMapper)
238         result = inst.route_path('flub', 'extra1', 'extra2',
239                                 a=1, b=2, c=3, _query={'a':1},
e6c2d2 240                                 _anchor=text_("foo"))
2c9d14 241         self.assertEqual(result, '/1/2/3/extra1/extra2?a=1#foo')
CM 242
9e2e1c 243     def test_static_url(self):
CM 244         from pyramid.interfaces import IStaticURLInfo
245         environ = {
246             'PATH_INFO':'/',
247             'SERVER_NAME':'example.com',
248             'SERVER_PORT':'5432',
249             'QUERY_STRING':'',
250             'wsgi.url_scheme':'http',
251             }
252         request = self._makeOne(environ)
253         info = DummyStaticURLInfo('abc')
254         self.config.registry.registerUtility(info, IStaticURLInfo)
255         result = request.static_url('pyramid.tests:static/foo.css')
256         self.assertEqual(result, 'abc')
257         self.assertEqual(info.args,
258                          ('pyramid.tests:static/foo.css', request, {}) )
920990 259
CM 260     def test_is_response_false(self):
02ce7d 261         request = self._makeOne()
920990 262         request.registry = self.config.registry
CM 263         self.assertEqual(request.is_response('abc'), False)
264
71cd93 265     def test_is_response_true_ob_is_pyramid_response(self):
CM 266         from pyramid.response import Response
267         r = Response('hello')
268         request = self._makeOne()
269         request.registry = self.config.registry
270         self.assertEqual(request.is_response(r), True)
271
920990 272     def test_is_response_false_adapter_is_not_self(self):
CM 273         from pyramid.interfaces import IResponse
02ce7d 274         request = self._makeOne()
920990 275         request.registry = self.config.registry
CM 276         def adapter(ob):
277             return object()
278         class Foo(object):
279             pass
280         foo = Foo()
281         request.registry.registerAdapter(adapter, (Foo,), IResponse)
282         self.assertEqual(request.is_response(foo), False)
9e2e1c 283         
920990 284     def test_is_response_adapter_true(self):
CM 285         from pyramid.interfaces import IResponse
02ce7d 286         request = self._makeOne()
920990 287         request.registry = self.config.registry
CM 288         class Foo(object):
289             pass
290         foo = Foo()
291         def adapter(ob):
292             return ob
293         request.registry.registerAdapter(adapter, (Foo,), IResponse)
294         self.assertEqual(request.is_response(foo), True)
9e2e1c 295
6a0602 296     def test_json_body_invalid_json(self):
578c4a 297         request = self._makeOne({'REQUEST_METHOD':'POST'})
8e606d 298         request.body = b'{'
6a0602 299         self.assertRaises(ValueError, getattr, request, 'json_body')
CM 300         
301     def test_json_body_valid_json(self):
302         request = self._makeOne({'REQUEST_METHOD':'POST'})
8e606d 303         request.body = b'{"a":1}'
6a0602 304         self.assertEqual(request.json_body, {'a':1})
b78eff 305
6a0602 306     def test_json_body_alternate_charset(self):
5cf9fc 307         import json
578c4a 308         request = self._makeOne({'REQUEST_METHOD':'POST'})
632a91 309         inp = text_(
CM 310             b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf',
311             'utf-8'
312             )
bc37a5 313         if PY2:
632a91 314             body = json.dumps({'a':inp}).decode('utf-8').encode('utf-16')
bc37a5 315         else:
MM 316             body = bytes(json.dumps({'a':inp}), 'utf-16')
b78eff 317         request.body = body
632a91 318         request.content_type = 'application/json; charset=utf-16'
CM 319         self.assertEqual(request.json_body, {'a':inp})
6a0602 320
CM 321     def test_json_body_GET_request(self):
322         request = self._makeOne({'REQUEST_METHOD':'GET'})
323         self.assertRaises(ValueError, getattr, request, 'json_body')
b78eff 324
577db9 325     def test_set_property(self):
02ce7d 326         request = self._makeOne()
577db9 327         opts = [2, 1]
MM 328         def connect(obj):
329             return opts.pop()
330         request.set_property(connect, name='db')
331         self.assertEqual(1, request.db)
332         self.assertEqual(2, request.db)
333
334     def test_set_property_reify(self):
02ce7d 335         request = self._makeOne()
577db9 336         opts = [2, 1]
MM 337         def connect(obj):
338             return opts.pop()
339         request.set_property(connect, name='db', reify=True)
340         self.assertEqual(1, request.db)
341         self.assertEqual(1, request.db)
342
11644e 343 class Test_route_request_iface(unittest.TestCase):
dfc2b6 344     def _callFUT(self, name):
b60bdb 345         from pyramid.request import route_request_iface
11644e 346         return route_request_iface(name)
dfc2b6 347
CM 348     def test_it(self):
11644e 349         iface = self._callFUT('routename')
de8212 350         self.assertEqual(iface.__name__, 'routename_IRequest')
ff1213 351         self.assertTrue(hasattr(iface, 'combined'))
CM 352         self.assertEqual(iface.combined.__name__, 'routename_combined_IRequest')
dfc2b6 353
873d9b 354     def test_it_routename_with_spaces(self):
CM 355         #  see https://github.com/Pylons/pyramid/issues/232
356         iface = self._callFUT('routename with spaces')
357         self.assertEqual(iface.__name__, 'routename with spaces_IRequest')
358         self.assertTrue(hasattr(iface, 'combined'))
359         self.assertEqual(iface.combined.__name__,
360                          'routename with spaces_combined_IRequest')
361         
362
839ea0 363 class Test_add_global_response_headers(unittest.TestCase):
CM 364     def _callFUT(self, request, headerlist):
b60bdb 365         from pyramid.request import add_global_response_headers
839ea0 366         return add_global_response_headers(request, headerlist)
CM 367
164677 368     def test_it(self):
839ea0 369         request = DummyRequest()
844e98 370         response = DummyResponse()
839ea0 371         self._callFUT(request, [('c', 1)])
844e98 372         self.assertEqual(len(request.response_callbacks), 1)
CM 373         request.response_callbacks[0](None, response)
a58c4a 374         self.assertEqual(response.headerlist,  [('c', 1)] )
839ea0 375
65a5a9 376 class Test_call_app_with_subpath_as_path_info(unittest.TestCase):
CM 377     def _callFUT(self, request, app):
378         from pyramid.request import call_app_with_subpath_as_path_info
379         return call_app_with_subpath_as_path_info(request, app)
380
381     def test_it_all_request_and_environment_data_missing(self):
382         request = DummyRequest({})
383         response = self._callFUT(request, 'app')
384         self.assertTrue(request.copied)
385         self.assertEqual(response, 'app')
386         self.assertEqual(request.environ['SCRIPT_NAME'], '')
387         self.assertEqual(request.environ['PATH_INFO'], '/')
388
389     def test_it_with_subpath_and_path_info(self):
390         request = DummyRequest({'PATH_INFO':'/hello'})
391         request.subpath = ('hello',)
392         response = self._callFUT(request, 'app')
393         self.assertTrue(request.copied)
394         self.assertEqual(response, 'app')
395         self.assertEqual(request.environ['SCRIPT_NAME'], '')
396         self.assertEqual(request.environ['PATH_INFO'], '/hello')
397
398     def test_it_with_subpath_and_path_info_path_info_endswith_slash(self):
399         request = DummyRequest({'PATH_INFO':'/hello/'})
400         request.subpath = ('hello',)
401         response = self._callFUT(request, 'app')
402         self.assertTrue(request.copied)
403         self.assertEqual(response, 'app')
404         self.assertEqual(request.environ['SCRIPT_NAME'], '')
405         self.assertEqual(request.environ['PATH_INFO'], '/hello/')
406
407     def test_it_with_subpath_and_path_info_extra_script_name(self):
408         request = DummyRequest({'PATH_INFO':'/hello', 'SCRIPT_NAME':'/script'})
409         request.subpath = ('hello',)
410         response = self._callFUT(request, 'app')
411         self.assertTrue(request.copied)
412         self.assertEqual(response, 'app')
413         self.assertEqual(request.environ['SCRIPT_NAME'], '/script')
414         self.assertEqual(request.environ['PATH_INFO'], '/hello')
415
416     def test_it_with_extra_slashes_in_path_info(self):
417         request = DummyRequest({'PATH_INFO':'//hello/',
418                                 'SCRIPT_NAME':'/script'})
419         request.subpath = ('hello',)
420         response = self._callFUT(request, 'app')
421         self.assertTrue(request.copied)
422         self.assertEqual(response, 'app')
423         self.assertEqual(request.environ['SCRIPT_NAME'], '/script')
424         self.assertEqual(request.environ['PATH_INFO'], '/hello/')
425
426     def test_subpath_path_info_and_script_name_have_utf8(self):
c779f1 427         encoded = native_(text_(b'La Pe\xc3\xb1a'))
CM 428         decoded = text_(bytes_(encoded), 'utf-8')
429         request = DummyRequest({'PATH_INFO':'/' + encoded,
430                                 'SCRIPT_NAME':'/' + encoded})
431         request.subpath = (decoded, )
65a5a9 432         response = self._callFUT(request, 'app')
CM 433         self.assertTrue(request.copied)
434         self.assertEqual(response, 'app')
c779f1 435         self.assertEqual(request.environ['SCRIPT_NAME'], '/' + encoded)
CM 436         self.assertEqual(request.environ['PATH_INFO'], '/' + encoded)
65a5a9 437
04cc91 438 class Test_apply_request_extensions(unittest.TestCase):
MM 439     def setUp(self):
440         self.config = testing.setUp()
441
442     def tearDown(self):
443         testing.tearDown()
444
445     def _callFUT(self, request, extensions=None):
446         from pyramid.request import apply_request_extensions
447         return apply_request_extensions(request, extensions=extensions)
448
449     def test_it_with_registry(self):
450         from pyramid.interfaces import IRequestExtensions
451         extensions = Dummy()
452         extensions.methods = {'foo': lambda x, y: y}
453         extensions.descriptors = {'bar': property(lambda x: 'bar')}
454         self.config.registry.registerUtility(extensions, IRequestExtensions)
455         request = DummyRequest()
456         request.registry = self.config.registry
457         self._callFUT(request)
458         self.assertEqual(request.bar, 'bar')
459         self.assertEqual(request.foo('abc'), 'abc')
460
461     def test_it_override_extensions(self):
462         from pyramid.interfaces import IRequestExtensions
463         ignore = Dummy()
464         ignore.methods = {'x': lambda x, y, z: 'asdf'}
465         ignore.descriptors = {'bar': property(lambda x: 'asdf')}
466         self.config.registry.registerUtility(ignore, IRequestExtensions)
467         request = DummyRequest()
468         request.registry = self.config.registry
469
470         extensions = Dummy()
471         extensions.methods = {'foo': lambda x, y: y}
472         extensions.descriptors = {'bar': property(lambda x: 'bar')}
473         self._callFUT(request, extensions=extensions)
474         self.assertRaises(AttributeError, lambda: request.x)
475         self.assertEqual(request.bar, 'bar')
476         self.assertEqual(request.foo('abc'), 'abc')
477
478 class Dummy(object):
479     pass
480
697213 481 class Test_subclassing_Request(unittest.TestCase):
BJR 482     def test_subclass(self):
942e7d 483         from pyramid.interfaces import IRequest
697213 484         from pyramid.request import Request
BJR 485
486         class RequestSub(Request):
487             pass
488
489         self.assertTrue(hasattr(Request, '__provides__'))
942e7d 490         self.assertTrue(hasattr(Request, '__implemented__'))
BJR 491         self.assertTrue(hasattr(Request, '__providedBy__'))
697213 492         self.assertFalse(hasattr(RequestSub, '__provides__'))
942e7d 493         self.assertTrue(hasattr(RequestSub, '__providedBy__'))
BJR 494         self.assertTrue(hasattr(RequestSub, '__implemented__'))
495
496         self.assertTrue(IRequest.implementedBy(RequestSub))
497         # The call to implementedBy will add __provides__ to the class
498         self.assertTrue(hasattr(RequestSub, '__provides__'))
499
697213 500
BJR 501     def test_subclass_with_implementer(self):
502         from pyramid.interfaces import IRequest
503         from pyramid.request import Request
d7734b 504         from pyramid.util import InstancePropertyHelper
CM 505         from zope.interface import implementer
697213 506
BJR 507         @implementer(IRequest)
508         class RequestSub(Request):
509             pass
510
511         self.assertTrue(hasattr(Request, '__provides__'))
942e7d 512         self.assertTrue(hasattr(Request, '__implemented__'))
BJR 513         self.assertTrue(hasattr(Request, '__providedBy__'))
697213 514         self.assertTrue(hasattr(RequestSub, '__provides__'))
942e7d 515         self.assertTrue(hasattr(RequestSub, '__providedBy__'))
BJR 516         self.assertTrue(hasattr(RequestSub, '__implemented__'))
697213 517
BJR 518         req = RequestSub({})
d7734b 519         helper = InstancePropertyHelper()
CM 520         helper.apply_properties(req, {'b': 'b'})
942e7d 521
BJR 522         self.assertTrue(IRequest.providedBy(req))
523         self.assertTrue(IRequest.implementedBy(RequestSub))
524
c7c02f 525     def test_subclass_mutate_before_providedBy(self):
BJR 526         from pyramid.interfaces import IRequest
527         from pyramid.request import Request
d7734b 528         from pyramid.util import InstancePropertyHelper
c7c02f 529
BJR 530         class RequestSub(Request):
531             pass
532
533         req = RequestSub({})
d7734b 534         helper = InstancePropertyHelper()
CM 535         helper.apply_properties(req, {'b': 'b'})
c7c02f 536
BJR 537         self.assertTrue(IRequest.providedBy(req))
538         self.assertTrue(IRequest.implementedBy(RequestSub))
539
697213 540
04cc91 541 class DummyRequest(object):
d66bfb 542     def __init__(self, environ=None):
CM 543         if environ is None:
544             environ = {}
545         self.environ = environ
77a146 546
844e98 547     def add_response_callback(self, callback):
CM 548         self.response_callbacks = [callback]
549
65a5a9 550     def get_response(self, app):
CM 551         return app
552
553     def copy(self):
554         self.copied = True
555         return self
556
844e98 557 class DummyResponse:
CM 558     def __init__(self):
a58c4a 559         self.headerlist = []
77a146 560
CM 561
4eafaa 562 class DummyContext:
CM 563     pass
564
565 class DummyRoutesMapper:
566     raise_exc = None
567     def __init__(self, route=None, raise_exc=False):
568         self.route = route
569
570     def get_route(self, route_name):
571         return self.route
572
573 class DummyRoute:
574     pregenerator = None
575     def __init__(self, result='/1/2/3'):
576         self.result = result
577
578     def generate(self, kw):
579         self.kw = kw
580         return self.result
9e2e1c 581
CM 582 class DummyStaticURLInfo:
583     def __init__(self, result):
584         self.result = result
585
586     def generate(self, path, request, **kw):
587         self.args = path, request, kw
588         return self.result