Show More
@@ -40,7 +40,7 b' BACKENDS = {' | |||
|
40 | 40 | CELERY_ENABLED = False |
|
41 | 41 | CELERY_EAGER = False |
|
42 | 42 | |
|
43 |
# link to config for py |
|
|
43 | # link to config for pyramid | |
|
44 | 44 | CONFIG = {} |
|
45 | 45 | |
|
46 | 46 | # Populated with the settings dictionary from application init in |
@@ -911,7 +911,8 b' def update_repo(' | |||
|
911 | 911 | repo_enable_downloads=enable_downloads |
|
912 | 912 | if not isinstance(enable_downloads, Optional) else repo.enable_downloads) |
|
913 | 913 | |
|
914 |
ref_choices, _labels = ScmModel().get_repo_landing_revs( |
|
|
914 | ref_choices, _labels = ScmModel().get_repo_landing_revs( | |
|
915 | request.translate, repo=repo) | |
|
915 | 916 | |
|
916 | 917 | old_values = repo.get_api_data() |
|
917 | 918 | schema = repo_schema.RepoSchema().bind( |
@@ -32,7 +32,7 b' def assert_auth_settings_updated(respons' | |||
|
32 | 32 | |
|
33 | 33 | |
|
34 | 34 | @pytest.mark.usefixtures("autologin_user", "app") |
|
35 |
class TestAuthSettings |
|
|
35 | class TestAuthSettingsView(object): | |
|
36 | 36 | |
|
37 | 37 | def _enable_plugins(self, plugins_list, csrf_token, override=None, |
|
38 | 38 | verify_response=False): |
@@ -385,7 +385,7 b' class TestAdminUsersView(TestController)' | |||
|
385 | 385 | 'csrf_token': self.csrf_token, |
|
386 | 386 | }) |
|
387 | 387 | |
|
388 | msg = '???' | |
|
388 | msg = u'Username "%(username)s" is forbidden' | |
|
389 | 389 | msg = h.html_escape(msg % {'username': 'new_user'}) |
|
390 | 390 | response.mustcontain('<span class="error-message">%s</span>' % msg) |
|
391 | 391 | response.mustcontain( |
@@ -53,8 +53,6 b' log = logging.getLogger(__name__)' | |||
|
53 | 53 | class AdminPermissionsView(BaseAppView, DataGridAppView): |
|
54 | 54 | def load_default_context(self): |
|
55 | 55 | c = self._get_local_tmpl_context() |
|
56 | ||
|
57 | ||
|
58 | 56 | PermissionModel().set_global_permission_choices( |
|
59 | 57 | c, gettext_translator=self.request.translate) |
|
60 | 58 | return c |
@@ -58,7 +58,7 b' class AdminReposView(BaseAppView, DataGr' | |||
|
58 | 58 | c.repo_groups = RepoGroup.groups_choices(groups=acl_groups) |
|
59 | 59 | c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups) |
|
60 | 60 | c.landing_revs_choices, c.landing_revs = \ |
|
61 | ScmModel().get_repo_landing_revs() | |
|
61 | ScmModel().get_repo_landing_revs(self.request.translate) | |
|
62 | 62 | c.personal_repo_group = self._rhodecode_user.personal_repo_group |
|
63 | 63 | |
|
64 | 64 | @LoginRequired() |
@@ -660,7 +660,7 b' class AdminSettingsView(BaseAppView):' | |||
|
660 | 660 | c.active = 'search' |
|
661 | 661 | |
|
662 | 662 | searcher = searcher_from_config(self.request.registry.settings) |
|
663 | c.statistics = searcher.statistics() | |
|
663 | c.statistics = searcher.statistics(self.request.translate) | |
|
664 | 664 | |
|
665 | 665 | return self._get_template_context(c) |
|
666 | 666 |
@@ -259,7 +259,6 b' class UsersView(UserAppView):' | |||
|
259 | 259 | PermissionModel().set_global_permission_choices( |
|
260 | 260 | c, gettext_translator=req.translate) |
|
261 | 261 | |
|
262 | ||
|
263 | 262 | return c |
|
264 | 263 | |
|
265 | 264 | @LoginRequired() |
@@ -88,32 +88,31 b' class TestLoginController(object):' | |||
|
88 | 88 | def test_login_admin_ok(self): |
|
89 | 89 | response = self.app.post(route_path('login'), |
|
90 | 90 | {'username': 'test_admin', |
|
91 | 'password': 'test12'}) | |
|
92 | assert response.status == '302 Found' | |
|
91 | 'password': 'test12'}, status=302) | |
|
92 | response = response.follow() | |
|
93 | 93 | session = response.get_session_from_response() |
|
94 | 94 | username = session['rhodecode_user'].get('username') |
|
95 | 95 | assert username == 'test_admin' |
|
96 | response = response.follow() | |
|
97 | 96 | response.mustcontain('/%s' % HG_REPO) |
|
98 | 97 | |
|
99 | 98 | def test_login_regular_ok(self): |
|
100 | 99 | response = self.app.post(route_path('login'), |
|
101 | 100 | {'username': 'test_regular', |
|
102 | 'password': 'test12'}) | |
|
101 | 'password': 'test12'}, status=302) | |
|
103 | 102 | |
|
104 | assert response.status == '302 Found' | |
|
103 | response = response.follow() | |
|
105 | 104 | session = response.get_session_from_response() |
|
106 | 105 | username = session['rhodecode_user'].get('username') |
|
107 | 106 | assert username == 'test_regular' |
|
108 | response = response.follow() | |
|
107 | ||
|
109 | 108 | response.mustcontain('/%s' % HG_REPO) |
|
110 | 109 | |
|
111 | 110 | def test_login_ok_came_from(self): |
|
112 | 111 | test_came_from = '/_admin/users?branch=stable' |
|
113 | 112 | _url = '{}?came_from={}'.format(route_path('login'), test_came_from) |
|
114 | 113 | response = self.app.post( |
|
115 | _url, {'username': 'test_admin', 'password': 'test12'}) | |
|
116 | assert response.status == '302 Found' | |
|
114 | _url, {'username': 'test_admin', 'password': 'test12'}, status=302) | |
|
115 | ||
|
117 | 116 | assert 'branch=stable' in response.location |
|
118 | 117 | response = response.follow() |
|
119 | 118 | |
@@ -124,8 +123,8 b' class TestLoginController(object):' | |||
|
124 | 123 | with fixture.anon_access(False): |
|
125 | 124 | kwargs = {'branch': 'stable'} |
|
126 | 125 | response = self.app.get( |
|
127 |
h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs) |
|
|
128 |
|
|
|
126 | h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs), | |
|
127 | status=302) | |
|
129 | 128 | |
|
130 | 129 | response_query = urlparse.parse_qsl(response.location) |
|
131 | 130 | assert 'branch=stable' in response_query[0][1] |
@@ -200,13 +199,12 b' class TestLoginController(object):' | |||
|
200 | 199 | self.destroy_users.add(temp_user) |
|
201 | 200 | response = self.app.post(route_path('login'), |
|
202 | 201 | {'username': temp_user, |
|
203 | 'password': 'test123'}) | |
|
202 | 'password': 'test123'}, status=302) | |
|
204 | 203 | |
|
205 | assert response.status == '302 Found' | |
|
204 | response = response.follow() | |
|
206 | 205 | session = response.get_session_from_response() |
|
207 | 206 | username = session['rhodecode_user'].get('username') |
|
208 | 207 | assert username == temp_user |
|
209 | response = response.follow() | |
|
210 | 208 | response.mustcontain('/%s' % HG_REPO) |
|
211 | 209 | |
|
212 | 210 | # new password should be bcrypted, after log-in and transfer |
@@ -233,7 +231,7 b' class TestLoginController(object):' | |||
|
233 | 231 | ) |
|
234 | 232 | |
|
235 | 233 | assertr = response.assert_response() |
|
236 | msg = '???' | |
|
234 | msg = 'Username "%(username)s" already exists' | |
|
237 | 235 | msg = msg % {'username': uname} |
|
238 | 236 | assertr.element_contains('#username+.error-message', msg) |
|
239 | 237 | |
@@ -251,7 +249,7 b' class TestLoginController(object):' | |||
|
251 | 249 | ) |
|
252 | 250 | |
|
253 | 251 | assertr = response.assert_response() |
|
254 | msg = '???' | |
|
252 | msg = u'This e-mail address is already taken' | |
|
255 | 253 | assertr.element_contains('#email+.error-message', msg) |
|
256 | 254 | |
|
257 | 255 | def test_register_err_same_email_case_sensitive(self): |
@@ -267,7 +265,7 b' class TestLoginController(object):' | |||
|
267 | 265 | } |
|
268 | 266 | ) |
|
269 | 267 | assertr = response.assert_response() |
|
270 | msg = '???' | |
|
268 | msg = u'This e-mail address is already taken' | |
|
271 | 269 | assertr.element_contains('#email+.error-message', msg) |
|
272 | 270 | |
|
273 | 271 | def test_register_err_wrong_data(self): |
@@ -321,7 +319,7 b' class TestLoginController(object):' | |||
|
321 | 319 | ) |
|
322 | 320 | |
|
323 | 321 | assertr = response.assert_response() |
|
324 | msg = '???' | |
|
322 | msg = u'Username "%(username)s" already exists' | |
|
325 | 323 | msg = msg % {'username': usr} |
|
326 | 324 | assertr.element_contains('#username+.error-message', msg) |
|
327 | 325 | |
@@ -338,7 +336,7 b' class TestLoginController(object):' | |||
|
338 | 336 | } |
|
339 | 337 | ) |
|
340 | 338 | |
|
341 | msg = '???' | |
|
339 | msg = u'Invalid characters (non-ascii) in password' | |
|
342 | 340 | response.mustcontain(msg) |
|
343 | 341 | |
|
344 | 342 | def test_register_password_mismatch(self): |
@@ -353,7 +351,7 b' class TestLoginController(object):' | |||
|
353 | 351 | 'lastname': 'test' |
|
354 | 352 | } |
|
355 | 353 | ) |
|
356 | msg = '???' | |
|
354 | msg = u'Passwords do not match' | |
|
357 | 355 | response.mustcontain(msg) |
|
358 | 356 | |
|
359 | 357 | def test_register_ok(self): |
@@ -363,6 +361,11 b' class TestLoginController(object):' | |||
|
363 | 361 | name = 'testname' |
|
364 | 362 | lastname = 'testlastname' |
|
365 | 363 | |
|
364 | # this initializes a session | |
|
365 | response = self.app.get(route_path('register')) | |
|
366 | response.mustcontain('Create an Account') | |
|
367 | ||
|
368 | ||
|
366 | 369 | response = self.app.post( |
|
367 | 370 | route_path('register'), |
|
368 | 371 | { |
@@ -373,9 +376,10 b' class TestLoginController(object):' | |||
|
373 | 376 | 'firstname': name, |
|
374 | 377 | 'lastname': lastname, |
|
375 | 378 | 'admin': True |
|
376 | } | |
|
377 | ) # This should be overriden | |
|
378 | assert response.status == '302 Found' | |
|
379 | }, | |
|
380 | status=302 | |
|
381 | ) # This should be overridden | |
|
382 | ||
|
379 | 383 | assert_session_flash( |
|
380 | 384 | response, 'You have successfully registered with RhodeCode') |
|
381 | 385 | |
@@ -391,6 +395,9 b' class TestLoginController(object):' | |||
|
391 | 395 | |
|
392 | 396 | def test_forgot_password_wrong_mail(self): |
|
393 | 397 | bad_email = 'marcin@wrongmail.org' |
|
398 | # this initializes a session | |
|
399 | self.app.get(route_path('reset_password')) | |
|
400 | ||
|
394 | 401 | response = self.app.post( |
|
395 | 402 | route_path('reset_password'), {'email': bad_email, } |
|
396 | 403 | ) |
@@ -398,8 +405,8 b' class TestLoginController(object):' | |||
|
398 | 405 | 'If such email exists, a password reset link was sent to it.') |
|
399 | 406 | |
|
400 | 407 | def test_forgot_password(self, user_util): |
|
401 | response = self.app.get(route_path('reset_password')) | |
|
402 | assert response.status == '200 OK' | |
|
408 | # this initializes a session | |
|
409 | self.app.get(route_path('reset_password')) | |
|
403 | 410 | |
|
404 | 411 | user = user_util.create_user() |
|
405 | 412 | user_id = user.user_id |
@@ -412,8 +419,7 b' class TestLoginController(object):' | |||
|
412 | 419 | |
|
413 | 420 | # BAD KEY |
|
414 | 421 | confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), 'badkey') |
|
415 | response = self.app.get(confirm_url) | |
|
416 | assert response.status == '302 Found' | |
|
422 | response = self.app.get(confirm_url, status=302) | |
|
417 | 423 | assert response.location.endswith(route_path('reset_password')) |
|
418 | 424 | assert_session_flash(response, 'Given reset token is invalid') |
|
419 | 425 |
@@ -58,7 +58,7 b' class TestRegisterCaptcha(object):' | |||
|
58 | 58 | ('privkey', '', CaptchaData(True, 'privkey', '')), |
|
59 | 59 | ('privkey', 'pubkey', CaptchaData(True, 'privkey', 'pubkey')), |
|
60 | 60 | ]) |
|
61 |
def test_get_captcha_data(self, private_key, public_key, expected, |
|
|
61 | def test_get_captcha_data(self, private_key, public_key, expected, | |
|
62 | 62 | request_stub, user_util): |
|
63 | 63 | request_stub.user = user_util.create_user().AuthUser() |
|
64 | 64 | request_stub.matched_route = AttributeDict({'name': 'login'}) |
@@ -32,7 +32,7 b' from recaptcha.client.captcha import sub' | |||
|
32 | 32 | |
|
33 | 33 | from rhodecode.apps._base import BaseAppView |
|
34 | 34 | from rhodecode.authentication.base import authenticate, HTTP_TYPE |
|
35 | from rhodecode.events import UserRegistered | |
|
35 | from rhodecode.events import UserRegistered, trigger | |
|
36 | 36 | from rhodecode.lib import helpers as h |
|
37 | 37 | from rhodecode.lib import audit_logger |
|
38 | 38 | from rhodecode.lib.auth import ( |
@@ -147,7 +147,7 b' class LoginView(BaseAppView):' | |||
|
147 | 147 | raise HTTPFound(c.came_from, headers=headers) |
|
148 | 148 | except UserCreationError as e: |
|
149 | 149 | log.error(e) |
|
150 |
|
|
|
150 | h.flash(e, category='error') | |
|
151 | 151 | |
|
152 | 152 | return self._get_template_context(c) |
|
153 | 153 | |
@@ -201,7 +201,7 b' class LoginView(BaseAppView):' | |||
|
201 | 201 | # the fly can throw this exception signaling that there's issue |
|
202 | 202 | # with user creation, explanation should be provided in |
|
203 | 203 | # Exception itself |
|
204 |
|
|
|
204 | h.flash(e, category='error') | |
|
205 | 205 | return self._get_template_context(c) |
|
206 | 206 | |
|
207 | 207 | @CSRFRequired() |
@@ -250,6 +250,7 b' class LoginView(BaseAppView):' | |||
|
250 | 250 | route_name='register', request_method='POST', |
|
251 | 251 | renderer='rhodecode:templates/register.mako') |
|
252 | 252 | def register_post(self): |
|
253 | self.load_default_context() | |
|
253 | 254 | captcha = self._get_captcha_data() |
|
254 | 255 | auto_active = 'hg.register.auto_activate' in User.get_default_user()\ |
|
255 | 256 | .AuthUser().permissions['global'] |
@@ -275,10 +276,10 b' class LoginView(BaseAppView):' | |||
|
275 | 276 | |
|
276 | 277 | new_user = UserModel().create_registration(form_result) |
|
277 | 278 | event = UserRegistered(user=new_user, session=self.session) |
|
278 |
|
|
|
279 |
|
|
|
279 | trigger(event) | |
|
280 | h.flash( | |
|
280 | 281 | _('You have successfully registered with RhodeCode'), |
|
281 |
|
|
|
282 | category='success') | |
|
282 | 283 | Session().commit() |
|
283 | 284 | |
|
284 | 285 | redirect_ro = self.request.route_path('login') |
@@ -295,16 +296,17 b' class LoginView(BaseAppView):' | |||
|
295 | 296 | # the fly can throw this exception signaling that there's issue |
|
296 | 297 | # with user creation, explanation should be provided in |
|
297 | 298 | # Exception itself |
|
298 |
|
|
|
299 | h.flash(e, category='error') | |
|
299 | 300 | return self.register() |
|
300 | 301 | |
|
301 | 302 | @view_config( |
|
302 | 303 | route_name='reset_password', request_method=('GET', 'POST'), |
|
303 | 304 | renderer='rhodecode:templates/password_reset.mako') |
|
304 | 305 | def password_reset(self): |
|
306 | c = self.load_default_context() | |
|
305 | 307 | captcha = self._get_captcha_data() |
|
306 | 308 | |
|
307 |
|
|
|
309 | template_context = { | |
|
308 | 310 | 'captcha_active': captcha.active, |
|
309 | 311 | 'captcha_public_key': captcha.public_key, |
|
310 | 312 | 'defaults': {}, |
@@ -319,8 +321,8 b' class LoginView(BaseAppView):' | |||
|
319 | 321 | if h.HasPermissionAny('hg.password_reset.disabled')(): |
|
320 | 322 | _email = self.request.POST.get('email', '') |
|
321 | 323 | log.error('Failed attempt to reset password for `%s`.', _email) |
|
322 |
|
|
|
323 |
|
|
|
324 | h.flash(_('Password reset has been disabled.'), | |
|
325 | category='error') | |
|
324 | 326 | return HTTPFound(self.request.route_path('reset_password')) |
|
325 | 327 | |
|
326 | 328 | password_reset_form = PasswordResetForm(self.request.translate)() |
@@ -361,7 +363,7 b' class LoginView(BaseAppView):' | |||
|
361 | 363 | UserModel().reset_password_link( |
|
362 | 364 | form_result, password_reset_url) |
|
363 | 365 | # Display success message and redirect. |
|
364 |
|
|
|
366 | h.flash(msg, category='success') | |
|
365 | 367 | |
|
366 | 368 | action_data = {'email': user_email, |
|
367 | 369 | 'user_agent': self.request.user_agent} |
@@ -371,30 +373,30 b' class LoginView(BaseAppView):' | |||
|
371 | 373 | return HTTPFound(self.request.route_path('reset_password')) |
|
372 | 374 | |
|
373 | 375 | except formencode.Invalid as errors: |
|
374 |
|
|
|
376 | template_context.update({ | |
|
375 | 377 | 'defaults': errors.value, |
|
376 | 378 | 'errors': errors.error_dict, |
|
377 | 379 | }) |
|
378 | 380 | if not self.request.POST.get('email'): |
|
379 | 381 | # case of empty email, we want to report that |
|
380 | return render_ctx | |
|
382 | return self._get_template_context(c, **template_context) | |
|
381 | 383 | |
|
382 | 384 | if 'recaptcha_field' in errors.error_dict: |
|
383 | 385 | # case of failed captcha |
|
384 | return render_ctx | |
|
386 | return self._get_template_context(c, **template_context) | |
|
385 | 387 | |
|
386 | 388 | log.debug('faking response on invalid password reset') |
|
387 | 389 | # make this take 2s, to prevent brute forcing. |
|
388 | 390 | time.sleep(2) |
|
389 |
|
|
|
391 | h.flash(msg, category='success') | |
|
390 | 392 | return HTTPFound(self.request.route_path('reset_password')) |
|
391 | 393 | |
|
392 | return render_ctx | |
|
394 | return self._get_template_context(c, **template_context) | |
|
393 | 395 | |
|
394 | 396 | @view_config(route_name='reset_password_confirmation', |
|
395 | 397 | request_method='GET') |
|
396 | 398 | def password_reset_confirmation(self): |
|
397 | ||
|
399 | self.load_default_context() | |
|
398 | 400 | if self.request.GET and self.request.GET.get('key'): |
|
399 | 401 | # make this take 2s, to prevent brute forcing. |
|
400 | 402 | time.sleep(2) |
@@ -407,18 +409,18 b' class LoginView(BaseAppView):' | |||
|
407 | 409 | log.debug('Got token with role:%s expected is %s', |
|
408 | 410 | getattr(token, 'role', 'EMPTY_TOKEN'), |
|
409 | 411 | UserApiKeys.ROLE_PASSWORD_RESET) |
|
410 |
|
|
|
411 |
_('Given reset token is invalid'), |
|
|
412 | h.flash( | |
|
413 | _('Given reset token is invalid'), category='error') | |
|
412 | 414 | return HTTPFound(self.request.route_path('reset_password')) |
|
413 | 415 | |
|
414 | 416 | try: |
|
415 | 417 | owner = token.user |
|
416 | 418 | data = {'email': owner.email, 'token': token.api_key} |
|
417 | 419 | UserModel().reset_password(data) |
|
418 |
|
|
|
420 | h.flash( | |
|
419 | 421 | _('Your password reset was successful, ' |
|
420 | 422 | 'a new password has been sent to your email'), |
|
421 |
|
|
|
423 | category='success') | |
|
422 | 424 | except Exception as e: |
|
423 | 425 | log.error(e) |
|
424 | 426 | return HTTPFound(self.request.route_path('reset_password')) |
@@ -198,6 +198,6 b' class TestMyAccountEdit(TestController):' | |||
|
198 | 198 | params=params) |
|
199 | 199 | |
|
200 | 200 | response.mustcontain('An email address must contain a single @') |
|
201 | msg = '???' | |
|
201 | msg = u'Username "%(username)s" already exists' | |
|
202 | 202 | msg = h.html_escape(msg % {'username': 'test_admin'}) |
|
203 | 203 | response.mustcontain(u"%s" % msg) |
@@ -572,6 +572,7 b' class MyAccountView(BaseAppView, DataGri' | |||
|
572 | 572 | route_name='my_account_pullrequests_data', |
|
573 | 573 | request_method='GET', renderer='json_ext') |
|
574 | 574 | def my_account_pullrequests_data(self): |
|
575 | self.load_default_context() | |
|
575 | 576 | req_get = self.request.GET |
|
576 | 577 | closed = str2bool(req_get.get('closed')) |
|
577 | 578 |
@@ -48,6 +48,7 b' def route_path(name, params=None, **kwar' | |||
|
48 | 48 | import urllib |
|
49 | 49 | |
|
50 | 50 | base_url = { |
|
51 | 'repo_summary': '/{repo_name}', | |
|
51 | 52 | 'repo_archivefile': '/{repo_name}/archive/{fname}', |
|
52 | 53 | 'repo_files_diff': '/{repo_name}/diff/{f_path}', |
|
53 | 54 | 'repo_files_diff_2way_redirect': '/{repo_name}/diff-2way/{f_path}', |
@@ -999,8 +1000,11 b' class TestFilesViewOtherCases(object):' | |||
|
999 | 1000 | .format(repo_file_add_url)) |
|
1000 | 1001 | |
|
1001 | 1002 | def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms( |
|
1002 |
self, backend_stub, |
|
|
1003 | self, backend_stub, autologin_regular_user): | |
|
1003 | 1004 | repo = backend_stub.create_repo() |
|
1005 | # init session for anon user | |
|
1006 | route_path('repo_summary', repo_name=repo.repo_name) | |
|
1007 | ||
|
1004 | 1008 | repo_file_add_url = route_path( |
|
1005 | 1009 | 'repo_files_add_file', |
|
1006 | 1010 | repo_name=repo.repo_name, |
@@ -53,11 +53,11 b' class RepoForksView(RepoAppView, DataGri' | |||
|
53 | 53 | perm_set=['group.write', 'group.admin']) |
|
54 | 54 | c.repo_groups = RepoGroup.groups_choices(groups=acl_groups) |
|
55 | 55 | c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups) |
|
56 |
choices, c.landing_revs = ScmModel().get_repo_landing_revs( |
|
|
56 | choices, c.landing_revs = ScmModel().get_repo_landing_revs( | |
|
57 | self.request.translate) | |
|
57 | 58 | c.landing_revs_choices = choices |
|
58 | 59 | c.personal_repo_group = c.rhodecode_user.personal_repo_group |
|
59 | 60 | |
|
60 | ||
|
61 | 61 | return c |
|
62 | 62 | |
|
63 | 63 | @LoginRequired() |
@@ -78,6 +78,7 b' class RepoForksView(RepoAppView, DataGri' | |||
|
78 | 78 | renderer='json_ext', xhr=True) |
|
79 | 79 | def repo_forks_data(self): |
|
80 | 80 | _ = self.request.translate |
|
81 | self.load_default_context() | |
|
81 | 82 | column_map = { |
|
82 | 83 | 'fork_name': 'repo_name', |
|
83 | 84 | 'fork_date': 'created_on', |
@@ -173,6 +173,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
173 | 173 | route_name='pullrequest_show_all_data', request_method='GET', |
|
174 | 174 | renderer='json_ext', xhr=True) |
|
175 | 175 | def pull_request_list_data(self): |
|
176 | self.load_default_context() | |
|
176 | 177 | |
|
177 | 178 | # additional filters |
|
178 | 179 | req_get = self.request.GET |
@@ -675,6 +676,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
675 | 676 | route_name='pullrequest_repo_refs', request_method='GET', |
|
676 | 677 | renderer='json_ext', xhr=True) |
|
677 | 678 | def pull_request_repo_refs(self): |
|
679 | self.load_default_context() | |
|
678 | 680 | target_repo_name = self.request.matchdict['target_repo_name'] |
|
679 | 681 | repo = Repository.get_by_repo_name(target_repo_name) |
|
680 | 682 | if not repo: |
@@ -745,6 +747,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
745 | 747 | def pull_request_create(self): |
|
746 | 748 | _ = self.request.translate |
|
747 | 749 | self.assure_not_empty_repo() |
|
750 | self.load_default_context() | |
|
748 | 751 | |
|
749 | 752 | controls = peppercorn.parse(self.request.POST.items()) |
|
750 | 753 | |
@@ -878,6 +881,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
878 | 881 | pull_request = PullRequest.get_or_404( |
|
879 | 882 | self.request.matchdict['pull_request_id']) |
|
880 | 883 | |
|
884 | self.load_default_context() | |
|
881 | 885 | # only owner or admin can update it |
|
882 | 886 | allowed_to_update = PullRequestModel().check_user_update( |
|
883 | 887 | pull_request, self._rhodecode_user) |
@@ -976,6 +980,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
976 | 980 | pull_request = PullRequest.get_or_404( |
|
977 | 981 | self.request.matchdict['pull_request_id']) |
|
978 | 982 | |
|
983 | self.load_default_context() | |
|
979 | 984 | check = MergeCheck.validate(pull_request, self._rhodecode_db_user, |
|
980 | 985 | translator=self.request.translate) |
|
981 | 986 | merge_possible = not check.failed |
@@ -1048,6 +1053,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1048 | 1053 | |
|
1049 | 1054 | pull_request = PullRequest.get_or_404( |
|
1050 | 1055 | self.request.matchdict['pull_request_id']) |
|
1056 | self.load_default_context() | |
|
1051 | 1057 | |
|
1052 | 1058 | pr_closed = pull_request.is_closed() |
|
1053 | 1059 | allowed_to_delete = PullRequestModel().check_user_delete( |
@@ -62,10 +62,11 b' class RepoSettingsView(RepoAppView):' | |||
|
62 | 62 | # we might be in missing requirement state, so we load things |
|
63 | 63 | # without touching scm_instance() |
|
64 | 64 | c.landing_revs_choices, c.landing_revs = \ |
|
65 | ScmModel().get_repo_landing_revs() | |
|
65 | ScmModel().get_repo_landing_revs(self.request.translate) | |
|
66 | 66 | else: |
|
67 | 67 | c.landing_revs_choices, c.landing_revs = \ |
|
68 |
ScmModel().get_repo_landing_revs( |
|
|
68 | ScmModel().get_repo_landing_revs( | |
|
69 | self.request.translate, self.db_repo) | |
|
69 | 70 | |
|
70 | 71 | c.personal_repo_group = c.auth_user.personal_repo_group |
|
71 | 72 | c.repo_fields = RepositoryField.query()\ |
@@ -428,7 +428,7 b' class UserGroupsView(UserGroupAppView):' | |||
|
428 | 428 | |
|
429 | 429 | try: |
|
430 | 430 | # first stage that verifies the checkbox |
|
431 | _form = UserIndividualPermissionsForm() | |
|
431 | _form = UserIndividualPermissionsForm(self.request.translate) | |
|
432 | 432 | form_result = _form.to_python(dict(self.request.POST)) |
|
433 | 433 | inherit_perms = form_result['inherit_default_permissions'] |
|
434 | 434 | user_group.inherit_default_permissions = inherit_perms |
@@ -74,9 +74,6 b' def load_pyramid_environment(global_conf' | |||
|
74 | 74 | # Initialize the database connection. |
|
75 | 75 | utils.initialize_database(settings_merged) |
|
76 | 76 | |
|
77 | # TODO(marcink): base_path handling ? | |
|
78 | # repos_path = list(db_cfg.items('paths'))[0][1] | |
|
79 | ||
|
80 | 77 | load_rcextensions(root_path=settings_merged['here']) |
|
81 | 78 | |
|
82 | 79 | # Limit backends to `vcs.backends` from configuration |
@@ -29,7 +29,6 b' import socket' | |||
|
29 | 29 | |
|
30 | 30 | import markupsafe |
|
31 | 31 | import ipaddress |
|
32 | import pyramid.threadlocal | |
|
33 | 32 | |
|
34 | 33 | from paste.auth.basic import AuthBasicAuthenticator |
|
35 | 34 | from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception |
@@ -41,15 +40,11 b' from rhodecode.lib import auth, utils2' | |||
|
41 | 40 | from rhodecode.lib import helpers as h |
|
42 | 41 | from rhodecode.lib.auth import AuthUser, CookieStoreWrapper |
|
43 | 42 | from rhodecode.lib.exceptions import UserCreationError |
|
44 | from rhodecode.lib.utils import ( | |
|
45 | get_repo_slug, set_rhodecode_config, password_changed, | |
|
46 | get_enabled_hook_classes) | |
|
43 | from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes) | |
|
47 | 44 | from rhodecode.lib.utils2 import ( |
|
48 | 45 | str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist, safe_str) |
|
49 | from rhodecode.model import meta | |
|
50 | 46 | from rhodecode.model.db import Repository, User, ChangesetComment |
|
51 | 47 | from rhodecode.model.notification import NotificationModel |
|
52 | from rhodecode.model.scm import ScmModel | |
|
53 | 48 | from rhodecode.model.settings import VcsSettingsModel, SettingsModel |
|
54 | 49 | |
|
55 | 50 | log = logging.getLogger(__name__) |
@@ -440,6 +435,8 b' def get_auth_user(request):' | |||
|
440 | 435 | # AuthUser |
|
441 | 436 | auth_user = AuthUser(ip_addr=ip_addr) |
|
442 | 437 | |
|
438 | # in case someone changes a password for user it triggers session | |
|
439 | # flush and forces a re-login | |
|
443 | 440 | if password_changed(auth_user, session): |
|
444 | 441 | session.invalidate() |
|
445 | 442 | cookie_store = CookieStoreWrapper(session.get('rhodecode_user')) |
@@ -23,19 +23,15 b'' | |||
|
23 | 23 | Set of diffing helpers, previously part of vcs |
|
24 | 24 | """ |
|
25 | 25 | |
|
26 | import re | |
|
26 | 27 | import collections |
|
27 | import re | |
|
28 | 28 | import difflib |
|
29 | 29 | import logging |
|
30 | 30 | |
|
31 | 31 | from itertools import tee, imap |
|
32 | 32 | |
|
33 | from rhodecode.translation import temp_translation_factory as _ | |
|
34 | ||
|
35 | 33 | from rhodecode.lib.vcs.exceptions import VCSError |
|
36 | 34 | from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode |
|
37 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |
|
38 | from rhodecode.lib.helpers import escape | |
|
39 | 35 | from rhodecode.lib.utils2 import safe_unicode |
|
40 | 36 | |
|
41 | 37 | log = logging.getLogger(__name__) |
@@ -51,69 +47,6 b' class OPS(object):' | |||
|
51 | 47 | DEL = 'D' |
|
52 | 48 | |
|
53 | 49 | |
|
54 | def wrap_to_table(str_): | |
|
55 | return '''<table class="code-difftable"> | |
|
56 | <tr class="line no-comment"> | |
|
57 | <td class="add-comment-line tooltip" title="%s"><span class="add-comment-content"></span></td> | |
|
58 | <td></td> | |
|
59 | <td class="lineno new"></td> | |
|
60 | <td class="code no-comment"><pre>%s</pre></td> | |
|
61 | </tr> | |
|
62 | </table>''' % (_('Click to comment'), str_) | |
|
63 | ||
|
64 | ||
|
65 | def wrapped_diff(filenode_old, filenode_new, diff_limit=None, file_limit=None, | |
|
66 | show_full_diff=False, ignore_whitespace=True, line_context=3, | |
|
67 | enable_comments=False): | |
|
68 | """ | |
|
69 | returns a wrapped diff into a table, checks for cut_off_limit for file and | |
|
70 | whole diff and presents proper message | |
|
71 | """ | |
|
72 | ||
|
73 | if filenode_old is None: | |
|
74 | filenode_old = FileNode(filenode_new.path, '', EmptyCommit()) | |
|
75 | ||
|
76 | if filenode_old.is_binary or filenode_new.is_binary: | |
|
77 | diff = wrap_to_table(_('Binary file')) | |
|
78 | stats = None | |
|
79 | size = 0 | |
|
80 | data = None | |
|
81 | ||
|
82 | elif diff_limit != -1 and (diff_limit is None or | |
|
83 | (filenode_old.size < diff_limit and filenode_new.size < diff_limit)): | |
|
84 | ||
|
85 | f_gitdiff = get_gitdiff(filenode_old, filenode_new, | |
|
86 | ignore_whitespace=ignore_whitespace, | |
|
87 | context=line_context) | |
|
88 | diff_processor = DiffProcessor( | |
|
89 | f_gitdiff, format='gitdiff', diff_limit=diff_limit, | |
|
90 | file_limit=file_limit, show_full_diff=show_full_diff) | |
|
91 | _parsed = diff_processor.prepare() | |
|
92 | ||
|
93 | diff = diff_processor.as_html(enable_comments=enable_comments) | |
|
94 | stats = _parsed[0]['stats'] if _parsed else None | |
|
95 | size = len(diff or '') | |
|
96 | data = _parsed[0] if _parsed else None | |
|
97 | else: | |
|
98 | diff = wrap_to_table(_('Changeset was too big and was cut off, use ' | |
|
99 | 'diff menu to display this diff')) | |
|
100 | stats = None | |
|
101 | size = 0 | |
|
102 | data = None | |
|
103 | if not diff: | |
|
104 | submodules = filter(lambda o: isinstance(o, SubModuleNode), | |
|
105 | [filenode_new, filenode_old]) | |
|
106 | if submodules: | |
|
107 | diff = wrap_to_table(escape('Submodule %r' % submodules[0])) | |
|
108 | else: | |
|
109 | diff = wrap_to_table(_('No changes detected')) | |
|
110 | ||
|
111 | cs1 = filenode_old.commit.raw_id | |
|
112 | cs2 = filenode_new.commit.raw_id | |
|
113 | ||
|
114 | return size, cs1, cs2, diff, stats, data | |
|
115 | ||
|
116 | ||
|
117 | 50 | def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3): |
|
118 | 51 | """ |
|
119 | 52 | Returns git style diff between given ``filenode_old`` and ``filenode_new``. |
@@ -916,6 +849,10 b' class DiffProcessor(object):' | |||
|
916 | 849 | """ |
|
917 | 850 | Return given diff as html table with customized css classes |
|
918 | 851 | """ |
|
852 | # TODO(marcink): not sure how to pass in translator | |
|
853 | # here in an efficient way, leave the _ for proper gettext extraction | |
|
854 | _ = lambda s: s | |
|
855 | ||
|
919 | 856 | def _link_to_if(condition, label, url): |
|
920 | 857 | """ |
|
921 | 858 | Generates a link if condition is meet or just the label if not. |
@@ -51,7 +51,7 b' def _obj_dump(obj):' | |||
|
51 | 51 | return str(obj) |
|
52 | 52 | elif isinstance(obj, complex): |
|
53 | 53 | return [obj.real, obj.imag] |
|
54 | elif rhodecode and isinstance(obj, rhodecode.translation.LazyString): | |
|
54 | elif rhodecode and isinstance(obj, rhodecode.translation._LazyString): | |
|
55 | 55 | return obj.eval() |
|
56 | 56 | else: |
|
57 | 57 | raise TypeError(repr(obj) + " is not JSON serializable") |
@@ -1929,12 +1929,12 b' def secure_form(form_url, method="POST",' | |||
|
1929 | 1929 | """ |
|
1930 | 1930 | from webhelpers.pylonslib.secure_form import insecure_form |
|
1931 | 1931 | |
|
1932 | session = None | |
|
1933 | ||
|
1934 | # TODO(marcink): after pyramid migration require request variable ALWAYS | |
|
1935 | 1932 | if 'request' in attrs: |
|
1936 | 1933 | session = attrs['request'].session |
|
1937 | 1934 | del attrs['request'] |
|
1935 | else: | |
|
1936 | raise ValueError( | |
|
1937 | 'Calling this form requires request= to be passed as argument') | |
|
1938 | 1938 | |
|
1939 | 1939 | form = insecure_form(form_url, method, multipart, **attrs) |
|
1940 | 1940 | token = literal( |
@@ -23,19 +23,18 b' Index schema for RhodeCode' | |||
|
23 | 23 | """ |
|
24 | 24 | |
|
25 | 25 | from __future__ import absolute_import |
|
26 | import logging | |
|
27 | 26 | import os |
|
28 | 27 | import re |
|
28 | import logging | |
|
29 | 29 | |
|
30 | from rhodecode.translation import temp_translation_factory as _ | |
|
31 | ||
|
32 | from whoosh import query as query_lib, sorting | |
|
30 | from whoosh import query as query_lib | |
|
33 | 31 | from whoosh.highlight import HtmlFormatter, ContextFragmenter |
|
34 | 32 | from whoosh.index import create_in, open_dir, exists_in, EmptyIndexError |
|
35 | 33 | from whoosh.qparser import QueryParser, QueryParserError |
|
36 | 34 | |
|
37 | 35 | import rhodecode.lib.helpers as h |
|
38 | 36 | from rhodecode.lib.index import BaseSearch |
|
37 | from rhodecode.lib.utils2 import safe_unicode | |
|
39 | 38 | |
|
40 | 39 | log = logging.getLogger(__name__) |
|
41 | 40 | |
@@ -122,7 +121,7 b' class Search(BaseSearch):' | |||
|
122 | 121 | allowed_repos_filter = self._get_repo_filter( |
|
123 | 122 | search_user, repo_name) |
|
124 | 123 | try: |
|
125 | query = qp.parse(unicode(query)) | |
|
124 | query = qp.parse(safe_unicode(query)) | |
|
126 | 125 | log.debug('query: %s (%s)' % (query, repr(query))) |
|
127 | 126 | |
|
128 | 127 | reverse, sortedby = False, None |
@@ -147,20 +146,20 b' class Search(BaseSearch):' | |||
|
147 | 146 | search_type, res_ln, whoosh_results) |
|
148 | 147 | |
|
149 | 148 | except QueryParserError: |
|
150 |
result['error'] = |
|
|
149 | result['error'] = 'Invalid search query. Try quoting it.' | |
|
151 | 150 | except (EmptyIndexError, IOError, OSError): |
|
152 |
msg = |
|
|
153 | 'Please run whoosh indexer') | |
|
151 | msg = 'There is no index to search in. Please run whoosh indexer' | |
|
154 | 152 | log.exception(msg) |
|
155 | 153 | result['error'] = msg |
|
156 | 154 | except Exception: |
|
157 |
msg = |
|
|
155 | msg = 'An error occurred during this search operation' | |
|
158 | 156 | log.exception(msg) |
|
159 | 157 | result['error'] = msg |
|
160 | 158 | |
|
161 | 159 | return result |
|
162 | 160 | |
|
163 | def statistics(self): | |
|
161 | def statistics(self, translator): | |
|
162 | _ = translator | |
|
164 | 163 | stats = [ |
|
165 | 164 | {'key': _('Index Type'), 'value': 'Whoosh'}, |
|
166 | 165 | {'key': _('File Index'), 'value': str(self.file_index)}, |
@@ -796,11 +796,11 b' def suuid(url=None, truncate_to=22, alph' | |||
|
796 | 796 | return "".join(output)[:truncate_to] |
|
797 | 797 | |
|
798 | 798 | |
|
799 | def get_current_rhodecode_user(): | |
|
799 | def get_current_rhodecode_user(request=None): | |
|
800 | 800 | """ |
|
801 | 801 | Gets rhodecode user from request |
|
802 | 802 | """ |
|
803 | pyramid_request = pyramid.threadlocal.get_current_request() | |
|
803 | pyramid_request = request or pyramid.threadlocal.get_current_request() | |
|
804 | 804 | |
|
805 | 805 | # web case |
|
806 | 806 | if pyramid_request and hasattr(pyramid_request, 'user'): |
@@ -48,7 +48,6 b' import formencode' | |||
|
48 | 48 | from pkg_resources import resource_filename |
|
49 | 49 | from formencode import All, Pipe |
|
50 | 50 | |
|
51 | from rhodecode.translation import temp_translation_factory as _ | |
|
52 | 51 | from pyramid.threadlocal import get_current_request |
|
53 | 52 | |
|
54 | 53 | from rhodecode import BACKENDS |
@@ -30,7 +30,6 b' import logging' | |||
|
30 | 30 | import cStringIO |
|
31 | 31 | import pkg_resources |
|
32 | 32 | |
|
33 | from rhodecode.translation import temp_translation_factory as _ | |
|
34 | 33 | from sqlalchemy import func |
|
35 | 34 | from zope.cachedescriptors.property import Lazy as LazyProperty |
|
36 | 35 | |
@@ -40,7 +39,6 b' from rhodecode.lib.vcs.exceptions import' | |||
|
40 | 39 | from rhodecode.lib.vcs.nodes import FileNode |
|
41 | 40 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
42 | 41 | from rhodecode.lib import helpers as h |
|
43 | ||
|
44 | 42 | from rhodecode.lib.auth import ( |
|
45 | 43 | HasRepoPermissionAny, HasRepoGroupPermissionAny, |
|
46 | 44 | HasUserGroupPermissionAny) |
@@ -748,14 +746,14 b' class ScmModel(BaseModel):' | |||
|
748 | 746 | def get_unread_journal(self): |
|
749 | 747 | return self.sa.query(UserLog).count() |
|
750 | 748 | |
|
751 | def get_repo_landing_revs(self, repo=None): | |
|
749 | def get_repo_landing_revs(self, translator, repo=None): | |
|
752 | 750 | """ |
|
753 | 751 | Generates select option with tags branches and bookmarks (for hg only) |
|
754 | 752 | grouped by type |
|
755 | 753 | |
|
756 | 754 | :param repo: |
|
757 | 755 | """ |
|
758 | ||
|
756 | _ = translator | |
|
759 | 757 | repo = self._get_repo(repo) |
|
760 | 758 | |
|
761 | 759 | hist_l = [ |
@@ -24,11 +24,10 b' users model for RhodeCode' | |||
|
24 | 24 | |
|
25 | 25 | import logging |
|
26 | 26 | import traceback |
|
27 | import datetime | |
|
28 | import ipaddress | |
|
27 | 29 | |
|
28 | import datetime | |
|
29 | from rhodecode.translation import temp_translation_factory as _ | |
|
30 | ||
|
31 | import ipaddress | |
|
30 | from pyramid.threadlocal import get_current_request | |
|
32 | 31 | from sqlalchemy.exc import DatabaseError |
|
33 | 32 | |
|
34 | 33 | from rhodecode import events |
@@ -163,8 +162,9 b' class UserModel(BaseModel):' | |||
|
163 | 162 | user = self._get_user(user) |
|
164 | 163 | if user.username == User.DEFAULT_USER: |
|
165 | 164 | raise DefaultUserException( |
|
166 |
|
|
|
167 |
|
|
|
165 | "You can't edit this user (`%(username)s`) since it's " | |
|
166 | "crucial for entire application" % { | |
|
167 | 'username': user.username}) | |
|
168 | 168 | |
|
169 | 169 | # first store only defaults |
|
170 | 170 | user_attrs = { |
@@ -247,6 +247,7 b' class UserModel(BaseModel):' | |||
|
247 | 247 | |
|
248 | 248 | :returns: new User object with injected `is_new_user` attribute. |
|
249 | 249 | """ |
|
250 | ||
|
250 | 251 | if not cur_user: |
|
251 | 252 | cur_user = getattr(get_current_rhodecode_user(), 'username', None) |
|
252 | 253 | |
@@ -330,8 +331,9 b' class UserModel(BaseModel):' | |||
|
330 | 331 | # we're not allowed to edit default user |
|
331 | 332 | if user.username == User.DEFAULT_USER: |
|
332 | 333 | raise DefaultUserException( |
|
333 |
|
|
|
334 |
|
|
|
334 | "You can't edit this user (`%(username)s`) since it's " | |
|
335 | "crucial for entire application" | |
|
336 | % {'username': user.username}) | |
|
335 | 337 | |
|
336 | 338 | # inject special attribute that will tell us if User is new or old |
|
337 | 339 | new_user.is_new_user = not edit |
@@ -497,40 +499,43 b' class UserModel(BaseModel):' | |||
|
497 | 499 | def delete(self, user, cur_user=None, handle_repos=None, |
|
498 | 500 | handle_repo_groups=None, handle_user_groups=None): |
|
499 | 501 | if not cur_user: |
|
500 | cur_user = getattr(get_current_rhodecode_user(), 'username', None) | |
|
502 | cur_user = getattr( | |
|
503 | get_current_rhodecode_user(), 'username', None) | |
|
501 | 504 | user = self._get_user(user) |
|
502 | 505 | |
|
503 | 506 | try: |
|
504 | 507 | if user.username == User.DEFAULT_USER: |
|
505 | 508 | raise DefaultUserException( |
|
506 |
|
|
|
507 |
|
|
|
509 | u"You can't remove this user since it's" | |
|
510 | u" crucial for entire application") | |
|
508 | 511 | |
|
509 | 512 | left_overs = self._handle_user_repos( |
|
510 | 513 | user.username, user.repositories, handle_repos) |
|
511 | 514 | if left_overs and user.repositories: |
|
512 | 515 | repos = [x.repo_name for x in user.repositories] |
|
513 | 516 | raise UserOwnsReposException( |
|
514 |
|
|
|
515 |
|
|
|
516 |
% |
|
|
517 | u'user "%(username)s" still owns %(len_repos)s repositories and cannot be ' | |
|
518 | u'removed. Switch owners or remove those repositories:%(list_repos)s' | |
|
519 | % {'username': user.username, 'len_repos': len(repos), | |
|
520 | 'list_repos': ', '.join(repos)}) | |
|
517 | 521 | |
|
518 | 522 | left_overs = self._handle_user_repo_groups( |
|
519 | 523 | user.username, user.repository_groups, handle_repo_groups) |
|
520 | 524 | if left_overs and user.repository_groups: |
|
521 | 525 | repo_groups = [x.group_name for x in user.repository_groups] |
|
522 | 526 | raise UserOwnsRepoGroupsException( |
|
523 |
|
|
|
524 |
|
|
|
525 |
% |
|
|
527 | u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be ' | |
|
528 | u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s' | |
|
529 | % {'username': user.username, 'len_repo_groups': len(repo_groups), | |
|
530 | 'list_repo_groups': ', '.join(repo_groups)}) | |
|
526 | 531 | |
|
527 | 532 | left_overs = self._handle_user_user_groups( |
|
528 | 533 | user.username, user.user_groups, handle_user_groups) |
|
529 | 534 | if left_overs and user.user_groups: |
|
530 | 535 | user_groups = [x.users_group_name for x in user.user_groups] |
|
531 | 536 | raise UserOwnsUserGroupsException( |
|
532 |
|
|
|
533 |
|
|
|
537 | u'user "%s" still owns %s user groups and cannot be ' | |
|
538 | u'removed. Switch owners or remove those user groups:%s' | |
|
534 | 539 | % (user.username, len(user_groups), ', '.join(user_groups))) |
|
535 | 540 | |
|
536 | 541 | # we might change the user data with detach/delete, make sure |
@@ -37,7 +37,6 b' from formencode.validators import (' | |||
|
37 | 37 | |
|
38 | 38 | from sqlalchemy.sql.expression import true |
|
39 | 39 | from sqlalchemy.util import OrderedSet |
|
40 | from webhelpers.pylonslib.secure_form import authentication_token | |
|
41 | 40 | |
|
42 | 41 | from rhodecode.authentication import ( |
|
43 | 42 | legacy_plugin_prefix, _import_legacy_plugin) |
@@ -455,21 +454,6 b' def ValidAuth(localizer):' | |||
|
455 | 454 | return _validator |
|
456 | 455 | |
|
457 | 456 | |
|
458 | def ValidAuthToken(localizer): | |
|
459 | _ = localizer | |
|
460 | ||
|
461 | class _validator(formencode.validators.FancyValidator): | |
|
462 | messages = { | |
|
463 | 'invalid_token': _(u'Token mismatch') | |
|
464 | } | |
|
465 | ||
|
466 | def validate_python(self, value, state): | |
|
467 | if value != authentication_token(): | |
|
468 | msg = M(self, 'invalid_token', state) | |
|
469 | raise formencode.Invalid(msg, value, state) | |
|
470 | return _validator | |
|
471 | ||
|
472 | ||
|
473 | 457 | def ValidRepoName(localizer, edit=False, old_data=None): |
|
474 | 458 | old_data = old_data or {} |
|
475 | 459 | _ = localizer |
@@ -195,12 +195,6 b' def write_js_routes_if_enabled(event):' | |||
|
195 | 195 | ) |
|
196 | 196 | |
|
197 | 197 | def get_routes(): |
|
198 | # pylons routes | |
|
199 | # TODO(marcink): remove when pyramid migration is finished | |
|
200 | if 'routes.map' in rhodecode.CONFIG: | |
|
201 | for route in rhodecode.CONFIG['routes.map'].jsroutes(): | |
|
202 | yield route | |
|
203 | ||
|
204 | 198 | # pyramid routes |
|
205 | 199 | for route in mapper.get_routes(): |
|
206 | 200 | if not route.name.startswith('__'): |
@@ -85,7 +85,8 b' def _get_backend(backend_type):' | |||
|
85 | 85 | class DBBackend(object): |
|
86 | 86 | _store = os.path.dirname(os.path.abspath(__file__)) |
|
87 | 87 | _type = None |
|
88 |
_base_ini_config = [{'app:main': {'vcs.start_server': 'false' |
|
|
88 | _base_ini_config = [{'app:main': {'vcs.start_server': 'false', | |
|
89 | 'startup.import_repos': 'false'}}] | |
|
89 | 90 | _db_url = [{'app:main': {'sqlalchemy.db1.url': ''}}] |
|
90 | 91 | _base_db_name = 'rhodecode_test_db_backend' |
|
91 | 92 | |
@@ -183,7 +184,7 b' class DBBackend(object):' | |||
|
183 | 184 | if not os.path.isdir(self._repos_location): |
|
184 | 185 | os.makedirs(self._repos_location) |
|
185 | 186 | self.execute( |
|
186 |
" |
|
|
187 | "rc-upgrade-db {0} --force-yes".format(ini_file)) | |
|
187 | 188 | |
|
188 | 189 | def setup_db(self): |
|
189 | 190 | raise NotImplementedError |
@@ -56,11 +56,22 b' class TestSessionBehaviorOnPasswordChang' | |||
|
56 | 56 | |
|
57 | 57 | def test_sessions_invalidated_when_password_is_changed( |
|
58 | 58 | self, app, autologin_user): |
|
59 | response = app.get(route_path('home'), status=200) | |
|
60 | session = response.get_session_from_response() | |
|
61 | ||
|
62 | # now mark as password change | |
|
59 | 63 | self.password_changed_mock.return_value = True |
|
64 | ||
|
65 | # flushes session first | |
|
66 | app.get(route_path('home')) | |
|
67 | ||
|
68 | # second call is now "different" with flushed empty session | |
|
60 | 69 | response = app.get(route_path('home')) |
|
70 | session = response.get_session_from_response() | |
|
71 | ||
|
72 | assert 'rhodecode_user' not in session | |
|
73 | ||
|
61 | 74 | assert_response = response.assert_response() |
|
62 | 75 | assert_response.element_contains('#quick_login_link .user', 'Sign in') |
|
63 | 76 | |
|
64 | session = response.get_session_from_response() | |
|
65 | assert 'rhodecode_user' not in session | |
|
66 | assert session.was_invalidated is True | |
|
77 |
@@ -74,8 +74,7 b' def get_environ(url, request_method):' | |||
|
74 | 74 | |
|
75 | 75 | ]) |
|
76 | 76 | def test_get_action(url, expected_action, request_method, baseapp, request_stub): |
|
77 | app = simplegit.SimpleGit(application=None, | |
|
78 | config={'auth_ret_code': '', 'base_path': ''}, | |
|
77 | app = simplegit.SimpleGit(config={'auth_ret_code': '', 'base_path': ''}, | |
|
79 | 78 | registry=request_stub.registry) |
|
80 | 79 | assert expected_action == app._get_action(get_environ(url, request_method)) |
|
81 | 80 | |
@@ -103,8 +102,7 b' def test_get_action(url, expected_action' | |||
|
103 | 102 | |
|
104 | 103 | ]) |
|
105 | 104 | def test_get_repository_name(url, expected_repo_name, request_method, baseapp, request_stub): |
|
106 | app = simplegit.SimpleGit(application=None, | |
|
107 | config={'auth_ret_code': '', 'base_path': ''}, | |
|
105 | app = simplegit.SimpleGit(config={'auth_ret_code': '', 'base_path': ''}, | |
|
108 | 106 | registry=request_stub.registry) |
|
109 | 107 | assert expected_repo_name == app._get_repository_name( |
|
110 | 108 | get_environ(url, request_method)) |
@@ -112,8 +110,7 b' def test_get_repository_name(url, expect' | |||
|
112 | 110 | |
|
113 | 111 | def test_get_config(user_util, baseapp, request_stub): |
|
114 | 112 | repo = user_util.create_repo(repo_type='git') |
|
115 | app = simplegit.SimpleGit(application=None, | |
|
116 | config={'auth_ret_code': '', 'base_path': ''}, | |
|
113 | app = simplegit.SimpleGit(config={'auth_ret_code': '', 'base_path': ''}, | |
|
117 | 114 | registry=request_stub.registry) |
|
118 | 115 | extras = {'foo': 'FOO', 'bar': 'BAR'} |
|
119 | 116 | |
@@ -137,6 +134,6 b' def test_create_wsgi_app_uses_scm_app_fr' | |||
|
137 | 134 | 'vcs.scm_app_implementation': |
|
138 | 135 | 'rhodecode.tests.lib.middleware.mock_scm_app', |
|
139 | 136 | } |
|
140 |
app = simplegit.SimpleGit( |
|
|
137 | app = simplegit.SimpleGit(config=config, registry=request_stub.registry) | |
|
141 | 138 | wsgi_app = app._create_wsgi_app('/tmp/test', 'test_repo', {}) |
|
142 | 139 | assert wsgi_app is mock_scm_app.mock_git_wsgi |
@@ -54,8 +54,7 b' def get_environ(url):' | |||
|
54 | 54 | ('/foo/bar?key=tip', 'pull'), |
|
55 | 55 | ]) |
|
56 | 56 | def test_get_action(url, expected_action, request_stub): |
|
57 | app = simplehg.SimpleHg(application=None, | |
|
58 | config={'auth_ret_code': '', 'base_path': ''}, | |
|
57 | app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''}, | |
|
59 | 58 | registry=request_stub.registry) |
|
60 | 59 | assert expected_action == app._get_action(get_environ(url)) |
|
61 | 60 | |
@@ -72,16 +71,14 b' def test_get_action(url, expected_action' | |||
|
72 | 71 | ('/foo/bar/baz/?cmd=listkeys&key=tip', 'foo/bar/baz'), |
|
73 | 72 | ]) |
|
74 | 73 | def test_get_repository_name(url, expected_repo_name, request_stub): |
|
75 | app = simplehg.SimpleHg(application=None, | |
|
76 | config={'auth_ret_code': '', 'base_path': ''}, | |
|
74 | app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''}, | |
|
77 | 75 | registry=request_stub.registry) |
|
78 | 76 | assert expected_repo_name == app._get_repository_name(get_environ(url)) |
|
79 | 77 | |
|
80 | 78 | |
|
81 | 79 | def test_get_config(user_util, baseapp, request_stub): |
|
82 | 80 | repo = user_util.create_repo(repo_type='git') |
|
83 | app = simplehg.SimpleHg(application=None, | |
|
84 | config={'auth_ret_code': '', 'base_path': ''}, | |
|
81 | app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''}, | |
|
85 | 82 | registry=request_stub.registry) |
|
86 | 83 | extras = [('foo', 'FOO', 'bar', 'BAR')] |
|
87 | 84 | |
@@ -123,7 +120,6 b' def test_create_wsgi_app_uses_scm_app_fr' | |||
|
123 | 120 | 'vcs.scm_app_implementation': |
|
124 | 121 | 'rhodecode.tests.lib.middleware.mock_scm_app', |
|
125 | 122 | } |
|
126 | app = simplehg.SimpleHg( | |
|
127 | application=None, config=config, registry=request_stub.registry) | |
|
123 | app = simplehg.SimpleHg(config=config, registry=request_stub.registry) | |
|
128 | 124 | wsgi_app = app._create_wsgi_app('/tmp/test', 'test_repo', {}) |
|
129 | 125 | assert wsgi_app is mock_scm_app.mock_hg_wsgi |
@@ -23,17 +23,16 b' from StringIO import StringIO' | |||
|
23 | 23 | import pytest |
|
24 | 24 | from mock import patch, Mock |
|
25 | 25 | |
|
26 | import rhodecode | |
|
27 | 26 | from rhodecode.lib.middleware.simplesvn import SimpleSvn, SimpleSvnApp |
|
27 | from rhodecode.lib.utils import get_rhodecode_base_path | |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | class TestSimpleSvn(object): |
|
31 | 31 | @pytest.fixture(autouse=True) |
|
32 | 32 | def simple_svn(self, baseapp, request_stub): |
|
33 | base_path = get_rhodecode_base_path() | |
|
33 | 34 | self.app = SimpleSvn( |
|
34 | application='None', | |
|
35 | config={'auth_ret_code': '', | |
|
36 | 'base_path': rhodecode.CONFIG['base_path']}, | |
|
35 | config={'auth_ret_code': '', 'base_path': base_path}, | |
|
37 | 36 | registry=request_stub.registry) |
|
38 | 37 | |
|
39 | 38 | def test_get_config(self): |
@@ -102,7 +101,6 b' class TestSimpleSvn(object):' | |||
|
102 | 101 | assert wsgi_app == wsgi_app_mock() |
|
103 | 102 | |
|
104 | 103 | |
|
105 | ||
|
106 | 104 | class TestSimpleSvnApp(object): |
|
107 | 105 | data = '<xml></xml>' |
|
108 | 106 | path = '/group/my-repo' |
@@ -121,8 +119,10 b' class TestSimpleSvnApp(object):' | |||
|
121 | 119 | |
|
122 | 120 | def setup_method(self, method): |
|
123 | 121 | self.host = 'http://localhost/' |
|
122 | base_path = get_rhodecode_base_path() | |
|
124 | 123 | self.app = SimpleSvnApp( |
|
125 |
config={'subversion_http_server_url': self.host |
|
|
124 | config={'subversion_http_server_url': self.host, | |
|
125 | 'base_path': base_path}) | |
|
126 | 126 | |
|
127 | 127 | def test_get_request_headers_with_content_type(self): |
|
128 | 128 | expected_headers = { |
@@ -79,8 +79,8 b' def vcscontroller(baseapp, config_stub, ' | |||
|
79 | 79 | config_stub.include('rhodecode.authentication') |
|
80 | 80 | |
|
81 | 81 | controller = StubVCSController( |
|
82 |
baseapp |
|
|
83 | app = HttpsFixup(controller, baseapp.config) | |
|
82 | baseapp.config.get_settings(), request_stub.registry) | |
|
83 | app = HttpsFixup(controller, baseapp.config.get_settings()) | |
|
84 | 84 | app = CustomTestApp(app) |
|
85 | 85 | |
|
86 | 86 | _remove_default_user_from_query_cache() |
@@ -136,8 +136,8 b' class StubFailVCSController(simplevcs.Si' | |||
|
136 | 136 | @pytest.fixture(scope='module') |
|
137 | 137 | def fail_controller(baseapp): |
|
138 | 138 | controller = StubFailVCSController( |
|
139 |
baseapp |
|
|
140 | controller = HttpsFixup(controller, baseapp.config) | |
|
139 | baseapp.config.get_settings(), baseapp.config) | |
|
140 | controller = HttpsFixup(controller, baseapp.config.get_settings()) | |
|
141 | 141 | controller = CustomTestApp(controller) |
|
142 | 142 | return controller |
|
143 | 143 | |
@@ -153,17 +153,15 b' def test_provides_traceback_for_appenlig' | |||
|
153 | 153 | |
|
154 | 154 | |
|
155 | 155 | def test_provides_utils_scm_app_as_scm_app_by_default(baseapp, request_stub): |
|
156 | controller = StubVCSController( | |
|
157 | baseapp, baseapp.config, request_stub.registry) | |
|
156 | controller = StubVCSController(baseapp.config.get_settings(), request_stub.registry) | |
|
158 | 157 | assert controller.scm_app is scm_app_http |
|
159 | 158 | |
|
160 | 159 | |
|
161 | 160 | def test_allows_to_override_scm_app_via_config(baseapp, request_stub): |
|
162 | config = baseapp.config.copy() | |
|
161 | config = baseapp.config.get_settings().copy() | |
|
163 | 162 | config['vcs.scm_app_implementation'] = ( |
|
164 | 163 | 'rhodecode.tests.lib.middleware.mock_scm_app') |
|
165 | controller = StubVCSController( | |
|
166 | baseapp, config, request_stub.registry) | |
|
164 | controller = StubVCSController(config, request_stub.registry) | |
|
167 | 165 | assert controller.scm_app is mock_scm_app |
|
168 | 166 | |
|
169 | 167 | |
@@ -225,7 +223,7 b' class TestShadowRepoExposure(object):' | |||
|
225 | 223 | underlying wsgi app. |
|
226 | 224 | """ |
|
227 | 225 | controller = StubVCSController( |
|
228 |
baseapp |
|
|
226 | baseapp.config.get_settings(), request_stub.registry) | |
|
229 | 227 | controller._check_ssl = mock.Mock() |
|
230 | 228 | controller.is_shadow_repo = True |
|
231 | 229 | controller._action = 'pull' |
@@ -250,7 +248,7 b' class TestShadowRepoExposure(object):' | |||
|
250 | 248 | underlying wsgi app. |
|
251 | 249 | """ |
|
252 | 250 | controller = StubVCSController( |
|
253 |
baseapp |
|
|
251 | baseapp.config.get_settings(), request_stub.registry) | |
|
254 | 252 | controller._check_ssl = mock.Mock() |
|
255 | 253 | controller.is_shadow_repo = True |
|
256 | 254 | controller._action = 'pull' |
@@ -274,7 +272,7 b' class TestShadowRepoExposure(object):' | |||
|
274 | 272 | Check that a push action to a shadow repo is aborted. |
|
275 | 273 | """ |
|
276 | 274 | controller = StubVCSController( |
|
277 |
baseapp |
|
|
275 | baseapp.config.get_settings(), request_stub.registry) | |
|
278 | 276 | controller._check_ssl = mock.Mock() |
|
279 | 277 | controller.is_shadow_repo = True |
|
280 | 278 | controller._action = 'push' |
@@ -300,7 +298,7 b' class TestShadowRepoExposure(object):' | |||
|
300 | 298 | """ |
|
301 | 299 | environ_stub = {} |
|
302 | 300 | controller = StubVCSController( |
|
303 |
baseapp |
|
|
301 | baseapp.config.get_settings(), request_stub.registry) | |
|
304 | 302 | controller._name = 'RepoGroup/MyRepo' |
|
305 | 303 | controller.set_repo_names(environ_stub) |
|
306 | 304 | assert not controller.is_shadow_repo |
@@ -324,7 +322,7 b' class TestShadowRepoExposure(object):' | |||
|
324 | 322 | pr_segment=TestShadowRepoRegularExpression.pr_segment, |
|
325 | 323 | shadow_segment=TestShadowRepoRegularExpression.shadow_segment) |
|
326 | 324 | controller = StubVCSController( |
|
327 |
baseapp |
|
|
325 | baseapp.config.get_settings(), request_stub.registry) | |
|
328 | 326 | controller._name = shadow_url |
|
329 | 327 | controller.set_repo_names({}) |
|
330 | 328 | |
@@ -352,7 +350,7 b' class TestShadowRepoExposure(object):' | |||
|
352 | 350 | pr_segment=TestShadowRepoRegularExpression.pr_segment, |
|
353 | 351 | shadow_segment=TestShadowRepoRegularExpression.shadow_segment) |
|
354 | 352 | controller = StubVCSController( |
|
355 |
baseapp |
|
|
353 | baseapp.config.get_settings(), request_stub.registry) | |
|
356 | 354 | controller._name = shadow_url |
|
357 | 355 | controller.set_repo_names({}) |
|
358 | 356 | |
@@ -362,7 +360,7 b' class TestShadowRepoExposure(object):' | |||
|
362 | 360 | controller.vcs_repo_name) |
|
363 | 361 | |
|
364 | 362 | |
|
365 |
@pytest.mark.usefixtures(' |
|
|
363 | @pytest.mark.usefixtures('baseapp') | |
|
366 | 364 | class TestGenerateVcsResponse(object): |
|
367 | 365 | |
|
368 | 366 | def test_ensures_that_start_response_is_called_early_enough(self): |
@@ -429,7 +427,7 b' class TestGenerateVcsResponse(object):' | |||
|
429 | 427 | 'vcs.hooks.direct_calls': False, |
|
430 | 428 | } |
|
431 | 429 | registry = AttributeDict() |
|
432 |
controller = StubVCSController( |
|
|
430 | controller = StubVCSController(settings, registry) | |
|
433 | 431 | controller._invalidate_cache = mock.Mock() |
|
434 | 432 | controller.stub_response_body = response_body |
|
435 | 433 | self.start_response = mock.Mock() |
@@ -482,7 +480,7 b' class TestPrepareHooksDaemon(object):' | |||
|
482 | 480 | expected_extras = {'extra1': 'value1'} |
|
483 | 481 | daemon = DummyHooksCallbackDaemon() |
|
484 | 482 | |
|
485 |
controller = StubVCSController( |
|
|
483 | controller = StubVCSController(app_settings, request_stub.registry) | |
|
486 | 484 | prepare_patcher = mock.patch.object( |
|
487 | 485 | simplevcs, 'prepare_callback_daemon', |
|
488 | 486 | return_value=(daemon, expected_extras)) |
@@ -44,9 +44,7 b' def test_vcs_unavailable_returns_vcs_err' | |||
|
44 | 44 | # Patch remote repo to raise an exception instead of making a RPC. |
|
45 | 45 | with mock.patch.object(RemoteRepo, '__getattr__') as remote_mock: |
|
46 | 46 | remote_mock.side_effect = VCSCommunicationError() |
|
47 | # Patch pylons error handling middleware to not re-raise exceptions. | |
|
48 | with mock.patch.object(PylonsErrorHandlingMiddleware, 'reraise') as r: | |
|
49 | r.return_value = False | |
|
47 | ||
|
50 | 48 |
|
|
51 | 49 | |
|
52 | 50 | assert response.status_code == 502 |
@@ -29,6 +29,6 b' from rhodecode.model.db import UserLog' | |||
|
29 | 29 | 'user_closed_pull_request', |
|
30 | 30 | 'user_merged_pull_request' |
|
31 | 31 | ]) |
|
32 | def test_action_map_pr_values(baseapp, pr_key): | |
|
33 | parser = ActionParser(UserLog(action="test:test")) | |
|
32 | def test_action_map_pr_values(request_stub, baseapp, pr_key): | |
|
33 | parser = ActionParser(request_stub, UserLog(action="test:test")) | |
|
34 | 34 | assert pr_key in parser.action_map |
@@ -23,7 +23,7 b' import textwrap' | |||
|
23 | 23 | import pytest |
|
24 | 24 | |
|
25 | 25 | from rhodecode.lib.diffs import ( |
|
26 |
DiffProcessor, |
|
|
26 | DiffProcessor, | |
|
27 | 27 | NEW_FILENODE, DEL_FILENODE, MOD_FILENODE, RENAMED_FILENODE, |
|
28 | 28 | CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE) |
|
29 | 29 | from rhodecode.tests.fixture import Fixture |
@@ -34,23 +34,6 b' from rhodecode.lib.vcs.backends.svn.repo' | |||
|
34 | 34 | fixture = Fixture() |
|
35 | 35 | |
|
36 | 36 | |
|
37 | def test_wrapped_diff_limited_file_diff(vcsbackend_random): | |
|
38 | vcsbackend = vcsbackend_random | |
|
39 | repo = vcsbackend.create_repo() | |
|
40 | vcsbackend.add_file(repo, 'a_file', content="line 1\nline 2\nline3\n") | |
|
41 | commit = repo.get_commit() | |
|
42 | file_node = commit.get_node('a_file') | |
|
43 | ||
|
44 | # Only limit the file diff to trigger the code path | |
|
45 | result = wrapped_diff( | |
|
46 | None, file_node, diff_limit=10000, file_limit=1) | |
|
47 | data = result[5] | |
|
48 | ||
|
49 | # Verify that the limits were applied | |
|
50 | assert data['exceeds_limit'] is True | |
|
51 | assert data['is_limited_diff'] is True | |
|
52 | ||
|
53 | ||
|
54 | 37 | def test_diffprocessor_as_html_with_comments(): |
|
55 | 38 | raw_diff = textwrap.dedent(''' |
|
56 | 39 | diff --git a/setup.py b/setup.py |
@@ -158,7 +158,7 b' def test_formatted_json():' | |||
|
158 | 158 | assert formatted_json(data) == expected_data |
|
159 | 159 | |
|
160 | 160 | |
|
161 |
def test_ |
|
|
161 | def test_lazy_translation_string(baseapp): | |
|
162 | 162 | data = {'label': _('hello')} |
|
163 | 163 | data2 = {'label2': _pluralize('singular', 'plural', 1)} |
|
164 | 164 |
@@ -65,7 +65,7 b' class TestPullRequestModel(object):' | |||
|
65 | 65 | 'rhodecode.model.notification.NotificationModel.create') |
|
66 | 66 | self.notification_patcher.start() |
|
67 | 67 | self.helper_patcher = mock.patch( |
|
68 |
'rhodecode.lib.helpers. |
|
|
68 | 'rhodecode.lib.helpers.route_path') | |
|
69 | 69 | self.helper_patcher.start() |
|
70 | 70 | |
|
71 | 71 | self.hook_patcher = mock.patch.object(PullRequestModel, |
@@ -183,7 +183,7 b' def test_get_non_unicode_reference(backe' | |||
|
183 | 183 | tags=non_unicode_list, alias=backend.alias) |
|
184 | 184 | |
|
185 | 185 | repo = Mock(__class__=db.Repository, scm_instance=scm_instance) |
|
186 | choices, __ = model.get_repo_landing_revs(repo=repo) | |
|
186 | choices, __ = model.get_repo_landing_revs(translator=lambda s: s, repo=repo) | |
|
187 | 187 | if backend.alias == 'hg': |
|
188 | 188 | valid_choices = [ |
|
189 | 189 | 'rev:tip', u'branch:Ad\xc4\xb1n\xc4\xb1', |
@@ -209,12 +209,6 b' def test_ValidAuth(localizer, config_stu' | |||
|
209 | 209 | formencode.Invalid, validator.to_python, invalid_creds) |
|
210 | 210 | |
|
211 | 211 | |
|
212 | def test_ValidAuthToken(localizer): | |
|
213 | validator = v.ValidAuthToken(localizer) | |
|
214 | pytest.raises(formencode.Invalid, validator.to_python, 'BadToken') | |
|
215 | validator | |
|
216 | ||
|
217 | ||
|
218 | 212 | def test_ValidRepoName(localizer): |
|
219 | 213 | validator = v.ValidRepoName(localizer) |
|
220 | 214 |
General Comments 0
You need to be logged in to leave comments.
Login now