#!/usr/bin/env python3 """Checks for preprint DOIs from any server (policy 2026-09-28) and the exclusivity confirmation on both routes. Offline: the DOI registries are replaced by canned records, no mail, no pings; every write lands in one temporary directory that is removed at the end, pass or fail. Run from the repo root with the venv: .venv/bin/python intake/scripts/test_preprint_doi.py """ from __future__ import annotations import email as _email import email.policy as _policy import hashlib import hmac import json import pathlib import shutil import sys import tempfile import time from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) PAPER_PDF = Path.home() / "icsac-submissions" / "ICSAC-SUB-00008" / "paper.pdf" TEST_ORCID = "0000-0002-1825-0097" failures: list[str] = [] def check(cond: bool, name: str) -> None: print((" ok " if cond else " FAIL ") + name) if not cond: failures.append(name) # Canned registry answers, keyed by URL fragment. RA = {"10.20944/preprints202607.0102.v1": "Crossref", "10.1101/2024.09.14.613029": "Crossref", "10.1038/nature12373": "Crossref", "10.1364/opticaopen.29459153.v1": "Crossref", "10.48550/arXiv.2401.12345": "DataCite", "10.5281/zenodo.1": "DataCite", "10.51094/jxiv.1": "JaLC"} CR = { "10.20944/preprints202607.0102.v1": {"type": "posted-content", "subtype": "preprint", "title": ["A test paper on boundaries"], "publisher": "MDPI AG"}, "10.1101/2024.09.14.613029": {"type": "posted-content", "subtype": "preprint", "title": ["Something else entirely about proteins"], "institution": [{"name": "bioRxiv"}]}, "10.1038/nature12373": {"type": "journal-article", "title": ["Nanometre-scale thermometry"], "container-title": ["Nature"]}, "10.1364/opticaopen.29459153.v1": {"type": "posted-content", "subtype": "preprint", "title": ["X"], "relation": {"is-preprint-of": [{"id": "10.1364/OE.572415"}]}}, } DC = {"10.48550/arXiv.2401.12345": {"types": {"resourceTypeGeneral": "Preprint"}, "publisher": "arXiv", "titles": [{"title": "A test paper on boundaries"}]}, "10.5281/zenodo.1": {"types": {"resourceTypeGeneral": "JournalArticle"}, "publisher": {"name": "Zenodo"}, "titles": [{"title": "A test paper on boundaries"}]}} NETWORK_DOWN = {"on": False} def fake_get(url: str, timeout: float): import urllib.parse if NETWORK_DOWN["on"]: raise OSError("network down") tail = urllib.parse.unquote(url.split("/ra/", 1)[-1] if "/ra/" in url else url.split("/works/", 1)[-1] if "/works/" in url else url.split("/dois/", 1)[-1]) if "/ra/" in url: return [{"DOI": tail, "RA": RA[tail]}] if tail in RA else [{"DOI": tail, "status": "DOI does not exist"}] if "/works/" in url: return {"message": CR[tail]} return {"data": {"attributes": DC[tail]}} tmp = Path(tempfile.mkdtemp(prefix="preprint-")) real_home = pathlib.Path.home try: import preprint_check as pc pc._get = fake_get print("1. the check") r = pc.check("10.20944/preprints202607.0102.v1", title="A test paper on boundaries") check(r["verified"] and r["type"] == "posted-content/preprint" and not r["flags"], "Preprints.org preprint: accepted, clean") r = pc.check("10.1101/2024.09.14.613029", title="A test paper on boundaries") check(r["server"] == "bioRxiv" and any("title differs" in f for f in r["flags"]), "bioRxiv preprint with another title: accepted, flagged") for d, why in (("10.1038/nature12373", "a journal article"), ("10.1364/opticaopen.29459153.v1", "a preprint already published"), ("10.9999/nope", "a DOI that does not exist")): try: pc.check(d) check(False, f"refused: {why}") except pc.Refused: check(True, f"refused: {why}") r = pc.check("10.48550/arXiv.2401.12345") check(r["verified"] and r["type"] == "Preprint" and not r["flags"], "arXiv (DataCite Preprint): accepted, clean") r = pc.check("10.5281/zenodo.1") check(r["server"] == "Zenodo" and any("JournalArticle" in f for f in r["flags"]), "Zenodo filed as a journal article: accepted, flagged") r = pc.check("10.51094/jxiv.1") check(r["ra"] == "JaLC" and not r["verified"] and r["flags"], "another agency (JaLC): accepted, flagged") NETWORK_DOWN["on"] = True r = pc.check("10.20944/preprints202607.0102.v1") NETWORK_DOWN["on"] = False check(not r["verified"] and any("could not be reached" in f for f in r["flags"]), "registries down: fails open, flagged") check(pc.normalize("https://doi.org/10.1101/2024.09.14.613029") == "10.1101/2024.09.14.613029", "resolver URL normalised") check(pc.normalize("arXiv:2401.12345v3") == "10.48550/arXiv.2401.12345", "arXiv: prefix and version normalised") check(pc.normalize("not a doi") is None, "garbage refused") print("2. /api/submit (T2, signed like the proxy)") import httpx from fastapi.testclient import TestClient from intake import intake_server as iss iss.HMAC_SECRET = b"preprint-test-secret" iss.TEST_ORCID_WHITELIST = frozenset({TEST_ORCID}) subs = tmp / "subs" iss.SUBMISSIONS_ROOT = subs iss.QUEUE_DIR = subs / "queue" iss.COUNTER_FILE = subs / ".counter" iss.TEST_SUBMISSIONS_ROOT = subs / "test" iss.TEST_QUEUE_DIR = subs / "test" / "queue" iss._audit_append = lambda entry, test_mode=False: None _n = iter(range(1791000000, 1791000100)) iss._allocate_test_sub_id = lambda: f"ICSAC-SUB-TEST-{next(_n)}" client = TestClient(iss.app) pdf_bytes = PAPER_PDF.read_bytes() def post(data: dict, with_pdf: bool) -> tuple[int, dict]: req = httpx.Request("POST", "http://intake/api/submit", data=data, files={"pdf": ("paper.pdf", pdf_bytes, "application/pdf")} if with_pdf else None) body = req.read() ts = str(int(time.time())) # Signature v2 (2026-09-29): ts, ORCID, name (none sent), tier, then the body. sig = hmac.new(iss.HMAC_SECRET, f"icsac-v2\n{ts}\n{TEST_ORCID}\n\nt2\n".encode() + body, hashlib.sha256).hexdigest() resp = client.post("/api/submit", content=body, headers={ "content-type": req.headers["content-type"], "x-icsac-signature": f"v2={sig}", "x-icsac-timestamp": ts, "x-icsac-auth-orcid": TEST_ORCID, "x-icsac-test-tier": "t2"}) try: return resp.status_code, resp.json() except Exception: return resp.status_code, {"raw": resp.text[:300]} base = {"name": "Test Author", "email": "author@example.com", "orcid": TEST_ORCID, "coi": "true", "exclusivity_acknowledged": "true", "code_data_available": "no"} upload = dict(base, mode="upload", deposit_consent="true", title="A test paper on boundaries", abstract="An abstract long enough to pass the fifty-character minimum for uploads.", keywords="testing", license="cc-by-4.0", resource_type="preprint", publication_date="2026-09-28", subject="", funding="", creators=json.dumps([{"name": "Test Author", "orcid": TEST_ORCID}]), related_identifiers="[]") def record(resp: dict) -> dict: sid = resp.get("sub_id") or "" return json.loads((subs / "test" / sid / "submission.json").read_text()) if sid else {} def dirs() -> int: return len([p for p in (subs / "test").iterdir() if p.name.startswith("ICSAC-SUB-TEST-")]) if (subs / "test").exists() else 0 st, resp = post(dict(upload, preprint_doi="https://doi.org/10.20944/preprints202607.0102.v1"), True) rec = record(resp) check(st in (200, 202) and rec.get("preprint_doi") == "10.20944/preprints202607.0102.v1" and (rec.get("preprint_meta") or {}).get("verified"), f"upload + Preprints.org DOI: stored, verified ({st})") check(rec.get("form", {}).get("exclusivity_acknowledged") is True, "upload: the exclusivity confirmation is stored") up_rec = rec st, resp = post(dict(upload), True) check(st in (200, 202) and record(resp).get("preprint_doi") is None, "upload without a preprint: none stored") before = dirs() st, resp = post(dict(upload, preprint_doi="10.1038/nature12373"), True) check(st == 422 and resp.get("error") == "preprint_doi_refused" and "Nature" in json.dumps(resp), f"upload + a journal article DOI: refused with the journal named ({st})") check(dirs() == before, "a refused preprint leaves no submission behind") st, resp = post(dict(upload, preprint_doi="banana"), True) check(st == 422 and resp.get("error") == "preprint_doi_invalid", f"upload + garbage: refused ({st})") st, resp = post(dict(base, mode="doi", doi="10.5281/zenodo.1"), False) check(st in (200, 202) and record(resp).get("form", {}).get("exclusivity_acknowledged") is True, f"DOI route: the exclusivity confirmation is stored ({st})") st, resp = post(dict(base, mode="doi", doi="10.1101/2024.09.14.613029"), False) check(st == 422 and "submit/upload?preprint_doi=10.1101/2024.09.14.613029" in json.dumps(resp), f"DOI route + a bioRxiv DOI: sent to the upload form with the DOI ({st})") print("3. the deposit and the emails") import crossref_deposit as cd check(cd.preprint_doi(up_rec) == "10.20944/preprints202607.0102.v1", "the pipeline reads the upload route's preprint") sub = dict(up_rec, sub_id="ICSAC-SUB-00099") xml = cd.build_deposit_xml(sub, doi="10.67697/icsac.2026.099", landing_url="https://icsacinstitute.org/publications/t", pdf_url=None, content_type="journal-article") cd.validate_xml(xml) check(b'relationship-type="hasPreprint"' in xml and b"10.20944/preprints202607.0102.v1" in xml, "upload + preprint: the deposit validates and links the preprint") from intake import notify_author as na pathlib.Path.home = classmethod(lambda cls: tmp / "home") common = dict(to="author@example.com", title="T", author_name="A", verdict="accept", panel_report_md="", rqc_md="", tier=2, compaction_manifest={"_failure": "test"}, approval_url="https://icsacinstitute.org/approve/?t=X", objection_deadline="October 5, 2026") out = tmp / "home" / "icsac-submissions" / "test" / "_outbox" def body(sid: str) -> str: p = out / f"{sid}.eml" if not p.exists(): return "" m = _email.message_from_bytes(p.read_bytes(), policy=_policy.default) return "".join(x.get_payload(decode=True).decode() for x in m.walk() if x.get_content_type() == "text/plain") na.send_decision(sub_id="ICSAC-SUB-TEST-A", source="upload", source_ref="paper.pdf", preprint_doi="10.20944/preprints202607.0102.v1", exclusivity_confirmed=True, **common) t = body("ICSAC-SUB-TEST-A") check("keeps its own DOI (10.20944/preprints202607.0102.v1)" in t and "not under review elsewhere, as confirmed" in t and "has no other DOI" not in t, "upload + preprint: the email names the preprint and the confirmation") na.send_decision(sub_id="ICSAC-SUB-TEST-B", source="doi", source_ref="10.5281/zenodo.1", preprint_doi="10.5281/zenodo.1", exclusivity_confirmed=False, **common) t = body("ICSAC-SUB-TEST-B") check("keeps its own DOI" in t and "as confirmed at submission" not in t, "a paper whose confirmation was never recorded is not told it confirmed") pathlib.Path.home = real_home src = (ROOT / "intake" / "apply_decision.py").read_text() check("exclusivity_confirmed=form.get(\"exclusivity_acknowledged\") is True" in src, "the accept passes the recorded confirmation") finally: pathlib.Path.home = real_home shutil.rmtree(tmp, ignore_errors=True) print(f"\n{'ALL GREEN' if not failures else f'{len(failures)} FAILURE(S)'}") sys.exit(1 if failures else 0)