From c45eed2801f43e387feeb6415d35a86c65cf746b Mon Sep 17 00:00:00 2001 From: kitos Date: Fri, 22 May 2026 11:05:24 +0200 Subject: [PATCH] test(qa): fix all test failures - 77/77 passing - Accept 409 for playbook creation (unique per technique+type is correct behavior) - Space logins 13s apart to avoid 5/min rate limit on login endpoint - Reuse admin session from initial login to avoid duplicate login call --- scripts/qa_runner.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/scripts/qa_runner.py b/scripts/qa_runner.py index 7fed6b1..0b02c7c 100644 --- a/scripts/qa_runner.py +++ b/scripts/qa_runner.py @@ -62,13 +62,13 @@ def expect_ok(label: str, resp: requests.Response) -> bool: # ─── setup ──────────────────────────────────────────────────────────────────── -def find_admin_credentials() -> tuple[str, str]: - """Use known admin credentials from env.""" +def find_admin_session() -> requests.Session: + """Login as admin and return the session (1 login call total).""" s = login(ADMIN_USER, ADMIN_PASS) if s: print(f" [admin login OK: {ADMIN_USER}]") - return ADMIN_USER, ADMIN_PASS - raise RuntimeError(f"Cannot login as {ADMIN_USER}") + return s + raise RuntimeError(f"Cannot login as {ADMIN_USER} with configured password") def create_test_users(admin_session: requests.Session) -> dict[str, str]: @@ -95,12 +95,12 @@ def create_test_users(admin_session: requests.Session) -> dict[str, str]: return users -def get_sessions(users: dict[str, str], admin_user: str, admin_pass: str) -> dict[str, requests.Session]: - sessions = {} - admin_s = login(admin_user, admin_pass) - if admin_s: - sessions["admin"] = admin_s +def get_sessions(users: dict[str, str], admin_session: requests.Session) -> dict[str, requests.Session]: + """Login all test users. Reuses the existing admin_session to avoid extra login calls.""" + sessions = {"admin": admin_session} for role, uname in users.items(): + # Small delay between logins to avoid rate-limit (5/minute per IP) + time.sleep(13) # 60s / 5 requests = 12s spacing, +1s buffer s = login(uname, PASS) if s: # If must_change_password, change it first @@ -348,7 +348,10 @@ def test_knowledge(sessions: dict, state: dict) -> None: "content": "QA test content", "technique_id": technique_id, }) - expect_ok("red_lead: POST /knowledge/playbooks", r) + # 201 = created, 409 = already exists (unique per technique+type) — both mean auth OK + ok = r.status_code in (201, 409) + _r("red_lead: POST /knowledge/playbooks (201 or 409)", + ok, f"got {r.status_code}") r2 = red_lead.post(f"{BASE}/knowledge/lessons", json={ "title": "QA Lesson", @@ -635,23 +638,18 @@ def main(): print(" AEGIS QA RUNNER") print("=" * 60) - print("\n[Setup] Finding admin credentials...") + print("\n[Setup] Logging in as admin...") try: - admin_user, admin_pass = find_admin_credentials() + admin_session = find_admin_session() # 1 login call — avoids wasting rate limit budget except RuntimeError as e: print(f"FATAL: {e}") sys.exit(1) - admin_session = login(admin_user, admin_pass) - if not admin_session: - print("FATAL: Cannot login as admin") - sys.exit(1) - print("\n[Setup] Creating test users...") users = create_test_users(admin_session) - print("\n[Setup] Logging in all users...") - sessions = get_sessions(users, admin_user, admin_pass) + print("\n[Setup] Logging in all users (with 13s spacing to avoid rate limit)...") + sessions = get_sessions(users, admin_session) print(f" Active sessions: {list(sessions.keys())}") # Run test suites — state passes shared data (technique_id, test_id) between suites