# Copyright (C) 2010-2024 RhodeCode GmbH # # 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 . # # 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/ import logging import mock import msgpack import pytest from rhodecode.lib import vcs from rhodecode.lib.vcs import client_http, exceptions def is_new_connection(logger, level, message): return logger == "requests.packages.urllib3.connectionpool" and message.startswith("Starting new HTTP") @pytest.fixture() def stub_session(): """ Stub of `requests.Session()`. """ session = mock.Mock() post = session.post() post.content = msgpack.packb({}) post.status_code = 200 session.reset_mock() return session @pytest.fixture() def stub_fail_session(): """ Stub of `requests.Session()`. """ session = mock.Mock() post = session.post() post.content = msgpack.packb({"error": "500"}) post.status_code = 500 session.reset_mock() return session @pytest.fixture() def stub_session_factory(stub_session): """ Stub of `rhodecode.lib.vcs.client_http.ThreadlocalSessionFactory`. """ session_factory = mock.Mock() session_factory.return_value = stub_session return session_factory @pytest.fixture() def stub_session_failing_factory(stub_fail_session): """ Stub of `rhodecode.lib.vcs.client_http.ThreadlocalSessionFactory`. """ session_factory = mock.Mock() session_factory.return_value = stub_fail_session return session_factory def test_uses_persistent_http_connections(caplog, vcsbackend_hg): repo = vcsbackend_hg.repo remote_call = repo._remote.branches with caplog.at_level(logging.INFO): for x in range(5): remote_call(normal=True, closed=False) new_connections = [r for r in caplog.record_tuples if is_new_connection(*r)] assert len(new_connections) <= 1 def test_repo_maker_uses_session_for_classmethods(stub_session_factory): repo_maker = client_http.RemoteVCSMaker("server_and_port", "endpoint", "test_dummy_scm", stub_session_factory) repo_maker.example_call() stub_session_factory().post.assert_called_with("http://server_and_port/endpoint", data=mock.ANY) def test_repo_maker_uses_session_for_instance_methods(stub_session_factory, config): repo_maker = client_http.RemoteVCSMaker("server_and_port", "endpoint", "test_dummy_scm", stub_session_factory) repo = repo_maker("stub_path", "stub_repo_id", config) repo.example_call() stub_session_factory().post.assert_called_with("http://server_and_port/endpoint", data=mock.ANY) @mock.patch("rhodecode.lib.vcs.client_http.ThreadlocalSessionFactory") @mock.patch("rhodecode.lib.vcs.connection") def test_connect_passes_in_the_same_session(connection, session_factory_class, stub_session): session_factory = session_factory_class.return_value session_factory.return_value = stub_session vcs.connect_http("server_and_port") def test_repo_maker_uses_session_that_throws_error(stub_session_failing_factory, config): repo_maker = client_http.RemoteVCSMaker( "server_and_port", "endpoint", "test_dummy_scm", stub_session_failing_factory ) repo = repo_maker("stub_path", "stub_repo_id", config) with pytest.raises(exceptions.HttpVCSCommunicationError): repo.example_call()