use boko::Book;
use sha1_smol::Sha1;
use std::path::Path;
fn sha1_hex(bytes: &[u8]) -> String {
Sha1::from(bytes).hexdigest()
}
#[test]
fn test_azw3_toc_resolution() {
let path = "tests/fixtures/epictetus.azw3";
if !std::path::Path::new(path).exists() {
eprintln!("Skipping test - fixture not found: {}", path);
return;
}
let mut book = Book::open(path).expect("Should open AZW3");
let _ = book.resolve_links().expect("Should resolve links");
let toc = book.toc();
fn count_with_fragments(entries: &[boko::model::TocEntry]) -> (usize, usize, usize) {
let mut total = 0;
let mut with_fragment = 0;
let mut with_target = 0;
for entry in entries {
total += 1;
if entry.href.contains('#') {
with_fragment += 1;
}
if entry.target.is_some() {
with_target += 1;
}
let (t, f, tgt) = count_with_fragments(&entry.children);
total += t;
with_fragment += f;
with_target += tgt;
}
(total, with_fragment, with_target)
}
let (_total, with_fragment, with_target) = count_with_fragments(toc);
assert!(
with_fragment > 0,
"Expected some TOC entries to have fragments, got {}",
with_fragment
);
assert!(
with_target > 0,
"Expected some TOC entries to have targets, got {}",
with_target
);
assert_unique_toc_hrefs(toc, "AZW3");
}
fn collect_toc_hrefs(entries: &[boko::model::TocEntry], hrefs: &mut Vec<String>) {
for entry in entries {
hrefs.push(entry.href.clone());
collect_toc_hrefs(&entry.children, hrefs);
}
}
fn assert_unique_toc_hrefs(toc: &[boko::model::TocEntry], format_name: &str) {
use std::collections::HashMap;
let mut all_hrefs = Vec::new();
collect_toc_hrefs(toc, &mut all_hrefs);
let mut href_counts: HashMap<&String, usize> = HashMap::new();
for href in &all_hrefs {
*href_counts.entry(href).or_default() += 1;
}
let unique_count = href_counts.len();
assert_eq!(
all_hrefs.len(),
unique_count,
"{}: Every TOC entry should have a unique href",
format_name
);
}
#[test]
fn test_epub_toc_resolution() {
let path = "tests/fixtures/epictetus.epub";
if !std::path::Path::new(path).exists() {
eprintln!("Skipping test - fixture not found: {}", path);
return;
}
let mut book = Book::open(path).expect("Should open EPUB");
let _ = book.resolve_links().expect("Should resolve links");
assert_unique_toc_hrefs(book.toc(), "EPUB");
}
#[test]
fn test_mobi_toc_resolution() {
let path = "tests/fixtures/epictetus.mobi";
if !std::path::Path::new(path).exists() {
eprintln!("Skipping test - fixture not found: {}", path);
return;
}
let mut book = Book::open(path).expect("Should open MOBI");
let _ = book.resolve_links().expect("Should resolve links");
assert_unique_toc_hrefs(book.toc(), "MOBI");
}
#[test]
fn test_kfx_toc_resolution() {
let path = "tests/fixtures/epictetus.kfx";
if !std::path::Path::new(path).exists() {
eprintln!("Skipping test - fixture not found: {}", path);
return;
}
let mut book = Book::open(path).expect("Should open KFX");
let _ = book.resolve_links().expect("Should resolve links");
assert_unique_toc_hrefs(book.toc(), "KFX");
}
#[test]
fn test_kfx_named_resource_returns_binary_asset() {
let path = "tests/fixtures/epictetus.kfx";
if !Path::new(path).exists() {
eprintln!("Skipping test - fixture not found: {}", path);
return;
}
let mut book = Book::open(path).expect("Should open KFX");
let expected = [
("resource/rsrc7", "dae4335aa095d1109e81a413cea05e1a3225e4ed"),
(
"resource/rsrc1DT",
"5d4c6c7573d11baff232c9ee8381f2012a4c9be2",
),
(
"resource/rsrc1DU",
"4a5dc446b5a102bbb11e9439ee586bcc5a4e0811",
),
];
for (resource_name, expected_sha1) in expected {
let bytes = book
.load_asset(Path::new(resource_name))
.expect("Expected named resource to exist in epictetus.kfx");
assert!(
!bytes.starts_with(&[0xE0, 0x01, 0x00, 0xEA]),
"Expected binary media bytes for {}, got Ion metadata payload ({} bytes)",
resource_name,
bytes.len()
);
assert!(
bytes.len() > 256,
"Expected substantial media payload for {}, got {} bytes",
resource_name,
bytes.len()
);
assert_eq!(
sha1_hex(bytes.as_slice()),
expected_sha1,
"Unexpected SHA-1 for {} in epictetus fixture",
resource_name
);
}
}
#[test]
fn test_kfx_direct_asset_id_1102_matches_named_resource_and_hash() {
let path = "tests/fixtures/epictetus.kfx";
if !Path::new(path).exists() {
eprintln!("Skipping test - fixture not found: {}", path);
return;
}
let mut book = Book::open(path).expect("Should open KFX");
assert!(
book.list_assets().iter().any(|p| p == Path::new("#1102")),
"Expected #1102 to be listed as a KFX asset"
);
let id_bytes = book
.load_asset(Path::new("#1102"))
.expect("Should load #1102 asset bytes");
let id_sha1 = sha1_hex(id_bytes.as_slice());
assert_eq!(
id_sha1, "5d4c6c7573d11baff232c9ee8381f2012a4c9be2",
"Unexpected SHA-1 for fixture asset #1102"
);
let named_sha1 = book
.load_asset(Path::new("resource/rsrc1DT"))
.map(|bytes| sha1_hex(bytes.as_slice()))
.expect("Should load resource/rsrc1DT by name");
assert_eq!(
named_sha1, id_sha1,
"Expected resource/rsrc1DT to match #1102 payload hash"
);
}