"""Pinned T78 evidence, edition-local aliases and an explicitly identified material.""" import copy import hashlib import json from pathlib import Path import sqlite3 import tempfile import unittest import xml.etree.ElementTree as ET from paper_atlas.collectors.acl import collect_xml, volume_scope from paper_atlas.collectors.acl_historical import event_scope, material_reason from paper_atlas.db.store import Store from paper_atlas.ingestion.records import ingest_file FIXTURE = Path(__file__).parent/'fixtures/acl-t78.xml' COMMIT = 'a7be623d01c48c61405dec31c871121cdbed5a01' class AclHistoricalTests(unittest.TestCase): def setUp(self): self.root = ET.parse(FIXTURE).getroot() self.volume = self.root.find('volume') self.meta = self.volume.find('meta') def test_frozen_volume_matches_official_snapshot(self): self.assertEqual(hashlib.sha256(FIXTURE.read_bytes()).hexdigest(), 'f712f94cff0cba05f786b7ac31dad2ea9408fde2a17a04531130df623f57dd84') self.assertEqual(len(self.volume.findall('paper')), 36) self.assertEqual(volume_scope(self.meta, '1', 'T78'), ('ACL+TINLAP', ['ACL', 'TINLAP'], 'proceedings_unspecified')) def test_unknown_year_collection_or_volume_is_not_globally_aliased(self): for collection, volume in [('T75', '1'), ('T87', '1'), ('T78', '2'), ('C65', '1')]: with self.subTest(collection=collection, volume=volume): self.assertIsNone(event_scope(self.meta, volume, collection)) self.assertIsNone(volume_scope(self.meta, volume, collection)) self.assertIsNone(volume_scope(self.meta, '1')) def test_known_volume_scope_rejects_critical_metadata_drift(self): for field, replacement in [('venue', 'acl'), ('year', '1979'), ('booktitle', 'Another TINLAP'), ('editor/first', 'Another')]: changed = copy.deepcopy(self.meta) changed.find(field).text = replacement with self.subTest(field=field), self.assertRaises(ValueError): event_scope(changed, '1', 'T78') changed = copy.deepcopy(self.meta) ET.SubElement(changed, 'year').text = '1978' with self.assertRaises(ValueError): event_scope(changed, '1', 'T78') def test_evidence_retains_original_series_dates_and_role_boundary(self): scope = event_scope(self.meta, '1', 'T78') self.assertEqual(scope['original_venue'], 'TINLAP') self.assertEqual(scope['event_dates'], ['1978-07-25', '1978-07-27']) self.assertEqual([e['pdf_pages'] for e in scope['evidence']], [[9, 10], [3]]) self.assertIn('not identified', scope['track_scope_note']) self.assertIn('pending', scope['evidence'][0]['scope']) def test_only_exact_session_questions_material_is_excluded(self): paper = copy.deepcopy(self.volume.findall('paper')[-1]) self.assertIn('Session question list', material_reason('T78', '1', paper)) self.assertIsNone(material_reason('C65', '1', paper)) self.assertIsNone(material_reason('T78', '2', paper)) paper.set('id', '37') self.assertIsNone(material_reason('T78', '1', paper)) paper.set('id', '36') ET.SubElement(paper, 'author') with self.assertRaises(ValueError): material_reason('T78', '1', paper) def test_all_authored_source_fields_and_material_receipt_survive_collection(self): with tempfile.TemporaryDirectory() as temporary: manifest, = collect_xml(FIXTURE, temporary, COMMIT) rows = [json.loads(raw) for raw in Path(manifest['file']).read_text().splitlines()] self.assertEqual(manifest['count'], 35) self.assertEqual(manifest['xml_paper_elements'], 36) self.assertEqual(manifest['missing_abstract_count'], 35) self.assertEqual(manifest['excluded_materials'][0]['external_id'], 'T78-1036') for number, row in enumerate(rows, 1): paper = self.volume.findall('paper')[number-1] self.assertEqual(row['external_id'], f'T78-1{number:03d}') self.assertEqual(row['title'], ' '.join(''.join(paper.find('title').itertext()).split())) self.assertEqual(row['metadata']['venue_codes'], ['tinlap']) self.assertEqual(row['metadata']['historical_event_scope']['original_venue'], 'TINLAP') self.assertEqual(row['metadata']['metadata_commit'], COMMIT) self.assertEqual((row['status'], row['track'], row['abstract']), ('accepted', 'proceedings_unspecified', None)) def test_old_coling_scope_is_unchanged_by_calendar_replacement_evidence(self): meta = ET.fromstring('coling1965COLING 1965') self.assertEqual(volume_scope(meta, '1', 'C65'), ('COLING', ['COLING'], 'main')) self.assertIsNone(event_scope(meta, '1', 'C65')) def test_native_ingestion_does_not_alias_other_tinlap_years_or_infer_negative_status(self): with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) manifest, = collect_xml(FIXTURE, root/'source', COMMIT) db = root/'sample.sqlite' store = Store(db) pid, rid = store.record('test_control', 'tinlap1975', 'https://example.org/tinlap1975', 'An earlier TINLAP contribution', None, ['Jane Doe']) store.decision(pid, rid, 'TINLAP', 1975, 'main', 'tinlap1975', 'https://example.org/tinlap1975', 'accepted', 'test control') store.conn.commit() store.conn.close() ingest_file(db, manifest['file']) ingest_file(db, manifest['file']) with sqlite3.connect(db) as c: self.assertEqual(c.execute('SELECT count(*) FROM papers').fetchone()[0], 36) self.assertEqual(c.execute('SELECT count(*) FROM submissions').fetchone()[0], 36) self.assertEqual(c.execute('SELECT count(*) FROM decisions').fetchone()[0], 36) self.assertEqual(c.execute("SELECT count(*) FROM decisions WHERE status!='accepted'").fetchone()[0], 0) self.assertIsNone(c.execute("SELECT 1 FROM source_records WHERE external_id='T78-1036'").fetchone()) aliases = c.execute('SELECT v.acronym,a.alias FROM venue_aliases a JOIN venues v ON v.id=a.venue_id ORDER BY a.alias').fetchall() self.assertEqual(aliases, [('ACL+TINLAP', 'ACL'), ('ACL+TINLAP', 'TINLAP')]) metadata = json.loads(c.execute("SELECT metadata_json FROM source_records WHERE external_id='T78-1001'").fetchone()[0]) self.assertEqual(metadata['historical_event_scope']['event_year'], 1978) self.assertEqual(c.execute('PRAGMA foreign_key_check').fetchall(), [])