"""Run the default application lifecycle over HTTP and verify persistence.""" import os import socket import subprocess import sys import time from contextlib import contextmanager from pathlib import Path import httpx from sqlalchemy import select from sqlalchemy.orm import Session from app.models.auth import User from app.services.auth import PASSWORD_HASHER @contextmanager def server(engine, *, authenticate=True): with socket.socket() as socket_: socket_.bind(("127.0.0.1", 0)) port = socket_.getsockname()[1] environment = { **os.environ, "DATABASE_URL": engine.url.render_as_string(hide_password=False), "APP_ENV": "test", "AUTH_COOKIE_SECURE": "false", } if authenticate: with Session(engine) as db: if db.scalar(select(User.id).where(User.username == "http_admin")) is None: db.add( User( username="http_admin", password_hash=PASSWORD_HASHER.hash("test-password-123"), role="admin", ) ) db.commit() backend = Path(__file__).resolve().parents[1] process = subprocess.Popen( [ sys.executable, "-m", "uvicorn", "app.main:app", "--host", "127.0.0.1", "--port", str(port), "--log-level", "error", ], cwd=backend, env=environment, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) try: with httpx.Client(base_url=f"http://127.0.0.1:{port}", timeout=2) as client: client.headers["Origin"] = "http://localhost:5173" deadline = time.monotonic() + 15 while time.monotonic() < deadline: if process.poll() is not None: raise AssertionError("默认后端进程启动失败") try: if client.get("/api/ready").status_code == 200: break except httpx.TransportError: pass time.sleep(0.05) else: raise AssertionError("默认后端未在时限内就绪") if authenticate: login = client.post( "/api/auth/login", json={"username": "http_admin", "password": "test-password-123"}, ) assert login.status_code == 200, login.text client.headers["X-CSRF-Token"] = login.json()["csrf_token"] yield client finally: process.terminate() try: process.wait(timeout=10) except subprocess.TimeoutExpired: process.kill() process.wait(timeout=5) def test_default_startup_and_persistence_across_restart(migrated_database): with server(migrated_database) as client: assert client.get("/docs").status_code == 200 response = client.post( "/api/knowledge", json={ "title": "持久化验收占位", "content": "仅为自动化测试数据", "source_url": "", }, ) assert response.status_code == 201 item = response.json() with server(migrated_database) as client: assert client.get(f"/api/knowledge/{item['id']}").json()["content"] == "仅为自动化测试数据" assert client.get("/api/knowledge").json()[0]["id"] == item["id"] def test_retrieval_run_persists_across_restart(migrated_database): with server(migrated_database) as client: item = client.post( "/api/knowledge", json={ "title": "HTTP检索测试", "content": "仅用于自动化测试的那拉提关键词。", "source_url": "https://example.com/", "collected_at": "2026-10-15T09:00:00+08:00", }, ).json() assert ( client.post( f"/api/knowledge/{item['id']}/review", json={ "expected_version": item["version"], "verification_status": "verified", "reviewer": "test-reviewer", "verification_notes": "仅验证检索流程", }, ).status_code == 200 ) response = client.post("/api/retrieval", json={"query": "那拉提"}) assert response.status_code == 200 run = response.json() assert run["results"][0]["knowledge_id"] == item["id"] with server(migrated_database) as client: assert client.get(f"/api/retrieval/runs/{run['run_id']}").json() == run def test_session_and_logout_survive_http_restart(migrated_database): from app.services.auth import COOKIE_NAME with server(migrated_database, authenticate=False) as client: payload = {"username": "http_user", "password": "test-password-123"} assert client.post("/api/auth/register", json=payload).status_code == 201 response = client.post("/api/auth/login", json=payload) cookie = client.cookies.get(COOKIE_NAME) token = response.json()["csrf_token"] with server(migrated_database, authenticate=False) as client: client.cookies.set(COOKIE_NAME, cookie) assert client.get("/api/auth/me").json()["username"] == "http_user" assert client.post("/api/auth/logout", headers={"X-CSRF-Token": token}).status_code == 204 with server(migrated_database, authenticate=False) as client: client.cookies.set(COOKIE_NAME, cookie) assert client.get("/api/auth/me").status_code == 401