2026-06-13 14:49:58 +08:00
|
|
|
|
"""FastAPI app 启动 + Swagger 测试 (T6.1).
|
|
|
|
|
|
|
|
|
|
|
|
验证:
|
|
|
|
|
|
- app 工厂可用
|
|
|
|
|
|
- lifespan 触发 bootstrap
|
|
|
|
|
|
- /openapi.json 含所有路由
|
|
|
|
|
|
- /docs 返回 HTML
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import sqlite3
|
|
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_create_app_runs_lifespan(client):
|
|
|
|
|
|
"""TestClient 上下文会触发 lifespan,验证 bootstrap 完成。"""
|
|
|
|
|
|
# 客户端创建时 lifespan 已运行 → 已有 admin 用户
|
|
|
|
|
|
resp = client.get("/health")
|
|
|
|
|
|
assert resp.status_code == 200
|
|
|
|
|
|
body = resp.json()
|
2026-06-20 18:36:23 +08:00
|
|
|
|
assert body.get("status") == "ok"
|
|
|
|
|
|
assert body.get("checks", {}).get("db_writable") is True
|
|
|
|
|
|
assert body.get("checks", {}).get("disk_writable") is True
|
|
|
|
|
|
assert body.get("checks", {}).get("settings_valid") is True
|
2026-06-13 14:49:58 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_openapi_json_exposes_all_routes(client):
|
|
|
|
|
|
"""/openapi.json 含所有 T6.1 路由。"""
|
|
|
|
|
|
resp = client.get("/openapi.json")
|
|
|
|
|
|
assert resp.status_code == 200
|
|
|
|
|
|
schema = resp.json()
|
|
|
|
|
|
paths = set(schema["paths"].keys())
|
|
|
|
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
|
|
"/health",
|
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
|
"/api/auth/me",
|
|
|
|
|
|
"/api/orders",
|
|
|
|
|
|
"/api/orders/{order_id}",
|
|
|
|
|
|
"/api/stats/dashboard",
|
|
|
|
|
|
"/api/stats/orders",
|
|
|
|
|
|
"/api/meta",
|
|
|
|
|
|
}
|
|
|
|
|
|
missing = expected - paths
|
|
|
|
|
|
assert not missing, f"OpenAPI 缺失路径: {missing}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_docs_serves_swagger_ui(client):
|
|
|
|
|
|
"""/docs 返回 HTML(200 + 含 swagger-ui 字样)。"""
|
|
|
|
|
|
resp = client.get("/docs")
|
|
|
|
|
|
assert resp.status_code == 200
|
|
|
|
|
|
assert "text/html" in resp.headers["content-type"]
|
|
|
|
|
|
body = resp.text.lower()
|
|
|
|
|
|
assert "swagger" in body or "openapi" in body
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_redoc_served(client):
|
|
|
|
|
|
"""/redoc 也可用。"""
|
|
|
|
|
|
resp = client.get("/redoc")
|
|
|
|
|
|
assert resp.status_code == 200
|
|
|
|
|
|
assert "text/html" in resp.headers["content-type"]
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-25 09:27:06 +08:00
|
|
|
|
def test_dashboard_page_served(client, auth_headers):
|
|
|
|
|
|
"""/dashboard 需要鉴权,鉴权后返回运营后台页面骨架。"""
|
|
|
|
|
|
resp = client.get("/dashboard", headers=auth_headers)
|
2026-06-13 14:49:58 +08:00
|
|
|
|
assert resp.status_code == 200
|
|
|
|
|
|
assert "text/html" in resp.headers["content-type"]
|
|
|
|
|
|
body = resp.text
|
2026-06-16 15:02:01 +08:00
|
|
|
|
assert "运营总览" in body
|
|
|
|
|
|
assert "核心经营指标" in body
|
2026-06-13 14:49:58 +08:00
|
|
|
|
assert "/static/dashboard.js" in body
|
2026-06-16 20:33:06 +08:00
|
|
|
|
assert "/static/portal-ui.css" in body
|
2026-06-13 14:49:58 +08:00
|
|
|
|
assert 'id="trend-chart"' in body
|
|
|
|
|
|
assert 'id="status-chart"' in body
|
|
|
|
|
|
assert 'id="source-chart"' in body
|
|
|
|
|
|
assert 'id="service-chart"' in body
|
|
|
|
|
|
assert 'id="range-7d"' in body
|
|
|
|
|
|
assert 'id="range-30d"' in body
|
2026-06-16 20:33:06 +08:00
|
|
|
|
assert 'id="system-status"' in body
|
|
|
|
|
|
assert 'id="pending-orders"' in body
|
feat(stats+dashboard): expand pending_orders into 3 actionable dimensions
## 后端 admin/stats.py
- compute_summary 在单条聚合 SQL 中加两个新字段:
- pending_overdue_24h: status='pending' AND created_at < (now - 24h)
(超时未付,需主动催付)
- pending_missing_intake: status='pending' AND
(order_intakes 无记录 OR order_intakes.status='draft')
(资料待补,需主动跟进)
- LEFT JOIN order_intakes AS i ON i.order_id = orders.id
- 关键 bug 修复: SQL ? 顺序与参数顺序必须匹配,
(overdue_cutoff 在 IN 子句后) 之前误用 (overdue_cutoff, *_REVENUE_STATUSES)
导致 overdue_cutoff datetime 字符串误入 IN 子句,
'completed' 误入 created_at < ? 子句, 收入少算 30%。
已修正为 (*_REVENUE_STATUSES, overdue_cutoff)
- 列名全部限定为 orders.status / orders.created_at,避免与 i.status 歧义
## 前端 admin/static/dashboard.html
- KPI 4 待处理订单卡加 .pending-breakdown 区域
- 两个 pill 标签: pending-tag-overdue(红色,超时) + pending-tag-missing(橙色,资料待补)
- 仅在对应字段 > 0 时才显示
- CSS .pending-tag[hidden] { display: none; } 确保 hidden 属性生效
## 前端 admin/static/dashboard.js
- renderSummary 新增 pending_overdue_24h / pending_missing_intake 渲染
- 至少一个非零才展开 breakdown 区域
## 测试
- conftest.py orders_db fixture 同时建 order_intakes 表,避免 LEFT JOIN 缺表
- test_routes_stats_dashboard.py:
- test_dashboard_empty_db_shape 锁住 summary 必须含 12 字段
- test_dashboard_summary_pending_orders 更新 revenue 期望 (paid + serving 计入)
- 新增 test_dashboard_summary_pending_overdue_24h (2天前 vs 12h前)
- 新增 test_dashboard_summary_pending_intake (submitted/draft/无 intake 4 种组合)
- test_app.py 加 pending-breakdown 结构断言
- pytest 52 passed
## 真实环境验证
- admin/test-pass-123 登录后:
pending=7 overdue=0 missing_intake=7
- 视觉验证: KPI 4 显示 '7' 主值 + '资料待补 7' 橙色 pill
'超时' pill 因 overdue=0 自动隐藏
- 数据一致性: by_status.pending=7 与 summary.pending_orders=7 一致
2026-06-16 21:48:18 +08:00
|
|
|
|
assert 'id="pending-overdue-24h"' in body
|
|
|
|
|
|
assert 'id="pending-missing-intake"' in body
|
|
|
|
|
|
assert 'id="pending-breakdown"' in body
|
|
|
|
|
|
assert 'id="pending-overdue-tag"' in body
|
|
|
|
|
|
assert 'id="pending-missing-tag"' in body
|
2026-06-20 18:36:23 +08:00
|
|
|
|
assert "pending-tag-overdue" in body
|
|
|
|
|
|
assert "pending-tag-missing" in body
|
2026-06-16 20:33:06 +08:00
|
|
|
|
assert 'id="orders-spark"' in body
|
|
|
|
|
|
assert 'id="revenue-spark"' in body
|
|
|
|
|
|
assert 'id="status-title"' in body
|
|
|
|
|
|
assert 'id="quick-refresh-btn"' in body
|
|
|
|
|
|
assert 'id="quick-logout-btn"' in body
|
2026-06-17 10:42:14 +08:00
|
|
|
|
assert 'id="dev-seed-panel"' in body
|
|
|
|
|
|
assert 'id="dev-seed-overdue-btn"' in body
|
|
|
|
|
|
assert 'id="dev-seed-clean-btn"' in body
|
|
|
|
|
|
assert 'id="dashboard-title"' in body
|
2026-06-16 20:33:06 +08:00
|
|
|
|
assert 'id="login-form"' in body
|
|
|
|
|
|
assert 'id="quick-links"' in body
|
|
|
|
|
|
assert 'class="card empty"' in body
|
|
|
|
|
|
assert 'class="chart-empty"' in body
|
|
|
|
|
|
assert 'data-card="orders"' in body
|
|
|
|
|
|
assert 'data-card="revenue"' in body
|
|
|
|
|
|
assert 'data-card="users"' in body
|
|
|
|
|
|
assert 'data-card="pending"' in body
|
|
|
|
|
|
assert "尚未连接" in body
|
|
|
|
|
|
assert "立即刷新" in body
|
|
|
|
|
|
assert "退出登录" in body
|
|
|
|
|
|
assert "等待加载状态分布" in body
|
|
|
|
|
|
assert "等待加载来源分布" in body
|
|
|
|
|
|
assert "等待加载服务版本分布" in body
|
|
|
|
|
|
assert "登录后展示订单与收入趋势" in body
|
2026-06-13 14:49:58 +08:00
|
|
|
|
assert "/static/vendor/echarts.min.js" in body
|
|
|
|
|
|
assert "jsdelivr.net" not in body
|
2026-06-16 15:02:01 +08:00
|
|
|
|
assert "最小仪表盘" not in body
|
|
|
|
|
|
assert "admin_users 总数" not in body
|
|
|
|
|
|
assert "接口: <code>/api/stats/dashboard</code>" not in body
|
2026-06-13 14:49:58 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_dashboard_static_js_served(client):
|
|
|
|
|
|
"""前端脚本包含趋势切换与 3 张分布图渲染逻辑。"""
|
|
|
|
|
|
resp = client.get("/static/dashboard.js")
|
|
|
|
|
|
assert resp.status_code == 200
|
|
|
|
|
|
assert "javascript" in resp.headers["content-type"]
|
|
|
|
|
|
body = resp.text
|
|
|
|
|
|
assert "renderSummary" in body
|
|
|
|
|
|
assert "/api/stats/dashboard" in body
|
|
|
|
|
|
assert "status-chart" in body
|
|
|
|
|
|
assert "source-chart" in body
|
|
|
|
|
|
assert "service-chart" in body
|
|
|
|
|
|
assert 'range: "7d"' in body
|
|
|
|
|
|
assert "trends?.[state.range]" in body
|
|
|
|
|
|
assert "sessionStorage" in body
|
|
|
|
|
|
assert "localStorage" not in body
|
2026-06-16 15:02:01 +08:00
|
|
|
|
assert "admin_users 总数" not in body
|
2026-06-16 20:33:06 +08:00
|
|
|
|
assert "setStatus" in body
|
|
|
|
|
|
assert "setChartEmpty" in body
|
|
|
|
|
|
assert "pending-orders" in body
|
feat(stats+dashboard): expand pending_orders into 3 actionable dimensions
## 后端 admin/stats.py
- compute_summary 在单条聚合 SQL 中加两个新字段:
- pending_overdue_24h: status='pending' AND created_at < (now - 24h)
(超时未付,需主动催付)
- pending_missing_intake: status='pending' AND
(order_intakes 无记录 OR order_intakes.status='draft')
(资料待补,需主动跟进)
- LEFT JOIN order_intakes AS i ON i.order_id = orders.id
- 关键 bug 修复: SQL ? 顺序与参数顺序必须匹配,
(overdue_cutoff 在 IN 子句后) 之前误用 (overdue_cutoff, *_REVENUE_STATUSES)
导致 overdue_cutoff datetime 字符串误入 IN 子句,
'completed' 误入 created_at < ? 子句, 收入少算 30%。
已修正为 (*_REVENUE_STATUSES, overdue_cutoff)
- 列名全部限定为 orders.status / orders.created_at,避免与 i.status 歧义
## 前端 admin/static/dashboard.html
- KPI 4 待处理订单卡加 .pending-breakdown 区域
- 两个 pill 标签: pending-tag-overdue(红色,超时) + pending-tag-missing(橙色,资料待补)
- 仅在对应字段 > 0 时才显示
- CSS .pending-tag[hidden] { display: none; } 确保 hidden 属性生效
## 前端 admin/static/dashboard.js
- renderSummary 新增 pending_overdue_24h / pending_missing_intake 渲染
- 至少一个非零才展开 breakdown 区域
## 测试
- conftest.py orders_db fixture 同时建 order_intakes 表,避免 LEFT JOIN 缺表
- test_routes_stats_dashboard.py:
- test_dashboard_empty_db_shape 锁住 summary 必须含 12 字段
- test_dashboard_summary_pending_orders 更新 revenue 期望 (paid + serving 计入)
- 新增 test_dashboard_summary_pending_overdue_24h (2天前 vs 12h前)
- 新增 test_dashboard_summary_pending_intake (submitted/draft/无 intake 4 种组合)
- test_app.py 加 pending-breakdown 结构断言
- pytest 52 passed
## 真实环境验证
- admin/test-pass-123 登录后:
pending=7 overdue=0 missing_intake=7
- 视觉验证: KPI 4 显示 '7' 主值 + '资料待补 7' 橙色 pill
'超时' pill 因 overdue=0 自动隐藏
- 数据一致性: by_status.pending=7 与 summary.pending_orders=7 一致
2026-06-16 21:48:18 +08:00
|
|
|
|
assert "pending-overdue-24h" in body
|
|
|
|
|
|
assert "pending-missing-intake" in body
|
|
|
|
|
|
assert "setChartEmpty" in body
|
|
|
|
|
|
assert "pending-breakdown" in body
|
2026-06-17 10:42:14 +08:00
|
|
|
|
assert "postDevSeed" in body
|
|
|
|
|
|
assert "dev-seed-panel" in body
|
|
|
|
|
|
assert "/api/admin/orders/dev-seed" in body
|
2026-06-13 14:49:58 +08:00
|
|
|
|
|
2026-06-20 18:36:23 +08:00
|
|
|
|
|
2026-06-13 14:49:58 +08:00
|
|
|
|
def test_bootstrap_admin_only_once(client, settings):
|
|
|
|
|
|
"""lifespan 已 bootstrap 后,再次调用应报告已存在,不再创建。"""
|
|
|
|
|
|
from admin.db import AdminUserRepo, bootstrap_admin
|
|
|
|
|
|
|
|
|
|
|
|
# client fixture 已触发 lifespan → 已有 bootstrap 用户
|
|
|
|
|
|
created1, msg1 = bootstrap_admin(settings)
|
|
|
|
|
|
assert created1 is False
|
|
|
|
|
|
assert "已存在" in msg1
|
|
|
|
|
|
|
|
|
|
|
|
repo = AdminUserRepo(settings.db_path)
|
|
|
|
|
|
assert repo.count() == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_default_admin_login_works(client, settings):
|
|
|
|
|
|
"""默认 admin/admin123 (此处覆写为 test-pass-123) 可登录。"""
|
|
|
|
|
|
resp = client.post(
|
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
|
json={"username": "admin", "password": "test-pass-123"},
|
|
|
|
|
|
)
|
|
|
|
|
|
assert resp.status_code == 200
|
|
|
|
|
|
body = resp.json()
|
|
|
|
|
|
assert body["token_type"] == "bearer"
|
|
|
|
|
|
assert body["expires_in"] == settings.jwt_expire_minutes * 60
|
|
|
|
|
|
assert isinstance(body["access_token"], str)
|
|
|
|
|
|
assert len(body["access_token"]) > 20
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_create_app_bootstraps_orders_schema(tmp_path, monkeypatch):
|
|
|
|
|
|
"""真实启动流程会顺手初始化 orders DB,fresh app 的 dashboard 空库可用。"""
|
|
|
|
|
|
db_path = tmp_path / "admin.db"
|
|
|
|
|
|
orders_db_path = tmp_path / "orders.db"
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_ENV", "dev")
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_DB_PATH", str(db_path))
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_ORDERS_DB_PATH", str(orders_db_path))
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_JWT_SECRET", "x" * 64)
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_ADMIN_USER", "admin")
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_ADMIN_PASS", "test-pass-123")
|
|
|
|
|
|
|
|
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
|
|
|
|
|
|
from admin.app import create_app
|
|
|
|
|
|
from admin.config import load_settings
|
|
|
|
|
|
|
|
|
|
|
|
app = create_app(load_settings())
|
|
|
|
|
|
with TestClient(app) as client:
|
|
|
|
|
|
login = client.post(
|
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
|
json={"username": "admin", "password": "test-pass-123"},
|
|
|
|
|
|
)
|
|
|
|
|
|
assert login.status_code == 200
|
|
|
|
|
|
token = login.json()["access_token"]
|
|
|
|
|
|
|
|
|
|
|
|
resp = client.get(
|
|
|
|
|
|
"/api/stats/dashboard",
|
|
|
|
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
|
|
|
|
)
|
|
|
|
|
|
assert resp.status_code == 200
|
|
|
|
|
|
assert resp.json()["summary"] == {
|
|
|
|
|
|
"total_orders": 0,
|
|
|
|
|
|
"total_revenue_cents": 0,
|
|
|
|
|
|
"total_users": 1,
|
2026-06-16 21:16:39 +08:00
|
|
|
|
"pending_orders": 0,
|
feat(stats+dashboard): expand pending_orders into 3 actionable dimensions
## 后端 admin/stats.py
- compute_summary 在单条聚合 SQL 中加两个新字段:
- pending_overdue_24h: status='pending' AND created_at < (now - 24h)
(超时未付,需主动催付)
- pending_missing_intake: status='pending' AND
(order_intakes 无记录 OR order_intakes.status='draft')
(资料待补,需主动跟进)
- LEFT JOIN order_intakes AS i ON i.order_id = orders.id
- 关键 bug 修复: SQL ? 顺序与参数顺序必须匹配,
(overdue_cutoff 在 IN 子句后) 之前误用 (overdue_cutoff, *_REVENUE_STATUSES)
导致 overdue_cutoff datetime 字符串误入 IN 子句,
'completed' 误入 created_at < ? 子句, 收入少算 30%。
已修正为 (*_REVENUE_STATUSES, overdue_cutoff)
- 列名全部限定为 orders.status / orders.created_at,避免与 i.status 歧义
## 前端 admin/static/dashboard.html
- KPI 4 待处理订单卡加 .pending-breakdown 区域
- 两个 pill 标签: pending-tag-overdue(红色,超时) + pending-tag-missing(橙色,资料待补)
- 仅在对应字段 > 0 时才显示
- CSS .pending-tag[hidden] { display: none; } 确保 hidden 属性生效
## 前端 admin/static/dashboard.js
- renderSummary 新增 pending_overdue_24h / pending_missing_intake 渲染
- 至少一个非零才展开 breakdown 区域
## 测试
- conftest.py orders_db fixture 同时建 order_intakes 表,避免 LEFT JOIN 缺表
- test_routes_stats_dashboard.py:
- test_dashboard_empty_db_shape 锁住 summary 必须含 12 字段
- test_dashboard_summary_pending_orders 更新 revenue 期望 (paid + serving 计入)
- 新增 test_dashboard_summary_pending_overdue_24h (2天前 vs 12h前)
- 新增 test_dashboard_summary_pending_intake (submitted/draft/无 intake 4 种组合)
- test_app.py 加 pending-breakdown 结构断言
- pytest 52 passed
## 真实环境验证
- admin/test-pass-123 登录后:
pending=7 overdue=0 missing_intake=7
- 视觉验证: KPI 4 显示 '7' 主值 + '资料待补 7' 橙色 pill
'超时' pill 因 overdue=0 自动隐藏
- 数据一致性: by_status.pending=7 与 summary.pending_orders=7 一致
2026-06-16 21:48:18 +08:00
|
|
|
|
"pending_overdue_24h": 0,
|
|
|
|
|
|
"pending_missing_intake": 0,
|
2026-06-13 14:49:58 +08:00
|
|
|
|
"orders_today": 0,
|
|
|
|
|
|
"orders_7d": 0,
|
|
|
|
|
|
"orders_30d": 0,
|
|
|
|
|
|
"revenue_today_cents": 0,
|
|
|
|
|
|
"revenue_7d_cents": 0,
|
|
|
|
|
|
"revenue_30d_cents": 0,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
with sqlite3.connect(orders_db_path) as conn:
|
2026-06-18 16:12:22 +08:00
|
|
|
|
orders_row = conn.execute(
|
2026-06-13 14:49:58 +08:00
|
|
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='orders'"
|
|
|
|
|
|
).fetchone()
|
2026-06-18 16:12:22 +08:00
|
|
|
|
intake_row = conn.execute(
|
|
|
|
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='order_intakes'"
|
|
|
|
|
|
).fetchone()
|
|
|
|
|
|
assert orders_row is not None
|
|
|
|
|
|
assert intake_row is not None
|
2026-06-13 14:49:58 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_prod_rejects_default_admin_password(tmp_path, monkeypatch):
|
|
|
|
|
|
db_path = tmp_path / "admin.db"
|
|
|
|
|
|
orders_db_path = tmp_path / "orders.db"
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_ENV", "prod")
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_DB_PATH", str(db_path))
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_ORDERS_DB_PATH", str(orders_db_path))
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_JWT_SECRET", "x" * 64)
|
2026-06-15 09:21:45 +08:00
|
|
|
|
monkeypatch.setenv("GAOKAO_PORTAL_TOKEN_SECRET", "Z" * 64)
|
2026-06-13 14:49:58 +08:00
|
|
|
|
monkeypatch.setenv("GAOKAO_ADMIN_USER", "admin")
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_ADMIN_PASS", "admin123")
|
2026-06-18 10:21:38 +08:00
|
|
|
|
monkeypatch.setenv("GAOKAO_PAYMENT_PROVIDER", "alipay")
|
2026-06-28 12:11:43 +08:00
|
|
|
|
monkeypatch.setenv("GAOKAO_LLM_PROVIDER", "dashscope")
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_LLM_API_KEY", "sk-test")
|
2026-06-15 09:21:45 +08:00
|
|
|
|
# 显式提供合规 webhook secret,避免被 P2-5 fail-closed 提前拦截,
|
|
|
|
|
|
# 让本测试聚焦于管理员密码策略。
|
|
|
|
|
|
monkeypatch.setenv("GAOKAO_PAYMENT_WEBHOOK_SECRET", "P" + "r" * 31 + "!" * 32)
|
2026-06-13 14:49:58 +08:00
|
|
|
|
|
2026-06-18 10:21:38 +08:00
|
|
|
|
from admin.app import _validate_and_log_settings
|
2026-06-13 14:49:58 +08:00
|
|
|
|
from admin.config import load_settings
|
|
|
|
|
|
|
2026-06-20 18:36:23 +08:00
|
|
|
|
with pytest.raises(
|
|
|
|
|
|
RuntimeError, match="GAOKAO_ADMIN_PASS invalid in prod|默认管理员密码"
|
|
|
|
|
|
):
|
2026-06-18 10:21:38 +08:00
|
|
|
|
_validate_and_log_settings(load_settings())
|