"""SQLite 应用状态库测试:建表幂等 / 留痕追加与查询 / 候选增删查 / 任务表 / 旧文件迁移。""" from __future__ import annotations import json from app.storage import db REVIEW_REC = {"candidate_id": "cand_A", "action": "accept", "operator": "张三", "reason": "证据充分", "before": {"status": "pending"}, "after": {"status": "active"}, "time": "2026-08-01 10:00:00"} NODE_REC = {"kind": "node_update", "node_id": "job_X", "operator": "李四", "reason": "校正", "before": {"level": None}, "after": {"level": "高级"}, "time": "2026-08-01 11:00:00"} CAND = {"job_id": "cand_旧候选", "title": "旧候选", "category": "新岗位候选", "required_skills": ["Python"], "status": "pending", "source_count": 10, "company_count": 6} def test_init_schema_idempotent(tmp_path): """重复打开/建表无副作用,四张表齐备。""" p = tmp_path / "state.db" for _ in range(2): conn = db.connect(p) tables = {r[0] for r in conn.execute( "SELECT name FROM sqlite_master WHERE type='table'")} assert {"audit_log", "review_candidates", "tasks", "meta"} <= tables conn.close() def test_audit_append_and_query(tmp_path): """留痕追加后按新→旧返回,结构还原为旧 jsonl 行,支持过滤与 limit。""" p = tmp_path / "state.db" db.append_audit(p, REVIEW_REC) db.append_audit(p, NODE_REC) recs = db.get_audit(p) assert [r["operator"] for r in recs] == ["李四", "张三"] # 新→旧 assert recs[1]["candidate_id"] == "cand_A" and recs[1]["action"] == "accept" assert recs[1]["before"] == {"status": "pending"} assert recs[0]["kind"] == "node_update" and recs[0]["after"] == {"level": "高级"} assert db.get_audit(p, node_id="cand_A") == [recs[1]] # 过滤 assert len(db.get_audit(p, limit=1)) == 1 # limit def test_candidates_crud(tmp_path): """候选:save/get/list/delete,save 为按 job_id 的 upsert。""" p = tmp_path / "state.db" assert db.list_candidates(p) == [] db.save_candidate(p, CAND) db.save_candidate(p, {**CAND, "job_id": "cand_B", "title": "另一个"}) db.save_candidate(p, {**CAND, "title": "旧候选-改"}) # upsert 覆盖 assert [c["job_id"] for c in db.list_candidates(p)] == ["cand_旧候选", "cand_B"] assert db.get_candidate(p, "cand_旧候选")["title"] == "旧候选-改" assert db.get_candidate(p, "cand_不存在") is None db.delete_candidate(p, "cand_B") assert [c["job_id"] for c in db.list_candidates(p)] == ["cand_旧候选"] def test_tasks_upsert_get_list(tmp_path): """任务表:upsert 更新状态与 payload,get/list 可读回。""" p = tmp_path / "state.db" db.upsert_task(p, "t1", kind="jd_import", status="pending", payload={"status": "pending"}) db.upsert_task(p, "t1", kind="jd_import", status="done", payload={"status": "done", "result": {"jobs": 18}}) row = db.get_task(p, "t1") assert row["status"] == "done" and row["payload"]["result"]["jobs"] == 18 assert db.get_task(p, "不存在") is None assert [t["task_id"] for t in db.list_tasks(p)] == ["t1"] def test_overrides_crud(tmp_path): """人工覆写层:save/get/list/delete,save 为按 name 的 upsert。""" p = tmp_path / "state.db" assert db.list_overrides(p) == [] db.save_override(p, "cand_X", {"kind": "job_accept", "job": {"job_id": "cand_X"}}) db.save_override(p, "job_Y", {"kind": "node_update", "node_id": "job_Y", "updates": {"level": "高级"}}) db.save_override(p, "cand_X", {"kind": "job_accept", "job": {"job_id": "cand_X", "title": "改"}}) # upsert 覆盖 assert db.get_override(p, "cand_X")["job"]["title"] == "改" assert db.get_override(p, "不存在") is None assert [o["kind"] for o in db.list_overrides(p)] == ["job_accept", "node_update"] db.delete_override(p, "cand_X") assert [o["kind"] for o in db.list_overrides(p)] == ["node_update"] def test_legacy_migration(tmp_path): """旧 audit.jsonl / pending.json 一次性导入:表空才迁、原文件保留、重复打开不重复迁。""" audit = tmp_path / "audit.jsonl" audit.write_text("\n".join(json.dumps(r, ensure_ascii=False) for r in (REVIEW_REC, NODE_REC)) + "\n", encoding="utf-8") pending = tmp_path / "pending.json" pending.write_text(json.dumps([CAND], ensure_ascii=False), encoding="utf-8") p = tmp_path / "state.db" db.connect(p, legacy_audit=audit, legacy_pending=pending).close() recs = db.get_audit(p) assert len(recs) == 2 and recs[1]["candidate_id"] == "cand_A" assert [c["job_id"] for c in db.list_candidates(p)] == ["cand_旧候选"] assert audit.exists() and pending.exists() # 原文件保留 # 再次打开不重复迁移(meta 标记 + 表非空双保险) db.connect(p, legacy_audit=audit, legacy_pending=pending).close() assert len(db.get_audit(p)) == 2 and len(db.list_candidates(p)) == 1 # 迁移后新写入的数据不受影响 db.append_audit(p, REVIEW_REC) assert len(db.get_audit(p)) == 3