test_simplesvn.py
231 lines
| 9.5 KiB
| text/x-python
|
PythonLexer
r5608 | # Copyright (C) 2010-2024 RhodeCode GmbH | |||
r1 | # | |||
# This program is free software: you can redistribute it and/or modify | ||||
# it under the terms of the GNU Affero General Public License, version 3 | ||||
# (only), as published by the Free Software Foundation. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU Affero General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
# | ||||
# This program is dual-licensed. If you wish to learn more about the | ||||
# RhodeCode Enterprise Edition, including its added features, Support services, | ||||
# and proprietary license terms, please see https://rhodecode.com/licenses/ | ||||
r4973 | import io | |||
r5224 | from base64 import b64encode | |||
r1 | ||||
import pytest | ||||
r5224 | from unittest.mock import patch, Mock, MagicMock | |||
r1 | ||||
from rhodecode.lib.middleware.simplesvn import SimpleSvn, SimpleSvnApp | ||||
r5356 | from rhodecode.lib.utils import get_rhodecode_repo_store_path | |||
r5224 | from rhodecode.tests import SVN_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS | |||
r1 | ||||
class TestSimpleSvn(object): | ||||
@pytest.fixture(autouse=True) | ||||
r2351 | def simple_svn(self, baseapp, request_stub): | |||
r5356 | base_path = get_rhodecode_repo_store_path() | |||
r1 | self.app = SimpleSvn( | |||
r5356 | config={'auth_ret_code': '', 'repo_store.path': base_path}, | |||
r2140 | registry=request_stub.registry) | |||
r1 | ||||
def test_get_config(self): | ||||
extras = {'foo': 'FOO', 'bar': 'BAR'} | ||||
config = self.app._create_config(extras, repo_name='test-repo') | ||||
assert config == extras | ||||
@pytest.mark.parametrize( | ||||
'method', ['OPTIONS', 'PROPFIND', 'GET', 'REPORT']) | ||||
def test_get_action_returns_pull(self, method): | ||||
environment = {'REQUEST_METHOD': method} | ||||
action = self.app._get_action(environment) | ||||
assert action == 'pull' | ||||
@pytest.mark.parametrize( | ||||
'method', [ | ||||
'MKACTIVITY', 'PROPPATCH', 'PUT', 'CHECKOUT', 'MKCOL', 'MOVE', | ||||
'COPY', 'DELETE', 'LOCK', 'UNLOCK', 'MERGE' | ||||
]) | ||||
def test_get_action_returns_push(self, method): | ||||
environment = {'REQUEST_METHOD': method} | ||||
action = self.app._get_action(environment) | ||||
assert action == 'push' | ||||
@pytest.mark.parametrize( | ||||
'path, expected_name', [ | ||||
('/hello-svn', 'hello-svn'), | ||||
('/hello-svn/', 'hello-svn'), | ||||
('/group/hello-svn/', 'group/hello-svn'), | ||||
('/group/hello-svn/!svn/vcc/default', 'group/hello-svn'), | ||||
]) | ||||
def test_get_repository_name(self, path, expected_name): | ||||
environment = {'PATH_INFO': path} | ||||
name = self.app._get_repository_name(environment) | ||||
assert name == expected_name | ||||
def test_get_repository_name_subfolder(self, backend_svn): | ||||
repo = backend_svn.repo | ||||
environment = { | ||||
'PATH_INFO': '/{}/path/with/subfolders'.format(repo.repo_name)} | ||||
name = self.app._get_repository_name(environment) | ||||
assert name == repo.repo_name | ||||
def test_create_wsgi_app(self): | ||||
r754 | with patch.object(SimpleSvn, '_is_svn_enabled') as mock_method: | |||
mock_method.return_value = False | ||||
with patch('rhodecode.lib.middleware.simplesvn.DisabledSimpleSvnApp') as ( | ||||
wsgi_app_mock): | ||||
config = Mock() | ||||
wsgi_app = self.app._create_wsgi_app( | ||||
repo_path='', repo_name='', config=config) | ||||
wsgi_app_mock.assert_called_once_with(config) | ||||
assert wsgi_app == wsgi_app_mock() | ||||
r1 | ||||
r754 | def test_create_wsgi_app_when_enabled(self): | |||
with patch.object(SimpleSvn, '_is_svn_enabled') as mock_method: | ||||
mock_method.return_value = True | ||||
with patch('rhodecode.lib.middleware.simplesvn.SimpleSvnApp') as ( | ||||
wsgi_app_mock): | ||||
config = Mock() | ||||
wsgi_app = self.app._create_wsgi_app( | ||||
repo_path='', repo_name='', config=config) | ||||
wsgi_app_mock.assert_called_once_with(config) | ||||
assert wsgi_app == wsgi_app_mock() | ||||
r1 | ||||
r5224 | def basic_auth(username, password): | |||
token = b64encode(f"{username}:{password}".encode('utf-8')).decode("ascii") | ||||
return f'Basic {token}' | ||||
r1 | class TestSimpleSvnApp(object): | |||
r5179 | data = b'<xml></xml>' | |||
r5224 | path = SVN_REPO | |||
r5179 | wsgi_input = io.BytesIO(data) | |||
r1 | environment = { | |||
'HTTP_DAV': ( | ||||
r5224 | 'http://subversion.tigris.org/xmlns/dav/svn/depth, ' | |||
'http://subversion.tigris.org/xmlns/dav/svn/mergeinfo'), | ||||
'HTTP_USER_AGENT': 'SVN/1.14.1 (x86_64-linux) serf/1.3.8', | ||||
r1 | 'REQUEST_METHOD': 'OPTIONS', | |||
'PATH_INFO': path, | ||||
'wsgi.input': wsgi_input, | ||||
'CONTENT_TYPE': 'text/xml', | ||||
r5224 | 'CONTENT_LENGTH': '130', | |||
'Authorization': basic_auth(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS) | ||||
r1 | } | |||
def setup_method(self, method): | ||||
r5224 | # note(marcink): this is hostname from docker compose used for testing... | |||
self.host = 'http://svn:8090' | ||||
r5356 | base_path = get_rhodecode_repo_store_path() | |||
r1 | self.app = SimpleSvnApp( | |||
r2358 | config={'subversion_http_server_url': self.host, | |||
'base_path': base_path}) | ||||
r1 | ||||
def test_get_request_headers_with_content_type(self): | ||||
expected_headers = { | ||||
'Dav': self.environment['HTTP_DAV'], | ||||
'User-Agent': self.environment['HTTP_USER_AGENT'], | ||||
'Content-Type': self.environment['CONTENT_TYPE'], | ||||
r5224 | 'Content-Length': self.environment['CONTENT_LENGTH'], | |||
'Authorization': self.environment['Authorization'] | ||||
r1 | } | |||
headers = self.app._get_request_headers(self.environment) | ||||
assert headers == expected_headers | ||||
def test_get_request_headers_without_content_type(self): | ||||
environment = self.environment.copy() | ||||
environment.pop('CONTENT_TYPE') | ||||
expected_headers = { | ||||
'Dav': environment['HTTP_DAV'], | ||||
'Content-Length': self.environment['CONTENT_LENGTH'], | ||||
'User-Agent': environment['HTTP_USER_AGENT'], | ||||
r5224 | 'Authorization': self.environment['Authorization'] | |||
r1 | } | |||
request_headers = self.app._get_request_headers(environment) | ||||
assert request_headers == expected_headers | ||||
def test_get_response_headers(self): | ||||
headers = { | ||||
'Connection': 'keep-alive', | ||||
'Keep-Alive': 'timeout=5, max=100', | ||||
'Transfer-Encoding': 'chunked', | ||||
'Content-Encoding': 'gzip', | ||||
'MS-Author-Via': 'DAV', | ||||
'SVN-Supported-Posts': 'create-txn-with-props' | ||||
} | ||||
expected_headers = [ | ||||
('MS-Author-Via', 'DAV'), | ||||
Martin Bornhold
|
r611 | ('SVN-Supported-Posts', 'create-txn-with-props'), | ||
r1 | ] | |||
response_headers = self.app._get_response_headers(headers) | ||||
assert sorted(response_headers) == sorted(expected_headers) | ||||
r3323 | @pytest.mark.parametrize('svn_http_url, path_info, expected_url', [ | |||
('http://localhost:8200', '/repo_name', 'http://localhost:8200/repo_name'), | ||||
('http://localhost:8200///', '/repo_name', 'http://localhost:8200/repo_name'), | ||||
('http://localhost:8200', '/group/repo_name', 'http://localhost:8200/group/repo_name'), | ||||
('http://localhost:8200/', '/group/repo_name', 'http://localhost:8200/group/repo_name'), | ||||
('http://localhost:8200/prefix', '/repo_name', 'http://localhost:8200/prefix/repo_name'), | ||||
('http://localhost:8200/prefix', 'repo_name', 'http://localhost:8200/prefix/repo_name'), | ||||
('http://localhost:8200/prefix', '/group/repo_name', 'http://localhost:8200/prefix/group/repo_name') | ||||
]) | ||||
def test_get_url(self, svn_http_url, path_info, expected_url): | ||||
url = self.app._get_url(svn_http_url, path_info) | ||||
r1 | assert url == expected_url | |||
def test_call(self): | ||||
start_response = Mock() | ||||
response_mock = Mock() | ||||
response_mock.headers = { | ||||
'Content-Encoding': 'gzip', | ||||
'MS-Author-Via': 'DAV', | ||||
'SVN-Supported-Posts': 'create-txn-with-props' | ||||
} | ||||
r5224 | ||||
from rhodecode.lib.middleware.simplesvn import requests | ||||
original_request = requests.Session.request | ||||
with patch('rhodecode.lib.middleware.simplesvn.requests.Session.request', autospec=True) as request_mock: | ||||
# Use side_effect to call the original method | ||||
request_mock.side_effect = original_request | ||||
r1 | self.app(self.environment, start_response) | |||
r5224 | expected_url = f'{self.host.strip("/")}/{self.path}' | |||
r1 | expected_request_headers = { | |||
'Dav': self.environment['HTTP_DAV'], | ||||
'User-Agent': self.environment['HTTP_USER_AGENT'], | ||||
r5224 | 'Authorization': self.environment['Authorization'], | |||
r1 | 'Content-Type': self.environment['CONTENT_TYPE'], | |||
r5224 | 'Content-Length': self.environment['CONTENT_LENGTH'], | |||
r1 | } | |||
r5224 | ||||
# Check if the method was called | ||||
assert request_mock.called | ||||
assert request_mock.call_count == 1 | ||||
# Extract the session instance from the first call | ||||
called_with_session = request_mock.call_args[0][0] | ||||
request_mock.assert_called_once_with( | ||||
called_with_session, | ||||
self.environment['REQUEST_METHOD'], expected_url, | ||||
data=self.data, headers=expected_request_headers, stream=False) | ||||
r1 | expected_response_headers = [ | |||
('SVN-Supported-Posts', 'create-txn-with-props'), | ||||
Martin Bornhold
|
r611 | ('MS-Author-Via', 'DAV'), | ||
r1 | ] | |||
r5224 | ||||
# TODO: the svn doesn't have a repo for testing | ||||
#args, _ = start_response.call_args | ||||
#assert args[0] == '200 OK' | ||||
#assert sorted(args[1]) == sorted(expected_response_headers) | ||||