| 1 | import copy |
| 2 | from pathlib import Path |
| 3 | import json |
| 4 | import subprocess |
| 5 | import tempfile |
| 6 | import unittest |
| 7 | from random_edit_campaign import EDITOR, ROOT, export, verify |
| 8 | from document_model import ordered_pages |
| 9 | |
| 10 | |
| 11 | class RandomEditOracleTests(unittest.TestCase): |
| 12 | def test_a_random_replacement_cannot_be_a_no_op(self): |
| 13 | with tempfile.TemporaryDirectory() as temporary: |
| 14 | source = Path(temporary) / 'source.one' |
| 15 | subprocess.run([ROOT / 'target/debug/examples/create_section', source, ' café ', 'No-op regression'], check=True) |
| 16 | before = export(source) |
| 17 | for mode in ['create', 'in-place']: |
| 18 | destination = Path(temporary) / (mode + '.one') |
| 19 | if mode == 'create': |
| 20 | args = [EDITOR, source, destination, '301'] |
| 21 | else: |
| 22 | destination.write_bytes(source.read_bytes()) |
| 23 | args = [EDITOR, destination, '--in-place', '301'] |
| 24 | record = json.loads(subprocess.check_output(args)) |
| 25 | self.assertEqual(record['range'], [0, 0]) |
| 26 | self.assertNotEqual(source.read_bytes(), destination.read_bytes()) |
| 27 | verify(before, export(destination), record) |
| 28 | |
| 29 | def test_native_fixture_splice_and_unrelated_damage(self): |
| 30 | source = ROOT / 'corpus/native/20260905-05/snapshots/06-attachment/notebook/synthetic.one' |
| 31 | with tempfile.TemporaryDirectory() as temporary: |
| 32 | destination = Path(temporary) / 'edited.one' |
| 33 | record = json.loads(subprocess.check_output([EDITOR, source, destination, '42'])) |
| 34 | before, after = export(source), export(destination) |
| 35 | verify(before, after, record) |
| 36 | sid, oid = record['space'], record['object'] |
| 37 | rid = after[0]['spaces'][sid]['contexts']['{00000000-0000-0000-0000-000000000000},0'] |
| 38 | for kind in ['format', 'metadata', 'payload', 'structure', 'ancestor-time', 'unrelated-time']: |
| 39 | damaged = copy.deepcopy(after) |
| 40 | if kind == 'format': damaged[1][sid][rid][oid][0]['format']['bold'] = not damaged[1][sid][rid][oid][0]['format']['bold'] |
| 41 | if kind == 'metadata': damaged[0]['spaces'][sid]['revisions'][rid]['nodes'][oid]['created'] = 0 |
| 42 | if kind == 'payload': damaged[2].append('another payload') |
| 43 | if kind == 'structure': damaged[0]['spaces'][sid]['revisions'][rid]['nodes'][oid]['children'].append('another object') |
| 44 | if kind == 'ancestor-time': damaged[0]['spaces'][sid]['revisions'][rid]['nodes'][record['page']]['modified'] = 0 |
| 45 | if kind == 'unrelated-time': |
| 46 | old_rid = before[0]['spaces'][sid]['contexts']['{00000000-0000-0000-0000-000000000000},0'] |
| 47 | old = before[0]['spaces'][sid]['revisions'][old_rid]['nodes'] |
| 48 | nodes = damaged[0]['spaces'][sid]['revisions'][rid]['nodes'] |
| 49 | key = next(key for key, node in nodes.items() if node['modified'] is not None and node['modified'] == old[key]['modified']) |
| 50 | nodes[key]['modified'] = 0 |
| 51 | with self.assertRaises(AssertionError, msg=kind): verify(before, damaged, record) |
| 52 | |
| 53 | def test_seeded_edits_work_on_native_legacy_text(self): |
| 54 | source = ROOT / 'corpus/native/20260905-05/snapshots/02-text/notebook/synthetic.one' |
| 55 | with tempfile.TemporaryDirectory() as temporary: |
| 56 | for seed in range(42, 74): |
| 57 | destination = Path(temporary) / f'{seed}.one' |
| 58 | result = subprocess.run([EDITOR, source, destination, str(seed)], capture_output=True, text=True) |
| 59 | self.assertEqual(result.returncode, 0, (seed, result.stderr)) |
| 60 | self.assertTrue(destination.is_file()) |
| 61 | |
| 62 | def test_title_cache_changes_are_required_and_bounded(self): |
| 63 | source = ROOT / 'corpus/m6/native-structure-01/notebook/synthetic.one' |
| 64 | before = export(source) |
| 65 | sid, _, revision, page = next(p for p in ordered_pages(before[0]) if any( |
| 66 | n['kind']['type'] == 'RichText' and any(f['id'] == 0x88001cb4 for f in n['extra'][0]) |
| 67 | for n in p[2]['nodes'].values())) |
| 68 | with tempfile.TemporaryDirectory() as temporary: |
| 69 | destination = Path(temporary) / 'edited.one' |
| 70 | record = json.loads(subprocess.check_output([EDITOR, source, destination, '98', page])) |
| 71 | self.assertTrue(any(f['id'] == 0x88001cb4 for f in revision['nodes'][record['object']]['extra'][0])) |
| 72 | after = export(destination) |
| 73 | verify(before, after, record) |
| 74 | rid = after[0]['spaces'][sid]['contexts']['{00000000-0000-0000-0000-000000000000},0'] |
| 75 | damaged = copy.deepcopy(after) |
| 76 | damaged[0]['spaces'][sid]['revisions'][rid]['nodes'][revision['roots']['2']]['kind']['title'] = revision['nodes'][revision['roots']['2']]['kind']['title'] |
| 77 | with self.assertRaises(AssertionError): verify(before, damaged, record) |
| 78 | |
| 79 | |
| 80 | if __name__ == '__main__': |
| 81 | unittest.main() |