Files
paper-atlas/tests/test_openreview_snapshot.py
codex-bot 1c18cbe03a
Metadata integrity checks / metadata (push) Successful in 1m12s
Package Paper Atlas with organized modules and a unified CLI
2026-11-06 17:32:24 +08:00

109 lines
6.0 KiB
Python

import hashlib
import io
import json
from pathlib import Path
import sqlite3
import tarfile
import tempfile
import unittest
from paper_atlas.collectors.openreview import collect,decision_record,paper_record
from paper_atlas.ingestion.records import ingest_file
class SnapshotTests(unittest.TestCase):
def setUp(self):
self.tmp=tempfile.TemporaryDirectory();self.addCleanup(self.tmp.cleanup)
self.root=Path(self.tmp.name)
def paper(self,forum='paper1',v2=True):
content={'title':'A public paper','abstract':'A public abstract','authors':['Jane Doe'],
'authorids':['private@example.org'],'keywords':['learning']}
if v2:content={key:{'value':value} for key,value in content.items()}
number=int(forum[-1]) if forum[-1].isdigit() else 42
return {'id':forum,'forum':forum,'number':number,'readers':['everyone'],'cdate':1760000000000,
'invitations':['ICLR.cc/2026/Conference/-/Submission'],'content':content}
def decision(self,forum='paper1',identifier='decision1',value='Reject'):
return {'id':identifier,'forum':forum,'_paper_forum':forum,'readers':['everyone'],
'invitations':['ICLR.cc/2026/Conference/Submission42/-/Decision'],
'signatures':['ICLR.cc/2026/Conference/Program_Chairs'],'cdate':1770000000000,
'content':{'decision':{'value':value},'comment':{'value':'PRIVATE REVIEW BODY'}}}
def archive(self,papers,reviews):
path=self.root/'snapshot.tar.bz2'
with tarfile.open(path,'w:bz2') as archive:
# Real release order is not guaranteed; decisions precede submissions.
for kind,rows in [('reviews',reviews),('papers',papers)]:
data=(''.join(json.dumps(row)+'\n' for row in rows)).encode()
entry=tarfile.TarInfo('data/iclr_2026/'+kind+'.jsonl');entry.size=len(data)
archive.addfile(entry,io.BytesIO(data))
return path,hashlib.sha256(path.read_bytes()).hexdigest()
def test_public_v1_and_v2_metadata_omit_authorids_and_review_bodies(self):
for v2 in (False,True):
row,reason=paper_record(self.paper(v2=v2),2026)
self.assertIsNone(reason);self.assertEqual(row['authors'],['Jane Doe'])
self.assertNotIn('private@example.org',json.dumps(row))
self.assertEqual(row['identifiers']['openreview'],'paper1')
note=self.paper();note['readers']=['chairs']
self.assertEqual(paper_record(note,2026)[1],'not_public')
def test_only_public_signed_formal_decisions_count(self):
note=self.decision();row,reason=decision_record(note,2026)
self.assertIsNone(reason);self.assertEqual(row['status'],'rejected')
self.assertNotIn('PRIVATE REVIEW BODY',json.dumps(row))
note['invitations']=['ICLR.cc/2026/Conference/Submission42/-/Official_Review']
self.assertEqual(decision_record(note,2026)[1],'not_decision')
note=self.decision();note['signatures']=['ICLR.cc/2026/Conference/Submission42/Reviewer_1']
self.assertEqual(decision_record(note,2026)[1],'unverified_signature')
note=self.decision();note['readers']=['chairs']
self.assertEqual(decision_record(note,2026)[1],'not_public')
note=self.decision(value='Borderline Accept')
self.assertEqual(decision_record(note,2026)[0]['status'],'unknown')
def test_unordered_snapshot_import_is_idempotent_and_preserves_unknown_and_orphans(self):
path,sha=self.archive([self.paper(),self.paper('paper2'),self.paper('paper3')],
[self.decision(),self.decision('paper2','decision2','Accept (poster)'),self.decision('absent','orphan')])
out=self.root/'out';manifest=collect(path,out,[2026],sha)[2026]
self.assertEqual(manifest['submission_status_counts'],{'rejected':1,'accepted':1,'unknown':1})
self.assertEqual(manifest['unmatched_public_decisions'],1)
self.assertEqual(len(Path(manifest['unmatched_evidence_file']).read_text().splitlines()),1)
rows=[json.loads(line) for line in Path(manifest['file']).read_text().splitlines()]
self.assertEqual(rows[2]['decision_events'],[])
self.assertNotIn('PRIVATE REVIEW BODY',json.dumps(rows))
db=self.root/'db.sqlite'
ingest_file(db,manifest['file']);ingest_file(db,manifest['file'])
with sqlite3.connect(db) as connection:
self.assertEqual(connection.execute('SELECT count(*) FROM submissions').fetchone()[0],3)
self.assertEqual(connection.execute('SELECT count(*) FROM decisions').fetchone()[0],2)
self.assertEqual(connection.execute('PRAGMA foreign_key_check').fetchall(),[])
def test_identical_copies_coalesce_and_conflicting_copies_fail(self):
paper=self.paper();decision=self.decision()
path,sha=self.archive([paper,paper],[decision,decision])
manifest=collect(path,self.root/'out',[2026],sha)[2026]
self.assertEqual(manifest['count'],1)
self.assertEqual(manifest['archive_counts']['duplicate_decisions'],1)
different=self.paper();different['content']['title']={'value':'Conflicting title'}
path,sha=self.archive([paper,different],[decision])
with self.assertRaisesRegex(ValueError,'Conflicting copies'):
collect(path,self.root/'bad',[2026],sha)
self.assertFalse((self.root/'bad'/'openreview-snapshot-2026-main.jsonl').exists())
def test_checksum_failure_does_not_publish_output(self):
path,sha=self.archive([self.paper()],[self.decision()])
with self.assertRaisesRegex(ValueError,'checksum mismatch'):
collect(path,self.root/'out',[2026],'0'*64)
self.assertFalse((self.root/'out').exists())
def test_legacy_invitation_and_plain_decision_value(self):
note=self.decision();note['invitation']=note.pop('invitations')[0]
note['content']['decision']='Desk Reject'
self.assertEqual(decision_record(note,2026)[0]['status'],'desk_rejected')
note['_paper_forum']='wrong'
self.assertEqual(decision_record(note,2026)[1],'forum_mismatch')
if __name__=='__main__':unittest.main()