86 lines
4.8 KiB
Python
86 lines
4.8 KiB
Python
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from paper_atlas.collectors import aamas, aamas2025
|
|
|
|
|
|
FIXTURE = Path(__file__).parent/'fixtures/aamas2025'
|
|
|
|
|
|
class Aamas2025Tests(unittest.TestCase):
|
|
def parse(self, html=None):
|
|
return aamas2025.parse_contents(html or (FIXTURE/'contents.html').read_text(),
|
|
{'observed_at': '2026-11-09T12:31:50+00:00', 'url': aamas2025.URLS['contents']})
|
|
|
|
def test_real_toc_preserves_every_title_author_and_affiliation(self):
|
|
expected = json.loads((FIXTURE/'expected-authors.json').read_text())
|
|
self.assertEqual(hashlib.sha256((FIXTURE/'contents.html').read_bytes()).hexdigest(), expected['source_sha256'])
|
|
rows, excluded = self.parse()
|
|
self.assertEqual((len(rows), len(excluded)), (476, 3))
|
|
by_url = {r['paper_url']: r for r in expected['entries']}
|
|
for row in rows:
|
|
original = by_url[row['metadata']['paper_url']]
|
|
self.assertEqual(row['title'], original['title'])
|
|
self.assertEqual(row['authors'], [a['name'] for a in original['authors']])
|
|
self.assertEqual(row['metadata']['author_affiliations'], [a['affiliation'] for a in original['authors']])
|
|
self.assertIsNone(row['abstract'])
|
|
self.assertEqual(row['identifiers'], {})
|
|
self.assertEqual(row['status'], 'accepted')
|
|
self.assertNotIn('decision_date', row)
|
|
self.assertTrue(all(t['namespace'] == 'aamas_proceedings:publication_format' for t in row['tags']))
|
|
|
|
def test_publication_format_and_track_are_separate(self):
|
|
rows, excluded = self.parse()
|
|
groups = {k: sum(r['track'] == k for r in rows) for k in {r['track'] for r in rows}}
|
|
self.assertEqual(groups, {'proceedings_unspecified': 409, 'blue_sky': 12, 'doctoral': 25, 'demo': 21, 'jaamas': 9})
|
|
competition = next(r for r in rows if r['external_id'] == '2025/p3000')
|
|
self.assertEqual(competition['year'], 2025)
|
|
self.assertEqual(competition['metadata']['publication_format'], 'competition_report')
|
|
self.assertEqual(competition['metadata']['section_heading'], 'Demo Track')
|
|
self.assertEqual(competition['track'], 'proceedings_unspecified')
|
|
self.assertEqual(len({r['external_id'] for r in rows}), 476)
|
|
self.assertEqual([r['publisher_page'] for r in excluded], [1, 3, 4])
|
|
|
|
def test_refuses_changed_edition_inventory_or_author_markup(self):
|
|
original = (FIXTURE/'contents.html').read_text()
|
|
for html in [original.replace('AAMAS 2025:', 'AAMAS 2026:', 1),
|
|
original.replace('../pdfs/p5.pdf', '../pdfs/p14.pdf', 1),
|
|
original.replace('Paper Track</font>', 'Workshop Track</font>'),
|
|
original.replace('Amir Abolfazli', '', 1),
|
|
original.replace('[COMP24]', '[CHANGED]', 1)]:
|
|
with self.subTest(change=html[:60]):
|
|
with self.assertRaises(ValueError): self.parse(html)
|
|
|
|
def test_frozen_collection_is_partial_and_never_fetches_pdfs(self):
|
|
with tempfile.TemporaryDirectory() as tmp, patch.object(aamas2025, 'fetch', side_effect=AssertionError('No HTTP in frozen mode')):
|
|
out = Path(tmp)/'out'
|
|
report = aamas.collect(2025, out, snapshots=FIXTURE)
|
|
self.assertEqual(report['records'], 476)
|
|
self.assertFalse(report['database_written'])
|
|
self.assertFalse(report['pdfs_downloaded'])
|
|
self.assertEqual(len(report['files']), 5)
|
|
for m in report['files']:
|
|
self.assertEqual(m['status'], 'partial')
|
|
self.assertEqual(m['missing_abstract_count'], m['count'])
|
|
self.assertEqual(hashlib.sha256(Path(m['file']).read_bytes()).hexdigest(), m['file_sha256'])
|
|
receipt = report['source_receipts'][0]['response_receipt']
|
|
self.assertEqual(receipt['at_precision'], 'filesystem_write_time_recovered')
|
|
self.assertTrue((out/'aamas-proceedings-2025.json').is_file())
|
|
|
|
def test_rejects_tampered_or_unsafe_frozen_response(self):
|
|
import shutil
|
|
for mode in ['tamper', 'path', 'status']:
|
|
with self.subTest(mode=mode), tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp); source = root/'source'; shutil.copytree(FIXTURE, source)
|
|
p = source/'receipts.json'; r = json.loads(p.read_text())
|
|
if mode == 'tamper': (source/'contents.html').write_text('bad')
|
|
elif mode == 'path': r['receipts'][0]['file'] = '../index.html'
|
|
else: r['receipts'][0]['http_status'] = 403
|
|
p.write_text(json.dumps(r))
|
|
with self.assertRaises(ValueError): aamas.collect(2025, root/'out', snapshots=source)
|
|
self.assertFalse((root/'out/aamas-proceedings-2025.json').exists())
|