test: B8 收敛——sqlite 测试 DDL 单一事实源 _ddl.py(10 文件 45 处手写 DDL 清零)+localtime 时区收敛(B5 评审 P3-4, utcnow 边界修复)+测试内遗留建表清理, 196 绿; docs 同步 B8 完成状态

This commit is contained in:
2026-09-06 23:37:03 +08:00
parent e7bd8880e2
commit c6ad0885d3
12 changed files with 62 additions and 515 deletions
+5 -48
View File
@@ -8,9 +8,9 @@ from datetime import datetime, timedelta
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlalchemy import create_engine, text
from sqlalchemy.pool import StaticPool
from sqlalchemy import text
from _ddl import create_sqlite_engine
from app.api import risk as risk_api
from app.api.deps import assert_customer_access
from app.api.risk import router as risk_router
@@ -51,51 +51,8 @@ def _alert(alert_id, customer, atype, status="pending_review", score=70):
@pytest.fixture()
def env():
engine = create_engine(
"sqlite://", poolclass=StaticPool, connect_args={"check_same_thread": False}
)
engine = create_sqlite_engine() # DDL 单一事实源(B4 评审 P3-12)
with engine.begin() as conn:
for ddl in [
"""CREATE TABLE core_customer (
customer_id VARCHAR(64) PRIMARY KEY, display_name VARCHAR(128), age INTEGER,
occupation VARCHAR(64), open_date DATE, is_active TINYINT DEFAULT 1)""",
"""CREATE TABLE core_customer_risk (
customer_id VARCHAR(64), risk_code VARCHAR(8), evaluated_at TIMESTAMP)""",
"""CREATE TABLE core_customer_advisor (
advisor_id VARCHAR(64), customer_id VARCHAR(64), rel_status VARCHAR(16))""",
"""CREATE TABLE core_product (
product_id VARCHAR(64) PRIMARY KEY, product_name VARCHAR(128),
min_risk_code VARCHAR(8), product_type VARCHAR(32))""",
"""CREATE TABLE risk_aml_list (
id INTEGER PRIMARY KEY AUTOINCREMENT, list_id VARCHAR(64), list_type VARCHAR(16),
full_name VARCHAR(128), match_threshold REAL, source VARCHAR(64),
list_version VARCHAR(16), effective_date DATE, is_active TINYINT DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)""",
"""CREATE TABLE risk_alert (
alert_id VARCHAR(64) PRIMARY KEY, trace_id VARCHAR(64), customer_id VARCHAR(64),
trade_id VARCHAR(64), alert_type VARCHAR(16), triggered_rules TEXT,
risk_score INTEGER, status VARCHAR(24) DEFAULT 'pending_review', payload TEXT,
handler_id VARCHAR(64), handler_result VARCHAR(64), handler_comment VARCHAR(512),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, handled_at TIMESTAMP)""",
"""CREATE TABLE audit_log (
id INTEGER PRIMARY KEY, trace_id VARCHAR(64), event_type VARCHAR(64),
agent_type VARCHAR(16), actor_id VARCHAR(64), customer_id VARCHAR(64),
rule_id VARCHAR(64), input_summary TEXT, decision VARCHAR(64), risk_score INTEGER,
handler_id VARCHAR(64), handler_result VARCHAR(64), handler_comment VARCHAR(512),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)""",
"""CREATE TABLE risk_suitability_log (
id INTEGER PRIMARY KEY AUTOINCREMENT, trace_id VARCHAR(64), customer_id VARCHAR(64),
product_id VARCHAR(64), customer_risk_level VARCHAR(8), product_risk_level VARCHAR(8),
is_matched TINYINT, is_blocked TINYINT, block_reason VARCHAR(512),
request_ref VARCHAR(64), profile_l1_version VARCHAR(32),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)""",
"""CREATE TABLE customer_profile_l3 (
customer_id VARCHAR(64) PRIMARY KEY, monitor_tier VARCHAR(16) NOT NULL,
risk_score INTEGER, score_dimensions TEXT, monitor_tags TEXT,
last_alert_id VARCHAR(64), computed_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)""",
]:
conn.execute(text(ddl))
conn.execute(
text(
"INSERT INTO core_customer (customer_id, display_name, age, is_active) VALUES"
@@ -193,9 +150,9 @@ def test_other_roles_cannot_list_alerts(client):
def test_alerts_date_filter_and_pagination(client):
"""复审 P3:start_date/end_date 过滤与分页边界回归保护。
sqlite CURRENT_TIMESTAMP 为 UTC,窗口用 utcnow 构造(localtime 收敛挂账 B8)。
DDL 收敛后 created_at 为 localtime(_ddl.py,B5 评审 P3-4),窗口用本地时间构造。
"""
now = datetime.utcnow()
now = datetime.now()
r = client.get("/api/risk/alerts", params={"start_date": (now - timedelta(hours=1)).isoformat()}, headers=OFFICER)
assert r.json()["total"] == 3 # 窗口内全命中
r = client.get("/api/risk/alerts", params={"start_date": (now + timedelta(hours=1)).isoformat()}, headers=OFFICER)