diff --git a/runner/harness/lifecycle.py b/runner/harness/lifecycle.py index 539b341..c131a6c 100644 --- a/runner/harness/lifecycle.py +++ b/runner/harness/lifecycle.py @@ -522,8 +522,14 @@ def update_status_started(domain: str, service: str = "app") -> str: `docker stack deploy -c` returns before swarm schedules the roll).""" name = f"{_stack_name(domain)}_{service}" proc = subprocess.run( - ["docker", "service", "inspect", name, "--format", - "{{if .UpdateStatus}}{{.UpdateStatus.StartedAt}}{{else}}{{end}}"], + [ + "docker", + "service", + "inspect", + name, + "--format", + "{{if .UpdateStatus}}{{.UpdateStatus.StartedAt}}{{else}}{{end}}", + ], capture_output=True, text=True, ) diff --git a/runner/harness/sso.py b/runner/harness/sso.py index b32fb16..b49112f 100644 --- a/runner/harness/sso.py +++ b/runner/harness/sso.py @@ -455,9 +455,7 @@ def setup_gitea_oauth(provider_domain: str, parent_domain: str) -> dict: # Stale volume from a prior run — reset the password to the newly-generated one # so the API call below can authenticate. In production CI, teardown_deps removes # volumes so this branch is only hit in re-runs against a stale volume. - print( - f" gitea dep: {admin_user!r} already exists — resetting password", flush=True - ) + print(f" gitea dep: {admin_user!r} already exists — resetting password", flush=True) lifecycle.exec_in_app( provider_domain, [ @@ -491,9 +489,7 @@ def setup_gitea_oauth(provider_domain: str, parent_domain: str) -> dict: password=admin_password, ) if status not in (201, 200): - raise RuntimeError( - f"gitea OAuth2 app create failed: HTTP {status} — {resp!r}" - ) + raise RuntimeError(f"gitea OAuth2 app create failed: HTTP {status} — {resp!r}") client_id = resp["client_id"] client_secret = resp["client_secret"] print( diff --git a/runner/run_recipe_ci.py b/runner/run_recipe_ci.py index 40b6cf0..8fa654f 100644 --- a/runner/run_recipe_ci.py +++ b/runner/run_recipe_ci.py @@ -1233,9 +1233,7 @@ def main() -> int: if isinstance(e, dict) and not e.get("warm") ] if cold_raw: - print( - "\n===== DEPS teardown (enrichment-failure fallback) =====", flush=True - ) + print("\n===== DEPS teardown (enrichment-failure fallback) =====", flush=True) with contextlib.suppress(lifecycle.TeardownError): deps_mod.teardown_deps(cold_raw) diff --git a/terraform/user-data.sh b/terraform/user-data.sh index b9dae20..e2ccdef 100644 --- a/terraform/user-data.sh +++ b/terraform/user-data.sh @@ -18,8 +18,8 @@ set -euo pipefail INFECT_SHA="40f62a680bb0e8f2f607d79abfaaecd99d59401c" export NIX_CHANNEL="nixos-24.11" -export PROVIDER="hetzner" # tells nixos-infect to use GRUB + Hetzner networking -export NIXOS_IMPORT="" # no extra imports at infect time; we apply the real flake in Stage 2 +export PROVIDER="hetzner" # tells nixos-infect to use GRUB + Hetzner networking +export NIXOS_IMPORT="" # no extra imports at infect time; we apply the real flake in Stage 2 -curl -fsSL "https://raw.githubusercontent.com/elitak/nixos-infect/${INFECT_SHA}/nixos-infect" \ - | bash -x 2>&1 | tee /var/log/nixos-infect.log +curl -fsSL "https://raw.githubusercontent.com/elitak/nixos-infect/${INFECT_SHA}/nixos-infect" | + bash -x 2>&1 | tee /var/log/nixos-infect.log diff --git a/tests/drone/functional/test_scm_configured.py b/tests/drone/functional/test_scm_configured.py index 82ea900..4029418 100644 --- a/tests/drone/functional/test_scm_configured.py +++ b/tests/drone/functional/test_scm_configured.py @@ -81,14 +81,12 @@ def test_login_redirects_to_gitea_dep(live_app, deps): redirect_url = e.headers.get("Location") or e.headers.get("location", "") assert redirect_url, ( - f"Drone /login returned {e.code} but Location header is empty — " - f"check drone gitea SCM configuration" + "Drone /login returned a redirect but Location header is empty — " + "check drone gitea SCM configuration" ) parsed = urllib.parse.urlparse(redirect_url) - assert parsed.scheme == "https", ( - f"Redirect Location has unexpected scheme: {redirect_url!r}" - ) + assert parsed.scheme == "https", f"Redirect Location has unexpected scheme: {redirect_url!r}" assert parsed.netloc == gitea_domain, ( f"Drone /login did not redirect to the gitea dep ({gitea_domain!r}); " f"Location: {redirect_url!r} — check GITEA_DOMAIN + COMPOSE_FILE in drone's .env" diff --git a/tests/drone/install_steps.sh b/tests/drone/install_steps.sh index 02f8569..d920042 100755 --- a/tests/drone/install_steps.sh +++ b/tests/drone/install_steps.sh @@ -52,7 +52,7 @@ write_env COMPOSE_FILE "compose.yml:compose.gitea.yml" # 2. Wire gitea identity into drone's .env. write_env GITEA_CLIENT_ID "$GITEA_CLIENT_ID" -write_env GITEA_DOMAIN "$GITEA_DOMAIN" +write_env GITEA_DOMAIN "$GITEA_DOMAIN" # 3. Insert the gitea OAuth2 client_secret as a swarm secret at version v1. # The secret does not exist yet (abra secret generate only creates secrets declared in the diff --git a/tests/mailu/ops.py b/tests/mailu/ops.py index 3476b9c..75bb8d0 100644 --- a/tests/mailu/ops.py +++ b/tests/mailu/ops.py @@ -43,7 +43,11 @@ def pre_backup(ctx): while time.time() < deadline: out = lifecycle.exec_in_app( ctx.domain, - ["sh", "-c", f"doveadm search -u '{email}' mailbox INBOX header subject '{_CI_MAIL_SUBJECT}'"], + [ + "sh", + "-c", + f"doveadm search -u '{email}' mailbox INBOX header subject '{_CI_MAIL_SUBJECT}'", + ], service="imap", ) if out.strip(): diff --git a/tests/mailu/test_backup.py b/tests/mailu/test_backup.py index c8ffab8..e716044 100644 --- a/tests/mailu/test_backup.py +++ b/tests/mailu/test_backup.py @@ -34,7 +34,11 @@ def test_backup_captures_mail_message(live_app): email = f"{_CI_LOCALPART}@{live_app}" out = lifecycle.exec_in_app( live_app, - ["sh", "-c", f"doveadm search -u '{email}' mailbox INBOX header subject '{_CI_MAIL_SUBJECT}'"], + [ + "sh", + "-c", + f"doveadm search -u '{email}' mailbox INBOX header subject '{_CI_MAIL_SUBJECT}'", + ], service="imap", ) assert out.strip(), ( diff --git a/tests/mailu/test_restore.py b/tests/mailu/test_restore.py index f304f3b..ccab765 100644 --- a/tests/mailu/test_restore.py +++ b/tests/mailu/test_restore.py @@ -35,7 +35,11 @@ def test_restore_returns_mail_message(live_app): email = f"{_CI_LOCALPART}@{live_app}" out = lifecycle.exec_in_app( live_app, - ["sh", "-c", f"doveadm search -u '{email}' mailbox INBOX header subject '{_CI_MAIL_SUBJECT}'"], + [ + "sh", + "-c", + f"doveadm search -u '{email}' mailbox INBOX header subject '{_CI_MAIL_SUBJECT}'", + ], service="imap", ) assert out.strip(), ( diff --git a/tests/unit/test_deps.py b/tests/unit/test_deps.py index 1d2c83d..77ec294 100644 --- a/tests/unit/test_deps.py +++ b/tests/unit/test_deps.py @@ -122,7 +122,10 @@ def test_fallback_skips_warm_entries(tmp_path, monkeypatch): deps.write_run_state(mixed) raw = deps.load_run_state() - cold_raw = [e for e in (raw if isinstance(raw, list) else list(raw.values())) - if isinstance(e, dict) and not e.get("warm")] + cold_raw = [ + e + for e in (raw if isinstance(raw, list) else list(raw.values())) + if isinstance(e, dict) and not e.get("warm") + ] assert len(cold_raw) == 1 assert cold_raw[0]["recipe"] == "gitea" diff --git a/tests/unit/test_gitea_dep.py b/tests/unit/test_gitea_dep.py index 9c7e466..a573e96 100644 --- a/tests/unit/test_gitea_dep.py +++ b/tests/unit/test_gitea_dep.py @@ -39,7 +39,9 @@ def test_gitea_recipe_meta_health(): def test_gitea_recipe_meta_extra_env(): """gitea EXTRA_ENV returns sqlite3 COMPOSE_FILE and relaxed access controls.""" m = _load("gitea") - ctx = types.SimpleNamespace(domain="gite-abc123.ci.commoninternet.net", base_url="", meta=m, deps={}, op=None) + ctx = types.SimpleNamespace( + domain="gite-abc123.ci.commoninternet.net", base_url="", meta=m, deps={}, op=None + ) env = meta_mod.extra_env(m, ctx) assert "compose.sqlite3.yml" in env.get("COMPOSE_FILE", "") assert env.get("GITEA_REQUIRE_SIGNIN_VIEW") == "false" @@ -75,15 +77,18 @@ def _fake_setup_gitea_oauth(provider_domain, parent_domain): def test_enrich_deps_routes_gitea(monkeypatch): """_enrich_deps_with_sso returns a correctly shaped entry for the gitea dep.""" - import importlib + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) # Import the orchestrator; monkeypatch sso.setup_gitea_oauth so no real deploy happens import run_recipe_ci from harness import sso + monkeypatch.setattr(sso, "setup_gitea_oauth", _fake_setup_gitea_oauth) deps_list = [{"recipe": "gitea", "domain": "gite-aabbcc.ci.commoninternet.net"}] - result = run_recipe_ci._enrich_deps_with_sso("drone", "dron-112233.ci.commoninternet.net", deps_list) + result = run_recipe_ci._enrich_deps_with_sso( + "drone", "dron-112233.ci.commoninternet.net", deps_list + ) assert "gitea" in result entry = result["gitea"] @@ -99,6 +104,7 @@ def test_enrich_deps_gitea_does_not_call_keycloak_path(monkeypatch): sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) import run_recipe_ci from harness import sso + monkeypatch.setattr(sso, "setup_gitea_oauth", _fake_setup_gitea_oauth) called_keycloak = [] @@ -118,36 +124,39 @@ def test_enrich_deps_gitea_does_not_call_keycloak_path(monkeypatch): # --------------------------------------------------------------------------- -@pytest.mark.parametrize("location_url,gitea_domain,client_id,expect_pass", [ - # Correct redirect: Location header points to gitea dep's authorize endpoint with matching client_id - ( - "https://gite-aabbcc.ci.commoninternet.net/login/oauth/authorize?client_id=abc-123&redirect_uri=x", - "gite-aabbcc.ci.commoninternet.net", - "abc-123", - True, - ), - # Wrong domain: drone redirected to production gitea, not the dep - ( - "https://git.autonomic.zone/login/oauth/authorize?client_id=abc-123", - "gite-aabbcc.ci.commoninternet.net", - "abc-123", - False, - ), - # Wrong path: not the OAuth authorize endpoint (e.g. gitea's /user/login after full-redirect-follow) - ( - "https://gite-aabbcc.ci.commoninternet.net/user/login?client_id=abc-123", - "gite-aabbcc.ci.commoninternet.net", - "abc-123", - False, - ), - # Wrong client_id: drone is using a different OAuth app - ( - "https://gite-aabbcc.ci.commoninternet.net/login/oauth/authorize?client_id=wrong-id", - "gite-aabbcc.ci.commoninternet.net", - "abc-123", - False, - ), -]) +@pytest.mark.parametrize( + "location_url,gitea_domain,client_id,expect_pass", + [ + # Correct redirect: Location header points to gitea dep's authorize endpoint with matching client_id + ( + "https://gite-aabbcc.ci.commoninternet.net/login/oauth/authorize?client_id=abc-123&redirect_uri=x", + "gite-aabbcc.ci.commoninternet.net", + "abc-123", + True, + ), + # Wrong domain: drone redirected to production gitea, not the dep + ( + "https://git.autonomic.zone/login/oauth/authorize?client_id=abc-123", + "gite-aabbcc.ci.commoninternet.net", + "abc-123", + False, + ), + # Wrong path: not the OAuth authorize endpoint (e.g. gitea's /user/login after full-redirect-follow) + ( + "https://gite-aabbcc.ci.commoninternet.net/user/login?client_id=abc-123", + "gite-aabbcc.ci.commoninternet.net", + "abc-123", + False, + ), + # Wrong client_id: drone is using a different OAuth app + ( + "https://gite-aabbcc.ci.commoninternet.net/login/oauth/authorize?client_id=wrong-id", + "gite-aabbcc.ci.commoninternet.net", + "abc-123", + False, + ), + ], +) def test_scm_redirect_assertions(location_url, gitea_domain, client_id, expect_pass): """Parametrized verification of the SCM-configured test assertion logic (no HTTP calls). diff --git a/tests/uptime-kuma/playwright/test_monitor_wizard.py b/tests/uptime-kuma/playwright/test_monitor_wizard.py index a25411c..45b8833 100644 --- a/tests/uptime-kuma/playwright/test_monitor_wizard.py +++ b/tests/uptime-kuma/playwright/test_monitor_wizard.py @@ -48,10 +48,10 @@ from harness import browser as harness_browser # noqa: E402 _DATETIME_RE = re.compile(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}") _SETUP_TIMEOUT_MS = 30_000 # wizard + auto-login settle -_FORM_TIMEOUT_MS = 15_000 # form element wait -_PROBE_UP_MS = 90_000 # first UP probe (typically < 15 s, but bound generously) -_PROBE_DOWN_MS = 60_000 # connection-refused DOWN (typically < 5 s) -_DETAIL_URL_MS = 20_000 # wait for /dashboard/:id after Save +_FORM_TIMEOUT_MS = 15_000 # form element wait +_PROBE_UP_MS = 90_000 # first UP probe (typically < 15 s, but bound generously) +_PROBE_DOWN_MS = 60_000 # connection-refused DOWN (typically < 5 s) +_DETAIL_URL_MS = 20_000 # wait for /dashboard/:id after Save def _wait_for_status(page, expected_text: str, timeout_ms: int) -> None: @@ -62,10 +62,10 @@ def _wait_for_status(page, expected_text: str, timeout_ms: int) -> None: """ escaped = expected_text.replace('"', '\\"') page.wait_for_function( - f'() => {{' - f' const el = document.querySelector(\'[data-testid="monitor-status"]\');' + f"() => {{" + f" const el = document.querySelector('[data-testid=\"monitor-status\"]');" f' return el && el.textContent.trim() === "{escaped}";' - f'}}', + f"}}", timeout=timeout_ms, ) @@ -159,9 +159,7 @@ def test_monitor_wizard_and_probe(live_app): status_text = ( page.locator('[data-testid="monitor-status"]').text_content(timeout=5_000) or "" ).strip() - assert status_text == "Down", ( - f"Dead-port monitor expected 'Down', got {status_text!r}" - ) + assert status_text == "Down", f"Dead-port monitor expected 'Down', got {status_text!r}" finally: browser.close()