"""SQLite 数据层离线测:写入幂等 + 拍平 + 分页筛选(临时库,不碰 data/app.db)。""" from __future__ import annotations from acquisition import store def _db(tmp_path): return store.connect(tmp_path / "t.db") def test_connect_uses_ck_sqlite_path(tmp_path, monkeypatch): db = tmp_path / "legacy_data" / "app.db" monkeypatch.setenv("CK_SQLITE_PATH", str(db)) c = store.connect() try: assert db.exists() assert store.list_runs(c) == [] finally: c.close() def test_insert_queries_idempotent_and_axes(tmp_path): c = _db(tmp_path) items = [{"query": "q1", "实质": "军人", "阶段": "灵感"}, {"query": "q2", "形式": "排比"}] assert store.insert_queries(c, "r1", "多轴", items, ts=100) == 2 # 重导同 (run,method) → 覆盖不叠加 assert store.insert_queries(c, "r1", "多轴", items, ts=100) == 2 res = store.list_queries(c, method="多轴") assert res["total"] == 2 assert res["items"][0]["axes"] == {"实质": "军人", "阶段": "灵感"} # query 不进 axes assert "query" not in res["items"][0]["axes"] def test_insert_queries_skips_empty_query(tmp_path): c = _db(tmp_path) n = store.insert_queries(c, "r1", "m", [{"query": ""}, {"实质": "x"}, {"query": "ok"}], ts=1) assert n == 1 def test_search_flatten_topk_and_ok(tmp_path): c = _db(tmp_path) recs = [ {"method": "M", "query": "q1", "douyin": {"ok": [{"title": "t", "url": "u", "cover": "/c.jpg", "video": "/v.mp4"}, {"title": "t2", "url": "u2", "cover": "/c2.jpg", "video": "/v2.mp4"}], "error": None}, "weixin": {"ok": [{"title": "w", "url": "wu", "cover": "/wc.jpg", "nick": "号"}], "error": None}}, {"method": "M", "query": "q2", "douyin": {"ok": [], "error": "未搜到"}, "weixin": {"ok": [], "error": "搜索失败: x"}, "xiaohongshu": {"ok": [{"title": "x", "url": "xu", "cover": "/xc.jpg", "nick": "薯"}], "error": None}}, ] # q1(无 xhs 键,向后兼容跳过): 2 抖音 + 1 微信 = 3;q2: 抖音空 + 微信空 + 1 小红书 = 3 → 共 6 assert store.insert_search_results(c, "s1", recs, ts=5) == 6 xhs = store.list_search(c, platform="xiaohongshu", ok=True) assert xhs["total"] == 1 and xhs["items"][0]["extra"]["nick"] == "薯" ok_dy = store.list_search(c, platform="douyin", ok=True) assert ok_dy["total"] == 2 # q1 的两个视频 assert {i["video"] for i in ok_dy["items"]} == {"/v.mp4", "/v2.mp4"} # 搜过但无结果 → ok=0 行带 error(按 query 取得到,按钮才显示「搜过·无结果」) bad = store.list_search(c, platform="weixin", ok=False) assert bad["total"] == 1 and "失败" in bad["items"][0]["extra"]["error"] good_wx = store.list_search(c, platform="weixin", ok=True) assert good_wx["items"][0]["extra"]["nick"] == "号" # summary:q1 有 3 个结果(抖音2+微信1);q2 抖音/微信空 + 小红书 1 → ok=1, total=3 行 s = store.search_summary(c) assert s["q1"]["ok"] == 3 and s["q2"]["ok"] == 1 and s["q2"]["total"] == 3 def test_pagination(tmp_path): c = _db(tmp_path) store.insert_queries(c, "r", "m", [{"query": f"q{i}"} for i in range(25)], ts=1) p1 = store.list_queries(c, page=1, size=10) p3 = store.list_queries(c, page=3, size=10) assert p1["total"] == 25 and len(p1["items"]) == 10 assert len(p3["items"]) == 5 # 余 5 条 assert p1["items"][0]["query"] != p3["items"][0]["query"] def test_runs_and_methods(tmp_path): c = _db(tmp_path) store.insert_queries(c, "rq", "方法A", [{"query": "a"}], ts=2) store.insert_search_results(c, "rs", [{"method": "方法B", "query": "b", "douyin": None, "weixin": None}], ts=3) runs = {r["run_id"]: r["kind"] for r in store.list_runs(c)} assert runs == {"rq": "queries", "rs": "search"} assert store.list_methods(c, "queries") == ["方法A"] assert store.list_methods(c, "search_results") == ["方法B"]