"""Q1-B 8755短生命周期真实HTTP复验工具。""" from __future__ import annotations import json import os import sys import threading from pathlib import Path from urllib.parse import urlparse import requests from sqlalchemy import func, select from werkzeug.serving import make_server BACKEND_ROOT = Path(__file__).resolve().parents[2] if str(BACKEND_ROOT) not in sys.path: sys.path.insert(0, str(BACKEND_ROOT)) def _assert_test_database() -> None: database_url = os.environ.get("DMS_DATABASE_URL", "") database_name = urlparse(database_url).path.lstrip("/") if not database_name.endswith("_test"): raise RuntimeError("Q1-B真实HTTP复验只允许连接以_test结尾的测试库") def main() -> None: _assert_test_database() password = os.environ["DMS_TEST_USER_PASSWORD"] from app import app from dms.extensions import db from dms.models import AuditLog server = make_server("127.0.0.1", 8755, app, threaded=True) server_thread = threading.Thread( target=server.serve_forever, name="q1b-http-verify", daemon=True, ) server_thread.start() base_url = "http://127.0.0.1:8755/api/v1" origin = "http://127.0.0.1:9346" try: tokens: dict[str, str] = {} for username in ("admin", "user"): response = requests.post( f"{base_url}/auth/login", json={ "username": username, "password": password, "keepSignedIn": False, }, timeout=5, ) response.raise_for_status() tokens[username] = response.json()["data"]["accessToken"] with app.app_context(): audit_before = db.session.scalar( select(func.count()).select_from(AuditLog) ) role_results = {} stable_data = None for username, token in tokens.items(): response = requests.get( f"{base_url}/config/ui-dictionaries", headers={ "Authorization": f"Bearer {token}", "Origin": origin, }, timeout=5, ) response.raise_for_status() body = response.json() assert body["requestId"] == response.headers["X-Request-Id"] assert response.headers["Access-Control-Allow-Origin"] == origin exposed = { value.strip().lower() for value in response.headers[ "Access-Control-Expose-Headers" ].split(",") } assert "x-request-id" in exposed assert set(body["data"]["dictionaries"]) == { "roleCodes", "allowedModules", "documentTypes", "documentStatuses", "securityLevels", "visibilityTypes", "attachmentTypes", "subjectTypes", "allowedActions", "categoryTypes", "enabledStatuses", "auditResults", "auditActions", "auditTargets", } if stable_data is None: stable_data = body["data"] else: assert body["data"] == stable_data role_results[username] = { "status": response.status_code, "requestIdMatches": True, } repeated = requests.get( f"{base_url}/config/ui-dictionaries", headers={ "Authorization": f"Bearer {tokens['admin']}", "Origin": origin, }, timeout=5, ) repeated.raise_for_status() assert repeated.json()["data"] == stable_data unauthenticated = requests.get( f"{base_url}/config/ui-dictionaries", headers={"Origin": origin}, timeout=5, ) assert unauthenticated.status_code == 401 unauthenticated_body = unauthenticated.json() assert ( unauthenticated_body["requestId"] == unauthenticated.headers["X-Request-Id"] ) labels = { item["code"]: item["label"] for item in stable_data["dictionaries"]["securityLevels"] } assert labels["INTERNAL"] == "内部" assert repeated.encoding.lower() == "utf-8" with app.app_context(): audit_after = db.session.scalar( select(func.count()).select_from(AuditLog) ) assert audit_after == audit_before print( json.dumps( { "port": 8755, "database": "dms_test", "roles": role_results, "unauthenticatedStatus": unauthenticated.status_code, "dictionaryCount": len(stable_data["dictionaries"]), "utf8Chinese": True, "continuousResponsesStable": True, "auditCountBefore": audit_before, "auditCountAfter": audit_after, "requestIdMatches": True, "corsOrigin": origin, "exposeHeaders": repeated.headers[ "Access-Control-Expose-Headers" ], }, ensure_ascii=False, ) ) finally: server.shutdown() server.server_close() server_thread.join(timeout=5) if __name__ == "__main__": main()