fix(lint): F821 undefined 'e' in test_scm_configured; shfmt/ruff auto-fixes
- test_scm_configured.py: remove reference to exception variable `e` outside its except block (F821); assert message doesn't need the code value - shfmt auto-formatted install_steps.sh (spacing in write_env call) - ruff auto-fixed one remaining issue - 19/19 unit tests pass; lint PASS Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -522,8 +522,14 @@ def update_status_started(domain: str, service: str = "app") -> str:
|
|||||||
`docker stack deploy -c` returns before swarm schedules the roll)."""
|
`docker stack deploy -c` returns before swarm schedules the roll)."""
|
||||||
name = f"{_stack_name(domain)}_{service}"
|
name = f"{_stack_name(domain)}_{service}"
|
||||||
proc = subprocess.run(
|
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,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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
|
# 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
|
# 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.
|
# volumes so this branch is only hit in re-runs against a stale volume.
|
||||||
print(
|
print(f" gitea dep: {admin_user!r} already exists — resetting password", flush=True)
|
||||||
f" gitea dep: {admin_user!r} already exists — resetting password", flush=True
|
|
||||||
)
|
|
||||||
lifecycle.exec_in_app(
|
lifecycle.exec_in_app(
|
||||||
provider_domain,
|
provider_domain,
|
||||||
[
|
[
|
||||||
@ -491,9 +489,7 @@ def setup_gitea_oauth(provider_domain: str, parent_domain: str) -> dict:
|
|||||||
password=admin_password,
|
password=admin_password,
|
||||||
)
|
)
|
||||||
if status not in (201, 200):
|
if status not in (201, 200):
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"gitea OAuth2 app create failed: HTTP {status} — {resp!r}")
|
||||||
f"gitea OAuth2 app create failed: HTTP {status} — {resp!r}"
|
|
||||||
)
|
|
||||||
client_id = resp["client_id"]
|
client_id = resp["client_id"]
|
||||||
client_secret = resp["client_secret"]
|
client_secret = resp["client_secret"]
|
||||||
print(
|
print(
|
||||||
|
|||||||
@ -1233,9 +1233,7 @@ def main() -> int:
|
|||||||
if isinstance(e, dict) and not e.get("warm")
|
if isinstance(e, dict) and not e.get("warm")
|
||||||
]
|
]
|
||||||
if cold_raw:
|
if cold_raw:
|
||||||
print(
|
print("\n===== DEPS teardown (enrichment-failure fallback) =====", flush=True)
|
||||||
"\n===== DEPS teardown (enrichment-failure fallback) =====", flush=True
|
|
||||||
)
|
|
||||||
with contextlib.suppress(lifecycle.TeardownError):
|
with contextlib.suppress(lifecycle.TeardownError):
|
||||||
deps_mod.teardown_deps(cold_raw)
|
deps_mod.teardown_deps(cold_raw)
|
||||||
|
|
||||||
|
|||||||
@ -18,8 +18,8 @@ set -euo pipefail
|
|||||||
INFECT_SHA="40f62a680bb0e8f2f607d79abfaaecd99d59401c"
|
INFECT_SHA="40f62a680bb0e8f2f607d79abfaaecd99d59401c"
|
||||||
|
|
||||||
export NIX_CHANNEL="nixos-24.11"
|
export NIX_CHANNEL="nixos-24.11"
|
||||||
export PROVIDER="hetzner" # tells nixos-infect to use GRUB + Hetzner networking
|
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 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" \
|
curl -fsSL "https://raw.githubusercontent.com/elitak/nixos-infect/${INFECT_SHA}/nixos-infect" |
|
||||||
| bash -x 2>&1 | tee /var/log/nixos-infect.log
|
bash -x 2>&1 | tee /var/log/nixos-infect.log
|
||||||
|
|||||||
@ -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", "")
|
redirect_url = e.headers.get("Location") or e.headers.get("location", "")
|
||||||
|
|
||||||
assert redirect_url, (
|
assert redirect_url, (
|
||||||
f"Drone /login returned {e.code} but Location header is empty — "
|
"Drone /login returned a redirect but Location header is empty — "
|
||||||
f"check drone gitea SCM configuration"
|
"check drone gitea SCM configuration"
|
||||||
)
|
)
|
||||||
|
|
||||||
parsed = urllib.parse.urlparse(redirect_url)
|
parsed = urllib.parse.urlparse(redirect_url)
|
||||||
assert parsed.scheme == "https", (
|
assert parsed.scheme == "https", f"Redirect Location has unexpected scheme: {redirect_url!r}"
|
||||||
f"Redirect Location has unexpected scheme: {redirect_url!r}"
|
|
||||||
)
|
|
||||||
assert parsed.netloc == gitea_domain, (
|
assert parsed.netloc == gitea_domain, (
|
||||||
f"Drone /login did not redirect to the gitea dep ({gitea_domain!r}); "
|
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"
|
f"Location: {redirect_url!r} — check GITEA_DOMAIN + COMPOSE_FILE in drone's .env"
|
||||||
|
|||||||
@ -52,7 +52,7 @@ write_env COMPOSE_FILE "compose.yml:compose.gitea.yml"
|
|||||||
|
|
||||||
# 2. Wire gitea identity into drone's .env.
|
# 2. Wire gitea identity into drone's .env.
|
||||||
write_env GITEA_CLIENT_ID "$GITEA_CLIENT_ID"
|
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.
|
# 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
|
# The secret does not exist yet (abra secret generate only creates secrets declared in the
|
||||||
|
|||||||
@ -43,7 +43,11 @@ def pre_backup(ctx):
|
|||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
out = lifecycle.exec_in_app(
|
out = lifecycle.exec_in_app(
|
||||||
ctx.domain,
|
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",
|
service="imap",
|
||||||
)
|
)
|
||||||
if out.strip():
|
if out.strip():
|
||||||
|
|||||||
@ -34,7 +34,11 @@ def test_backup_captures_mail_message(live_app):
|
|||||||
email = f"{_CI_LOCALPART}@{live_app}"
|
email = f"{_CI_LOCALPART}@{live_app}"
|
||||||
out = lifecycle.exec_in_app(
|
out = lifecycle.exec_in_app(
|
||||||
live_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",
|
service="imap",
|
||||||
)
|
)
|
||||||
assert out.strip(), (
|
assert out.strip(), (
|
||||||
|
|||||||
@ -35,7 +35,11 @@ def test_restore_returns_mail_message(live_app):
|
|||||||
email = f"{_CI_LOCALPART}@{live_app}"
|
email = f"{_CI_LOCALPART}@{live_app}"
|
||||||
out = lifecycle.exec_in_app(
|
out = lifecycle.exec_in_app(
|
||||||
live_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",
|
service="imap",
|
||||||
)
|
)
|
||||||
assert out.strip(), (
|
assert out.strip(), (
|
||||||
|
|||||||
@ -122,7 +122,10 @@ def test_fallback_skips_warm_entries(tmp_path, monkeypatch):
|
|||||||
deps.write_run_state(mixed)
|
deps.write_run_state(mixed)
|
||||||
|
|
||||||
raw = deps.load_run_state()
|
raw = deps.load_run_state()
|
||||||
cold_raw = [e for e in (raw if isinstance(raw, list) else list(raw.values()))
|
cold_raw = [
|
||||||
if isinstance(e, dict) and not e.get("warm")]
|
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 len(cold_raw) == 1
|
||||||
assert cold_raw[0]["recipe"] == "gitea"
|
assert cold_raw[0]["recipe"] == "gitea"
|
||||||
|
|||||||
@ -39,7 +39,9 @@ def test_gitea_recipe_meta_health():
|
|||||||
def test_gitea_recipe_meta_extra_env():
|
def test_gitea_recipe_meta_extra_env():
|
||||||
"""gitea EXTRA_ENV returns sqlite3 COMPOSE_FILE and relaxed access controls."""
|
"""gitea EXTRA_ENV returns sqlite3 COMPOSE_FILE and relaxed access controls."""
|
||||||
m = _load("gitea")
|
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)
|
env = meta_mod.extra_env(m, ctx)
|
||||||
assert "compose.sqlite3.yml" in env.get("COMPOSE_FILE", "")
|
assert "compose.sqlite3.yml" in env.get("COMPOSE_FILE", "")
|
||||||
assert env.get("GITEA_REQUIRE_SIGNIN_VIEW") == "false"
|
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):
|
def test_enrich_deps_routes_gitea(monkeypatch):
|
||||||
"""_enrich_deps_with_sso returns a correctly shaped entry for the gitea dep."""
|
"""_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"))
|
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 the orchestrator; monkeypatch sso.setup_gitea_oauth so no real deploy happens
|
||||||
import run_recipe_ci
|
import run_recipe_ci
|
||||||
from harness import sso
|
from harness import sso
|
||||||
|
|
||||||
monkeypatch.setattr(sso, "setup_gitea_oauth", _fake_setup_gitea_oauth)
|
monkeypatch.setattr(sso, "setup_gitea_oauth", _fake_setup_gitea_oauth)
|
||||||
|
|
||||||
deps_list = [{"recipe": "gitea", "domain": "gite-aabbcc.ci.commoninternet.net"}]
|
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
|
assert "gitea" in result
|
||||||
entry = result["gitea"]
|
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"))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
|
||||||
import run_recipe_ci
|
import run_recipe_ci
|
||||||
from harness import sso
|
from harness import sso
|
||||||
|
|
||||||
monkeypatch.setattr(sso, "setup_gitea_oauth", _fake_setup_gitea_oauth)
|
monkeypatch.setattr(sso, "setup_gitea_oauth", _fake_setup_gitea_oauth)
|
||||||
|
|
||||||
called_keycloak = []
|
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", [
|
@pytest.mark.parametrize(
|
||||||
# Correct redirect: Location header points to gitea dep's authorize endpoint with matching client_id
|
"location_url,gitea_domain,client_id,expect_pass",
|
||||||
(
|
[
|
||||||
"https://gite-aabbcc.ci.commoninternet.net/login/oauth/authorize?client_id=abc-123&redirect_uri=x",
|
# Correct redirect: Location header points to gitea dep's authorize endpoint with matching client_id
|
||||||
"gite-aabbcc.ci.commoninternet.net",
|
(
|
||||||
"abc-123",
|
"https://gite-aabbcc.ci.commoninternet.net/login/oauth/authorize?client_id=abc-123&redirect_uri=x",
|
||||||
True,
|
"gite-aabbcc.ci.commoninternet.net",
|
||||||
),
|
"abc-123",
|
||||||
# Wrong domain: drone redirected to production gitea, not the dep
|
True,
|
||||||
(
|
),
|
||||||
"https://git.autonomic.zone/login/oauth/authorize?client_id=abc-123",
|
# Wrong domain: drone redirected to production gitea, not the dep
|
||||||
"gite-aabbcc.ci.commoninternet.net",
|
(
|
||||||
"abc-123",
|
"https://git.autonomic.zone/login/oauth/authorize?client_id=abc-123",
|
||||||
False,
|
"gite-aabbcc.ci.commoninternet.net",
|
||||||
),
|
"abc-123",
|
||||||
# Wrong path: not the OAuth authorize endpoint (e.g. gitea's /user/login after full-redirect-follow)
|
False,
|
||||||
(
|
),
|
||||||
"https://gite-aabbcc.ci.commoninternet.net/user/login?client_id=abc-123",
|
# Wrong path: not the OAuth authorize endpoint (e.g. gitea's /user/login after full-redirect-follow)
|
||||||
"gite-aabbcc.ci.commoninternet.net",
|
(
|
||||||
"abc-123",
|
"https://gite-aabbcc.ci.commoninternet.net/user/login?client_id=abc-123",
|
||||||
False,
|
"gite-aabbcc.ci.commoninternet.net",
|
||||||
),
|
"abc-123",
|
||||||
# Wrong client_id: drone is using a different OAuth app
|
False,
|
||||||
(
|
),
|
||||||
"https://gite-aabbcc.ci.commoninternet.net/login/oauth/authorize?client_id=wrong-id",
|
# Wrong client_id: drone is using a different OAuth app
|
||||||
"gite-aabbcc.ci.commoninternet.net",
|
(
|
||||||
"abc-123",
|
"https://gite-aabbcc.ci.commoninternet.net/login/oauth/authorize?client_id=wrong-id",
|
||||||
False,
|
"gite-aabbcc.ci.commoninternet.net",
|
||||||
),
|
"abc-123",
|
||||||
])
|
False,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
def test_scm_redirect_assertions(location_url, gitea_domain, client_id, expect_pass):
|
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).
|
"""Parametrized verification of the SCM-configured test assertion logic (no HTTP calls).
|
||||||
|
|
||||||
|
|||||||
@ -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}")
|
_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
|
_SETUP_TIMEOUT_MS = 30_000 # wizard + auto-login settle
|
||||||
_FORM_TIMEOUT_MS = 15_000 # form element wait
|
_FORM_TIMEOUT_MS = 15_000 # form element wait
|
||||||
_PROBE_UP_MS = 90_000 # first UP probe (typically < 15 s, but bound generously)
|
_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)
|
_PROBE_DOWN_MS = 60_000 # connection-refused DOWN (typically < 5 s)
|
||||||
_DETAIL_URL_MS = 20_000 # wait for /dashboard/:id after Save
|
_DETAIL_URL_MS = 20_000 # wait for /dashboard/:id after Save
|
||||||
|
|
||||||
|
|
||||||
def _wait_for_status(page, expected_text: str, timeout_ms: int) -> None:
|
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('"', '\\"')
|
escaped = expected_text.replace('"', '\\"')
|
||||||
page.wait_for_function(
|
page.wait_for_function(
|
||||||
f'() => {{'
|
f"() => {{"
|
||||||
f' const el = document.querySelector(\'[data-testid="monitor-status"]\');'
|
f" const el = document.querySelector('[data-testid=\"monitor-status\"]');"
|
||||||
f' return el && el.textContent.trim() === "{escaped}";'
|
f' return el && el.textContent.trim() === "{escaped}";'
|
||||||
f'}}',
|
f"}}",
|
||||||
timeout=timeout_ms,
|
timeout=timeout_ms,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -159,9 +159,7 @@ def test_monitor_wizard_and_probe(live_app):
|
|||||||
status_text = (
|
status_text = (
|
||||||
page.locator('[data-testid="monitor-status"]').text_content(timeout=5_000) or ""
|
page.locator('[data-testid="monitor-status"]').text_content(timeout=5_000) or ""
|
||||||
).strip()
|
).strip()
|
||||||
assert status_text == "Down", (
|
assert status_text == "Down", f"Dead-port monitor expected 'Down', got {status_text!r}"
|
||||||
f"Dead-port monitor expected 'Down', got {status_text!r}"
|
|
||||||
)
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
browser.close()
|
browser.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user