Spaces:
Running
Running
| """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.") | |