"""Smoke tests for the submission validator. Run: ``python test_validator.py``. No pytest dependency — plain asserts against real sample files in the repo plus a deliberately corrupted in-memory copy. Exits non-zero on first failure. """ from __future__ import annotations import csv import os import tempfile import openpyxl import hypotheses import validator REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) GOOD_XLSX = [ ("260428_coalescing_with_John2/M3H1_decreases_phagocytosis_papers_and_findings_Oishi.xlsx", hypotheses.BY_SLUG["3h1-decreased-phagocytosis"]), ("260421_New_files/abca1-reduces-ad-risk_submission.xlsx", None), ("260421_New_files/apoe4-increases-amyloid_submission.xlsx", None), ] def _p(rel: str) -> str: return os.path.join(REPO, rel) def check(name: str, cond: bool, detail: str = "") -> None: status = "ok " if cond else "FAIL" print(f"[{status}] {name}{(' — ' + detail) if detail else ''}") if not cond: raise SystemExit(1) def test_good_xlsx() -> None: for rel, hyp in GOOD_XLSX: parsed = validator.parse(_p(rel)) rep = validator.validate(parsed, hyp) n = rep.summary check(f"good xlsx parses+validates: {os.path.basename(rel)}", rep.ok, f"{n['n_papers']}p/{n['n_findings']}f errors={rep.errors}") check(f" has papers: {os.path.basename(rel)}", n["n_papers"] > 0) def test_csv_roundtrip() -> None: """Export a good xlsx to CSV and confirm it validates identically.""" rel, hyp = GOOD_XLSX[0] parsed_x = validator.parse(_p(rel)) grid, _ = validator._load_grid(_p(rel)) # noqa: SLF001 - intentional in test with tempfile.NamedTemporaryFile("w", suffix=".csv", delete=False, newline="") as f: csv.writer(f).writerows( [["" if c is None else c for c in row] for row in grid]) csv_path = f.name parsed_c = validator.parse(csv_path) os.unlink(csv_path) check("csv export validates", validator.validate(parsed_c, hyp).ok) check("csv paper count matches xlsx", len(parsed_c.papers) == len(parsed_x.papers), f"{len(parsed_c.papers)} vs {len(parsed_x.papers)}") def test_corrupted() -> None: """Build a broken sheet: bad relevance (5), literal 'None' DOI, a paper with no findings.""" wb = openpyxl.Workbook(); ws = wb.active ws.append(validator.CANONICAL_HEADERS) ws.append([""] + [None] * 13) # empty hypothesis ws.append([None, "None", "PMID", "111", None, "P1", None]) # 'None' DOI, no paper_id useful ws.append([None, None, None, None, "a finding", "P1.F1", 5]) # relevance out of range ws.append([None, "10.1/x", "PMID", "222", None, "P2", None]) # P2 has no findings with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as f: wb.save(f.name); path = f.name parsed = validator.parse(path) os.unlink(path) rep = validator.validate(parsed, hypotheses.BY_CODE["3H1"]) check("corrupted file fails validation", not rep.ok, f"errors={len(rep.errors)}") joined = " | ".join(rep.errors) check(" flags empty hypothesis", "A2" in joined) check(" flags out-of-range relevance", "out of the 0–1" in joined) check(" flags paper with no findings", "no findings" in joined) err_issues = [i for i in rep.issues if i.severity == "error"] check(" every error has an actionable fix", all(i.fix for i in err_issues), f"{len(err_issues)} error issues") check(" every error is anchored to a location", all(i.anchor for i in err_issues)) a2 = next(i for i in rep.issues if "A2" in i.message) check(" empty-hypothesis issue anchors to A2", a2.anchor == "A2") def test_is_valid_url() -> None: check("accepts https url", validator.is_valid_url("https://github.com/you/repo")) check("accepts http url", validator.is_valid_url("http://example.com/wiki")) check("rejects bare text", not validator.is_valid_url("not a url")) check("rejects scheme-less host", not validator.is_valid_url("github.com/you")) check("rejects empty", not validator.is_valid_url("")) def test_canonical_emit() -> None: parsed = validator.parse(_p(GOOD_XLSX[0][0])) xlsx_bytes, js = validator.to_canonical(parsed) check("canonical xlsx is non-empty", len(xlsx_bytes) > 0) check("json has hypothesis+papers", "hypothesis" in js and len(js["papers"]) > 0) check("json findings_text is plain strings", all(isinstance(t, str) for p in js["papers"] for t in p["findings_text"])) if __name__ == "__main__": test_good_xlsx() test_csv_roundtrip() test_corrupted() test_is_valid_url() test_canonical_emit() print("\nAll validator smoke tests passed.")