1import hashlib
2import json
3from pathlib import Path
4import runpy
5import shutil
6from tempfile import TemporaryDirectory
7import unittest
8import xml.etree.ElementTree as ET
9
10from native_page_removal import CASES
11from native_xml import ns
12
13ROOT = Path(__file__).resolve().parent.parent
14FIXTURE = ROOT / 'corpus/page-lifecycle/removal'
15compare = runpy.run_path(str(ROOT / 'tools/verify-document.py'))['compare']
16
17
18class PageRemovalTest(unittest.TestCase):
19 def test_captures_match_requests_and_native_navigation(self):
20 provenance = json.loads((FIXTURE / 'provenance.json').read_text())
21 self.assertEqual(provenance.keys(), CASES.keys())
22 machines = set()
23 for case, (_, count, selected, permanent) in CASES.items():
24 with self.subTest(case=case):
25 root = FIXTURE / case
26 request = json.loads((root / 'removal.json').read_text())
27 self.assertEqual(request['selected'], selected)
28 self.assertEqual(request['permanent'], permanent)
29 for relative, digest in provenance[case]['sha256'].items():
30 self.assertEqual(hashlib.sha256((root / relative).read_bytes()).hexdigest(), digest)
31 for phase in ['native', 'cold']:
32 machine = json.loads((root / phase / 'machine.json').read_text())['name']
33 self.assertNotIn(machine, machines)
34 machines.add(machine)
35 self.assertTrue(json.loads((root / phase / 'teardown.json').read_text())['absent'])
36 before = ET.parse(root / 'native/before/hierarchy.xml').findall('.//one:Page', ns)
37 self.assertEqual(len(before), count)
38 expected = [(page.get('name'), page.get('pageLevel', '1'))
39 for index, page in enumerate(before) if index not in selected]
40 if expected:
41 expected[0] = (expected[0][0], '1')
42 for hierarchy in ['hierarchy.xml', 'reopened-hierarchy.xml']:
43 xml = (root / 'native/after' / hierarchy).read_text(encoding='utf-8-sig').strip()
44 pages = ET.fromstring(xml).findall('.//one:Page', ns) if xml not in ('', '<?xml version="1.0"?>') else []
45 self.assertEqual([(page.get('name'), page.get('pageLevel', '1')) for page in pages], expected)
46 self.assertEqual(len(machines), 22)
47
48 def test_native_xml_and_payloads_match_source_and_cold_stores(self):
49 for case in CASES:
50 for notebook, read in [('native/before/notebook', 'native/before-read'),
51 ('native/after/notebook', 'native/read'),
52 ('native/after/notebook', 'cold/read'),
53 ('cold/notebook', 'cold/read')]:
54 with self.subTest(case=case, notebook=notebook, read=read), TemporaryDirectory() as temporary:
55 destination = Path(temporary) / 'read'
56 shutil.copytree(FIXTURE / case / read, destination)
57 compare(FIXTURE / case / notebook, destination)
58
59
60if __name__ == '__main__':
61 unittest.main()
62
63
64class RustRemovalFollowupTest(unittest.TestCase):
65 """OneNote creates a page in a section after Rust removed pages from it, then reopens cold."""
66
67 CASES = ('parent', 'all', 'features', 'leading-parent')
68
69 def test_native_page_after_rust_removal_matches_its_captures(self):
70 for case in self.CASES:
71 with self.subTest(case=case):
72 root = FIXTURE / 'rust-followup' / case
73 title = (root / 'followup/native-title.txt').read_text(encoding='utf-8-sig')
74 self.assertTrue(title.startswith('Native after Rust removal'))
75 for phase in ('followup', 'followup-cold'):
76 compare(root / phase / 'notebook', root / phase / 'read')
77 hierarchy = ET.parse(root / phase / 'read/hierarchy.xml').getroot()
78 names = [page.get('name') for page in hierarchy.findall('.//one:Page', ns)]
79 self.assertEqual(names.count(title), 1, names)
80 self.assertTrue(json.loads((root / phase / 'teardown.json').read_text())['absent'])