100 lines
6.6 KiB
Python
100 lines
6.6 KiB
Python
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'],{})
|
|
self.assertEqual(rows[0]['url'],'https://iclr.cc/virtual/2020/papers.html')
|
|
|
|
def test_only_referenced_official_assets_and_real_navigation_years(self):
|
|
html='<a href="/archive/2013/">2013</a><a href="/Conferences/2026">2026</a><a href="https://other.example/2025">2025</a>'
|
|
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()
|