Files
gaokao-volunteer-system/admin/tests/test_app.py

257 lines
9.1 KiB
Python
Raw Normal View History

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()
feat(ops 6/20 v2.1.3): 生产加固 + L-A 送审前修复 + crowd_db 质量契约 ## 实现内容 (6 项改动) 1. **admin /health 端点增强** — 主键契约 status:ok 保持 + checks 子对象 - db_writable: connect + CREATE TEMP TABLE + INSERT + SELECT + DROP - disk_writable: 在 ops_alert_log_path 目录创建临时文件 + 删除 - settings_valid: 复用 is_jwt_secret_secure 判断 2. **_enforce_jwt_secret_policy** — prod env 使用 dev 默认 JWT / 长度<32 → fail-closed 3. **_enforce_default_admin_password_policy** — prod env 用 admin123 / 长度<10 / 字符类<3 → fail-closed 4. **L-A R7: admin UI footer** — dashboard.html (592 行) + ui.py 内联 admin/orders/new 模板加 footer 隐私政策 + 数据删除 + 服务说明链接, 与 portal _render_footer_links() 同口径 5. **L-A R1+R4: LEGAL_PRIVACY_BASELINE 文档同步** - §6 移除孤儿 'admin' consent_channel 值 (代码侧从未实际产生) - §7 已具备/尚缺 重写, 显式归到 6/20 增量 6. **Q-A: tests/test_crowd_db_data_quality.py** — 8 个测试锁住 - 27 省总数 + 仅湖南 high + 其它 26 省 ≤ usable - 高考生源大省 (广东/江苏/北京/上海/山东/河南/四川/湖北) 不在 high 集合 - data_year=2025 (6/25 后需显式更新) ## 测试 - 4/4 RED → GREEN (admin/tests/test_health.py: JWT/admin password 拒绝 dev 默认值) - 8/8 GREEN (data/crowd_db/tests/test_crowd_db_data_quality.py: 数据质量契约) - 3 回归修复 (test_app.py + test_routes.py + test_health.py 适配新 /health checks 字段) - 31/31 GREEN (admin/tests/test_app.py + test_routes.py + test_health.py) ## 报告 - reports/LA_LEGAL_PRIVACY_PRE_AUDIT_2026-06-20.md (363 行, 9 风险 0 阻塞) - reports/QA_CROWD_DB_NON_HUNAN_DENSITY_AUDIT.md (45 行, CRITICAL 文档失真已规避) ## 文件 M CHANGELOG.md (v2.1.3) M admin/config.py (2 个 _enforce_*_policy + load_settings post-load) M admin/routes/health.py (3 个 _check_* + checks 子对象) M admin/routes/ui.py (admin/orders/new 模板加 footer) M admin/static/dashboard.html (footer 块) M admin/tests/test_app.py (适配 checks 字段 + regex 兼容) M admin/tests/test_health.py (4 个新测试) M admin/tests/test_routes.py (适配 checks 字段) M docs/CURRENT_STATE.md (0.3-0.5 增量段) M docs/LEGAL_PRIVACY_BASELINE.md (§6 清理 + §7 重写) + data/crowd_db/tests/test_crowd_db_data_quality.py (8 tests) + reports/LA_LEGAL_PRIVACY_PRE_AUDIT_2026-06-20.md + reports/QA_CROWD_DB_NON_HUNAN_DENSITY_AUDIT.md
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"]
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
assert "运营总览" in body
assert "核心经营指标" in body
2026-06-13 14:49:58 +08:00
assert "/static/dashboard.js" in body
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
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
feat(ops 6/20 v2.1.3): 生产加固 + L-A 送审前修复 + crowd_db 质量契约 ## 实现内容 (6 项改动) 1. **admin /health 端点增强** — 主键契约 status:ok 保持 + checks 子对象 - db_writable: connect + CREATE TEMP TABLE + INSERT + SELECT + DROP - disk_writable: 在 ops_alert_log_path 目录创建临时文件 + 删除 - settings_valid: 复用 is_jwt_secret_secure 判断 2. **_enforce_jwt_secret_policy** — prod env 使用 dev 默认 JWT / 长度<32 → fail-closed 3. **_enforce_default_admin_password_policy** — prod env 用 admin123 / 长度<10 / 字符类<3 → fail-closed 4. **L-A R7: admin UI footer** — dashboard.html (592 行) + ui.py 内联 admin/orders/new 模板加 footer 隐私政策 + 数据删除 + 服务说明链接, 与 portal _render_footer_links() 同口径 5. **L-A R1+R4: LEGAL_PRIVACY_BASELINE 文档同步** - §6 移除孤儿 'admin' consent_channel 值 (代码侧从未实际产生) - §7 已具备/尚缺 重写, 显式归到 6/20 增量 6. **Q-A: tests/test_crowd_db_data_quality.py** — 8 个测试锁住 - 27 省总数 + 仅湖南 high + 其它 26 省 ≤ usable - 高考生源大省 (广东/江苏/北京/上海/山东/河南/四川/湖北) 不在 high 集合 - data_year=2025 (6/25 后需显式更新) ## 测试 - 4/4 RED → GREEN (admin/tests/test_health.py: JWT/admin password 拒绝 dev 默认值) - 8/8 GREEN (data/crowd_db/tests/test_crowd_db_data_quality.py: 数据质量契约) - 3 回归修复 (test_app.py + test_routes.py + test_health.py 适配新 /health checks 字段) - 31/31 GREEN (admin/tests/test_app.py + test_routes.py + test_health.py) ## 报告 - reports/LA_LEGAL_PRIVACY_PRE_AUDIT_2026-06-20.md (363 行, 9 风险 0 阻塞) - reports/QA_CROWD_DB_NON_HUNAN_DENSITY_AUDIT.md (45 行, CRITICAL 文档失真已规避) ## 文件 M CHANGELOG.md (v2.1.3) M admin/config.py (2 个 _enforce_*_policy + load_settings post-load) M admin/routes/health.py (3 个 _check_* + checks 子对象) M admin/routes/ui.py (admin/orders/new 模板加 footer) M admin/static/dashboard.html (footer 块) M admin/tests/test_app.py (适配 checks 字段 + regex 兼容) M admin/tests/test_health.py (4 个新测试) M admin/tests/test_routes.py (适配 checks 字段) M docs/CURRENT_STATE.md (0.3-0.5 增量段) M docs/LEGAL_PRIVACY_BASELINE.md (§6 清理 + §7 重写) + data/crowd_db/tests/test_crowd_db_data_quality.py (8 tests) + reports/LA_LEGAL_PRIVACY_PRE_AUDIT_2026-06-20.md + reports/QA_CROWD_DB_NON_HUNAN_DENSITY_AUDIT.md
2026-06-20 18:36:23 +08:00
assert "pending-tag-overdue" in body
assert "pending-tag-missing" in body
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
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
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
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
assert "admin_users 总数" not in body
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
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
feat(ops 6/20 v2.1.3): 生产加固 + L-A 送审前修复 + crowd_db 质量契约 ## 实现内容 (6 项改动) 1. **admin /health 端点增强** — 主键契约 status:ok 保持 + checks 子对象 - db_writable: connect + CREATE TEMP TABLE + INSERT + SELECT + DROP - disk_writable: 在 ops_alert_log_path 目录创建临时文件 + 删除 - settings_valid: 复用 is_jwt_secret_secure 判断 2. **_enforce_jwt_secret_policy** — prod env 使用 dev 默认 JWT / 长度<32 → fail-closed 3. **_enforce_default_admin_password_policy** — prod env 用 admin123 / 长度<10 / 字符类<3 → fail-closed 4. **L-A R7: admin UI footer** — dashboard.html (592 行) + ui.py 内联 admin/orders/new 模板加 footer 隐私政策 + 数据删除 + 服务说明链接, 与 portal _render_footer_links() 同口径 5. **L-A R1+R4: LEGAL_PRIVACY_BASELINE 文档同步** - §6 移除孤儿 'admin' consent_channel 值 (代码侧从未实际产生) - §7 已具备/尚缺 重写, 显式归到 6/20 增量 6. **Q-A: tests/test_crowd_db_data_quality.py** — 8 个测试锁住 - 27 省总数 + 仅湖南 high + 其它 26 省 ≤ usable - 高考生源大省 (广东/江苏/北京/上海/山东/河南/四川/湖北) 不在 high 集合 - data_year=2025 (6/25 后需显式更新) ## 测试 - 4/4 RED → GREEN (admin/tests/test_health.py: JWT/admin password 拒绝 dev 默认值) - 8/8 GREEN (data/crowd_db/tests/test_crowd_db_data_quality.py: 数据质量契约) - 3 回归修复 (test_app.py + test_routes.py + test_health.py 适配新 /health checks 字段) - 31/31 GREEN (admin/tests/test_app.py + test_routes.py + test_health.py) ## 报告 - reports/LA_LEGAL_PRIVACY_PRE_AUDIT_2026-06-20.md (363 行, 9 风险 0 阻塞) - reports/QA_CROWD_DB_NON_HUNAN_DENSITY_AUDIT.md (45 行, CRITICAL 文档失真已规避) ## 文件 M CHANGELOG.md (v2.1.3) M admin/config.py (2 个 _enforce_*_policy + load_settings post-load) M admin/routes/health.py (3 个 _check_* + checks 子对象) M admin/routes/ui.py (admin/orders/new 模板加 footer) M admin/static/dashboard.html (footer 块) M admin/tests/test_app.py (适配 checks 字段 + regex 兼容) M admin/tests/test_health.py (4 个新测试) M admin/tests/test_routes.py (适配 checks 字段) M docs/CURRENT_STATE.md (0.3-0.5 增量段) M docs/LEGAL_PRIVACY_BASELINE.md (§6 清理 + §7 重写) + data/crowd_db/tests/test_crowd_db_data_quality.py (8 tests) + reports/LA_LEGAL_PRIVACY_PRE_AUDIT_2026-06-20.md + reports/QA_CROWD_DB_NON_HUNAN_DENSITY_AUDIT.md
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 DBfresh 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,
"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:
orders_row = conn.execute(
2026-06-13 14:49:58 +08:00
"SELECT name FROM sqlite_master WHERE type='table' AND name='orders'"
).fetchone()
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)
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")
monkeypatch.setenv("GAOKAO_PAYMENT_PROVIDER", "alipay")
monkeypatch.setenv("GAOKAO_LLM_PROVIDER", "dashscope")
monkeypatch.setenv("GAOKAO_LLM_API_KEY", "sk-test")
# 显式提供合规 webhook secret,避免被 P2-5 fail-closed 提前拦截,
# 让本测试聚焦于管理员密码策略。
monkeypatch.setenv("GAOKAO_PAYMENT_WEBHOOK_SECRET", "P" + "r" * 31 + "!" * 32)
2026-06-13 14:49:58 +08:00
from admin.app import _validate_and_log_settings
2026-06-13 14:49:58 +08:00
from admin.config import load_settings
feat(ops 6/20 v2.1.3): 生产加固 + L-A 送审前修复 + crowd_db 质量契约 ## 实现内容 (6 项改动) 1. **admin /health 端点增强** — 主键契约 status:ok 保持 + checks 子对象 - db_writable: connect + CREATE TEMP TABLE + INSERT + SELECT + DROP - disk_writable: 在 ops_alert_log_path 目录创建临时文件 + 删除 - settings_valid: 复用 is_jwt_secret_secure 判断 2. **_enforce_jwt_secret_policy** — prod env 使用 dev 默认 JWT / 长度<32 → fail-closed 3. **_enforce_default_admin_password_policy** — prod env 用 admin123 / 长度<10 / 字符类<3 → fail-closed 4. **L-A R7: admin UI footer** — dashboard.html (592 行) + ui.py 内联 admin/orders/new 模板加 footer 隐私政策 + 数据删除 + 服务说明链接, 与 portal _render_footer_links() 同口径 5. **L-A R1+R4: LEGAL_PRIVACY_BASELINE 文档同步** - §6 移除孤儿 'admin' consent_channel 值 (代码侧从未实际产生) - §7 已具备/尚缺 重写, 显式归到 6/20 增量 6. **Q-A: tests/test_crowd_db_data_quality.py** — 8 个测试锁住 - 27 省总数 + 仅湖南 high + 其它 26 省 ≤ usable - 高考生源大省 (广东/江苏/北京/上海/山东/河南/四川/湖北) 不在 high 集合 - data_year=2025 (6/25 后需显式更新) ## 测试 - 4/4 RED → GREEN (admin/tests/test_health.py: JWT/admin password 拒绝 dev 默认值) - 8/8 GREEN (data/crowd_db/tests/test_crowd_db_data_quality.py: 数据质量契约) - 3 回归修复 (test_app.py + test_routes.py + test_health.py 适配新 /health checks 字段) - 31/31 GREEN (admin/tests/test_app.py + test_routes.py + test_health.py) ## 报告 - reports/LA_LEGAL_PRIVACY_PRE_AUDIT_2026-06-20.md (363 行, 9 风险 0 阻塞) - reports/QA_CROWD_DB_NON_HUNAN_DENSITY_AUDIT.md (45 行, CRITICAL 文档失真已规避) ## 文件 M CHANGELOG.md (v2.1.3) M admin/config.py (2 个 _enforce_*_policy + load_settings post-load) M admin/routes/health.py (3 个 _check_* + checks 子对象) M admin/routes/ui.py (admin/orders/new 模板加 footer) M admin/static/dashboard.html (footer 块) M admin/tests/test_app.py (适配 checks 字段 + regex 兼容) M admin/tests/test_health.py (4 个新测试) M admin/tests/test_routes.py (适配 checks 字段) M docs/CURRENT_STATE.md (0.3-0.5 增量段) M docs/LEGAL_PRIVACY_BASELINE.md (§6 清理 + §7 重写) + data/crowd_db/tests/test_crowd_db_data_quality.py (8 tests) + reports/LA_LEGAL_PRIVACY_PRE_AUDIT_2026-06-20.md + reports/QA_CROWD_DB_NON_HUNAN_DENSITY_AUDIT.md
2026-06-20 18:36:23 +08:00
with pytest.raises(
RuntimeError, match="GAOKAO_ADMIN_PASS invalid in prod|默认管理员密码"
):
_validate_and_log_settings(load_settings())