challenge-submission / test_validator.py
NKalavros's picture
Interactive validator feedback, 5-per-hypothesis quota, team-name pooling, optional wiki link
ef22719 verified
Raw
History Blame Contribute Delete
4.75 kB
"""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.")