##// END OF EJS Templates
tests: add require methods to testing request....
marcink -
r2317:830b678e default
parent child Browse files
Show More
@@ -1,649 +1,664 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 The base Controller API
23 23 Provides the BaseController class for subclassing. And usage in different
24 24 controllers
25 25 """
26 26
27 27 import logging
28 28 import socket
29 29
30 30 import markupsafe
31 31 import ipaddress
32 32 import pyramid.threadlocal
33 33
34 34 from paste.auth.basic import AuthBasicAuthenticator
35 35 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
36 36 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
37 37
38 38 import rhodecode
39 39 from rhodecode.authentication.base import VCS_TYPE
40 40 from rhodecode.lib import auth, utils2
41 41 from rhodecode.lib import helpers as h
42 42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 43 from rhodecode.lib.exceptions import UserCreationError
44 44 from rhodecode.lib.utils import (
45 45 get_repo_slug, set_rhodecode_config, password_changed,
46 46 get_enabled_hook_classes)
47 47 from rhodecode.lib.utils2 import (
48 48 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist, safe_str)
49 49 from rhodecode.model import meta
50 50 from rhodecode.model.db import Repository, User, ChangesetComment
51 51 from rhodecode.model.notification import NotificationModel
52 52 from rhodecode.model.scm import ScmModel
53 53 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
54 54
55 55 # NOTE(marcink): remove after base controller is no longer required
56 56 from pylons.controllers import WSGIController
57 57 from pylons.i18n import translation
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 # hack to make the migration to pyramid easier
63 63 def render(template_name, extra_vars=None, cache_key=None,
64 64 cache_type=None, cache_expire=None):
65 65 """Render a template with Mako
66 66
67 67 Accepts the cache options ``cache_key``, ``cache_type``, and
68 68 ``cache_expire``.
69 69
70 70 """
71 71 from pylons.templating import literal
72 72 from pylons.templating import cached_template, pylons_globals
73 73
74 74 # Create a render callable for the cache function
75 75 def render_template():
76 76 # Pull in extra vars if needed
77 77 globs = extra_vars or {}
78 78
79 79 # Second, get the globals
80 80 globs.update(pylons_globals())
81 81
82 82 globs['_ungettext'] = globs['ungettext']
83 83 # Grab a template reference
84 84 template = globs['app_globals'].mako_lookup.get_template(template_name)
85 85
86 86 return literal(template.render_unicode(**globs))
87 87
88 88 return cached_template(template_name, render_template, cache_key=cache_key,
89 89 cache_type=cache_type, cache_expire=cache_expire)
90 90
91 91 def _filter_proxy(ip):
92 92 """
93 93 Passed in IP addresses in HEADERS can be in a special format of multiple
94 94 ips. Those comma separated IPs are passed from various proxies in the
95 95 chain of request processing. The left-most being the original client.
96 96 We only care about the first IP which came from the org. client.
97 97
98 98 :param ip: ip string from headers
99 99 """
100 100 if ',' in ip:
101 101 _ips = ip.split(',')
102 102 _first_ip = _ips[0].strip()
103 103 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
104 104 return _first_ip
105 105 return ip
106 106
107 107
108 108 def _filter_port(ip):
109 109 """
110 110 Removes a port from ip, there are 4 main cases to handle here.
111 111 - ipv4 eg. 127.0.0.1
112 112 - ipv6 eg. ::1
113 113 - ipv4+port eg. 127.0.0.1:8080
114 114 - ipv6+port eg. [::1]:8080
115 115
116 116 :param ip:
117 117 """
118 118 def is_ipv6(ip_addr):
119 119 if hasattr(socket, 'inet_pton'):
120 120 try:
121 121 socket.inet_pton(socket.AF_INET6, ip_addr)
122 122 except socket.error:
123 123 return False
124 124 else:
125 125 # fallback to ipaddress
126 126 try:
127 127 ipaddress.IPv6Address(safe_unicode(ip_addr))
128 128 except Exception:
129 129 return False
130 130 return True
131 131
132 132 if ':' not in ip: # must be ipv4 pure ip
133 133 return ip
134 134
135 135 if '[' in ip and ']' in ip: # ipv6 with port
136 136 return ip.split(']')[0][1:].lower()
137 137
138 138 # must be ipv6 or ipv4 with port
139 139 if is_ipv6(ip):
140 140 return ip
141 141 else:
142 142 ip, _port = ip.split(':')[:2] # means ipv4+port
143 143 return ip
144 144
145 145
146 146 def get_ip_addr(environ):
147 147 proxy_key = 'HTTP_X_REAL_IP'
148 148 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
149 149 def_key = 'REMOTE_ADDR'
150 150 _filters = lambda x: _filter_port(_filter_proxy(x))
151 151
152 152 ip = environ.get(proxy_key)
153 153 if ip:
154 154 return _filters(ip)
155 155
156 156 ip = environ.get(proxy_key2)
157 157 if ip:
158 158 return _filters(ip)
159 159
160 160 ip = environ.get(def_key, '0.0.0.0')
161 161 return _filters(ip)
162 162
163 163
164 164 def get_server_ip_addr(environ, log_errors=True):
165 165 hostname = environ.get('SERVER_NAME')
166 166 try:
167 167 return socket.gethostbyname(hostname)
168 168 except Exception as e:
169 169 if log_errors:
170 170 # in some cases this lookup is not possible, and we don't want to
171 171 # make it an exception in logs
172 172 log.exception('Could not retrieve server ip address: %s', e)
173 173 return hostname
174 174
175 175
176 176 def get_server_port(environ):
177 177 return environ.get('SERVER_PORT')
178 178
179 179
180 180 def get_access_path(environ):
181 181 path = environ.get('PATH_INFO')
182 182 org_req = environ.get('pylons.original_request')
183 183 if org_req:
184 184 path = org_req.environ.get('PATH_INFO')
185 185 return path
186 186
187 187
188 188 def get_user_agent(environ):
189 189 return environ.get('HTTP_USER_AGENT')
190 190
191 191
192 192 def vcs_operation_context(
193 193 environ, repo_name, username, action, scm, check_locking=True,
194 194 is_shadow_repo=False):
195 195 """
196 196 Generate the context for a vcs operation, e.g. push or pull.
197 197
198 198 This context is passed over the layers so that hooks triggered by the
199 199 vcs operation know details like the user, the user's IP address etc.
200 200
201 201 :param check_locking: Allows to switch of the computation of the locking
202 202 data. This serves mainly the need of the simplevcs middleware to be
203 203 able to disable this for certain operations.
204 204
205 205 """
206 206 # Tri-state value: False: unlock, None: nothing, True: lock
207 207 make_lock = None
208 208 locked_by = [None, None, None]
209 209 is_anonymous = username == User.DEFAULT_USER
210 210 if not is_anonymous and check_locking:
211 211 log.debug('Checking locking on repository "%s"', repo_name)
212 212 user = User.get_by_username(username)
213 213 repo = Repository.get_by_repo_name(repo_name)
214 214 make_lock, __, locked_by = repo.get_locking_state(
215 215 action, user.user_id)
216 216
217 217 settings_model = VcsSettingsModel(repo=repo_name)
218 218 ui_settings = settings_model.get_ui_settings()
219 219
220 220 extras = {
221 221 'ip': get_ip_addr(environ),
222 222 'username': username,
223 223 'action': action,
224 224 'repository': repo_name,
225 225 'scm': scm,
226 226 'config': rhodecode.CONFIG['__file__'],
227 227 'make_lock': make_lock,
228 228 'locked_by': locked_by,
229 229 'server_url': utils2.get_server_url(environ),
230 230 'user_agent': get_user_agent(environ),
231 231 'hooks': get_enabled_hook_classes(ui_settings),
232 232 'is_shadow_repo': is_shadow_repo,
233 233 }
234 234 return extras
235 235
236 236
237 237 class BasicAuth(AuthBasicAuthenticator):
238 238
239 239 def __init__(self, realm, authfunc, registry, auth_http_code=None,
240 240 initial_call_detection=False, acl_repo_name=None):
241 241 self.realm = realm
242 242 self.initial_call = initial_call_detection
243 243 self.authfunc = authfunc
244 244 self.registry = registry
245 245 self.acl_repo_name = acl_repo_name
246 246 self._rc_auth_http_code = auth_http_code
247 247
248 248 def _get_response_from_code(self, http_code):
249 249 try:
250 250 return get_exception(safe_int(http_code))
251 251 except Exception:
252 252 log.exception('Failed to fetch response for code %s' % http_code)
253 253 return HTTPForbidden
254 254
255 255 def get_rc_realm(self):
256 256 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
257 257
258 258 def build_authentication(self):
259 259 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
260 260 if self._rc_auth_http_code and not self.initial_call:
261 261 # return alternative HTTP code if alternative http return code
262 262 # is specified in RhodeCode config, but ONLY if it's not the
263 263 # FIRST call
264 264 custom_response_klass = self._get_response_from_code(
265 265 self._rc_auth_http_code)
266 266 return custom_response_klass(headers=head)
267 267 return HTTPUnauthorized(headers=head)
268 268
269 269 def authenticate(self, environ):
270 270 authorization = AUTHORIZATION(environ)
271 271 if not authorization:
272 272 return self.build_authentication()
273 273 (authmeth, auth) = authorization.split(' ', 1)
274 274 if 'basic' != authmeth.lower():
275 275 return self.build_authentication()
276 276 auth = auth.strip().decode('base64')
277 277 _parts = auth.split(':', 1)
278 278 if len(_parts) == 2:
279 279 username, password = _parts
280 280 auth_data = self.authfunc(
281 281 username, password, environ, VCS_TYPE,
282 282 registry=self.registry, acl_repo_name=self.acl_repo_name)
283 283 if auth_data:
284 284 return {'username': username, 'auth_data': auth_data}
285 285 if username and password:
286 286 # we mark that we actually executed authentication once, at
287 287 # that point we can use the alternative auth code
288 288 self.initial_call = False
289 289
290 290 return self.build_authentication()
291 291
292 292 __call__ = authenticate
293 293
294 294
295 295 def calculate_version_hash(config):
296 296 return md5(
297 297 config.get('beaker.session.secret', '') +
298 298 rhodecode.__version__)[:8]
299 299
300 300
301 301 def get_current_lang(request):
302 302 # NOTE(marcink): remove after pyramid move
303 303 try:
304 304 return translation.get_lang()[0]
305 305 except:
306 306 pass
307 307
308 308 return getattr(request, '_LOCALE_', request.locale_name)
309 309
310 310
311 311 def attach_context_attributes(context, request, user_id):
312 312 """
313 313 Attach variables into template context called `c`, please note that
314 314 request could be pylons or pyramid request in here.
315 315 """
316 316 # NOTE(marcink): remove check after pyramid migration
317 317 if hasattr(request, 'registry'):
318 318 config = request.registry.settings
319 319 else:
320 320 from pylons import config
321 321
322 322 rc_config = SettingsModel().get_all_settings(cache=True)
323 323
324 324 context.rhodecode_version = rhodecode.__version__
325 325 context.rhodecode_edition = config.get('rhodecode.edition')
326 326 # unique secret + version does not leak the version but keep consistency
327 327 context.rhodecode_version_hash = calculate_version_hash(config)
328 328
329 329 # Default language set for the incoming request
330 330 context.language = get_current_lang(request)
331 331
332 332 # Visual options
333 333 context.visual = AttributeDict({})
334 334
335 335 # DB stored Visual Items
336 336 context.visual.show_public_icon = str2bool(
337 337 rc_config.get('rhodecode_show_public_icon'))
338 338 context.visual.show_private_icon = str2bool(
339 339 rc_config.get('rhodecode_show_private_icon'))
340 340 context.visual.stylify_metatags = str2bool(
341 341 rc_config.get('rhodecode_stylify_metatags'))
342 342 context.visual.dashboard_items = safe_int(
343 343 rc_config.get('rhodecode_dashboard_items', 100))
344 344 context.visual.admin_grid_items = safe_int(
345 345 rc_config.get('rhodecode_admin_grid_items', 100))
346 346 context.visual.repository_fields = str2bool(
347 347 rc_config.get('rhodecode_repository_fields'))
348 348 context.visual.show_version = str2bool(
349 349 rc_config.get('rhodecode_show_version'))
350 350 context.visual.use_gravatar = str2bool(
351 351 rc_config.get('rhodecode_use_gravatar'))
352 352 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
353 353 context.visual.default_renderer = rc_config.get(
354 354 'rhodecode_markup_renderer', 'rst')
355 355 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
356 356 context.visual.rhodecode_support_url = \
357 357 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
358 358
359 359 context.visual.affected_files_cut_off = 60
360 360
361 361 context.pre_code = rc_config.get('rhodecode_pre_code')
362 362 context.post_code = rc_config.get('rhodecode_post_code')
363 363 context.rhodecode_name = rc_config.get('rhodecode_title')
364 364 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
365 365 # if we have specified default_encoding in the request, it has more
366 366 # priority
367 367 if request.GET.get('default_encoding'):
368 368 context.default_encodings.insert(0, request.GET.get('default_encoding'))
369 369 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
370 370
371 371 # INI stored
372 372 context.labs_active = str2bool(
373 373 config.get('labs_settings_active', 'false'))
374 374 context.visual.allow_repo_location_change = str2bool(
375 375 config.get('allow_repo_location_change', True))
376 376 context.visual.allow_custom_hooks_settings = str2bool(
377 377 config.get('allow_custom_hooks_settings', True))
378 378 context.debug_style = str2bool(config.get('debug_style', False))
379 379
380 380 context.rhodecode_instanceid = config.get('instance_id')
381 381
382 382 context.visual.cut_off_limit_diff = safe_int(
383 383 config.get('cut_off_limit_diff'))
384 384 context.visual.cut_off_limit_file = safe_int(
385 385 config.get('cut_off_limit_file'))
386 386
387 387 # AppEnlight
388 388 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
389 389 context.appenlight_api_public_key = config.get(
390 390 'appenlight.api_public_key', '')
391 391 context.appenlight_server_url = config.get('appenlight.server_url', '')
392 392
393 393 # JS template context
394 394 context.template_context = {
395 395 'repo_name': None,
396 396 'repo_type': None,
397 397 'repo_landing_commit': None,
398 398 'rhodecode_user': {
399 399 'username': None,
400 400 'email': None,
401 401 'notification_status': False
402 402 },
403 403 'visual': {
404 404 'default_renderer': None
405 405 },
406 406 'commit_data': {
407 407 'commit_id': None
408 408 },
409 409 'pull_request_data': {'pull_request_id': None},
410 410 'timeago': {
411 411 'refresh_time': 120 * 1000,
412 412 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
413 413 },
414 414 'pyramid_dispatch': {
415 415
416 416 },
417 417 'extra': {'plugins': {}}
418 418 }
419 419 # END CONFIG VARS
420 420
421 421 # TODO: This dosn't work when called from pylons compatibility tween.
422 422 # Fix this and remove it from base controller.
423 423 # context.repo_name = get_repo_slug(request) # can be empty
424 424
425 425 diffmode = 'sideside'
426 426 if request.GET.get('diffmode'):
427 427 if request.GET['diffmode'] == 'unified':
428 428 diffmode = 'unified'
429 429 elif request.session.get('diffmode'):
430 430 diffmode = request.session['diffmode']
431 431
432 432 context.diffmode = diffmode
433 433
434 434 if request.session.get('diffmode') != diffmode:
435 435 request.session['diffmode'] = diffmode
436 436
437 437 context.csrf_token = auth.get_csrf_token(session=request.session)
438 438 context.backends = rhodecode.BACKENDS.keys()
439 439 context.backends.sort()
440 440 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
441 441
442 442 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
443 443 # given request will ALWAYS be pyramid one
444 444 pyramid_request = pyramid.threadlocal.get_current_request()
445 445 context.pyramid_request = pyramid_request
446 446
447 447 # web case
448 448 if hasattr(pyramid_request, 'user'):
449 449 context.auth_user = pyramid_request.user
450 450 context.rhodecode_user = pyramid_request.user
451 451
452 452 # api case
453 453 if hasattr(pyramid_request, 'rpc_user'):
454 454 context.auth_user = pyramid_request.rpc_user
455 455 context.rhodecode_user = pyramid_request.rpc_user
456 456
457 457 # attach the whole call context to the request
458 458 request.call_context = context
459 459
460 460
461 461 def get_auth_user(request):
462 462 environ = request.environ
463 463 session = request.session
464 464
465 465 ip_addr = get_ip_addr(environ)
466 466 # make sure that we update permissions each time we call controller
467 467 _auth_token = (request.GET.get('auth_token', '') or
468 468 request.GET.get('api_key', ''))
469 469
470 470 if _auth_token:
471 471 # when using API_KEY we assume user exists, and
472 472 # doesn't need auth based on cookies.
473 473 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
474 474 authenticated = False
475 475 else:
476 476 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
477 477 try:
478 478 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
479 479 ip_addr=ip_addr)
480 480 except UserCreationError as e:
481 481 h.flash(e, 'error')
482 482 # container auth or other auth functions that create users
483 483 # on the fly can throw this exception signaling that there's
484 484 # issue with user creation, explanation should be provided
485 485 # in Exception itself. We then create a simple blank
486 486 # AuthUser
487 487 auth_user = AuthUser(ip_addr=ip_addr)
488 488
489 489 if password_changed(auth_user, session):
490 490 session.invalidate()
491 491 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
492 492 auth_user = AuthUser(ip_addr=ip_addr)
493 493
494 494 authenticated = cookie_store.get('is_authenticated')
495 495
496 496 if not auth_user.is_authenticated and auth_user.is_user_object:
497 497 # user is not authenticated and not empty
498 498 auth_user.set_authenticated(authenticated)
499 499
500 500 return auth_user
501 501
502 502
503 503 class BaseController(WSGIController):
504 504
505 505 def __before__(self):
506 506 """
507 507 __before__ is called before controller methods and after __call__
508 508 """
509 509 # on each call propagate settings calls into global settings.
510 510 from pylons import config
511 511 from pylons import tmpl_context as c, request, url
512 512 set_rhodecode_config(config)
513 513 attach_context_attributes(c, request, self._rhodecode_user.user_id)
514 514
515 515 # TODO: Remove this when fixed in attach_context_attributes()
516 516 c.repo_name = get_repo_slug(request) # can be empty
517 517
518 518 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
519 519 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
520 520 self.sa = meta.Session
521 521 self.scm_model = ScmModel(self.sa)
522 522
523 523 # set user language
524 524 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
525 525 if user_lang:
526 526 translation.set_lang(user_lang)
527 527 log.debug('set language to %s for user %s',
528 528 user_lang, self._rhodecode_user)
529 529
530 530 def _dispatch_redirect(self, with_url, environ, start_response):
531 531 from webob.exc import HTTPFound
532 532 resp = HTTPFound(with_url)
533 533 environ['SCRIPT_NAME'] = '' # handle prefix middleware
534 534 environ['PATH_INFO'] = with_url
535 535 return resp(environ, start_response)
536 536
537 537 def __call__(self, environ, start_response):
538 538 """Invoke the Controller"""
539 539 # WSGIController.__call__ dispatches to the Controller method
540 540 # the request is routed to. This routing information is
541 541 # available in environ['pylons.routes_dict']
542 542 from rhodecode.lib import helpers as h
543 543 from pylons import tmpl_context as c, request, url
544 544
545 545 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
546 546 if environ.get('debugtoolbar.wants_pylons_context', False):
547 547 environ['debugtoolbar.pylons_context'] = c._current_obj()
548 548
549 549 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
550 550 environ['pylons.routes_dict']['action']])
551 551
552 552 self.rc_config = SettingsModel().get_all_settings(cache=True)
553 553 self.ip_addr = get_ip_addr(environ)
554 554
555 555 # The rhodecode auth user is looked up and passed through the
556 556 # environ by the pylons compatibility tween in pyramid.
557 557 # So we can just grab it from there.
558 558 auth_user = environ['rc_auth_user']
559 559
560 560 # set globals for auth user
561 561 request.user = auth_user
562 562 self._rhodecode_user = auth_user
563 563
564 564 log.info('IP: %s User: %s accessed %s [%s]' % (
565 565 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
566 566 _route_name)
567 567 )
568 568
569 569 user_obj = auth_user.get_instance()
570 570 if user_obj and user_obj.user_data.get('force_password_change'):
571 571 h.flash('You are required to change your password', 'warning',
572 572 ignore_duplicate=True)
573 573 return self._dispatch_redirect(
574 574 url('my_account_password'), environ, start_response)
575 575
576 576 return WSGIController.__call__(self, environ, start_response)
577 577
578 578
579 579 def h_filter(s):
580 580 """
581 581 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
582 582 we wrap this with additional functionality that converts None to empty
583 583 strings
584 584 """
585 585 if s is None:
586 586 return markupsafe.Markup()
587 587 return markupsafe.escape(s)
588 588
589 589
590 590 def add_events_routes(config):
591 591 """
592 592 Adds routing that can be used in events. Because some events are triggered
593 593 outside of pyramid context, we need to bootstrap request with some
594 594 routing registered
595 595 """
596 596
597 597 from rhodecode.apps._base import ADMIN_PREFIX
598 598
599 599 config.add_route(name='home', pattern='/')
600 600
601 601 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
602 602 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
603 603 config.add_route(name='repo_summary', pattern='/{repo_name}')
604 604 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
605 605 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
606 606
607 607 config.add_route(name='pullrequest_show',
608 608 pattern='/{repo_name}/pull-request/{pull_request_id}')
609 609 config.add_route(name='pull_requests_global',
610 610 pattern='/pull-request/{pull_request_id}')
611 611 config.add_route(name='repo_commit',
612 612 pattern='/{repo_name}/changeset/{commit_id}')
613 613
614 614 config.add_route(name='repo_files',
615 615 pattern='/{repo_name}/files/{commit_id}/{f_path}')
616 616
617 617
618 618 def bootstrap_config(request):
619 619 import pyramid.testing
620 620 registry = pyramid.testing.Registry('RcTestRegistry')
621
621 622 config = pyramid.testing.setUp(registry=registry, request=request)
622 623
623 624 # allow pyramid lookup in testing
624 625 config.include('pyramid_mako')
625 626
626 627 add_events_routes(config)
628
627 629 return config
628 630
629 631
630 632 def bootstrap_request(**kwargs):
631 633 import pyramid.testing
632 634
633 635 class TestRequest(pyramid.testing.DummyRequest):
634 636 application_url = kwargs.pop('application_url', 'http://example.com')
635 637 host = kwargs.pop('host', 'example.com:80')
636 638 domain = kwargs.pop('domain', 'example.com')
637 639
638 640 def translate(self, msg):
639 641 return msg
640 642
643 def plularize(self, singular, plural, n):
644 return singular
645
646 def get_partial_renderer(self, tmpl_name):
647
648 from rhodecode.lib.partial_renderer import get_partial_renderer
649 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
650
651 _call_context = {}
652 @property
653 def call_context(self):
654 return self._call_context
655
641 656 class TestDummySession(pyramid.testing.DummySession):
642 657 def save(*arg, **kw):
643 658 pass
644 659
645 660 request = TestRequest(**kwargs)
646 661 request.session = TestDummySession()
647 662
648 663 return request
649 664
General Comments 0
You need to be logged in to leave comments. Login now