#![cfg(not(miri))]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test code: a failing unwrap is a failing test"
)]
use urna_format::layout::{
SECTION_CHUNKS_CANONICAL, SECTION_EMBEDDINGS, SECTION_ENCODING_RAW, SECTION_ENCODING_ZSTD,
SectionEntry, URNA_FOOTER_SIZE, URNA_HEADER_SIZE, URNA_SECTION_ENTRY_SIZE, UrnaFooter,
};
use urna_format::manifest::{Capabilities, Manifest};
use urna_format::writer::{SectionEncoding, UrnaFileBuilder};
use urna_format::{ChunkInput, UrnaError, UrnaView};
fn manifest(n: u64) -> Manifest {
Manifest {
format_version: 1,
schema_version: 1,
embedding_model: "demo".into(),
embedding_dim: 4,
n_chunks: n,
dtype: "float32".into(),
metric: "ip".into(),
score_type: "cosine".into(),
normalize: "l2".into(),
index_type: "exact".into(),
rerank_policy: "none".into(),
model_hash: format!("sha256:{}", "0".repeat(64)),
chunker_version: "demo-chunker/1".into(),
capabilities: Capabilities {
supports_exact: true,
supports_reproducible_build: true,
supports_ann: false,
supports_bm25: false,
supports_citations: true,
},
title: None,
version: None,
created: None,
description: None,
authors: None,
license: None,
mrl_dim: None,
full_dim: None,
capabilities_ext: None,
extra: Default::default(),
}
}
fn good_chunks() -> Vec<ChunkInput> {
(0..3)
.map(|i| ChunkInput {
canonical_text: format!("paragraph number {} with some content", i),
source_uri: "doc.txt".into(),
byte_start: i * 100,
byte_end: i * 100 + 30,
embedding: {
let mut v = vec![0.0; 4];
v[(i % 4) as usize] = 1.0;
v
},
})
.collect()
}
fn build_raw() -> Vec<u8> {
UrnaFileBuilder::new(manifest(3))
.reproducible(true)
.add_chunks(good_chunks())
.build_bytes()
.unwrap()
}
fn build_zstd() -> Vec<u8> {
UrnaFileBuilder::new(manifest(3))
.text_encoding(SectionEncoding::Zstd)
.reproducible(true)
.add_chunks(good_chunks())
.build_bytes()
.unwrap()
}
fn find_entry(bytes: &[u8], section_id: u32) -> (usize, SectionEntry) {
let view = UrnaView::from_bytes(bytes).unwrap();
let table_off = view.header.section_table_offset as usize;
for (i, e) in view.section_table.iter().enumerate() {
if e.section_id == section_id {
return (table_off + i * URNA_SECTION_ENTRY_SIZE, *e);
}
}
panic!("section 0x{:02x} not found", section_id);
}
fn rewrite_file_hash(bytes: &mut [u8]) {
let body_end = bytes.len() - URNA_FOOTER_SIZE;
let new_hash = UrnaFooter::compute_file_hash(&bytes[..body_end]);
bytes[body_end + 8..body_end + 40].copy_from_slice(&new_hash);
}
#[test]
fn rejects_unsupported_encoding_value() {
let mut bytes = build_raw();
let (entry_off, _) = find_entry(&bytes, SECTION_CHUNKS_CANONICAL);
bytes[entry_off + 4..entry_off + 8].copy_from_slice(&99u32.to_le_bytes());
rewrite_file_hash(&mut bytes);
match UrnaView::from_bytes(&bytes) {
Err(UrnaError::UnsupportedSectionEncoding {
section_id,
encoding,
}) => {
assert_eq!(section_id, SECTION_CHUNKS_CANONICAL);
assert_eq!(encoding, 99);
}
other => panic!(
"expected UnsupportedSectionEncoding(99); got {:?}",
other.as_ref().err()
),
}
}
#[test]
fn rejects_zstd_encoding_on_embeddings_section() {
let mut bytes = build_raw();
let (entry_off, _) = find_entry(&bytes, SECTION_EMBEDDINGS);
bytes[entry_off + 4..entry_off + 8].copy_from_slice(&SECTION_ENCODING_ZSTD.to_le_bytes());
rewrite_file_hash(&mut bytes);
match UrnaView::from_bytes(&bytes) {
Err(UrnaError::UnsupportedSectionEncoding {
section_id,
encoding,
}) => {
assert_eq!(section_id, SECTION_EMBEDDINGS);
assert_eq!(encoding, SECTION_ENCODING_ZSTD);
}
other => panic!(
"expected UnsupportedSectionEncoding for zstd on embeddings; got {:?}",
other.as_ref().err()
),
}
}
#[test]
fn encoding_mismatch_passes_parse_but_fails_decode() {
let mut bytes = build_raw();
let (entry_off, _) = find_entry(&bytes, SECTION_CHUNKS_CANONICAL);
let enc_pre = u32::from_le_bytes(bytes[entry_off + 4..entry_off + 8].try_into().unwrap());
assert_eq!(enc_pre, SECTION_ENCODING_RAW);
bytes[entry_off + 4..entry_off + 8].copy_from_slice(&SECTION_ENCODING_ZSTD.to_le_bytes());
rewrite_file_hash(&mut bytes);
let view = UrnaView::from_bytes(&bytes).expect("parse should still succeed");
match view.decoded_section(SECTION_CHUNKS_CANONICAL) {
Err(UrnaError::MalformedSectionPayload {
section_id,
reason: _,
}) => {
assert_eq!(section_id, SECTION_CHUNKS_CANONICAL);
}
other => panic!(
"expected MalformedSectionPayload; got {:?}",
other.as_ref().err()
),
}
}
#[test]
fn truncated_zstd_payload_fails_decompression() {
let mut bytes = build_zstd();
let (entry_off, mut entry) = find_entry(&bytes, SECTION_CHUNKS_CANONICAL);
let payload_off = entry.offset as usize;
let payload_end = payload_off + entry.size as usize;
let new_size = (entry.size as usize).saturating_sub(8);
assert!(new_size > 4, "section too small to truncate");
for b in &mut bytes[payload_off + new_size..payload_end] {
*b = 0;
}
entry.size = new_size as u64;
entry.compute_checksum(&bytes[payload_off..payload_off + new_size]);
bytes[entry_off..entry_off + 4].copy_from_slice(&entry.section_id.to_le_bytes());
bytes[entry_off + 4..entry_off + 8].copy_from_slice(&entry.encoding.to_le_bytes());
bytes[entry_off + 8..entry_off + 16].copy_from_slice(&entry.offset.to_le_bytes());
bytes[entry_off + 16..entry_off + 24].copy_from_slice(&entry.size.to_le_bytes());
bytes[entry_off + 24..entry_off + 32].copy_from_slice(&entry.checksum);
rewrite_file_hash(&mut bytes);
let view = UrnaView::from_bytes(&bytes).expect("parse should still succeed");
match view.decoded_section(SECTION_CHUNKS_CANONICAL) {
Err(UrnaError::MalformedSectionPayload { section_id, .. }) => {
assert_eq!(section_id, SECTION_CHUNKS_CANONICAL);
}
other => panic!(
"expected MalformedSectionPayload from truncated zstd; got {:?}",
other.as_ref().err()
),
}
}
#[test]
fn corrupted_zstd_magic_fails_decompression() {
let mut bytes = build_zstd();
let (entry_off, mut entry) = find_entry(&bytes, SECTION_CHUNKS_CANONICAL);
let payload_off = entry.offset as usize;
bytes[payload_off..payload_off + 4].copy_from_slice(&[0xFF, 0xEE, 0xDD, 0xCC]);
entry.compute_checksum(&bytes[payload_off..payload_off + entry.size as usize]);
bytes[entry_off + 24..entry_off + 32].copy_from_slice(&entry.checksum);
rewrite_file_hash(&mut bytes);
let view = UrnaView::from_bytes(&bytes).expect("parse should still succeed");
let res = view.decoded_section(SECTION_CHUNKS_CANONICAL);
assert!(
matches!(res, Err(UrnaError::MalformedSectionPayload { .. })),
"expected MalformedSectionPayload from bad zstd magic; got {:?}",
res.as_ref().err()
);
}
#[test]
fn baseline_zstd_decodes_normally() {
let bytes = build_zstd();
let view = UrnaView::from_bytes(&bytes).unwrap();
let decoded = view.decoded_section(SECTION_CHUNKS_CANONICAL).unwrap();
assert!(!decoded.is_empty());
let _ = decoded; let _ = URNA_HEADER_SIZE; }