import json, pytest from pathlib import Path from jof250 import auditing, generation def test_load_manuscript_resolves_inputs_and_bib(synthetic_manuscript): ms = auditing.load_manuscript(synthetic_manuscript) assert ms["title"] == "A Synthetic Manuscript" and "3.2" in ms["abstract"] assert {s["role"] for s in ms["sections"] if s["level"] == 1} >= {"introduction", "results"} assert ms["tables"][0]["label"] == "tab:main" and ms["equations"][0]["label"] == "eq:main" assert "doe2098" in ms["bib_keys"] and ms["bib_dois"]["doe2098"].startswith("10.1111") def test_citation_check_flags_unknown_keys(synthetic_manuscript): ms = auditing.load_manuscript(synthetic_manuscript); c = auditing.citation_check(ms["text"], ms["bib_keys"]) assert c["unknown_keys"] == ["nothere2099"] def test_numeric_preservation(): r = auditing.numeric_preservation("effect 3.2 percent with 1,000 firms in 2018", "effect 3.2 percent with 1000 firms and 7.7 extra") assert r["preserved_rate"] == 1.0 and r["new_in_output"] == ["7.7"] def test_unsupported_claims_detection(): flagged = auditing.unsupported_claims("Leverage causes default in every economy we have ever studied. Table 2 shows that leverage increases default by 3 percent.") assert len(flagged) == 1 and "causes" in flagged[0]["verb"] def test_overlap_detection_on_synthetic_text(monkeypatch): monkeypatch.setattr(auditing, "corpus_shingles", lambda: {"docA": auditing._shingles("the quick brown fox jumps over the lazy dog again and again today")}) r = auditing.overlap_with_corpus("the quick brown fox jumps over the lazy dog again and again today") assert r["max_overlap_share"] == 1.0 and r["top_docs"][0]["doc_id"] == "docA" assert auditing.overlap_with_corpus("completely different words about econometrics and banking")["max_overlap_share"] == 0.0 def test_audit_report_renders(synthetic_manuscript, monkeypatch): monkeypatch.setattr(auditing, "verify_dois", lambda d, limit=60: {"checked": len(d), "resolved": 0, "unresolved": []}) monkeypatch.setattr(auditing, "corpus_shingles", lambda: {}) rep = auditing.audit_manuscript(synthetic_manuscript); txt = auditing.render_report(rep) assert "unknown citation key: nothere2099" in rep["issues"] and "Manuscript audit" in txt def test_evidence_for_claim(synthetic_manuscript): r = auditing.evidence_for_claim(synthetic_manuscript, "the effect is 3.2 percent") assert r["status"] == "supported_by_table" def test_missing_manuscript_fails_loudly(tmp_path): with pytest.raises(FileNotFoundError): auditing.load_manuscript(tmp_path) def test_genre_detection_and_profile_fallback(synthetic_manuscript): ms = auditing.load_manuscript(synthetic_manuscript); det = generation.detect_genre(ms) assert det["genre"] in ("empirical", "theoretical") name, prof, note = generation.profile_for("empirical", "real_estate_housing"); assert name and "outside the corpus" in note def test_engine_writes_proposal_file_without_touching_manuscript(synthetic_manuscript, tmp_path, monkeypatch): before = (synthetic_manuscript / "sections" / "intro.tex").read_text() fake = {"text": "Intro draft with 3.2 percent.\n=== CHANGE LOG ===\n- moves\n=== FLAGS ===\n- none", "json": None, "model": "fake", "stop_reason": "end_turn", "latency_s": 0.1, "usage": {"input": 1, "output": 1, "cache_read": 0, "cache_write": 0}, "cost_usd": 0.0, "request_id": "r", "cached": False, "ts": "t", "effort": "low"} monkeypatch.setattr(generation.llm, "call", lambda *a, **k: fake) monkeypatch.setattr(generation, "retrieve_for", lambda *a, **k: []) monkeypatch.setattr(generation.auditing, "overlap_with_corpus", lambda t, n=8: {"n_shingles": 0, "max_overlap_share": 0.0, "top_docs": []}) out = tmp_path / "prop" / "intro.tex" res = generation.run(synthetic_manuscript, "introduction", mode="write", output=out, condition="E_full_engine") assert out.exists() and out.read_text().startswith("% jof250") and out.with_suffix(".report.md").exists() assert (synthetic_manuscript / "sections" / "intro.tex").read_text() == before assert res["audit"]["final"]["numeric"]["new_in_output"] == [] and res["n_calls"] == 2