1use onestore::{
2 RevisionIndex, Store,
3 document::Document,
4 page::{Page, PageObject, ParagraphContent},
5};
6use std::{collections::BTreeSet, fs, path::Path};
7
8fn sections(directory: &Path, out: &mut Vec<std::path::PathBuf>) {
9 for entry in fs::read_dir(directory).unwrap() {
10 let path = entry.unwrap().path();
11 if path.is_dir() {
12 if !matches!(
13 path.file_name().and_then(|n| n.to_str()),
14 Some("private" | "recovered")
15 ) {
16 sections(&path, out);
17 }
18 } else if path.extension().is_some_and(|e| e == "one") {
19 out.push(path);
20 }
21 }
22}
23
24fn identities(page: &Page) -> Vec<onestore::ExGuid> {
25 fn paragraphs(list: &[onestore::page::PageParagraph], ids: &mut Vec<onestore::ExGuid>) {
26 for paragraph in list {
27 ids.push(paragraph.id);
28 match &paragraph.content {
29 ParagraphContent::Text(text) => ids.push(text.id),
30 ParagraphContent::Ink(ink) => ids.push(ink.id),
31 ParagraphContent::Table(table) => {
32 ids.push(table.id);
33 for row in &table.rows {
34 ids.push(row.id);
35 for cell in &row.cells {
36 ids.push(cell.id);
37 paragraphs(&cell.paragraphs, ids);
38 ids.extend(cell.unsupported.iter().map(|u| u.id));
39 }
40 }
41 }
42 ParagraphContent::Image(image) => ids.push(image.id),
43 ParagraphContent::Attachment(attachment) => ids.push(attachment.id),
44 ParagraphContent::Unsupported(unsupported) => ids.push(unsupported.id),
45 }
46 }
47 }
48 let mut ids = Vec::new();
49 for object in &page.objects {
50 ids.push(object.id());
51 match object {
52 PageObject::Outline(outline) => {
53 paragraphs(&outline.paragraphs, &mut ids);
54 ids.extend(outline.unsupported.iter().map(|u| u.id));
55 }
56 PageObject::Title(title) => {
57 for outline in &title.outlines {
58 ids.push(outline.id);
59 paragraphs(&outline.paragraphs, &mut ids);
60 ids.extend(outline.unsupported.iter().map(|u| u.id));
61 }
62 }
63 PageObject::Image(_)
64 | PageObject::Attachment(_)
65 | PageObject::Ink(_)
66 | PageObject::Unsupported(_) => {}
67 }
68 }
69 ids
70}
71
72#[test]
73fn every_corpus_page_builds_a_model_with_distinct_identities() {
74 let mut files = Vec::new();
75 sections(
76 Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../corpus")),
77 &mut files,
78 );
79 files.sort();
80 assert!(files.len() > 300, "{} sections", files.len());
81 let mut pages = 0;
82 let mut failures = Vec::new();
83 for path in &files {
84 let bytes = fs::read(path).unwrap();
85 let Ok(store) = Store::parse(&bytes) else {
86 continue;
87 };
88 let Ok(index) = RevisionIndex::parse(&store) else {
89 continue;
90 };
91 let Ok(document) = Document::parse(&index) else {
92 continue;
93 };
94 let Ok(listed) = document.pages() else {
95 continue;
96 };
97 for (space, _) in listed {
98 pages += 1;
99 match Page::from_space(&document, space) {
100 Ok(page) => {
101 let ids = identities(&page);
102 let distinct: BTreeSet<_> = ids.iter().collect();
103 if distinct.len() != ids.len() {
104 failures.push(format!("{}: repeated identity", path.display()));
105 }
106 }
107 Err(error) => failures.push(format!("{}: {}", path.display(), error.message)),
108 }
109 }
110 }
111 assert!(pages > 1000, "{pages} pages");
112 assert!(
113 failures.is_empty(),
114 "{}\n{} failures",
115 failures.join("\n"),
116 failures.len()
117 );
118}