"""SQLite store: paper lifecycle (upsert/freeze/rollover), digest idempotency, deliveries.""" from __future__ import annotations from datetime import date from _fakes import WEEK, make_paper, make_payload, make_summary from sebrief.models import DigestResult from sebrief.store import Store def make_store(tmp_path) -> Store: return Store(tmp_path / "data" / "test.sqlite3") def make_result(papers=None, degraded=False) -> DigestResult: payload = make_payload(papers=papers) return DigestResult( payload=payload, digest=None, markdown="# md", model_used="m", degraded=degraded ) # ----------------------------------------------------------------------------------- papers def test_upsert_inserts_and_counts_new(tmp_path): with make_store(tmp_path) as store: p1, p2 = make_paper("10.1/a"), make_paper("10.1/b") assert store.upsert_papers([p1, p2]) == 2 assert store.upsert_papers([p1]) == 0 # re-collection is not new assert store.paper_count() == 2 def test_upsert_refreshes_undigested_metadata(tmp_path): with make_store(tmp_path) as store: store.upsert_papers([make_paper("10.1/a", abstract="")]) store.upsert_papers([make_paper("10.1/a", abstract="now with abstract")]) [paper] = store.pending_papers(WEEK) assert paper.abstract == "now with abstract" def test_digested_papers_are_frozen_and_skipped(tmp_path): with make_store(tmp_path) as store: store.upsert_papers([make_paper("10.1/a", title="original")]) store.mark_digested(["10.1/a"], WEEK) store.upsert_papers([make_paper("10.1/a", title="rewritten")]) # not pending for a later week, and metadata unchanged assert store.pending_papers("2026-W37") == [] [paper] = store.pending_papers(WEEK) # --force re-run of the same week still sees it assert paper.title == "original" def test_pending_includes_undigested_leftovers_and_orders_by_date(tmp_path): with make_store(tmp_path) as store: old = make_paper("10.1/old", published_date=date(2026, 8, 20)) # last week's late arrival new = make_paper("10.1/new", published_date=date(2026, 9, 2)) undated = make_paper("10.1/undated", published_date=None) store.upsert_papers([new, undated, old]) dois = [p.doi for p in store.pending_papers(WEEK)] assert dois == ["10.1/old", "10.1/new", "10.1/undated"] # dated first (ascending), undated last def test_paper_roundtrip_preserves_fields(tmp_path): with make_store(tmp_path) as store: original = make_paper("10.1/a", authors=["김철수", "Bob"], topics=["Testing", "LLM"]) store.upsert_papers([original]) [loaded] = store.pending_papers(WEEK) assert loaded == original # ---------------------------------------------------------------------------- paper summaries def test_paper_summaries_roundtrip_and_survive_metadata_refresh(tmp_path): with make_store(tmp_path) as store: store.upsert_papers([make_paper("10.1/a")]) summary = make_summary(tldr="한 줄.") store.save_paper_summaries({"10.1/a": summary}) [paper] = store.pending_papers(WEEK) assert paper.summary == summary # a later re-collection refreshes metadata but must not clear the summary store.upsert_papers([make_paper("10.1/a", abstract="updated")]) [paper] = store.pending_papers(WEEK) assert paper.abstract == "updated" and paper.summary == summary def test_paper_summaries_allowed_on_frozen_papers(tmp_path): with make_store(tmp_path) as store: store.upsert_papers([make_paper("10.1/a")]) store.mark_digested(["10.1/a"], WEEK) store.save_paper_summaries({"10.1/a": make_summary()}) [paper] = store.pending_papers(WEEK) # digest_week = WEEK: visible on --force re-run assert paper.summary is not None def test_migration_adds_summary_column_to_old_db(tmp_path): import sqlite3 db = tmp_path / "data" / "old.sqlite3" db.parent.mkdir(parents=True) conn = sqlite3.connect(db) conn.executescript( """ CREATE TABLE papers ( doi TEXT PRIMARY KEY, title TEXT NOT NULL, abstract TEXT NOT NULL DEFAULT '', authors TEXT NOT NULL DEFAULT '[]', venue TEXT NOT NULL DEFAULT '', published_date TEXT, oa_url TEXT, pdf_url TEXT, topics TEXT NOT NULL DEFAULT '[]', source TEXT NOT NULL DEFAULT '', collected_at TEXT, digest_week TEXT ); INSERT INTO papers (doi, title) VALUES ('10.1/legacy', 'Legacy Paper'); """ ) conn.commit() conn.close() with Store(db) as store: [paper] = store.pending_papers(WEEK) assert paper.doi == "10.1/legacy" and paper.summary is None store.save_paper_summaries({"10.1/legacy": make_summary()}) [paper] = store.pending_papers(WEEK) assert paper.summary is not None # ---------------------------------------------------------------------------------- digests def test_digest_idempotency_and_upsert(tmp_path): with make_store(tmp_path) as store: assert store.has_digest(WEEK) is False store.save_digest(make_result()) assert store.has_digest(WEEK) is True store.save_digest(make_result(degraded=True)) # --force resend overwrites row = store._conn.execute("SELECT degraded FROM digests WHERE week = ?", (WEEK,)).fetchone() assert row["degraded"] == 1 def test_deliveries(tmp_path): with make_store(tmp_path) as store: store.save_digest(make_result()) store.mark_sent(WEEK, "file", "/tmp/2026-W36.md") store.mark_sent(WEEK, "file", "/tmp/2026-W36.md (resend)") rows = store.deliveries_for(WEEK) assert [r["channel"] for r in rows] == ["file", "file"] assert rows[1]["receipt"].endswith("(resend)")