1#!/usr/bin/env python3
2"""Verify captured native reads of Rust-written files."""
3import copy
4import hashlib
5import json
6from pathlib import Path
7import subprocess
8import xml.etree.ElementTree as ET
9from native_xml import ns, pages, texts
10
11root = Path(__file__).resolve().parent.parent
12corpus = root / 'corpus/writer'
13manifest = json.loads((corpus / 'manifest.json').read_text())
14for item in manifest['files']:
15 data = (corpus / item['path']).read_bytes()
16 assert len(data) == item['bytes']
17 assert hashlib.sha256(data).hexdigest() == item['sha256']
18
19for run in ['cold-edit-01', 'cold-edit-02']:
20 captured = pages(corpus / run / 'read')
21 assert len(captured) == 1 and texts(captured[0]) == ['Portable plain text...']
22
23for before, after in [
24 (root / 'corpus/native/20260905-05/snapshots/02-text/notebook/synthetic.one', corpus / 'edit-01/notebook/synthetic.one'),
25 (corpus / 'native-edit-02/notebook/synthetic.one', corpus / 'edit-02/notebook/synthetic.one'),
26 (root / 'corpus/native-ink/20260905-ui/notebook/synthetic.one', corpus / 'complex-01/notebook/synthetic.one'),
27]:
28 source = before.read_bytes()
29 assert after.read_bytes()[1024:len(source)] == source[1024:]
30
31native_saved = subprocess.check_output(['cargo', 'run', '-p', 'onestore', '--quiet', '--example', 'inventory', '--', str(corpus / 'native-edit-02/notebook/synthetic.one')], cwd=root, text=True)
32assert [bytes.fromhex(row.split('\t')[2]).decode('ascii') for row in native_saved.splitlines() if row.startswith('ascii\t')] == ['Fictitious plain text.']
33
34before = pages(root / 'corpus/native-ink/cold-ui-ink/read')[0]
35after = pages(corpus / 'cold-complex-01/read')[0]
36assert texts(after) == ['Compatible: café, 東京, مرحبا', 'Fictitious positioned outline.', 'Left cell', 'Right cell']
37
38
39def normalized(page):
40 page = copy.deepcopy(page)
41 for node in page.iter():
42 for name in ['ID', 'objectID', 'pathCache', 'selected']:
43 node.attrib.pop(name, None)
44 if node.text:
45 node.text = node.text.replace('Fictitious: café', 'Compatible: café')
46 if not node.text.strip():
47 node.text = None
48 node.tail = None
49 for outline in page.findall('one:Outline', ns):
50 if texts(outline) == ['Compatible: café, 東京, مرحبا']:
51 # OneNote recalculates outline height when the replacement text wraps.
52 outline.find('one:Size', ns).attrib.pop('height')
53 return page
54
55
56def fingerprint(node):
57 return node.tag, sorted(node.attrib.items()), node.text, [fingerprint(child) for child in node]
58
59
60assert fingerprint(normalized(before)) == fingerprint(normalized(after))
61assert next((root / 'corpus/native-ink/cold-ui-ink/read').glob('*.attachment')).read_bytes() == next((corpus / 'cold-complex-01/read').glob('*.attachment')).read_bytes()
62print('Passed: native edit/save/read cycle; Unicode edit preserves native formatting, positions, table, image, ink, and attachment bytes')
63
64assert ET.parse(corpus / 'toc-01/hierarchy.xml').getroot().get('color') == '#336699'
65created = ET.parse(corpus / 'create-05/page-000.xml').getroot()
66assert texts(created) == ['Created in Rust: café 東京 🦀']
67assert created.find('one:Outline', ns).get('author') == 'Fixture Author'
68assert texts(ET.parse(corpus / 'create-05/returned-page.xml').getroot()) == ['Rust edited it again, with a longer paragraph.']
69assert texts(ET.parse(corpus / 'create-notebook-01/page-000.xml').getroot()) == ['A notebook created in Rust.']
70assert len(ET.parse(corpus / 'create-notebook-01/hierarchy.xml').getroot().findall('one:Section', ns)) == 1
71print('Passed: native TOC color edit, template-free section and notebook creation, native save, and longer Rust replacement')
72
73append = root / 'corpus/append/round-01'
74for item in json.loads((append / 'manifest.json').read_text())['files']:
75 data = (append / item['path']).read_bytes()
76 assert len(data) == item['bytes'] and hashlib.sha256(data).hexdigest() == item['sha256']
77
78expected = {
79 'tx-17': 'Transaction 17', 'tx-255': 'Transaction 255',
80 'tx-256': 'Transaction 256', 'tx-511': 'Transaction 256',
81 'prepared-old': 'Transaction 255', 'tx-65535': 'Fictitious plain text.',
82 'tx-65536': 'Crossed the second counter byte.',
83 'tx-131071': 'Crossed the second counter byte.',
84 'tx-130816': 'Crossed the second counter byte.',
85 'shared-result': 'Rust committed through the SMB mount.',
86 **{f'metadata-{i}': 'Transaction 255' for i in range(8)},
87}
88for name, text in expected.items():
89 captured = pages(append / 'native' / f'append-01-{name}' / 'read')
90 assert len(captured) == 1 and texts(captured[0]) == [text], name
91
92after = pages(append / 'native/append-01-complex/read')[0]
93assert fingerprint(normalized(before)) == fingerprint(normalized(after))
94assert next((root / 'corpus/native-ink/cold-ui-ink/read').glob('*.attachment')).read_bytes() == next((append / 'native/append-01-complex/read').glob('*.attachment')).read_bytes()
95assert ET.parse(append / 'native/append-01-toc/read/hierarchy.xml').getroot().get('color') == '#336699'
96saved = subprocess.check_output(['cargo', 'run', '-p', 'onestore', '--quiet', '--example', 'inventory', '--', str(append / 'native-after-511.one')], cwd=root, text=True)
97assert [bytes.fromhex(row.split('\t')[2]).decode('ascii') for row in saved.splitlines() if row.startswith('ascii\t')] == ['Native after interrupted cleanup.']
98print('Passed: append encoding, native metadata and counter recovery, post-recovery native save, and filesystem-backed SMB commit')