import json from pathlib import Path from zotero_kb.cards import CardBuilder from zotero_kb.zotero_reader import ZoteroItemRecord class FakeLlmClient: def generate_card(self, source_bundle: dict[str, object]) -> dict[str, object]: language = str(source_bundle.get("card_language", "en")) return { "summary": "中文卡片摘要。" if language == "zh" else "Merged metadata and attachment text improve drafting support.", "core_claims": [ "中文卡片 claim。" if language == "zh" else "Project-scoped cards reduce irrelevant retrieval.", ], "methods": [ "Combines notes, metadata, and full text.", ], "evidence": [ "The merged pipeline improved citation precision.", ], "citations": [ { "claim": "Project-scoped cards reduce irrelevant retrieval.", "quote": "Project-scoped card retrieval improves citation precision during drafting.", "paraphrase": "Scoped retrieval improved citation precision during drafting.", "quote_source": "attachment_texts", "use_case": "direct_quote", } ], "quotable_passages": [ "Project-scoped card retrieval improves citation precision during drafting.", ], "writing_hints": [ "Use when arguing for scoped retrieval during drafting.", ], "keywords": ["retrieval", "writing"], } def test_build_card_writes_markdown_and_indexes(tmp_path: Path) -> None: item = ZoteroItemRecord( item_key="PAPER0001", title="Card Pipelines for Research Writing", creators=["Alice Smith", "Bob Li"], year="2024", item_type="journalArticle", abstract="Merged metadata and notes improve drafting support.", tags=["llm", "writing"], collection_paths=[["Theory", "Drafting"]], notes=["Merged notes matter."], attachments=[ { "path": str(tmp_path / "zotero" / "storage" / "ATTACH01" / "paper.txt"), "filename": "paper.txt", "content_type": "text/plain", "is_pdf": False, } ], attachment_texts=["This paper studies card pipelines for research writing."], ) builder = CardBuilder(workspace_dir=tmp_path, llm_client=FakeLlmClient()) result = builder.build_or_update(item, "en") expected_path = ( tmp_path / "library" / "collections" / "Theory" / "Drafting" / "Card Pipelines for Research Writing [PAPER0001][en].md" ) assert result.card_path == expected_path card_text = result.card_path.read_text(encoding="utf-8") assert card_text.startswith("---") assert "# Citations" in card_text assert "quote_source: attachment_texts" in card_text assert "locator:" not in card_text cards_index = json.loads((tmp_path / "library" / "index" / "cards.json").read_text(encoding="utf-8")) assert cards_index["PAPER0001"]["en"]["title"] == "Card Pipelines for Research Writing" assert cards_index["PAPER0001"]["en"]["language"] == "en" assert cards_index["PAPER0001"]["en"]["citations"][0]["use_case"] == "direct_quote" assert "locator" not in cards_index["PAPER0001"]["en"]["citations"][0] assert cards_index["PAPER0001"]["en"]["attachments"][0]["filename"] == "paper.txt" assert cards_index["PAPER0001"]["en"]["attachments"][0]["is_pdf"] is False items_index = json.loads((tmp_path / "library" / "index" / "items.json").read_text(encoding="utf-8")) assert items_index["PAPER0001"]["card_path"] == str(expected_path) assert items_index["PAPER0001"]["card_paths"]["en"] == str(expected_path) source_bundle = json.loads( (tmp_path / "library" / "cache" / "source-bundles" / "PAPER0001.en.json").read_text(encoding="utf-8") ) assert source_bundle["item_key"] == "PAPER0001" assert source_bundle["card_language"] == "en" assert source_bundle["attachments"][0]["filename"] == "paper.txt"