##// END OF EJS Templates
fix(tests): fixed svn tests
super-admin -
r5336:7c832518 default
parent child Browse files
Show More
@@ -1,269 +1,269 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import os
19 import os
20 import re
20 import re
21 import sys
21 import sys
22 import logging
22 import logging
23 import signal
23 import signal
24 import tempfile
24 import tempfile
25 from subprocess import Popen, PIPE
25 from subprocess import Popen, PIPE
26 import urllib.parse
26 import urllib.parse
27
27
28 from rhodecode_tools.lib.utils import safe_str
28 from rhodecode_tools.lib.utils import safe_str
29 from .base import SshVcsServer
29 from .base import SshVcsServer
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class SubversionTunnelWrapper(object):
34 class SubversionTunnelWrapper(object):
35 process = None
35 process = None
36
36
37 def __init__(self, server):
37 def __init__(self, server):
38 self.server = server
38 self.server = server
39 self.timeout = 30
39 self.timeout = 30
40 self.stdin = sys.stdin
40 self.stdin = sys.stdin
41 self.stdout = sys.stdout
41 self.stdout = sys.stdout
42 self.svn_conf_fd, self.svn_conf_path = tempfile.mkstemp()
42 self.svn_conf_fd, self.svn_conf_path = tempfile.mkstemp()
43 self.hooks_env_fd, self.hooks_env_path = tempfile.mkstemp()
43 self.hooks_env_fd, self.hooks_env_path = tempfile.mkstemp()
44
44
45 self.read_only = True # flag that we set to make the hooks readonly
45 self.read_only = True # flag that we set to make the hooks readonly
46
46
47 def create_svn_config(self):
47 def create_svn_config(self):
48 content = (
48 content = (
49 '[general]\n'
49 '[general]\n'
50 'hooks-env = {}\n').format(self.hooks_env_path)
50 'hooks-env = {}\n').format(self.hooks_env_path)
51 with os.fdopen(self.svn_conf_fd, 'w') as config_file:
51 with os.fdopen(self.svn_conf_fd, 'w') as config_file:
52 config_file.write(content)
52 config_file.write(content)
53
53
54 def create_hooks_env(self):
54 def create_hooks_env(self):
55 content = (
55 content = (
56 '[default]\n'
56 '[default]\n'
57 'LANG = en_US.UTF-8\n')
57 'LANG = en_US.UTF-8\n')
58 if self.read_only:
58 if self.read_only:
59 content += 'SSH_READ_ONLY = 1\n'
59 content += 'SSH_READ_ONLY = 1\n'
60 with os.fdopen(self.hooks_env_fd, 'w') as hooks_env_file:
60 with os.fdopen(self.hooks_env_fd, 'w') as hooks_env_file:
61 hooks_env_file.write(content)
61 hooks_env_file.write(content)
62
62
63 def remove_configs(self):
63 def remove_configs(self):
64 os.remove(self.svn_conf_path)
64 os.remove(self.svn_conf_path)
65 os.remove(self.hooks_env_path)
65 os.remove(self.hooks_env_path)
66
66
67 def command(self):
67 def command(self):
68 root = self.server.get_root_store()
68 root = self.server.get_root_store()
69 username = self.server.user.username
69 username = self.server.user.username
70
70
71 command = [
71 command = [
72 self.server.svn_path, '-t',
72 self.server.svn_path, '-t',
73 '--config-file', self.svn_conf_path,
73 '--config-file', self.svn_conf_path,
74 '--tunnel-user', username,
74 '--tunnel-user', username,
75 '-r', root]
75 '-r', root]
76 log.debug("Final CMD: %s", ' '.join(command))
76 log.debug("Final CMD: %s", ' '.join(command))
77 return command
77 return command
78
78
79 def start(self):
79 def start(self):
80 command = self.command()
80 command = self.command()
81 self.process = Popen(' '.join(command), stdin=PIPE, shell=True)
81 self.process = Popen(' '.join(command), stdin=PIPE, shell=True)
82
82
83 def sync(self):
83 def sync(self):
84 while self.process.poll() is None:
84 while self.process.poll() is None:
85 next_byte = self.stdin.buffer.read(1)
85 next_byte = self.stdin.buffer.read(1)
86 if not next_byte:
86 if not next_byte:
87 break
87 break
88 self.process.stdin.write(next_byte)
88 self.process.stdin.write(next_byte)
89 self.remove_configs()
89 self.remove_configs()
90
90
91 @property
91 @property
92 def return_code(self):
92 def return_code(self):
93 return self.process.returncode
93 return self.process.returncode
94
94
95 def get_first_client_response(self):
95 def get_first_client_response(self):
96 signal.signal(signal.SIGALRM, self.interrupt)
96 signal.signal(signal.SIGALRM, self.interrupt)
97 signal.alarm(self.timeout)
97 signal.alarm(self.timeout)
98 first_response = self._read_first_client_response()
98 first_response = self._read_first_client_response()
99 signal.alarm(0)
99 signal.alarm(0)
100 return (self._parse_first_client_response(first_response)
100 return (self._parse_first_client_response(first_response)
101 if first_response else None)
101 if first_response else None)
102
102
103 def patch_first_client_response(self, response, **kwargs):
103 def patch_first_client_response(self, response, **kwargs):
104 self.create_hooks_env()
104 self.create_hooks_env()
105
105
106 version = response['version']
106 version = response['version']
107 capabilities = response['capabilities']
107 capabilities = response['capabilities']
108 client = response['client'] or b''
108 client = response['client'] or b''
109
109
110 url = self._svn_bytes(response['url'])
110 url = self._svn_bytes(response['url'])
111 ra_client = self._svn_bytes(response['ra_client'])
111 ra_client = self._svn_bytes(response['ra_client'])
112
112
113 buffer_ = b"( %b ( %b ) %b%b( %b) ) " % (
113 buffer_ = b"( %b ( %b ) %b%b( %b) ) " % (
114 version,
114 version,
115 capabilities,
115 capabilities,
116 url,
116 url,
117 ra_client,
117 ra_client,
118 client
118 client
119 )
119 )
120 self.process.stdin.write(buffer_)
120 self.process.stdin.write(buffer_)
121
121
122 def fail(self, message):
122 def fail(self, message):
123 fail_msg = b"( failure ( ( 210005 %b 0: 0 ) ) )" % self._svn_bytes(message)
123 fail_msg = b"( failure ( ( 210005 %b 0: 0 ) ) )" % self._svn_bytes(message)
124 sys.stdout.buffer.write(fail_msg)
124 sys.stdout.buffer.write(fail_msg)
125 sys.stdout.flush()
125 sys.stdout.flush()
126 self.remove_configs()
126 self.remove_configs()
127 self.process.kill()
127 self.process.kill()
128 return 1
128 return 1
129
129
130 def interrupt(self, signum, frame):
130 def interrupt(self, signum, frame):
131 self.fail("Exited by timeout")
131 self.fail("Exited by timeout")
132
132
133 def _svn_bytes(self, bytes_: bytes) -> bytes:
133 def _svn_bytes(self, bytes_: bytes) -> bytes:
134 if not bytes_:
134 if not bytes_:
135 return b''
135 return b''
136
136
137 return f'{len(bytes_)}:'.encode() + bytes_ + b' '
137 return f'{len(bytes_)}:'.encode() + bytes_ + b' '
138
138
139 def _read_first_client_response(self):
139 def _read_first_client_response(self):
140 buffer_ = b""
140 buffer_ = b""
141 brackets_stack = []
141 brackets_stack = []
142 while True:
142 while True:
143 next_byte = self.stdin.buffer.read(1)
143 next_byte = self.stdin.buffer.read(1)
144 buffer_ += next_byte
144 buffer_ += next_byte
145 if next_byte == b"(":
145 if next_byte == b"(":
146 brackets_stack.append(next_byte)
146 brackets_stack.append(next_byte)
147 elif next_byte == b")":
147 elif next_byte == b")":
148 brackets_stack.pop()
148 brackets_stack.pop()
149 elif next_byte == b" " and not brackets_stack:
149 elif next_byte == b" " and not brackets_stack:
150 break
150 break
151
151
152 return buffer_
152 return buffer_
153
153
154 def _parse_first_client_response(self, buffer_: bytes):
154 def _parse_first_client_response(self, buffer_: bytes):
155 """
155 """
156 According to the Subversion RA protocol, the first request
156 According to the Subversion RA protocol, the first request
157 should look like:
157 should look like:
158
158
159 ( version:number ( cap:word ... ) url:string ? ra-client:string
159 ( version:number ( cap:word ... ) url:string ? ra-client:string
160 ( ? client:string ) )
160 ( ? client:string ) )
161
161
162 Please check https://svn.apache.org/repos/asf/subversion/trunk/subversion/libsvn_ra_svn/protocol
162 Please check https://svn.apache.org/repos/asf/subversion/trunk/subversion/libsvn_ra_svn/protocol
163 """
163 """
164 version_re = br'(?P<version>\d+)'
164 version_re = br'(?P<version>\d+)'
165 capabilities_re = br'\(\s(?P<capabilities>[\w\d\-\ ]+)\s\)'
165 capabilities_re = br'\(\s(?P<capabilities>[\w\d\-\ ]+)\s\)'
166 url_re = br'\d+\:(?P<url>[\W\w]+)'
166 url_re = br'\d+\:(?P<url>[\W\w]+)'
167 ra_client_re = br'(\d+\:(?P<ra_client>[\W\w]+)\s)'
167 ra_client_re = br'(\d+\:(?P<ra_client>[\W\w]+)\s)'
168 client_re = br'(\d+\:(?P<client>[\W\w]+)\s)*'
168 client_re = br'(\d+\:(?P<client>[\W\w]+)\s)*'
169 regex = re.compile(
169 regex = re.compile(
170 br'^\(\s%b\s%b\s%b\s%b'
170 br'^\(\s%b\s%b\s%b\s%b'
171 br'\(\s%b\)\s\)\s*$' % (
171 br'\(\s%b\)\s\)\s*$' % (
172 version_re,
172 version_re,
173 capabilities_re,
173 capabilities_re,
174 url_re,
174 url_re,
175 ra_client_re,
175 ra_client_re,
176 client_re)
176 client_re)
177 )
177 )
178 matcher = regex.match(buffer_)
178 matcher = regex.match(buffer_)
179
179
180 return matcher.groupdict() if matcher else None
180 return matcher.groupdict() if matcher else None
181
181
182 def _match_repo_name(self, url):
182 def _match_repo_name(self, url):
183 """
183 """
184 Given an server url, try to match it against ALL known repository names.
184 Given an server url, try to match it against ALL known repository names.
185 This handles a tricky SVN case for SSH and subdir commits.
185 This handles a tricky SVN case for SSH and subdir commits.
186 E.g if our repo name is my-svn-repo, a svn commit on file in a subdir would
186 E.g if our repo name is my-svn-repo, a svn commit on file in a subdir would
187 result in the url with this subdir added.
187 result in the url with this subdir added.
188 """
188 """
189 # case 1 direct match, we don't do any "heavy" lookups
189 # case 1 direct match, we don't do any "heavy" lookups
190 if url in self.server.user_permissions:
190 if url in self.server.user_permissions:
191 return url
191 return url
192
192
193 log.debug('Extracting repository name from subdir path %s', url)
193 log.debug('Extracting repository name from subdir path %s', url)
194 # case 2 we check all permissions, and match closes possible case...
194 # case 2 we check all permissions, and match closes possible case...
195 # NOTE(dan): In this case we only know that url has a subdir parts, it's safe
195 # NOTE(dan): In this case we only know that url has a subdir parts, it's safe
196 # to assume that it will have the repo name as prefix, we ensure the prefix
196 # to assume that it will have the repo name as prefix, we ensure the prefix
197 # for similar repositories isn't matched by adding a /
197 # for similar repositories isn't matched by adding a /
198 # e.g subgroup/repo-name/ and subgroup/repo-name-1/ would work correct.
198 # e.g subgroup/repo-name/ and subgroup/repo-name-1/ would work correct.
199 for repo_name in self.server.user_permissions:
199 for repo_name in self.server.user_permissions:
200 repo_name_prefix = repo_name + '/'
200 repo_name_prefix = repo_name + '/'
201 if url.startswith(repo_name_prefix):
201 if url.startswith(repo_name_prefix):
202 log.debug('Found prefix %s match, returning proper repository name',
202 log.debug('Found prefix %s match, returning proper repository name',
203 repo_name_prefix)
203 repo_name_prefix)
204 return repo_name
204 return repo_name
205
205
206 return
206 return
207
207
208 def run(self, extras):
208 def run(self, extras):
209 action = 'pull'
209 action = 'pull'
210 self.create_svn_config()
210 self.create_svn_config()
211 self.start()
211 self.start()
212
212
213 first_response = self.get_first_client_response()
213 first_response = self.get_first_client_response()
214 if not first_response:
214 if not first_response:
215 return self.fail("Repository name cannot be extracted")
215 return self.fail(b"Repository name cannot be extracted")
216
216
217 url_parts = urllib.parse.urlparse(first_response['url'])
217 url_parts = urllib.parse.urlparse(first_response['url'])
218
218
219 self.server.repo_name = self._match_repo_name(safe_str(url_parts.path).strip('/'))
219 self.server.repo_name = self._match_repo_name(safe_str(url_parts.path).strip('/'))
220
220
221 exit_code = self.server._check_permissions(action)
221 exit_code = self.server._check_permissions(action)
222 if exit_code:
222 if exit_code:
223 return exit_code
223 return exit_code
224
224
225 # set the readonly flag to False if we have proper permissions
225 # set the readonly flag to False if we have proper permissions
226 if self.server.has_write_perm():
226 if self.server.has_write_perm():
227 self.read_only = False
227 self.read_only = False
228 self.server.update_environment(action=action, extras=extras)
228 self.server.update_environment(action=action, extras=extras)
229
229
230 self.patch_first_client_response(first_response)
230 self.patch_first_client_response(first_response)
231 self.sync()
231 self.sync()
232 return self.return_code
232 return self.return_code
233
233
234
234
235 class SubversionServer(SshVcsServer):
235 class SubversionServer(SshVcsServer):
236 backend = 'svn'
236 backend = 'svn'
237 repo_user_agent = 'svn'
237 repo_user_agent = 'svn'
238
238
239 def __init__(self, store, ini_path, repo_name, user, user_permissions, settings, env):
239 def __init__(self, store, ini_path, repo_name, user, user_permissions, settings, env):
240 super().__init__(user, user_permissions, settings, env)
240 super().__init__(user, user_permissions, settings, env)
241 self.store = store
241 self.store = store
242 self.ini_path = ini_path
242 self.ini_path = ini_path
243 # NOTE(dan): repo_name at this point is empty,
243 # NOTE(dan): repo_name at this point is empty,
244 # this is set later in .run() based from parsed input stream
244 # this is set later in .run() based from parsed input stream
245 self.repo_name = repo_name
245 self.repo_name = repo_name
246 self._path = self.svn_path = settings['ssh.executable.svn']
246 self._path = self.svn_path = settings['ssh.executable.svn']
247
247
248 self.tunnel = SubversionTunnelWrapper(server=self)
248 self.tunnel = SubversionTunnelWrapper(server=self)
249
249
250 def _handle_tunnel(self, extras):
250 def _handle_tunnel(self, extras):
251
251
252 # pre-auth
252 # pre-auth
253 action = 'pull'
253 action = 'pull'
254 # Special case for SVN, we extract repo name at later stage
254 # Special case for SVN, we extract repo name at later stage
255 # exit_code = self._check_permissions(action)
255 # exit_code = self._check_permissions(action)
256 # if exit_code:
256 # if exit_code:
257 # return exit_code, False
257 # return exit_code, False
258
258
259 req = self.env.get('request')
259 req = self.env.get('request')
260 if req:
260 if req:
261 server_url = req.host_url + req.script_name
261 server_url = req.host_url + req.script_name
262 extras['server_url'] = server_url
262 extras['server_url'] = server_url
263
263
264 log.debug('Using %s binaries from path %s', self.backend, self._path)
264 log.debug('Using %s binaries from path %s', self.backend, self._path)
265 exit_code = self.tunnel.run(extras)
265 exit_code = self.tunnel.run(extras)
266
266
267 return exit_code, action == "push"
267 return exit_code, action == "push"
268
268
269
269
@@ -1,202 +1,203 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 import os
18 import os
19 import mock
19 import mock
20 import pytest
20 import pytest
21
21
22 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionServer
22 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionServer
23 from rhodecode.apps.ssh_support.tests.conftest import plain_dummy_env, plain_dummy_user
23 from rhodecode.apps.ssh_support.tests.conftest import plain_dummy_env, plain_dummy_user
24
24
25
25
26 class SubversionServerCreator(object):
26 class SubversionServerCreator(object):
27 root = '/tmp/repo/path/'
27 root = '/tmp/repo/path/'
28 svn_path = '/usr/local/bin/svnserve'
28 svn_path = '/usr/local/bin/svnserve'
29 config_data = {
29 config_data = {
30 'app:main': {
30 'app:main': {
31 'ssh.executable.svn': svn_path,
31 'ssh.executable.svn': svn_path,
32 'vcs.hooks.protocol': 'http',
32 'vcs.hooks.protocol': 'http',
33 }
33 }
34 }
34 }
35 repo_name = 'test-svn'
35 repo_name = 'test-svn'
36 user = plain_dummy_user()
36 user = plain_dummy_user()
37
37
38 def __init__(self):
38 def __init__(self):
39 pass
39 pass
40
40
41 def create(self, **kwargs):
41 def create(self, **kwargs):
42 parameters = {
42 parameters = {
43 'store': self.root,
43 'store': self.root,
44 'repo_name': self.repo_name,
44 'repo_name': self.repo_name,
45 'ini_path': '',
45 'ini_path': '',
46 'user': self.user,
46 'user': self.user,
47 'user_permissions': {
47 'user_permissions': {
48 self.repo_name: 'repository.admin'
48 self.repo_name: 'repository.admin'
49 },
49 },
50 'settings': self.config_data['app:main'],
50 'settings': self.config_data['app:main'],
51 'env': plain_dummy_env()
51 'env': plain_dummy_env()
52 }
52 }
53
53
54 parameters.update(kwargs)
54 parameters.update(kwargs)
55 server = SubversionServer(**parameters)
55 server = SubversionServer(**parameters)
56 return server
56 return server
57
57
58
58
59 @pytest.fixture()
59 @pytest.fixture()
60 def svn_server(app):
60 def svn_server(app):
61 return SubversionServerCreator()
61 return SubversionServerCreator()
62
62
63
63
64 class TestSubversionServer(object):
64 class TestSubversionServer(object):
65
65 def test_command(self, svn_server):
66 def test_command(self, svn_server):
66 server = svn_server.create()
67 server = svn_server.create()
67 expected_command = [
68 expected_command = [
68 svn_server.svn_path, '-t',
69 svn_server.svn_path, '-t',
69 '--config-file', server.tunnel.svn_conf_path,
70 '--config-file', server.tunnel.svn_conf_path,
70 '--tunnel-user', svn_server.user.username,
71 '--tunnel-user', svn_server.user.username,
71 '-r', svn_server.root
72 '-r', svn_server.root
72 ]
73 ]
73
74
74 assert expected_command == server.tunnel.command()
75 assert expected_command == server.tunnel.command()
75
76
76 @pytest.mark.parametrize('permissions, action, code', [
77 @pytest.mark.parametrize('permissions, action, code', [
77 ({}, 'pull', -2),
78 ({}, 'pull', -2),
78 ({'test-svn': 'repository.read'}, 'pull', 0),
79 ({'test-svn': 'repository.read'}, 'pull', 0),
79 ({'test-svn': 'repository.read'}, 'push', -2),
80 ({'test-svn': 'repository.read'}, 'push', -2),
80 ({'test-svn': 'repository.write'}, 'push', 0),
81 ({'test-svn': 'repository.write'}, 'push', 0),
81 ({'test-svn': 'repository.admin'}, 'push', 0),
82 ({'test-svn': 'repository.admin'}, 'push', 0),
82
83
83 ])
84 ])
84 def test_permission_checks(self, svn_server, permissions, action, code):
85 def test_permission_checks(self, svn_server, permissions, action, code):
85 server = svn_server.create(user_permissions=permissions)
86 server = svn_server.create(user_permissions=permissions)
86 result = server._check_permissions(action)
87 result = server._check_permissions(action)
87 assert result is code
88 assert result is code
88
89
89 @pytest.mark.parametrize('permissions, access_paths, expected_match', [
90 @pytest.mark.parametrize('permissions, access_paths, expected_match', [
90 # not matched repository name
91 # not matched repository name
91 ({
92 ({
92 'test-svn': ''
93 'test-svn': ''
93 }, ['test-svn-1', 'test-svn-1/subpath'],
94 }, ['test-svn-1', 'test-svn-1/subpath'],
94 None),
95 None),
95
96
96 # exact match
97 # exact match
97 ({
98 ({
98 'test-svn': ''
99 'test-svn': ''
99 },
100 },
100 ['test-svn'],
101 ['test-svn'],
101 'test-svn'),
102 'test-svn'),
102
103
103 # subdir commits
104 # subdir commits
104 ({
105 ({
105 'test-svn': ''
106 'test-svn': ''
106 },
107 },
107 ['test-svn/foo',
108 ['test-svn/foo',
108 'test-svn/foo/test-svn',
109 'test-svn/foo/test-svn',
109 'test-svn/trunk/development.txt',
110 'test-svn/trunk/development.txt',
110 ],
111 ],
111 'test-svn'),
112 'test-svn'),
112
113
113 # subgroups + similar patterns
114 # subgroups + similar patterns
114 ({
115 ({
115 'test-svn': '',
116 'test-svn': '',
116 'test-svn-1': '',
117 'test-svn-1': '',
117 'test-svn-subgroup/test-svn': '',
118 'test-svn-subgroup/test-svn': '',
118
119
119 },
120 },
120 ['test-svn-1',
121 ['test-svn-1',
121 'test-svn-1/foo/test-svn',
122 'test-svn-1/foo/test-svn',
122 'test-svn-1/test-svn',
123 'test-svn-1/test-svn',
123 ],
124 ],
124 'test-svn-1'),
125 'test-svn-1'),
125
126
126 # subgroups + similar patterns
127 # subgroups + similar patterns
127 ({
128 ({
128 'test-svn-1': '',
129 'test-svn-1': '',
129 'test-svn-10': '',
130 'test-svn-10': '',
130 'test-svn-100': '',
131 'test-svn-100': '',
131 },
132 },
132 ['test-svn-10',
133 ['test-svn-10',
133 'test-svn-10/foo/test-svn',
134 'test-svn-10/foo/test-svn',
134 'test-svn-10/test-svn',
135 'test-svn-10/test-svn',
135 ],
136 ],
136 'test-svn-10'),
137 'test-svn-10'),
137
138
138 # subgroups + similar patterns
139 # subgroups + similar patterns
139 ({
140 ({
140 'name': '',
141 'name': '',
141 'nameContains': '',
142 'nameContains': '',
142 'nameContainsThis': '',
143 'nameContainsThis': '',
143 },
144 },
144 ['nameContains',
145 ['nameContains',
145 'nameContains/This',
146 'nameContains/This',
146 'nameContains/This/test-svn',
147 'nameContains/This/test-svn',
147 ],
148 ],
148 'nameContains'),
149 'nameContains'),
149
150
150 # subgroups + similar patterns
151 # subgroups + similar patterns
151 ({
152 ({
152 'test-svn': '',
153 'test-svn': '',
153 'test-svn-1': '',
154 'test-svn-1': '',
154 'test-svn-subgroup/test-svn': '',
155 'test-svn-subgroup/test-svn': '',
155
156
156 },
157 },
157 ['test-svn-subgroup/test-svn',
158 ['test-svn-subgroup/test-svn',
158 'test-svn-subgroup/test-svn/foo/test-svn',
159 'test-svn-subgroup/test-svn/foo/test-svn',
159 'test-svn-subgroup/test-svn/trunk/example.txt',
160 'test-svn-subgroup/test-svn/trunk/example.txt',
160 ],
161 ],
161 'test-svn-subgroup/test-svn'),
162 'test-svn-subgroup/test-svn'),
162 ])
163 ])
163 def test_repo_extraction_on_subdir(self, svn_server, permissions, access_paths, expected_match):
164 def test_repo_extraction_on_subdir(self, svn_server, permissions, access_paths, expected_match):
164 server = svn_server.create(user_permissions=permissions)
165 server = svn_server.create(user_permissions=permissions)
165 for path in access_paths:
166 for path in access_paths:
166 repo_name = server.tunnel._match_repo_name(path)
167 repo_name = server.tunnel._match_repo_name(path)
167 assert repo_name == expected_match
168 assert repo_name == expected_match
168
169
169 def test_run_returns_executes_command(self, svn_server):
170 def test_run_returns_executes_command(self, svn_server):
170 server = svn_server.create()
171 server = svn_server.create()
171 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
172 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
172 os.environ['SSH_CLIENT'] = '127.0.0.1'
173 os.environ['SSH_CLIENT'] = '127.0.0.1'
173 with mock.patch.object(
174 with mock.patch.object(
174 SubversionTunnelWrapper, 'get_first_client_response',
175 SubversionTunnelWrapper, 'get_first_client_response',
175 return_value={'url': 'http://server/test-svn'}):
176 return_value={'url': 'http://server/test-svn'}):
176 with mock.patch.object(
177 with mock.patch.object(
177 SubversionTunnelWrapper, 'patch_first_client_response',
178 SubversionTunnelWrapper, 'patch_first_client_response',
178 return_value=0):
179 return_value=0):
179 with mock.patch.object(
180 with mock.patch.object(
180 SubversionTunnelWrapper, 'sync',
181 SubversionTunnelWrapper, 'sync',
181 return_value=0):
182 return_value=0):
182 with mock.patch.object(
183 with mock.patch.object(
183 SubversionTunnelWrapper, 'command',
184 SubversionTunnelWrapper, 'command',
184 return_value=['date']):
185 return_value=['date']):
185
186
186 exit_code = server.run()
187 exit_code = server.run()
187 # SVN has this differently configured, and we get in our mock env
188 # SVN has this differently configured, and we get in our mock env
188 # None as return code
189 # None as return code
189 assert exit_code == (None, False)
190 assert exit_code == (None, False)
190
191
191 def test_run_returns_executes_command_that_cannot_extract_repo_name(self, svn_server):
192 def test_run_returns_executes_command_that_cannot_extract_repo_name(self, svn_server):
192 server = svn_server.create()
193 server = svn_server.create()
193 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
194 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
194 with mock.patch.object(
195 with mock.patch.object(
195 SubversionTunnelWrapper, 'command',
196 SubversionTunnelWrapper, 'command',
196 return_value=['date']):
197 return_value=['date']):
197 with mock.patch.object(
198 with mock.patch.object(
198 SubversionTunnelWrapper, 'get_first_client_response',
199 SubversionTunnelWrapper, 'get_first_client_response',
199 return_value=None):
200 return_value=None):
200 exit_code = server.run()
201 exit_code = server.run()
201
202
202 assert exit_code == (1, False)
203 assert exit_code == (1, False)
General Comments 0
You need to be logged in to leave comments. Login now