All checks were successful
技能自动化发布 / release (push) Successful in 9s
- 状态标签:C3 触发且 D 未判 → 待补录,优先级对齐需求 §6.2 - broadcast-monthly 逐项目增量 emit,任务中心可见进行中进度 - progress_reports 回填 project_id;新增 items_json/updated_at;旧库重建式安全迁移(含回归测试) - 简报:TITLE 转义防注入、章节对齐 §10(独立「暂付款/结题提醒」)、页脚逐字统一 - 金标准测试 +17:±10%/20% 阈值恰界、C2 钳位、N2 反例、C3 逐字段、HTML 转义、async+cron 定位守护、迁移回归 - 文档 v1.1(开发文档 + REQUIREMENTS):ERROR:<CODE> <message>、reports_brief、--all-status、skill-detail 表外挂载、C1 level=warn - 落盘验收报告 acceptance-reports/20260924-105401-acceptance.md(338 测试全绿) - 版本 1.0.1 → 1.0.2(constants / SKILL.md / CHANGELOG 三处对齐)
107 lines
4.0 KiB
Python
107 lines
4.0 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""init_db:幂等创建元数据表与本技能业务表。"""
|
||
from __future__ import annotations
|
||
|
||
import unittest
|
||
|
||
from _support import IsolatedDataRoot
|
||
|
||
_BUSINESS_TABLES = {"fund_projects", "progress_reports", "alert_rules"}
|
||
|
||
|
||
class TestDbSmoke(unittest.TestCase):
|
||
def test_init_db_idempotent_metadata_only(self) -> None:
|
||
with IsolatedDataRoot():
|
||
from db.connection import get_conn, init_db
|
||
from db.display_metadata import METADATA_TABLES, iter_user_tables
|
||
|
||
init_db()
|
||
init_db()
|
||
|
||
conn = get_conn()
|
||
try:
|
||
cur = conn.cursor()
|
||
cur.execute(
|
||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
||
)
|
||
tables = {str(row[0]) for row in cur.fetchall()}
|
||
self.assertIn("_jiangchang_tables", tables)
|
||
self.assertIn("_jiangchang_columns", tables)
|
||
self.assertNotIn("task_logs", tables)
|
||
self.assertEqual(
|
||
sorted(iter_user_tables(cur)),
|
||
sorted(_BUSINESS_TABLES),
|
||
)
|
||
leftover = tables - METADATA_TABLES - _BUSINESS_TABLES
|
||
self.assertEqual(leftover, set())
|
||
finally:
|
||
conn.close()
|
||
|
||
def test_init_db_migrates_legacy_progress_reports(self) -> None:
|
||
"""v1.0.1 旧库(无 items_json/updated_at)→ init_db 重建迁移且数据不丢。"""
|
||
with IsolatedDataRoot():
|
||
from db.connection import get_conn, init_db
|
||
|
||
conn = get_conn()
|
||
try:
|
||
conn.execute(
|
||
"""
|
||
CREATE TABLE progress_reports (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
batch_no TEXT NOT NULL,
|
||
project_id INTEGER,
|
||
project_no TEXT,
|
||
project_name TEXT,
|
||
pi_name TEXT,
|
||
as_of TEXT,
|
||
spend_rate REAL,
|
||
time_progress REAL,
|
||
deviation REAL,
|
||
status_tag TEXT NOT NULL,
|
||
summary TEXT,
|
||
suggestions_json TEXT,
|
||
report_file TEXT,
|
||
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
||
)
|
||
"""
|
||
)
|
||
conn.execute(
|
||
"INSERT INTO progress_reports (batch_no, project_no, status_tag) VALUES (?, ?, ?)",
|
||
("legacy-batch", "OLD001", "严重滞后"),
|
||
)
|
||
conn.commit()
|
||
finally:
|
||
conn.close()
|
||
|
||
init_db()
|
||
|
||
conn = get_conn()
|
||
try:
|
||
cur = conn.cursor()
|
||
cur.execute("PRAGMA table_info(progress_reports)")
|
||
cols = [str(row[1]) for row in cur.fetchall()]
|
||
self.assertIn("items_json", cols)
|
||
self.assertIn("updated_at", cols)
|
||
|
||
row = cur.execute(
|
||
"SELECT project_no, status_tag FROM progress_reports WHERE batch_no = ?",
|
||
("legacy-batch",),
|
||
).fetchone()
|
||
self.assertEqual(row, ("OLD001", "严重滞后"))
|
||
|
||
leftovers = cur.execute(
|
||
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '_progress%'"
|
||
).fetchall()
|
||
self.assertEqual(leftovers, [])
|
||
|
||
triggers = cur.execute(
|
||
"SELECT name FROM sqlite_master WHERE type='trigger' AND tbl_name='progress_reports'"
|
||
).fetchall()
|
||
self.assertTrue(any("set_updated_at" in str(t[0]) for t in triggers))
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|