80 lines
3.5 KiB
Python
80 lines
3.5 KiB
Python
|
|
import json
|
||
|
|
from pathlib import Path
|
||
|
|
import subprocess
|
||
|
|
import sys
|
||
|
|
import tempfile
|
||
|
|
import unittest
|
||
|
|
|
||
|
|
from intelligence_radar.__main__ import draft, ledger, record, validate
|
||
|
|
|
||
|
|
FIXTURE = Path(__file__).resolve().parents[1] / 'examples/observation.json'
|
||
|
|
|
||
|
|
|
||
|
|
class WorkflowTests(unittest.TestCase):
|
||
|
|
def setUp(self):
|
||
|
|
self.temp = tempfile.TemporaryDirectory()
|
||
|
|
self.addCleanup(self.temp.cleanup)
|
||
|
|
self.root = Path(self.temp.name)
|
||
|
|
self.value = json.loads(FIXTURE.read_text())
|
||
|
|
|
||
|
|
def test_idempotence_and_append_only_correction(self):
|
||
|
|
original = record(self.root, self.value)
|
||
|
|
self.assertEqual(record(self.root, self.value), original)
|
||
|
|
replacement = dict(self.value, claim='Corrected synthetic claim', supersedes=original)
|
||
|
|
correction = record(self.root, replacement)
|
||
|
|
self.assertNotEqual(original, correction)
|
||
|
|
self.assertEqual(len(ledger(self.root)), 2)
|
||
|
|
text = draft(self.root, '2026-09-13').read_text()
|
||
|
|
self.assertIn(f'Supersedes: {original}', text)
|
||
|
|
with self.assertRaises(FileExistsError):
|
||
|
|
draft(self.root, '2026-09-13')
|
||
|
|
|
||
|
|
def test_tampered_record_is_rejected_by_reader_and_writer(self):
|
||
|
|
ident = record(self.root, self.value)
|
||
|
|
path = self.root / 'data/observations' / f'{ident}.json'
|
||
|
|
envelope = json.loads(path.read_text())
|
||
|
|
envelope['record']['claim'] = 'tampered'
|
||
|
|
path.write_text(json.dumps(envelope))
|
||
|
|
for fn in (lambda: ledger(self.root), lambda: record(self.root, self.value)):
|
||
|
|
with self.assertRaisesRegex(ValueError, 'integrity'):
|
||
|
|
fn()
|
||
|
|
|
||
|
|
def test_timezone_date_hash_and_shape_validation(self):
|
||
|
|
for change in ({'observed_at': '2026-09-13T20:00:00'},
|
||
|
|
{'event_date': '2026-09-14'}, {'content_sha256': 'bad'},
|
||
|
|
{'source_url': 'https://user:password@example.invalid'},
|
||
|
|
{'schema_version': True}, {'status': 'verified_by_magic'},
|
||
|
|
{'unexpected': 'field'}):
|
||
|
|
with self.subTest(change=change), self.assertRaises(ValueError):
|
||
|
|
validate(dict(self.value, **change))
|
||
|
|
|
||
|
|
def test_unknown_correction_target_rejected(self):
|
||
|
|
with self.assertRaisesRegex(ValueError, 'existing observation'):
|
||
|
|
record(self.root, dict(self.value, supersedes='0' * 64))
|
||
|
|
|
||
|
|
def test_iso_week_year_boundary_and_event_date_filter(self):
|
||
|
|
ident = record(self.root, dict(self.value, event_date='2025-12-29',
|
||
|
|
observed_at='2026-01-02T12:00:00Z'))
|
||
|
|
self.assertIn(ident, draft(self.root, '2026-W01').read_text())
|
||
|
|
self.assertIn('No recorded observations', draft(self.root, '2026-01-02').read_text())
|
||
|
|
with self.assertRaises(ValueError):
|
||
|
|
draft(self.root, '2026-W99')
|
||
|
|
|
||
|
|
def test_invalid_cli_input_exits_nonzero_without_traceback(self):
|
||
|
|
invalid = self.root / 'invalid.json'
|
||
|
|
invalid.write_text('[]')
|
||
|
|
result = subprocess.run([sys.executable, '-m', 'intelligence_radar',
|
||
|
|
'--root', str(self.root), 'record', str(invalid)],
|
||
|
|
capture_output=True, text=True)
|
||
|
|
self.assertEqual(result.returncode, 1)
|
||
|
|
self.assertIn('record must be an object', result.stderr)
|
||
|
|
self.assertNotIn('Traceback', result.stderr)
|
||
|
|
|
||
|
|
def test_empty_period_does_not_invent_news(self):
|
||
|
|
self.assertIn('does not establish that no events occurred',
|
||
|
|
draft(self.root, '2026-09-13').read_text())
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
unittest.main()
|