##// END OF EJS Templates
tests: stabilize Git committer in test_vcs_operations...
Mads Kiilerich -
r8768:d6d3cb59 stable
parent child Browse files
Show More
@@ -1,641 +1,644 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 Test suite for vcs push/pull operations.
16 16
17 17 The tests need Git > 1.8.1.
18 18
19 19 This file was forked by the Kallithea project in July 2014.
20 20 Original author and date, and relevant copyright and licensing information is below:
21 21 :created_on: Dec 30, 2010
22 22 :author: marcink
23 23 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 24 :license: GPLv3, see LICENSE.md for more details.
25 25
26 26 """
27 27
28 28 import json
29 29 import os
30 30 import re
31 31 import tempfile
32 32 import time
33 33 import urllib.request
34 34 from subprocess import PIPE, Popen
35 35 from tempfile import _RandomNameSequence
36 36
37 37 import pytest
38 38
39 39 import kallithea
40 40 from kallithea.lib.utils2 import ascii_bytes, safe_str
41 41 from kallithea.model import db, meta
42 42 from kallithea.model.ssh_key import SshKeyModel
43 43 from kallithea.model.user import UserModel
44 44 from kallithea.tests import base
45 45 from kallithea.tests.fixture import Fixture
46 46
47 47
48 48 DEBUG = True
49 49 HOST = '127.0.0.1:4999' # test host
50 50
51 51 fixture = Fixture()
52 52
53 53
54 54 # Parameterize different kinds of VCS testing - both the kind of VCS and the
55 55 # access method (HTTP/SSH)
56 56
57 57 # Mixin for using HTTP and SSH URLs
58 58 class HttpVcsTest(object):
59 59 @staticmethod
60 60 def repo_url_param(webserver, repo_name, **kwargs):
61 61 return webserver.repo_url(repo_name, **kwargs)
62 62
63 63 class SshVcsTest(object):
64 64 public_keys = {
65 65 base.TEST_USER_REGULAR_LOGIN: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUQ== kallithea@localhost',
66 66 base.TEST_USER_ADMIN_LOGIN: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6Ycnc2oUZHQnQwuqgZqTTdMDZD7ataf3JM7oG2Fw8JR6cdmz4QZLe5mfDwaFwG2pWHLRpVqzfrD/Pn3rIO++bgCJH5ydczrl1WScfryV1hYMJ/4EzLGM657J1/q5EI+b9SntKjf4ax+KP322L0TNQGbZUHLbfG2MwHMrYBQpHUq== kallithea@localhost',
67 67 }
68 68
69 69 @classmethod
70 70 def repo_url_param(cls, webserver, repo_name, username=base.TEST_USER_ADMIN_LOGIN, password=base.TEST_USER_ADMIN_PASS, client_ip=base.IP_ADDR):
71 71 user = db.User.get_by_username(username)
72 72 if user.ssh_keys:
73 73 ssh_key = user.ssh_keys[0]
74 74 else:
75 75 sshkeymodel = SshKeyModel()
76 76 ssh_key = sshkeymodel.create(user, 'test key', cls.public_keys[user.username])
77 77 meta.Session().commit()
78 78
79 79 return cls._ssh_param(repo_name, user, ssh_key, client_ip)
80 80
81 81 # Mixins for using Mercurial and Git
82 82 class HgVcsTest(object):
83 83 repo_type = 'hg'
84 84 repo_name = base.HG_REPO
85 85
86 86 class GitVcsTest(object):
87 87 repo_type = 'git'
88 88 repo_name = base.GIT_REPO
89 89
90 90 # Combine mixins to give the combinations we want to parameterize tests with
91 91 class HgHttpVcsTest(HgVcsTest, HttpVcsTest):
92 92 pass
93 93
94 94 class GitHttpVcsTest(GitVcsTest, HttpVcsTest):
95 95 pass
96 96
97 97 class HgSshVcsTest(HgVcsTest, SshVcsTest):
98 98 @staticmethod
99 99 def _ssh_param(repo_name, user, ssh_key, client_ip):
100 100 # Specify a custom ssh command on the command line
101 101 return r"""--config ui.ssh="bash -c 'SSH_ORIGINAL_COMMAND=\"\$2\" SSH_CONNECTION=\"%s 1024 127.0.0.1 22\" kallithea-cli ssh-serve -c %s %s %s' --" ssh://someuser@somehost/%s""" % (
102 102 client_ip,
103 103 kallithea.CONFIG['__file__'],
104 104 user.user_id,
105 105 ssh_key.user_ssh_key_id,
106 106 repo_name)
107 107
108 108 class GitSshVcsTest(GitVcsTest, SshVcsTest):
109 109 @staticmethod
110 110 def _ssh_param(repo_name, user, ssh_key, client_ip):
111 111 # Set a custom ssh command in the global environment
112 112 os.environ['GIT_SSH_COMMAND'] = r"""bash -c 'SSH_ORIGINAL_COMMAND="$2" SSH_CONNECTION="%s 1024 127.0.0.1 22" kallithea-cli ssh-serve -c %s %s %s' --""" % (
113 113 client_ip,
114 114 kallithea.CONFIG['__file__'],
115 115 user.user_id,
116 116 ssh_key.user_ssh_key_id)
117 117 return "ssh://someuser@somehost/%s""" % repo_name
118 118
119 119 parametrize_vcs_test = base.parametrize('vt', [
120 120 HgHttpVcsTest,
121 121 GitHttpVcsTest,
122 122 HgSshVcsTest,
123 123 GitSshVcsTest,
124 124 ])
125 125 parametrize_vcs_test_hg = base.parametrize('vt', [
126 126 HgHttpVcsTest,
127 127 HgSshVcsTest,
128 128 ])
129 129 parametrize_vcs_test_http = base.parametrize('vt', [
130 130 HgHttpVcsTest,
131 131 GitHttpVcsTest,
132 132 ])
133 133
134 134 class Command(object):
135 135
136 136 def __init__(self, cwd):
137 137 self.cwd = cwd
138 138
139 139 def execute(self, *args, **environ):
140 140 """
141 141 Runs command on the system with given ``args`` using simple space
142 142 join without safe quoting.
143 143 """
144 144 command = ' '.join(args)
145 145 ignoreReturnCode = environ.pop('ignoreReturnCode', False)
146 146 if DEBUG:
147 147 print('*** CMD %s ***' % command)
148 148 testenv = dict(os.environ)
149 149 testenv['LANG'] = 'en_US.UTF-8'
150 150 testenv['LANGUAGE'] = 'en_US:en'
151 151 testenv['HGPLAIN'] = ''
152 152 testenv['HGRCPATH'] = ''
153 testenv['GIT_COMMITTER_NAME'] = base.TEST_USER_ADMIN_LOGIN
154 testenv['GIT_COMMITTER_EMAIL'] = base.TEST_USER_ADMIN_EMAIL
155 testenv['GIT_AUTHOR_NAME'] = base.TEST_USER_REGULAR_LOGIN
156 testenv['GIT_AUTHOR_EMAIL'] = base.TEST_USER_REGULAR_EMAIL
153 157 testenv.update(environ)
154 158 p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd, env=testenv)
155 159 stdout, stderr = p.communicate()
156 160 if DEBUG:
157 161 if stdout:
158 162 print('stdout:', stdout)
159 163 if stderr:
160 164 print('stderr:', stderr)
161 165 if not ignoreReturnCode:
162 166 assert p.returncode == 0
163 167 return safe_str(stdout), safe_str(stderr)
164 168
165 169
166 170 def _get_tmp_dir(prefix='vcs_operations-', suffix=''):
167 171 return tempfile.mkdtemp(dir=base.TESTS_TMP_PATH, prefix=prefix, suffix=suffix)
168 172
169 173
170 174 def _add_files(vcs, dest_dir, files_no=3):
171 175 """
172 176 Generate some files, add it to dest_dir repo and push back
173 177 vcs is git or hg and defines what VCS we want to make those files for
174 178
175 179 :param vcs:
176 180 :param dest_dir:
177 181 """
178 182 added_file = '%ssetup.py' % next(_RandomNameSequence())
179 183 open(os.path.join(dest_dir, added_file), 'a').close()
180 184 Command(dest_dir).execute(vcs, 'add', added_file)
181 185
182 186 email = 'me@example.com'
183 187 if os.name == 'nt':
184 188 author_str = 'User <%s>' % email
185 189 else:
186 190 author_str = 'User ǝɯɐᴎ <%s>' % email
187 191 for i in range(files_no):
188 192 cmd = """echo "added_line%s" >> %s""" % (i, added_file)
189 193 Command(dest_dir).execute(cmd)
190 194 if vcs == 'hg':
191 195 cmd = """hg commit -m "committed new %s" -u "%s" "%s" """ % (
192 196 i, author_str, added_file
193 197 )
194 198 elif vcs == 'git':
195 199 cmd = """git commit -m "committed new %s" --author "%s" "%s" """ % (
196 200 i, author_str, added_file
197 201 )
198 # git commit needs EMAIL on some machines
199 Command(dest_dir).execute(cmd, EMAIL=email)
202 Command(dest_dir).execute(cmd)
200 203
201 204 def _add_files_and_push(webserver, vt, dest_dir, clone_url, ignoreReturnCode=False, files_no=3):
202 205 _add_files(vt.repo_type, dest_dir, files_no=files_no)
203 206 # PUSH it back
204 207 stdout = stderr = None
205 208 if vt.repo_type == 'hg':
206 209 stdout, stderr = Command(dest_dir).execute('hg push -f --verbose', clone_url, ignoreReturnCode=ignoreReturnCode)
207 210 elif vt.repo_type == 'git':
208 211 stdout, stderr = Command(dest_dir).execute('git push -f --verbose', clone_url, "master", ignoreReturnCode=ignoreReturnCode)
209 212
210 213 return stdout, stderr
211 214
212 215
213 216 def _check_outgoing(vcs, cwd, clone_url):
214 217 if vcs == 'hg':
215 218 # hg removes the password from default URLs, so we have to provide it here via the clone_url
216 219 return Command(cwd).execute('hg -q outgoing', clone_url, ignoreReturnCode=True)
217 220 elif vcs == 'git':
218 221 Command(cwd).execute('git remote update')
219 222 return Command(cwd).execute('git log origin/master..master')
220 223
221 224
222 225 def set_anonymous_access(enable=True):
223 226 user = db.User.get_default_user()
224 227 user.active = enable
225 228 meta.Session().commit()
226 229 if enable != db.User.get_default_user().active:
227 230 raise Exception('Cannot set anonymous access')
228 231
229 232
230 233 #==============================================================================
231 234 # TESTS
232 235 #==============================================================================
233 236
234 237
235 238 def _check_proper_git_push(stdout, stderr):
236 239 assert 'fatal' not in stderr
237 240 assert 'rejected' not in stderr
238 241 assert 'Pushing to' in stderr
239 242 assert 'master -> master' in stderr
240 243
241 244
242 245 @pytest.mark.usefixtures("test_context_fixture")
243 246 class TestVCSOperations(base.TestController):
244 247
245 248 @classmethod
246 249 def setup_class(cls):
247 250 # DISABLE ANONYMOUS ACCESS
248 251 set_anonymous_access(False)
249 252
250 253 @pytest.fixture()
251 254 def testhook_cleanup(self):
252 255 yield
253 256 # remove hook
254 257 for hook in ['prechangegroup', 'pretxnchangegroup', 'preoutgoing', 'changegroup', 'outgoing', 'incoming']:
255 258 entry = db.Ui.get_by_key('hooks', '%s.testhook' % hook)
256 259 if entry:
257 260 meta.Session().delete(entry)
258 261 meta.Session().commit()
259 262
260 263 @pytest.fixture(scope="module")
261 264 def testfork(self):
262 265 # create fork so the repo stays untouched
263 266 git_fork_name = '%s_fork%s' % (base.GIT_REPO, next(_RandomNameSequence()))
264 267 fixture.create_fork(base.GIT_REPO, git_fork_name)
265 268 hg_fork_name = '%s_fork%s' % (base.HG_REPO, next(_RandomNameSequence()))
266 269 fixture.create_fork(base.HG_REPO, hg_fork_name)
267 270 return {'git': git_fork_name, 'hg': hg_fork_name}
268 271
269 272 @parametrize_vcs_test
270 273 def test_clone_repo_by_admin(self, webserver, vt):
271 274 clone_url = vt.repo_url_param(webserver, vt.repo_name)
272 275 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir())
273 276
274 277 if vt.repo_type == 'git':
275 278 assert 'Cloning into' in stdout + stderr
276 279 assert stderr == '' or stdout == ''
277 280 elif vt.repo_type == 'hg':
278 281 assert 'requesting all changes' in stdout
279 282 assert 'adding changesets' in stdout
280 283 assert 'adding manifests' in stdout
281 284 assert 'adding file changes' in stdout
282 285 assert stderr == ''
283 286
284 287 @parametrize_vcs_test_http
285 288 def test_clone_wrong_credentials(self, webserver, vt):
286 289 clone_url = vt.repo_url_param(webserver, vt.repo_name, password='bad!')
287 290 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
288 291 if vt.repo_type == 'git':
289 292 assert 'fatal: Authentication failed' in stderr
290 293 elif vt.repo_type == 'hg':
291 294 assert 'abort: authorization failed' in stderr
292 295
293 296 def test_clone_git_dir_as_hg(self, webserver):
294 297 clone_url = HgHttpVcsTest.repo_url_param(webserver, base.GIT_REPO)
295 298 stdout, stderr = Command(base.TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
296 299 assert 'HTTP Error 404: Not Found' in stderr or "not a valid repository" in stdout and 'abort:' in stderr
297 300
298 301 def test_clone_hg_repo_as_git(self, webserver):
299 302 clone_url = GitHttpVcsTest.repo_url_param(webserver, base.HG_REPO)
300 303 stdout, stderr = Command(base.TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
301 304 assert 'not found' in stderr
302 305
303 306 @parametrize_vcs_test
304 307 def test_clone_non_existing_path(self, webserver, vt):
305 308 clone_url = vt.repo_url_param(webserver, 'trololo')
306 309 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
307 310 if vt.repo_type == 'git':
308 311 assert 'not found' in stderr or 'abort: Access to %r denied' % 'trololo' in stderr
309 312 elif vt.repo_type == 'hg':
310 313 assert 'HTTP Error 404: Not Found' in stderr or 'abort: no suitable response from remote hg' in stderr and 'remote: abort: Access to %r denied' % 'trololo' in stdout + stderr
311 314
312 315 @parametrize_vcs_test
313 316 def test_push_new_repo(self, webserver, vt):
314 317 # Clear the log so we know what is added
315 318 db.UserLog.query().delete()
316 319 meta.Session().commit()
317 320
318 321 # Create an empty server repo using the API
319 322 repo_name = 'new_%s_%s' % (vt.repo_type, next(_RandomNameSequence()))
320 323 usr = db.User.get_by_username(base.TEST_USER_ADMIN_LOGIN)
321 324 params = {
322 325 "id": 7,
323 326 "api_key": usr.api_key,
324 327 "method": 'create_repo',
325 328 "args": dict(repo_name=repo_name,
326 329 owner=base.TEST_USER_ADMIN_LOGIN,
327 330 repo_type=vt.repo_type),
328 331 }
329 332 req = urllib.request.Request(
330 333 'http://%s:%s/_admin/api' % webserver.server_address,
331 334 data=ascii_bytes(json.dumps(params)),
332 335 headers={'content-type': 'application/json'})
333 336 response = urllib.request.urlopen(req)
334 337 result = json.loads(response.read())
335 338 # Expect something like:
336 339 # {u'result': {u'msg': u'Created new repository `new_XXX`', u'task': None, u'success': True}, u'id': 7, u'error': None}
337 340 assert result['result']['success']
338 341
339 342 # Create local clone of the empty server repo
340 343 local_clone_dir = _get_tmp_dir()
341 344 clone_url = vt.repo_url_param(webserver, repo_name)
342 345 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, local_clone_dir)
343 346
344 347 # Make 3 commits and push to the empty server repo.
345 348 # The server repo doesn't have any other heads than the
346 349 # refs/heads/master we are pushing, but the `git log` in the push hook
347 350 # should still list the 3 commits.
348 351 stdout, stderr = _add_files_and_push(webserver, vt, local_clone_dir, clone_url=clone_url)
349 352 if vt.repo_type == 'git':
350 353 _check_proper_git_push(stdout, stderr)
351 354 elif vt.repo_type == 'hg':
352 355 assert 'pushing to ' in stdout
353 356 assert 'remote: added ' in stdout
354 357
355 358 # Verify that we got the right events in UserLog. Expect something like:
356 359 # <UserLog('id:new_git_XXX:started_following_repo')>
357 360 # <UserLog('id:new_git_XXX:user_created_repo')>
358 361 # <UserLog('id:new_git_XXX:pull')>
359 362 # <UserLog('id:new_git_XXX:push:aed9d4c1732a1927da3be42c47eb9afdc200d427,d38b083a07af10a9f44193486959a96a23db78da,4841ff9a2b385bec995f4679ef649adb3f437622')>
360 363 meta.Session.close() # make sure SA fetches all new log entries (apparently only needed for MariaDB/MySQL ...)
361 364 action_parts = [ul.action.split(':', 1) for ul in db.UserLog.query().order_by(db.UserLog.user_log_id)]
362 365 assert [(t[0], (t[1].count(',') + 1) if len(t) == 2 else 0) for t in action_parts] == ([
363 366 ('started_following_repo', 0),
364 367 ('user_created_repo', 0),
365 368 ('pull', 0),
366 369 ('push', 3)]
367 370 if vt.repo_type == 'git' else [
368 371 ('started_following_repo', 0),
369 372 ('user_created_repo', 0),
370 373 # (u'pull', 0), # Mercurial outgoing hook is not called for empty clones
371 374 ('push', 3)])
372 375
373 376 @parametrize_vcs_test
374 377 def test_push_new_file(self, webserver, testfork, vt):
375 378 db.UserLog.query().delete()
376 379 meta.Session().commit()
377 380
378 381 dest_dir = _get_tmp_dir()
379 382 clone_url = vt.repo_url_param(webserver, vt.repo_name)
380 383 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
381 384
382 385 clone_url = vt.repo_url_param(webserver, testfork[vt.repo_type])
383 386 stdout, stderr = _add_files_and_push(webserver, vt, dest_dir, clone_url=clone_url)
384 387
385 388 if vt.repo_type == 'git':
386 389 _check_proper_git_push(stdout, stderr)
387 390 elif vt.repo_type == 'hg':
388 391 assert 'pushing to' in stdout
389 392 assert 'Repository size' in stdout
390 393 assert 'Last revision is now' in stdout
391 394
392 395 meta.Session.close() # make sure SA fetches all new log entries (apparently only needed for MariaDB/MySQL ...)
393 396 action_parts = [ul.action.split(':', 1) for ul in db.UserLog.query().order_by(db.UserLog.user_log_id)]
394 397 assert [(t[0], (t[1].count(',') + 1) if len(t) == 2 else 0) for t in action_parts] == \
395 398 [('pull', 0), ('push', 3)]
396 399
397 400 @parametrize_vcs_test
398 401 def test_pull(self, webserver, testfork, vt):
399 402 db.UserLog.query().delete()
400 403 meta.Session().commit()
401 404
402 405 dest_dir = _get_tmp_dir()
403 406 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'init', dest_dir)
404 407
405 408 clone_url = vt.repo_url_param(webserver, vt.repo_name)
406 409 stdout, stderr = Command(dest_dir).execute(vt.repo_type, 'pull', clone_url)
407 410 meta.Session.close() # make sure SA fetches all new log entries (apparently only needed for MariaDB/MySQL ...)
408 411
409 412 if vt.repo_type == 'git':
410 413 assert 'FETCH_HEAD' in stderr
411 414 elif vt.repo_type == 'hg':
412 415 assert 'new changesets' in stdout
413 416
414 417 action_parts = [ul.action for ul in db.UserLog.query().order_by(db.UserLog.user_log_id)]
415 418 assert action_parts == ['pull']
416 419
417 420 # Test handling of URLs with extra '/' around repo_name
418 421 stdout, stderr = Command(dest_dir).execute(vt.repo_type, 'pull', clone_url.replace('/' + vt.repo_name, '/./%s/' % vt.repo_name), ignoreReturnCode=True)
419 422 if issubclass(vt, HttpVcsTest):
420 423 if vt.repo_type == 'git':
421 424 # NOTE: when pulling from http://hostname/./vcs_test_git/ , the git client will normalize that and issue an HTTP request to /vcs_test_git/info/refs
422 425 assert 'Already up to date.' in stdout
423 426 else:
424 427 assert vt.repo_type == 'hg'
425 428 assert "abort: HTTP Error 404: Not Found" in stderr
426 429 else:
427 430 assert issubclass(vt, SshVcsTest)
428 431 if vt.repo_type == 'git':
429 432 assert "abort: Access to './%s' denied" % vt.repo_name in stderr
430 433 else:
431 434 assert "abort: Access to './%s' denied" % vt.repo_name in stdout + stderr
432 435
433 436 stdout, stderr = Command(dest_dir).execute(vt.repo_type, 'pull', clone_url.replace('/' + vt.repo_name, '/%s/' % vt.repo_name), ignoreReturnCode=True)
434 437 if vt.repo_type == 'git':
435 438 assert 'Already up to date.' in stdout
436 439 else:
437 440 assert vt.repo_type == 'hg'
438 441 assert "no changes found" in stdout
439 442 assert "denied" not in stderr
440 443 assert "denied" not in stdout
441 444 assert "404" not in stdout
442 445
443 446 @parametrize_vcs_test
444 447 def test_push_invalidates_cache(self, webserver, testfork, vt):
445 448 pre_cached_tip = [repo.get_api_data()['last_changeset']['short_id'] for repo in db.Repository.query().filter(db.Repository.repo_name == testfork[vt.repo_type])]
446 449
447 450 dest_dir = _get_tmp_dir()
448 451 clone_url = vt.repo_url_param(webserver, testfork[vt.repo_type])
449 452 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
450 453
451 454 stdout, stderr = _add_files_and_push(webserver, vt, dest_dir, files_no=1, clone_url=clone_url)
452 455
453 456 if vt.repo_type == 'git':
454 457 _check_proper_git_push(stdout, stderr)
455 458
456 459 meta.Session.close() # expire session to make sure SA fetches new Repository instances after last_changeset has been updated by server side hook in another process
457 460 post_cached_tip = [repo.get_api_data()['last_changeset']['short_id'] for repo in db.Repository.query().filter(db.Repository.repo_name == testfork[vt.repo_type])]
458 461 assert pre_cached_tip != post_cached_tip
459 462
460 463 @parametrize_vcs_test_http
461 464 def test_push_wrong_credentials(self, webserver, vt):
462 465 dest_dir = _get_tmp_dir()
463 466 clone_url = vt.repo_url_param(webserver, vt.repo_name)
464 467 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
465 468
466 469 clone_url = webserver.repo_url(vt.repo_name, username='bad', password='name')
467 470 stdout, stderr = _add_files_and_push(webserver, vt, dest_dir,
468 471 clone_url=clone_url, ignoreReturnCode=True)
469 472
470 473 if vt.repo_type == 'git':
471 474 assert 'fatal: Authentication failed' in stderr
472 475 elif vt.repo_type == 'hg':
473 476 assert 'abort: authorization failed' in stderr
474 477
475 478 @parametrize_vcs_test
476 479 def test_push_with_readonly_credentials(self, webserver, vt):
477 480 db.UserLog.query().delete()
478 481 meta.Session().commit()
479 482
480 483 dest_dir = _get_tmp_dir()
481 484 clone_url = vt.repo_url_param(webserver, vt.repo_name, username=base.TEST_USER_REGULAR_LOGIN, password=base.TEST_USER_REGULAR_PASS)
482 485 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
483 486
484 487 stdout, stderr = _add_files_and_push(webserver, vt, dest_dir, ignoreReturnCode=True, clone_url=clone_url)
485 488
486 489 if vt.repo_type == 'git':
487 490 assert 'The requested URL returned error: 403' in stderr or 'abort: Push access to %r denied' % str(vt.repo_name) in stderr
488 491 elif vt.repo_type == 'hg':
489 492 assert 'abort: HTTP Error 403: Forbidden' in stderr or 'abort: push failed on remote' in stderr and 'remote: Push access to %r denied' % str(vt.repo_name) in stdout
490 493
491 494 meta.Session.close() # make sure SA fetches all new log entries (apparently only needed for MariaDB/MySQL ...)
492 495 action_parts = [ul.action.split(':', 1) for ul in db.UserLog.query().order_by(db.UserLog.user_log_id)]
493 496 assert [(t[0], (t[1].count(',') + 1) if len(t) == 2 else 0) for t in action_parts] == \
494 497 [('pull', 0)]
495 498
496 499 @parametrize_vcs_test
497 500 def test_push_back_to_wrong_url(self, webserver, vt):
498 501 dest_dir = _get_tmp_dir()
499 502 clone_url = vt.repo_url_param(webserver, vt.repo_name)
500 503 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
501 504
502 505 stdout, stderr = _add_files_and_push(
503 506 webserver, vt, dest_dir, clone_url='http://%s:%s/tmp' % (
504 507 webserver.server_address[0], webserver.server_address[1]),
505 508 ignoreReturnCode=True)
506 509
507 510 if vt.repo_type == 'git':
508 511 assert 'not found' in stderr
509 512 elif vt.repo_type == 'hg':
510 513 assert 'HTTP Error 404: Not Found' in stderr
511 514
512 515 @parametrize_vcs_test
513 516 def test_ip_restriction(self, webserver, vt):
514 517 user_model = UserModel()
515 518 try:
516 519 # Add IP constraint that excludes the test context:
517 520 user_model.add_extra_ip(base.TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
518 521 meta.Session().commit()
519 522 # IP permissions are cached, need to wait for the cache in the server process to expire
520 523 time.sleep(1.5)
521 524 clone_url = vt.repo_url_param(webserver, vt.repo_name)
522 525 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
523 526 if vt.repo_type == 'git':
524 527 # The message apparently changed in Git 1.8.3, so match it loosely.
525 528 assert re.search(r'\b403\b', stderr) or 'abort: User test_admin from 127.0.0.127 cannot be authorized' in stderr
526 529 elif vt.repo_type == 'hg':
527 530 assert 'abort: HTTP Error 403: Forbidden' in stderr or 'remote: abort: User test_admin from 127.0.0.127 cannot be authorized' in stdout + stderr
528 531 finally:
529 532 # release IP restrictions
530 533 for ip in db.UserIpMap.query():
531 534 db.UserIpMap.delete(ip.ip_id)
532 535 meta.Session().commit()
533 536 # IP permissions are cached, need to wait for the cache in the server process to expire
534 537 time.sleep(1.5)
535 538
536 539 clone_url = vt.repo_url_param(webserver, vt.repo_name)
537 540 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, _get_tmp_dir())
538 541
539 542 if vt.repo_type == 'git':
540 543 assert 'Cloning into' in stdout + stderr
541 544 assert stderr == '' or stdout == ''
542 545 elif vt.repo_type == 'hg':
543 546 assert 'requesting all changes' in stdout
544 547 assert 'adding changesets' in stdout
545 548 assert 'adding manifests' in stdout
546 549 assert 'adding file changes' in stdout
547 550
548 551 assert stderr == ''
549 552
550 553 @parametrize_vcs_test_hg # git hooks doesn't work like hg hooks
551 554 def test_custom_hooks_preoutgoing(self, testhook_cleanup, webserver, testfork, vt):
552 555 # set prechangegroup to failing hook (returns True)
553 556 db.Ui.create_or_update_hook('preoutgoing.testhook', 'python:kallithea.tests.fixture.failing_test_hook')
554 557 meta.Session().commit()
555 558 # clone repo
556 559 clone_url = vt.repo_url_param(webserver, testfork[vt.repo_type], username=base.TEST_USER_ADMIN_LOGIN, password=base.TEST_USER_ADMIN_PASS)
557 560 dest_dir = _get_tmp_dir()
558 561 stdout, stderr = Command(base.TESTS_TMP_PATH) \
559 562 .execute(vt.repo_type, 'clone', clone_url, dest_dir, ignoreReturnCode=True)
560 563 if vt.repo_type == 'hg':
561 564 assert 'preoutgoing.testhook hook failed' in stdout + stderr
562 565 elif vt.repo_type == 'git':
563 566 assert 'error: 406' in stderr
564 567
565 568 @parametrize_vcs_test_hg # git hooks doesn't work like hg hooks
566 569 def test_custom_hooks_prechangegroup(self, testhook_cleanup, webserver, testfork, vt):
567 570 # set prechangegroup to failing hook (returns exit code 1)
568 571 db.Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.failing_test_hook')
569 572 meta.Session().commit()
570 573 # clone repo
571 574 clone_url = vt.repo_url_param(webserver, testfork[vt.repo_type], username=base.TEST_USER_ADMIN_LOGIN, password=base.TEST_USER_ADMIN_PASS)
572 575 dest_dir = _get_tmp_dir()
573 576 stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, dest_dir)
574 577
575 578 stdout, stderr = _add_files_and_push(webserver, vt, dest_dir, clone_url,
576 579 ignoreReturnCode=True)
577 580 assert 'failing_test_hook failed' in stdout + stderr
578 581 assert 'Traceback' not in stdout + stderr
579 582 assert 'prechangegroup.testhook hook failed' in stdout + stderr
580 583 # there are still outgoing changesets
581 584 stdout, stderr = _check_outgoing(vt.repo_type, dest_dir, clone_url)
582 585 assert stdout != ''
583 586
584 587 # set prechangegroup hook to exception throwing method
585 588 db.Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.exception_test_hook')
586 589 meta.Session().commit()
587 590 # re-try to push
588 591 stdout, stderr = Command(dest_dir).execute('%s push' % vt.repo_type, clone_url, ignoreReturnCode=True)
589 592 if vt is HgHttpVcsTest:
590 593 # like with 'hg serve...' 'HTTP Error 500: INTERNAL SERVER ERROR' should be returned
591 594 assert 'HTTP Error 500: INTERNAL SERVER ERROR' in stderr
592 595 elif vt is HgSshVcsTest:
593 596 assert 'remote: Exception: exception_test_hook threw an exception' in stdout
594 597 else: assert False
595 598 # there are still outgoing changesets
596 599 stdout, stderr = _check_outgoing(vt.repo_type, dest_dir, clone_url)
597 600 assert stdout != ''
598 601
599 602 # set prechangegroup hook to method that returns False
600 603 db.Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.passing_test_hook')
601 604 meta.Session().commit()
602 605 # re-try to push
603 606 stdout, stderr = Command(dest_dir).execute('%s push' % vt.repo_type, clone_url, ignoreReturnCode=True)
604 607 assert 'passing_test_hook succeeded' in stdout + stderr
605 608 assert 'Traceback' not in stdout + stderr
606 609 assert 'prechangegroup.testhook hook failed' not in stdout + stderr
607 610 # no more outgoing changesets
608 611 stdout, stderr = _check_outgoing(vt.repo_type, dest_dir, clone_url)
609 612 assert stdout == ''
610 613 assert stderr == ''
611 614
612 615 def test_add_submodule_git(self, webserver, testfork):
613 616 dest_dir = _get_tmp_dir()
614 617 clone_url = GitHttpVcsTest.repo_url_param(webserver, base.GIT_REPO)
615 618
616 619 fork_url = GitHttpVcsTest.repo_url_param(webserver, testfork['git'])
617 620
618 621 # add submodule
619 622 stdout, stderr = Command(base.TESTS_TMP_PATH).execute('git clone', fork_url, dest_dir)
620 623 stdout, stderr = Command(dest_dir).execute('git submodule add', clone_url, 'testsubmodule')
621 stdout, stderr = Command(dest_dir).execute('git commit -am "added testsubmodule pointing to', clone_url, '"', EMAIL=base.TEST_USER_ADMIN_EMAIL)
624 stdout, stderr = Command(dest_dir).execute('git commit -am "added testsubmodule pointing to', clone_url, '"')
622 625 stdout, stderr = Command(dest_dir).execute('git push', fork_url, 'master')
623 626
624 627 # check for testsubmodule link in files page
625 628 self.log_user()
626 629 response = self.app.get(base.url(controller='files', action='index',
627 630 repo_name=testfork['git'],
628 631 revision='tip',
629 632 f_path='/'))
630 633 # check _repo_files_url that will be used to reload as AJAX
631 634 response.mustcontain('var _repo_files_url = ("/%s/files/");' % testfork['git'])
632 635
633 636 response.mustcontain('<a class="submodule-dir" href="%s" target="_blank"><i class="icon-file-submodule"></i><span>testsubmodule @ ' % clone_url)
634 637
635 638 # check that following a submodule link actually works - and redirects
636 639 response = self.app.get(base.url(controller='files', action='index',
637 640 repo_name=testfork['git'],
638 641 revision='tip',
639 642 f_path='/testsubmodule'),
640 643 status=302)
641 644 assert response.location == clone_url
General Comments 0
You need to be logged in to leave comments. Login now