import json from pathlib import Path import tempfile import unittest from unittest.mock import patch from urllib.error import HTTPError from paper_atlas.collectors.iclr import asset_urls, collect_year, discover, parse_program from paper_atlas.ingestion.records import ingest_file from paper_atlas.db.store import Store class ICLRTests(unittest.TestCase): def event(self,identifier=1,sourceid=10,**updates): row={'id':identifier,'sourceid':sourceid,'sourceurl':'https://openreview.net/group?id=ICLR.cc/2026/Conference', 'name':'A reliable paper on abstract metadata indexing','authors':[{'fullname':'Jane Doe'},{'fullname':'John Smith'}], 'eventtype':'Poster','event_type':'Poster','decision':'Accept (Oral)', 'paper_url':'https://openreview.net/forum?id=realForum','topic':'Learning->Theory','keywords':[]} return dict(row,**updates) def parse(self,events,abstracts=None,year=2026): return parse_program({'count':len(events),'next':None,'previous':None,'results':events},abstracts or {},year,f'https://iclr.cc/virtual/{year}/papers.html') def test_oral_placeholder_is_not_an_identifier_or_another_submission(self): poster=self.event() oral=self.event(2,-10,eventtype='Oral',event_type='Oral',paper_url='https://openreview.net/forum?id=2026-Oral--10-placeholder') rows,audit=self.parse([oral,poster],{'1':'The real abstract.'}) self.assertEqual(len(rows),1) self.assertEqual(rows[0]['identifiers'],{'openreview':'realForum'}) self.assertEqual(rows[0]['abstract'],'The real abstract.') self.assertEqual(rows[0]['metadata']['presentation_types'],['Oral','Poster']) self.assertEqual(audit['main_event_count'],2) def test_blog_and_journal_presentations_are_not_main_acceptances(self): rows,audit=self.parse([self.event(),self.event(2,20,sourceurl='https://openreview.net/group?id=ICLR.cc/2026/BlogPosts'),self.event(3,30,sourceurl='2026-TMLR-J2C-Spreadsheet')]) self.assertEqual(len(rows),1) self.assertEqual(sum(audit['excluded_events_by_source'].values()),2) self.assertEqual(audit['missing_abstract_count'],1) self.assertIsNone(rows[0]['abstract']) def test_title_revision_is_scoped_to_matching_signed_submission_and_authors(self): poster=self.event(name='AlphaEdit: Null-Space Constrained Knowledge Editing for Language Models') oral=self.event(2,-10,eventtype='Oral',name='AlphaEdit: Null-Space Constrained Model Editing for Language Models') rows,_=self.parse([oral,poster]) self.assertEqual(len(rows),1) self.assertEqual(rows[0]['title'],poster['name']) self.assertEqual(len(rows[0]['metadata']['presentation_titles']),2) with self.assertRaisesRegex(ValueError,'collides'): self.parse([poster,dict(oral,authors=[{'fullname':'Unrelated Author'}])]) def test_truncation_and_identity_collision_fail(self): with self.assertRaisesRegex(ValueError,'count differs'): parse_program({'count':2,'results':[self.event()]},{},2026,'https://iclr.cc/') with self.assertRaisesRegex(ValueError,'collides'): self.parse([self.event(),self.event(2,-10,name='A different paper')]) with self.assertRaisesRegex(ValueError,'Conflicting identifiers'): self.parse([self.event(),self.event(2,10,paper_url='https://openreview.net/forum?id=otherForum')]) def test_legacy_embedded_abstracts_and_paper_links(self): row=self.event(sourceurl=None,abstract='Historical abstract',paper_url=None,paper_pdf_url='https://openreview.net/forum?id=historicalForum',decision='Poster') rows,_=self.parse([row],year=2021) self.assertEqual(rows[0]['abstract'],'Historical abstract') self.assertEqual(rows[0]['identifiers'],{'openreview':'historicalForum'}) rows,_=self.parse([dict(row,paper_pdf_url=None)],year=2020) self.assertEqual(rows[0]['metadata']['identity_state'],'program_only') self.assertEqual(rows[0]['identifiers'],{}) def test_only_referenced_official_assets_and_real_navigation_years(self): html='201320262025' self.assertEqual([y for y,_ in discover(html)],[2013,2026]) html='start("/static/virtual/data/iclr-2026-orals-posters.json",true,"https://other.example/abstracts.json")' self.assertEqual(asset_urls(html,'https://iclr.cc/virtual/2026/papers.html',2026),{'orals-posters':'https://iclr.cc/static/virtual/data/iclr-2026-orals-posters.json'}) def test_missing_split_abstract_asset_does_not_discard_embedded_abstract(self): html=b'start("/static/virtual/data/iclr-2021-orals-posters.json",true,"/static/virtual/data/iclr-2021-abstracts.json")' event=self.event(sourceurl=None,decision='Poster',abstract='Embedded historical abstract') responses=[html,json.dumps({'count':1,'results':[event]}).encode(),HTTPError('https://iclr.cc/abstracts',404,'Not Found',None,None)] with tempfile.TemporaryDirectory() as tmp,patch('paper_atlas.collectors.iclr.fetch',side_effect=responses): manifest=collect_year(2021,tmp,tmp) self.assertEqual(manifest['status'],'collected') self.assertEqual(manifest['missing_abstract_count'],0) self.assertEqual(manifest['abstract_asset_status'],'unavailable_404') def test_import_repeat_preserves_one_submission_and_queryable_platform_tags(self): rows,_=self.parse([self.event(),self.event(2,-10,eventtype='Oral')],{'1':'A searchable abstract.'}) with tempfile.TemporaryDirectory() as tmp: path=Path(tmp)/'iclr.jsonl';db=Path(tmp)/'papers.sqlite' path.write_text(''.join(json.dumps(r)+'\n' for r in rows)) path.with_suffix('.manifest.json').write_text(json.dumps({'source':'iclr_program','parser_version':2,'list_url':'https://iclr.cc/virtual/2026/papers.html','count':1,'status':'collected'})) ingest_file(db,path);ingest_file(db,path) store=Store(db);self.addCleanup(store.conn.close) self.assertEqual(store.conn.execute('SELECT count(*) FROM submissions').fetchone()[0],1) self.assertEqual(store.conn.execute('SELECT count(*) FROM decisions').fetchone()[0],1) self.assertEqual(store.conn.execute('SELECT namespace,value FROM tags').fetchall(),[('iclr_program:topic','Learning->Theory')]) self.assertEqual(store.conn.execute('PRAGMA foreign_key_check').fetchall(),[]) if __name__=='__main__':unittest.main()