1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//! The typed `boko::Error` variants must classify failures consistently
//! across formats: a missing resource is `NotFound`, corrupt bytes are
//! `Malformed`, and an export-only format requested for import is
//! `UnsupportedFormat`. These are the guarantees the 0.4 error API makes.
use std::path::Path;
use boko::{Book, Error, Format};
const EPUB: &str = "tests/fixtures/epictetus.epub";
const KFX: &str = "tests/fixtures/epictetus.kfx";
#[test]
fn missing_asset_is_not_found_for_every_format() {
for (path, format) in [(EPUB, "epub"), (KFX, "kfx")] {
if !Path::new(path).exists() {
continue;
}
let book = Book::open(path).expect("fixture opens");
let err = book
.load_asset("does/not/exist.xyz")
.expect_err("missing asset must error");
assert!(
matches!(err, Error::NotFound { .. }),
"{format}: missing asset should be NotFound, got {err:?}"
);
}
}
#[test]
fn corrupt_kfx_is_malformed_not_io() {
// Truncated KFX: the container header is intact enough to open but entity
// parsing fails. The failure must classify as Malformed { Kfx }, not the
// generic Io that a real disk error would produce.
let Ok(bytes) = std::fs::read(KFX) else {
return;
};
// Corrupt the tail so entity/Ion parsing hits garbage.
let mut corrupt = bytes.clone();
for b in corrupt.iter_mut().skip(bytes.len() / 2) {
*b = 0xFF;
}
match Book::from_bytes(&corrupt, Format::Kfx) {
// Failure at open time is acceptable, but it must be typed.
Err(e) => assert!(
matches!(
e,
Error::Malformed {
format: Format::Kfx,
..
}
),
"corrupt KFX open should be Malformed, got {e:?}"
),
// If it opens, driving a chapter must surface Malformed, never Io.
Ok(book) => {
let spine: Vec<_> = book.spine().to_vec();
for entry in spine {
if let Err(e) = book.load_chapter(entry.id) {
assert!(
matches!(
e,
Error::Malformed {
format: Format::Kfx,
..
}
),
"corrupt KFX chapter should be Malformed, got {e:?}"
);
}
}
}
}
}
#[test]
fn markdown_import_is_unsupported_format() {
match Book::from_bytes(b"# hi", Format::Markdown) {
Err(e) => assert!(matches!(e, Error::UnsupportedFormat { .. }), "got {e:?}"),
Ok(_) => panic!("markdown import should be unsupported"),
}
}