""" Task 1 冒烟测试脚本 — httpx(幂等版,可重复跑) 确保后端已启动: uvicorn app.main:app --reload """ import httpx import random BASE = "http://localhost:8000" HEADERS = {"Content-Type": "application/json"} def login(username: str, password: str) -> str: r = httpx.post(f"{BASE}/api/auth/login", json={"username": username, "password": password}) r.raise_for_status() return r.cookies.get("milsci_session", "") def step(name: str, expected_status: int, method: str, url: str, **kwargs): cookies = kwargs.pop("cookies", {}) if method == "GET": r = httpx.get(url, cookies=cookies, **kwargs) elif method == "POST": r = httpx.post(url, cookies=cookies, **kwargs) elif method == "PATCH": r = httpx.patch(url, cookies=cookies, **kwargs) elif method == "DELETE": r = httpx.delete(url, cookies=cookies, **kwargs) else: raise ValueError(method) status_ok = r.status_code == expected_status mark = "[OK]" if status_ok else f"[FAIL expected {expected_status} got {r.status_code}]" print(f"{mark} [{method}] {name}") if not status_ok: print(f" Response: {r.text[:300]}") return r, status_ok def main(): print("=== Login ===") session_cookie = login("simonkoson", "liutong65") if not session_cookie: print("WARNING: session cookie empty, trying anyway") session_cookie = "" cookies = {"milsci_session": session_cookie} # 使用随机后缀避免与上次数据冲突 suffix = random.randint(1000, 9999) print("\n=== Step 1: Create zebian account ===") r, ok = step( "POST /api/users create zebian", 201, "POST", f"{BASE}/api/users", json={ "username": f"test_zebian_{suffix}", "display_name": f"Test Zebra {suffix}", "password": "Test1234", "role": "zebian", }, cookies=cookies, ) zebian_id = r.json().get("id") if ok else None if ok: print(f" zebian created, ID={zebian_id}") print("\n=== Step 2: Create 2026 yearly target (idempotent) ===") # 先尝试删已存在的(方便重跑) r_list = httpx.get(f"{BASE}/api/yearly_targets", cookies=cookies) if r_list.status_code == 200: for t in r_list.json(): if t.get("year") == 2026: del_id = t["id"] httpx.delete(f"{BASE}/api/yearly_targets/{del_id}", cookies=cookies) print(f" cleaned up existing 2026 target ID={del_id}") # 再建新的 r, ok = step( "POST /api/yearly_targets create 2026", 201, "POST", f"{BASE}/api/yearly_targets", json={"year": 2026, "base_target": 0.65, "stretch_target": 0.90}, cookies=cookies, ) target_2026_id = r.json().get("id") if ok else None if ok: print(f" target created, ID={target_2026_id}") print("\n=== Step 3: Create 2026 again (should be 409) ===") step( "POST /api/yearly_targets duplicate 2026", 409, "POST", f"{BASE}/api/yearly_targets", json={"year": 2026, "base_target": 0.70, "stretch_target": 0.95}, cookies=cookies, ) print("\n=== Step 4: Create episode with editor_id=null (idempotent) ===") # 先清理 episode_number r_list = httpx.get(f"{BASE}/api/episodes?limit=100", cookies=cookies) if r_list.status_code == 200: for ep in r_list.json(): if ep.get("episode_number") == 9999: httpx.delete(f"{BASE}/api/episodes/{ep['id']}", cookies=cookies) print(f" cleaned up existing episode 9999 ID={ep['id']}") r, ok = step( "POST /api/episodes editor_id=null", 201, "POST", f"{BASE}/api/episodes", json={ "episode_number": 9999, "program_name": "Test Episode Smoke", "air_date": "2026-05-15", "editor_id": None, "editor_name_snapshot": "Test Director Zhang", "audience_share": 0.72, }, cookies=cookies, ) episode_9999_id = r.json().get("id") if ok else None if ok: print(f" episode created, ID={episode_9999_id}") print("\n=== Step 5: GET /api/episodes list ===") step( "GET /api/episodes list", 200, "GET", f"{BASE}/api/episodes?limit=10", cookies=cookies, ) print("\n=== Step 6: PATCH yearly_target try to change year ===") if target_2026_id: r, ok = step( "PATCH try to change year field", 200, "PATCH", f"{BASE}/api/yearly_targets/{target_2026_id}", json={"year": 2027, "base_target": 0.65, "stretch_target": 0.90}, cookies=cookies, ) if ok: resp_year = r.json().get("year") if resp_year == 2026: print(f" [OK] year field unchanged, still 2026 (correct: year blocked)") else: print(f" [FAIL] year changed to {resp_year} (should be blocked)") print("\n=== All steps done ===") if __name__ == "__main__": main()