1import copy
2from pathlib import Path
3import json
4import subprocess
5import tempfile
6import unittest
7from random_edit_campaign import EDITOR, ROOT, export, verify
8from document_model import ordered_pages
9
10
11class 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
80if __name__ == '__main__':
81 unittest.main()