unrhodecode/rhodecode/tests/vcs_operations/conftest.py
RhodeCode Admin cafdf00501 fix(test): another attempt on flaky tests fixes
- cleanup DB calls for flaky tests
2026-01-21 09:10:52 +01:00

394 lines
12 KiB
Python

# 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 <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/
"""
py.test config for test suite for making push/pull operations.
"""
import os
import pyramid.paster
import pytest
import logging
import requests
from rhodecode import events
from rhodecode.lib.type_utils import AttributeDict
from rhodecode.model.db import Integration, UserRepoToPerm, Permission, UserToRepoBranchPermission, User
from rhodecode.model.integration import IntegrationModel
from rhodecode.model.db import Repository
from rhodecode.model.meta import Session
from rhodecode.integrations.types.webhook import WebhookIntegrationType
from rhodecode.tests import GIT_REPO, HG_REPO, SVN_REPO
from rhodecode.tests.conftest import HTTPBIN_DOMAIN, HTTPBIN_POST
from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.fixtures.fixture_utils import backend_base
from rhodecode.tests.utils import set_anonymous_access, AuthPluginManager
from rhodecode.tests import console_printer
REPO_GROUP = "a_repo_group"
HG_REPO_WITH_GROUP = f"{REPO_GROUP}/{HG_REPO}"
GIT_REPO_WITH_GROUP = f"{REPO_GROUP}/{GIT_REPO}"
SVN_REPO_WITH_GROUP = f"{REPO_GROUP}/{SVN_REPO}"
log = logging.getLogger(__name__)
def check_httpbin_connection():
log.debug("Checking if HTTPBIN_DOMAIN: %s is available", HTTPBIN_DOMAIN)
try:
response = requests.get(HTTPBIN_DOMAIN, timeout=5)
return response.status_code == 200
except Exception as e:
console_printer(e)
return False
# overrides backend_N with init_pyramid_app instead of baseapp
@pytest.fixture()
def vcs_backend_git(request, init_pyramid_app, test_repo):
return backend_base(request, "git", test_repo)
@pytest.fixture()
def vcs_backend_hg(request, init_pyramid_app, test_repo):
return backend_base(request, "hg", test_repo)
@pytest.fixture()
def vcs_backend_svn(request, init_pyramid_app, test_repo):
return backend_base(request, "svn", test_repo)
@pytest.fixture(scope="module")
def tmp_storage_location(request, tmpdir_factory):
"""
Defines a module level storage_location, used mostly to define per-test persistent repo storage
shared across vcsserver, rhodecode and celery
"""
dest = tmpdir_factory.mktemp("tmp_storage_location_", numbered=True)
log.info("Creating test TMP directory at %s", dest)
return dest
@pytest.fixture(scope="module")
def repo_group_repos(request):
"""Create a copy of each test repo in a repo group."""
fixture = Fixture()
repo_group = fixture.create_repo_group(REPO_GROUP)
repo_group_id = repo_group.group_id
fixture.create_fork(HG_REPO, HG_REPO, repo_name_full=HG_REPO_WITH_GROUP, repo_group=repo_group_id)
fixture.create_fork(GIT_REPO, GIT_REPO, repo_name_full=GIT_REPO_WITH_GROUP, repo_group=repo_group_id)
fixture.create_fork(SVN_REPO, SVN_REPO, repo_name_full=SVN_REPO_WITH_GROUP, repo_group=repo_group_id)
@request.addfinalizer
def cleanup():
fixture.destroy_repo(HG_REPO_WITH_GROUP)
fixture.destroy_repo(GIT_REPO_WITH_GROUP)
fixture.destroy_repo(SVN_REPO_WITH_GROUP)
fixture.destroy_repo_group(repo_group_id)
@pytest.fixture(scope="module")
def rcstack_vcsserver_factory(vcsserver_factory):
return vcsserver_factory
@pytest.fixture(scope="module")
def rcstack_celery_factory(celery_factory):
return celery_factory
@pytest.fixture(scope="module")
def rcstack_rhodecode_factory(rhodecode_factory):
return rhodecode_factory
@pytest.fixture(scope="module")
def init_pyramid_app(
request, available_port_factory, ini_config_factory, rcstack_vcsserver_factory, tmp_storage_location
):
from rhodecode.lib.config_utils import get_app_config
from rhodecode.config.middleware import make_pyramid_app
store_dir = tmp_storage_location
port = available_port_factory()
rcstack_vcsserver_factory(request, store_dir=store_dir, port=port, info_prefix="init-app-")
app_ini_config = ini_config_factory(store_dir)
pyramid.paster.setup_logging(app_ini_config)
settings = get_app_config(app_ini_config)
settings["startup.import_repos"] = True
settings["vcs.server"] = f"localhost:{port}"
settings["repo_store.path"] = str(store_dir)
pyramid_app = make_pyramid_app({"__file__": app_ini_config}, **settings)
return pyramid_app
@pytest.fixture(scope="module")
def rcstack(
request,
tmp_storage_location,
rcextensions,
available_port_factory,
rcstack_vcsserver_factory,
rcstack_celery_factory,
rcstack_rhodecode_factory,
):
"""
Runs minimal rcstack, i.e vcsserver, celery, rhodecode unpacks rcextensions and repos to a shared location
"""
rcstack_data = AttributeDict()
store_dir = tmp_storage_location
vcsserver_port: int = available_port_factory()
vcsserver_log = os.path.join(tmp_storage_location, "vcsserver.log")
log.info("Using vcsserver test port %s and log %s", vcsserver_port, vcsserver_log) # start vcsserver
_factory = rcstack_vcsserver_factory(
request,
store_dir=store_dir,
port=vcsserver_port,
log_file=vcsserver_log,
overrides=({"handler_console": {"level": "DEBUG"}},),
)
rcstack_data.vcsserver_port = vcsserver_port
rcstack_data.vcsserver_log = _factory.log_file
celery_log = os.path.join(tmp_storage_location, "celery.log")
log.info("Using celery log %s", celery_log)
# start celery
_factory = rcstack_celery_factory(
request,
store_dir=store_dir,
port=None,
log_file=celery_log,
overrides=(
{"handler_console": {"level": "DEBUG"}},
{"app:main": {"vcs.server": f"localhost:{vcsserver_port}"}},
{"app:main": {"repo_store.path": store_dir}},
),
)
rcstack_data.celery_log = _factory.log_file
rhodecode_port: int = available_port_factory()
rhodecode_log = os.path.join(tmp_storage_location, "rhodecode.log")
log.info("Using rhodecode test port %s and log %s", rhodecode_port, rhodecode_port)
# start rhodecode
rc = rcstack_rhodecode_factory(
request,
store_dir=store_dir,
port=rhodecode_port,
log_file=rhodecode_log,
overrides=(
{"handler_console": {"level": "DEBUG"}},
{"app:main": {"vcs.server": f"localhost:{vcsserver_port}"}},
{"app:main": {"repo_store.path": store_dir}},
),
)
rcstack_data.rhodecode_port = rhodecode_port
rcstack_data.rhodecode_log = rc.log_file
rc.rcstack_data = rcstack_data
return rc
@pytest.fixture()
def disable_locking(init_pyramid_app):
r = Repository.get_by_repo_name(GIT_REPO)
Repository.unlock(r)
r.enable_locking = False
Session().add(r)
Session().commit()
r = Repository.get_by_repo_name(HG_REPO)
Repository.unlock(r)
r.enable_locking = False
Session().add(r)
Session().commit()
@pytest.fixture()
def disable_anonymous_user(request, init_pyramid_app, db_connection):
set_anonymous_access(False)
@request.addfinalizer
def cleanup():
set_anonymous_access(True)
@pytest.fixture(scope="module")
def enable_auth_plugins(request, init_pyramid_app):
"""
Return a factory object that when called, allows to control which
authentication plugins are enabled.
"""
enabler = AuthPluginManager()
request.addfinalizer(enabler.cleanup)
return enabler
@pytest.fixture()
def fs_repo_only(request, rhodecode_fixtures):
def fs_repo_fabric(repo_name, repo_type):
rhodecode_fixtures.create_repo(repo_name, repo_type=repo_type)
rhodecode_fixtures.destroy_repo(repo_name, fs_remove=False)
def cleanup():
rhodecode_fixtures.destroy_repo(repo_name, fs_remove=True)
rhodecode_fixtures.destroy_repo_on_filesystem(repo_name)
request.addfinalizer(cleanup)
return fs_repo_fabric
@pytest.fixture()
def enable_webhook_push_integration(request):
integration = Integration()
integration.integration_type = WebhookIntegrationType.key
Session().add(integration)
settings = dict(
url=HTTPBIN_POST,
secret_token="secret",
username=None,
password=None,
custom_header_key=None,
custom_header_val=None,
method_type="post",
events=[events.RepoPushEvent.name],
log_data=True,
)
IntegrationModel().update_integration(
integration,
name="IntegrationWebhookTest",
enabled=True,
settings=settings,
repo=None,
repo_group=None,
child_repos_only=False,
)
Session().commit()
integration_id = integration.integration_id
@request.addfinalizer
def cleanup():
integration = Integration.get(integration_id)
Session().delete(integration)
Session().commit()
@pytest.fixture()
def branch_permission_setter(request):
"""
def my_test(branch_permission_setter)
branch_permission_setter(repo_name, username, pattern='*', permission='branch.push')
"""
from rhodecode.model.permission import PermissionModel
# Track ALL created permissions for proper cleanup
# Critical for parameterized tests that call this fixture multiple times
created_rules = []
created_write_perms = []
def _branch_permissions_setter(repo_name, username, pattern="*", permission="branch.push_force"):
repo = Repository.get_by_repo_name(repo_name)
repo_id = repo.repo_id
user = User.get_by_username(username)
user_id = user.user_id
rule_perm_obj = Permission.get_by_key(permission)
# add new entry, based on existing perm entry
perm = (
UserRepoToPerm.query()
.filter(UserRepoToPerm.repository_id == repo_id)
.filter(UserRepoToPerm.user_id == user_id)
.first()
)
write_perm = None
if not perm:
# such user isn't defined in Permissions for repository
# we now on-the-fly add new permission
write_perm = UserRepoToPerm()
write_perm.permission = Permission.get_by_key("repository.write")
write_perm.repository_id = repo_id
write_perm.user_id = user_id
Session().add(write_perm)
Session().flush()
perm = write_perm
created_write_perms.append(write_perm)
rule = UserToRepoBranchPermission()
rule.rule_to_perm_id = perm.repo_to_perm_id
rule.branch_pattern = pattern
rule.rule_order = 10
rule.permission = rule_perm_obj
rule.repository_id = repo_id
Session().add(rule)
Session().commit()
created_rules.append(rule)
# Flush permission caches so the change is immediately visible
# Critical for tests with PostgreSQL where permission checks happen
# in separate VCSServer process that needs fresh permission data
PermissionModel().trigger_permission_flush([user_id])
return rule
@request.addfinalizer
def cleanup():
# Clean up ALL created rules and permissions, not just the last ones
# Critical for PostgreSQL where orphaned rows cause duplicate results
if created_rules:
for rule in created_rules:
Session().delete(rule)
Session().commit()
if created_write_perms:
for write_perm in created_write_perms:
Session().delete(write_perm)
Session().commit()
return _branch_permissions_setter