Show More
@@ -0,0 +1,141 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2010-2017 RhodeCode GmbH | |
|
4 | # | |
|
5 | # This program is free software: you can redistribute it and/or modify | |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | """ | |
|
22 | Test suite for making push/pull operations, on specially modified INI files | |
|
23 | ||
|
24 | .. important:: | |
|
25 | ||
|
26 | You must have git >= 1.8.5 for tests to work fine. With 68b939b git started | |
|
27 | to redirect things to stderr instead of stdout. | |
|
28 | """ | |
|
29 | ||
|
30 | import pytest | |
|
31 | import requests | |
|
32 | ||
|
33 | from rhodecode import events | |
|
34 | from rhodecode.model.db import Integration | |
|
35 | from rhodecode.model.integration import IntegrationModel | |
|
36 | from rhodecode.model.meta import Session | |
|
37 | ||
|
38 | from rhodecode.tests import GIT_REPO, HG_REPO | |
|
39 | from rhodecode.tests.other.vcs_operations import Command, _add_files_and_push | |
|
40 | from rhodecode.integrations.types.webhook import WebhookIntegrationType | |
|
41 | ||
|
42 | ||
|
43 | def check_connection(): | |
|
44 | try: | |
|
45 | response = requests.get('http://httpbin.org') | |
|
46 | return response.status_code == 200 | |
|
47 | except Exception as e: | |
|
48 | print(e) | |
|
49 | ||
|
50 | return False | |
|
51 | ||
|
52 | ||
|
53 | connection_available = pytest.mark.skipif( | |
|
54 | not check_connection(), reason="No outside internet connection available") | |
|
55 | ||
|
56 | ||
|
57 | @pytest.fixture | |
|
58 | def enable_webhook_push_integration(request): | |
|
59 | integration = Integration() | |
|
60 | integration.integration_type = WebhookIntegrationType.key | |
|
61 | Session().add(integration) | |
|
62 | ||
|
63 | settings = dict( | |
|
64 | url='http://httpbin.org', | |
|
65 | secret_token='secret', | |
|
66 | username=None, | |
|
67 | password=None, | |
|
68 | custom_header_key=None, | |
|
69 | custom_header_val=None, | |
|
70 | method_type='get', | |
|
71 | events=[events.RepoPushEvent.name], | |
|
72 | log_data=True | |
|
73 | ) | |
|
74 | ||
|
75 | IntegrationModel().update_integration( | |
|
76 | integration, | |
|
77 | name='IntegrationWebhookTest', | |
|
78 | enabled=True, | |
|
79 | settings=settings, | |
|
80 | repo=None, | |
|
81 | repo_group=None, | |
|
82 | child_repos_only=False, | |
|
83 | ) | |
|
84 | Session().commit() | |
|
85 | integration_id = integration.integration_id | |
|
86 | ||
|
87 | @request.addfinalizer | |
|
88 | def cleanup(): | |
|
89 | integration = Integration.get(integration_id) | |
|
90 | Session().delete(integration) | |
|
91 | Session().commit() | |
|
92 | ||
|
93 | ||
|
94 | @pytest.mark.usefixtures( | |
|
95 | "disable_locking", "disable_anonymous_user", | |
|
96 | "enable_webhook_push_integration") | |
|
97 | class TestVCSOperationsOnCustomIniConfig(object): | |
|
98 | ||
|
99 | def test_push_tag_with_commit_hg(self, rc_web_server, tmpdir): | |
|
100 | clone_url = rc_web_server.repo_clone_url(HG_REPO) | |
|
101 | stdout, stderr = Command('/tmp').execute( | |
|
102 | 'hg clone', clone_url, tmpdir.strpath) | |
|
103 | ||
|
104 | push_url = rc_web_server.repo_clone_url(HG_REPO) | |
|
105 | _add_files_and_push( | |
|
106 | 'hg', tmpdir.strpath, clone_url=push_url, | |
|
107 | tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}]) | |
|
108 | ||
|
109 | rc_log = rc_web_server.get_rc_log() | |
|
110 | assert 'ERROR' not in rc_log | |
|
111 | assert "'name': u'v1.0.0'" in rc_log | |
|
112 | ||
|
113 | def test_push_tag_with_commit_git( | |
|
114 | self, rc_web_server, tmpdir): | |
|
115 | clone_url = rc_web_server.repo_clone_url(GIT_REPO) | |
|
116 | stdout, stderr = Command('/tmp').execute( | |
|
117 | 'git clone', clone_url, tmpdir.strpath) | |
|
118 | ||
|
119 | push_url = rc_web_server.repo_clone_url(GIT_REPO) | |
|
120 | _add_files_and_push( | |
|
121 | 'git', tmpdir.strpath, clone_url=push_url, | |
|
122 | tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}]) | |
|
123 | ||
|
124 | rc_log = rc_web_server.get_rc_log() | |
|
125 | assert 'ERROR' not in rc_log | |
|
126 | assert "'name': u'v1.0.0'" in rc_log | |
|
127 | ||
|
128 | def test_push_tag_with_no_commit_git( | |
|
129 | self, rc_web_server, tmpdir): | |
|
130 | clone_url = rc_web_server.repo_clone_url(GIT_REPO) | |
|
131 | stdout, stderr = Command('/tmp').execute( | |
|
132 | 'git clone', clone_url, tmpdir.strpath) | |
|
133 | ||
|
134 | push_url = rc_web_server.repo_clone_url(GIT_REPO) | |
|
135 | _add_files_and_push( | |
|
136 | 'git', tmpdir.strpath, clone_url=push_url, | |
|
137 | tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}]) | |
|
138 | ||
|
139 | rc_log = rc_web_server.get_rc_log() | |
|
140 | assert 'ERROR' not in rc_log | |
|
141 | assert "'name': u'v1.0.0'" in rc_log |
@@ -18,6 +18,7 b'' | |||
|
18 | 18 | |
|
19 | 19 | import collections |
|
20 | 20 | import logging |
|
21 | import datetime | |
|
21 | 22 | |
|
22 | 23 | from rhodecode.translation import lazy_ugettext |
|
23 | 24 | from rhodecode.model.db import User, Repository, Session |
@@ -58,22 +59,60 b' def _commits_as_dict(event, commit_ids, ' | |||
|
58 | 59 | return commits # return early if we have the commits we need |
|
59 | 60 | |
|
60 | 61 | vcs_repo = repo.scm_instance(cache=False) |
|
62 | ||
|
61 | 63 | try: |
|
62 | 64 | # use copy of needed_commits since we modify it while iterating |
|
63 | 65 | for commit_id in list(needed_commits): |
|
64 | try: | |
|
65 |
|
|
|
66 | except CommitDoesNotExistError: | |
|
67 | continue # maybe its in next repo | |
|
66 | if commit_id.startswith('tag=>'): | |
|
67 | raw_id = commit_id[5:] | |
|
68 | cs_data = { | |
|
69 | 'raw_id': commit_id, 'short_id': commit_id, | |
|
70 | 'branch': None, | |
|
71 | 'git_ref_change': 'tag_add', | |
|
72 | 'message': 'Added new tag {}'.format(raw_id), | |
|
73 | 'author': event.actor.full_contact, | |
|
74 | 'date': datetime.datetime.now(), | |
|
75 | 'refs': { | |
|
76 | 'branches': [], | |
|
77 | 'bookmarks': [], | |
|
78 | 'tags': [] | |
|
79 | } | |
|
80 | } | |
|
81 | commits.append(cs_data) | |
|
68 | 82 | |
|
69 | cs_data = cs.__json__() | |
|
70 | cs_data['refs'] = cs._get_refs() | |
|
83 | elif commit_id.startswith('delete_branch=>'): | |
|
84 | raw_id = commit_id[15:] | |
|
85 | cs_data = { | |
|
86 | 'raw_id': commit_id, 'short_id': commit_id, | |
|
87 | 'branch': None, | |
|
88 | 'git_ref_change': 'branch_delete', | |
|
89 | 'message': 'Deleted branch {}'.format(raw_id), | |
|
90 | 'author': event.actor.full_contact, | |
|
91 | 'date': datetime.datetime.now(), | |
|
92 | 'refs': { | |
|
93 | 'branches': [], | |
|
94 | 'bookmarks': [], | |
|
95 | 'tags': [] | |
|
96 | } | |
|
97 | } | |
|
98 | commits.append(cs_data) | |
|
99 | ||
|
100 | else: | |
|
101 | try: | |
|
102 | cs = vcs_repo.get_changeset(commit_id) | |
|
103 | except CommitDoesNotExistError: | |
|
104 | continue # maybe its in next repo | |
|
105 | ||
|
106 | cs_data = cs.__json__() | |
|
107 | cs_data['refs'] = cs._get_refs() | |
|
108 | ||
|
71 | 109 | cs_data['mentions'] = extract_mentioned_users(cs_data['message']) |
|
72 | 110 | cs_data['reviewers'] = reviewers |
|
73 | 111 | cs_data['url'] = RepoModel().get_commit_url( |
|
74 | 112 | repo, cs_data['raw_id'], request=event.request) |
|
75 | 113 | cs_data['permalink_url'] = RepoModel().get_commit_url( |
|
76 |
repo, cs_data['raw_id'], request=event.request, |
|
|
114 | repo, cs_data['raw_id'], request=event.request, | |
|
115 | permalink=True) | |
|
77 | 116 | urlified_message, issues_data = process_patterns( |
|
78 | 117 | cs_data['message'], repo.repo_name) |
|
79 | 118 | cs_data['issues'] = issues_data |
@@ -85,8 +124,8 b' def _commits_as_dict(event, commit_ids, ' | |||
|
85 | 124 | |
|
86 | 125 | needed_commits.remove(commit_id) |
|
87 | 126 | |
|
88 |
except Exception |
|
|
89 | log.exception(e) | |
|
127 | except Exception: | |
|
128 | log.exception('Failed to extract commits data') | |
|
90 | 129 | # we don't send any commits when crash happens, only full list |
|
91 | 130 | # matters we short circuit then. |
|
92 | 131 | return [] |
@@ -248,6 +287,7 b' class RepoPushEvent(RepoVCSEvent):' | |||
|
248 | 287 | def __init__(self, repo_name, pushed_commit_ids, extras): |
|
249 | 288 | super(RepoPushEvent, self).__init__(repo_name, extras) |
|
250 | 289 | self.pushed_commit_ids = pushed_commit_ids |
|
290 | self.new_refs = extras.new_refs | |
|
251 | 291 | |
|
252 | 292 | def as_dict(self): |
|
253 | 293 | data = super(RepoPushEvent, self).as_dict() |
@@ -256,6 +296,10 b' class RepoPushEvent(RepoVCSEvent):' | |||
|
256 | 296 | return '{}/changelog?branch={}'.format( |
|
257 | 297 | data['repo']['url'], branch_name) |
|
258 | 298 | |
|
299 | def tag_url(tag_name): | |
|
300 | return '{}/files/{}/'.format( | |
|
301 | data['repo']['url'], tag_name) | |
|
302 | ||
|
259 | 303 | commits = _commits_as_dict( |
|
260 | 304 | self, commit_ids=self.pushed_commit_ids, repos=[self.repo]) |
|
261 | 305 | |
@@ -265,8 +309,21 b' class RepoPushEvent(RepoVCSEvent):' | |||
|
265 | 309 | last_branch = commit['branch'] |
|
266 | 310 | issues = _issues_as_dict(commits) |
|
267 | 311 | |
|
268 | branches = set( | |
|
269 | commit['branch'] for commit in commits if commit['branch']) | |
|
312 | branches = set() | |
|
313 | tags = set() | |
|
314 | for commit in commits: | |
|
315 | if commit['refs']['tags']: | |
|
316 | for tag in commit['refs']['tags']: | |
|
317 | tags.add(tag) | |
|
318 | if commit['branch']: | |
|
319 | branches.add(commit['branch']) | |
|
320 | ||
|
321 | # maybe we have branches in new_refs ? | |
|
322 | try: | |
|
323 | branches = branches.union(set(self.new_refs['branches'])) | |
|
324 | except Exception: | |
|
325 | pass | |
|
326 | ||
|
270 | 327 | branches = [ |
|
271 | 328 | { |
|
272 | 329 | 'name': branch, |
@@ -275,9 +332,24 b' class RepoPushEvent(RepoVCSEvent):' | |||
|
275 | 332 | for branch in branches |
|
276 | 333 | ] |
|
277 | 334 | |
|
335 | # maybe we have branches in new_refs ? | |
|
336 | try: | |
|
337 | tags = tags.union(set(self.new_refs['tags'])) | |
|
338 | except Exception: | |
|
339 | pass | |
|
340 | ||
|
341 | tags = [ | |
|
342 | { | |
|
343 | 'name': tag, | |
|
344 | 'url': tag_url(tag) | |
|
345 | } | |
|
346 | for tag in tags | |
|
347 | ] | |
|
348 | ||
|
278 | 349 | data['push'] = { |
|
279 | 350 | 'commits': commits, |
|
280 | 351 | 'issues': issues, |
|
281 | 352 | 'branches': branches, |
|
353 | 'tags': tags, | |
|
282 | 354 | } |
|
283 | 355 | return data |
@@ -110,6 +110,11 b' class WebhookHandler(object):' | |||
|
110 | 110 | |
|
111 | 111 | branches_commits = OrderedDict() |
|
112 | 112 | for commit in data['push']['commits']: |
|
113 | if commit.get('git_ref_change'): | |
|
114 | # special case for GIT that allows creating tags, | |
|
115 | # deleting branches without associated commit | |
|
116 | continue | |
|
117 | ||
|
113 | 118 | if commit['branch'] not in branches_commits: |
|
114 | 119 | branch_commits = {'branch': branch_data[commit['branch']], |
|
115 | 120 | 'commits': []} |
@@ -378,7 +383,8 b' def post_to_webhook(url_calls, settings)' | |||
|
378 | 383 | |
|
379 | 384 | log.debug('calling Webhook with method: %s, and auth:%s', |
|
380 | 385 | call_method, auth) |
|
381 | ||
|
386 | if settings.get('log_data'): | |
|
387 | log.debug('calling webhook with data: %s', data) | |
|
382 | 388 | resp = call_method(url, json={ |
|
383 | 389 | 'token': token, |
|
384 | 390 | 'event': data |
@@ -404,7 +404,6 b' class RepoIntegrationsView(IntegrationSe' | |||
|
404 | 404 | c.repo_name = self.db_repo.repo_name |
|
405 | 405 | c.repository_pull_requests = ScmModel().get_pull_requests(self.repo) |
|
406 | 406 | |
|
407 | ||
|
408 | 407 | return c |
|
409 | 408 | |
|
410 | 409 | @LoginRequired() |
@@ -198,7 +198,6 b' class IntegrationOptionsSchemaBase(colan' | |||
|
198 | 198 | ) |
|
199 | 199 | |
|
200 | 200 | |
|
201 | ||
|
202 | 201 | def make_integration_schema(IntegrationType, settings=None): |
|
203 | 202 | """ |
|
204 | 203 | Return a colander schema for an integration type |
@@ -21,6 +21,7 b'' | |||
|
21 | 21 | |
|
22 | 22 | import pytest |
|
23 | 23 | from rhodecode import events |
|
24 | from rhodecode.lib.utils2 import AttributeDict | |
|
24 | 25 | |
|
25 | 26 | |
|
26 | 27 | @pytest.fixture |
@@ -34,7 +35,7 b' def repo_push_event(backend, user_regula' | |||
|
34 | 35 | ] |
|
35 | 36 | commit_ids = backend.create_master_repo(commits).values() |
|
36 | 37 | repo = backend.create_repo() |
|
37 | scm_extras = { | |
|
38 | scm_extras = AttributeDict({ | |
|
38 | 39 | 'ip': '127.0.0.1', |
|
39 | 40 | 'username': user_regular.username, |
|
40 | 41 | 'user_id': user_regular.user_id, |
@@ -46,7 +47,7 b' def repo_push_event(backend, user_regula' | |||
|
46 | 47 | 'make_lock': None, |
|
47 | 48 | 'locked_by': [None], |
|
48 | 49 | 'commit_ids': commit_ids, |
|
49 | } | |
|
50 | }) | |
|
50 | 51 | |
|
51 | 52 | return events.RepoPushEvent(repo_name=repo.repo_name, |
|
52 | 53 | pushed_commit_ids=commit_ids, |
@@ -77,12 +77,13 b' class Command(object):' | |||
|
77 | 77 | assert self.process.returncode == 0 |
|
78 | 78 | |
|
79 | 79 | |
|
80 | def _add_files_and_push(vcs, dest, clone_url=None, **kwargs): | |
|
80 | def _add_files_and_push(vcs, dest, clone_url=None, tags=None, **kwargs): | |
|
81 | 81 | """ |
|
82 | 82 | Generate some files, add it to DEST repo and push back |
|
83 | 83 | vcs is git or hg and defines what VCS we want to make those files for |
|
84 | 84 | """ |
|
85 | 85 | # commit some stuff into this repo |
|
86 | tags = tags or [] | |
|
86 | 87 | cwd = path = jn(dest) |
|
87 | 88 | added_file = jn(path, '%ssetup.py' % tempfile._RandomNameSequence().next()) |
|
88 | 89 | Command(cwd).execute('touch %s' % added_file) |
@@ -92,7 +93,7 b' def _add_files_and_push(vcs, dest, clone' | |||
|
92 | 93 | git_ident = "git config user.name {} && git config user.email {}".format( |
|
93 | 94 | 'Marcin KuΕΊminski', 'me@email.com') |
|
94 | 95 | |
|
95 |
for i in |
|
|
96 | for i in range(kwargs.get('files_no', 3)): | |
|
96 | 97 | cmd = """echo 'added_line%s' >> %s""" % (i, added_file) |
|
97 | 98 | Command(cwd).execute(cmd) |
|
98 | 99 | if vcs == 'hg': |
@@ -104,6 +105,22 b' def _add_files_and_push(vcs, dest, clone' | |||
|
104 | 105 | git_ident, i, added_file) |
|
105 | 106 | Command(cwd).execute(cmd) |
|
106 | 107 | |
|
108 | for tag in tags: | |
|
109 | if vcs == 'hg': | |
|
110 | stdout, stderr = Command(cwd).execute( | |
|
111 | 'hg tag', tag['name']) | |
|
112 | elif vcs == 'git': | |
|
113 | if tag['commit']: | |
|
114 | # annotated tag | |
|
115 | stdout, stderr = Command(cwd).execute( | |
|
116 | """%s && git tag -a %s -m "%s" """ % ( | |
|
117 | git_ident, tag['name'], tag['commit'])) | |
|
118 | else: | |
|
119 | # lightweight tag | |
|
120 | stdout, stderr = Command(cwd).execute( | |
|
121 | """%s && git tag %s""" % ( | |
|
122 | git_ident, tag['name'])) | |
|
123 | ||
|
107 | 124 | # PUSH it back |
|
108 | 125 | stdout = stderr = None |
|
109 | 126 | if vcs == 'hg': |
@@ -111,7 +128,8 b' def _add_files_and_push(vcs, dest, clone' | |||
|
111 | 128 | 'hg push --verbose', clone_url) |
|
112 | 129 | elif vcs == 'git': |
|
113 | 130 | stdout, stderr = Command(cwd).execute( |
|
114 | """%s && git push --verbose %s master""" % ( | |
|
131 | """%s && | |
|
132 | git push --verbose --tags %s master""" % ( | |
|
115 | 133 | git_ident, clone_url)) |
|
116 | 134 | |
|
117 | 135 | return stdout, stderr |
@@ -57,20 +57,24 b' def assert_no_running_instance(url):' | |||
|
57 | 57 | "Port is not free at %s, cannot start web interface" % url) |
|
58 | 58 | |
|
59 | 59 | |
|
60 | def get_port(pyramid_config): | |
|
61 | config = ConfigParser.ConfigParser() | |
|
62 | config.read(pyramid_config) | |
|
63 | return config.get('server:main', 'port') | |
|
64 | ||
|
65 | ||
|
60 | 66 | def get_host_url(pyramid_config): |
|
61 | 67 | """Construct the host url using the port in the test configuration.""" |
|
62 | config = ConfigParser.ConfigParser() | |
|
63 | config.read(pyramid_config) | |
|
64 | ||
|
65 | return '127.0.0.1:%s' % config.get('server:main', 'port') | |
|
68 | return '127.0.0.1:%s' % get_port(pyramid_config) | |
|
66 | 69 | |
|
67 | 70 | |
|
68 | 71 | class RcWebServer(object): |
|
69 | 72 | """ |
|
70 | 73 | Represents a running RCE web server used as a test fixture. |
|
71 | 74 | """ |
|
72 | def __init__(self, pyramid_config): | |
|
75 | def __init__(self, pyramid_config, log_file): | |
|
73 | 76 | self.pyramid_config = pyramid_config |
|
77 | self.log_file = log_file | |
|
74 | 78 | |
|
75 | 79 | def repo_clone_url(self, repo_name, **kwargs): |
|
76 | 80 | params = { |
@@ -86,6 +90,10 b' class RcWebServer(object):' | |||
|
86 | 90 | def host_url(self): |
|
87 | 91 | return 'http://' + get_host_url(self.pyramid_config) |
|
88 | 92 | |
|
93 | def get_rc_log(self): | |
|
94 | with open(self.log_file) as f: | |
|
95 | return f.read() | |
|
96 | ||
|
89 | 97 | |
|
90 | 98 | @pytest.fixture(scope="module") |
|
91 | 99 | def rcextensions(request, baseapp, tmpdir_factory): |
@@ -155,12 +163,11 b' def rc_web_server(' | |||
|
155 | 163 | env = os.environ.copy() |
|
156 | 164 | env['RC_NO_TMP_PATH'] = '1' |
|
157 | 165 | |
|
158 | rc_log = RC_LOG | |
|
159 | server_out = open(rc_log, 'w') | |
|
166 | rc_log = list(RC_LOG.partition('.log')) | |
|
167 | rc_log.insert(1, get_port(rc_web_server_config)) | |
|
168 | rc_log = ''.join(rc_log) | |
|
160 | 169 | |
|
161 | # TODO: Would be great to capture the output and err of the subprocess | |
|
162 | # and make it available in a section of the py.test report in case of an | |
|
163 | # error. | |
|
170 | server_out = open(rc_log, 'w') | |
|
164 | 171 | |
|
165 | 172 | host_url = 'http://' + get_host_url(rc_web_server_config) |
|
166 | 173 | assert_no_running_instance(host_url) |
@@ -184,7 +191,7 b' def rc_web_server(' | |||
|
184 | 191 | server_out.flush() |
|
185 | 192 | server_out.close() |
|
186 | 193 | |
|
187 | return RcWebServer(rc_web_server_config) | |
|
194 | return RcWebServer(rc_web_server_config, log_file=rc_log) | |
|
188 | 195 | |
|
189 | 196 | |
|
190 | 197 | @pytest.fixture |
General Comments 0
You need to be logged in to leave comments.
Login now