From 093fc9e79f96a35cadda075439e3cced9f30607a Mon Sep 17 00:00:00 2001 From: erseco Date: Wed, 8 Jul 2026 00:58:56 +0100 Subject: [PATCH] Harden integration tests against the post-boot login race The Docker-backed integration suite intermittently failed at fixture setup with 'Moodle login failed: invalid username or password', on whichever test a pytest-xdist worker ran first. This is a transient race: right after a freshly-started Moodle container begins serving its login page, the very first login attempt sometimes fails while Moodle finishes initializing caches/sessions; a moment later the same credentials work (which is why reruns always went green). Add a session-scoped, autouse warmup fixture that performs a retrying login (up to 6 attempts, 5s apart) once per session -- i.e. once per xdist worker -- before any integration test runs. A successful warmup means the Moodle server is warm, so every subsequent per-test (new-session) login is reliable. It is a no-op unless --integration is passed, so unit runs are unaffected. No library/CLI change; this only touches the integration test harness. --- tests/conftest.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 0bf0088..4115be3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ import logging import os import tempfile +import time import uuid from contextlib import contextmanager from dataclasses import dataclass @@ -165,6 +166,47 @@ def pytest_collection_modifyitems(config, items): item.add_marker(skip_integration) +#: Retry budget for the per-session login warmup (attempts x delay seconds). +_LOGIN_WARMUP_ATTEMPTS = 6 +_LOGIN_WARMUP_DELAY_SECONDS = 5 + + +@pytest.fixture(scope="session", autouse=True) +def _warm_up_moodle_login(request): + """Warm up Moodle login once per session to absorb the post-boot race. + + Immediately after a freshly-started Moodle container begins serving its + login page, the first login attempt intermittently fails with + "invalid username or password" while Moodle finishes initializing + (caches, sessions). This surfaced as flaky ``LoginError`` failures in + the integration suite, on whichever test a ``pytest-xdist`` worker + happened to run first. Perform a retrying warmup login once per session + (i.e. once per xdist worker) so the per-test logins that follow are + reliable; a successful warmup means the Moodle server is warm for every + subsequent (new-session) login. + + No-op unless integration tests are actually running. + """ + if not request.config.getoption("--integration"): + return + target = request.config.moodle_target + from py_moodle.auth import LoginError, login + + last_error = None + for attempt in range(1, _LOGIN_WARMUP_ATTEMPTS + 1): + try: + login(url=target.url, username=target.username, password=target.password) + return + except LoginError as error: + last_error = error + if attempt < _LOGIN_WARMUP_ATTEMPTS: + time.sleep(_LOGIN_WARMUP_DELAY_SECONDS) + pytest.exit( + f"Moodle at '{target.name}' never accepted login after " + f"{_LOGIN_WARMUP_ATTEMPTS} warmup attempts: {last_error}" + ) + + @pytest.fixture(scope="function") def moodle(request): """Provides an authenticated Moodle session for the target environment."""