#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::path::{Path, PathBuf};
use std::process::Command;
fn xberg_bin() -> &'static str {
env!("CARGO_BIN_EXE_xberg")
}
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("crates/xberg-cli parent")
.parent()
.expect("crates parent")
.to_path_buf()
}
fn text_fixture() -> PathBuf {
repo_root().join("test_documents").join("text").join("fake_text.txt")
}
fn docx_fixture() -> PathBuf {
repo_root().join("test_documents").join("docx").join("word_tables.docx")
}
fn require(path: &Path) {
assert!(
path.is_file(),
"fixture missing at {} — this test must not silently pass",
path.display()
);
}
struct Output {
stdout: String,
stderr: String,
success: bool,
}
fn run(args: &[&str]) -> Output {
let output = Command::new(xberg_bin())
.args(args)
.output()
.unwrap_or_else(|error| panic!("failed to run `xberg {}`: {error}", args.join(" ")));
Output {
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
success: output.status.success(),
}
}
#[test]
fn extract_text_keeps_stdout_free_of_envelope_output() {
let fixture = text_fixture();
require(&fixture);
let output = run(&["extract", &fixture.to_string_lossy()]);
assert!(output.success, "extraction failed: {}", output.stderr);
assert!(
output.stdout.contains("This is a test document to use for unit tests."),
"stdout must carry the extracted content; got {:?}",
output.stdout
);
assert!(
!output.stdout.contains("--- extraction envelope ---"),
"the envelope summary must not pollute the content stream; got {:?}",
output.stdout
);
}
#[test]
fn extract_text_reports_the_extraction_envelope_on_stderr() {
let fixture = text_fixture();
require(&fixture);
let output = run(&["extract", &fixture.to_string_lossy()]);
assert!(output.success, "extraction failed: {}", output.stderr);
assert!(
output.stderr.contains("--- extraction envelope ---"),
"text mode must emit the envelope summary; got {:?}",
output.stderr
);
assert!(
output.stderr.contains("mime type: text/plain"),
"the envelope summary must report the resolved MIME type; got {:?}",
output.stderr
);
assert!(
output.stderr.contains("extraction time: "),
"the envelope summary must report the extraction time; got {:?}",
output.stderr
);
}
#[test]
fn extract_toon_includes_the_timing_envelope() {
let fixture = text_fixture();
require(&fixture);
let output = run(&["extract", &fixture.to_string_lossy(), "--format", "toon"]);
assert!(output.success, "extraction failed: {}", output.stderr);
for field in ["extraction_time_ms", "peak_memory_bytes"] {
assert!(
output.stdout.contains(field),
"TOON output must include the `{field}` envelope field; got {:?}",
output.stdout
);
}
assert!(
output.stdout.contains("This is a test document to use for unit tests."),
"TOON output must still carry the extracted document; got {:?}",
output.stdout
);
}
#[test]
fn extract_json_includes_the_timing_envelope() {
let fixture = text_fixture();
require(&fixture);
let output = run(&["extract", &fixture.to_string_lossy(), "--format", "json"]);
assert!(output.success, "extraction failed: {}", output.stderr);
let parsed: serde_json::Value =
serde_json::from_str(&output.stdout).unwrap_or_else(|error| panic!("JSON output was not valid: {error}"));
assert!(
parsed.get("extraction_time_ms").is_some(),
"JSON envelope must include extraction_time_ms; got {parsed}"
);
assert!(
parsed.get("peak_memory_bytes").is_some(),
"JSON envelope must include peak_memory_bytes; got {parsed}"
);
}
#[test]
fn formats_command_agrees_with_actual_extraction_capability() {
let fixture = docx_fixture();
require(&fixture);
let listed = run(&["formats", "--format", "json"]);
assert!(listed.success, "`xberg formats` failed: {}", listed.stderr);
let formats: serde_json::Value =
serde_json::from_str(&listed.stdout).unwrap_or_else(|error| panic!("formats JSON was not valid: {error}"));
let advertises_docx = formats
.as_array()
.expect("formats output must be a JSON array")
.iter()
.any(|entry| entry.get("extension").and_then(serde_json::Value::as_str) == Some("docx"));
let extracted = run(&["extract", &fixture.to_string_lossy()]);
assert_eq!(
advertises_docx, extracted.success,
"`xberg formats` advertises docx = {advertises_docx}, but extracting a docx succeeded = {}. \
The catalogue must describe the built binary. stderr: {}",
extracted.success, extracted.stderr
);
}
#[test]
fn formats_command_reflects_the_compiled_feature_set() {
let listed = run(&["formats", "--format", "json"]);
assert!(listed.success, "`xberg formats` failed: {}", listed.stderr);
let formats: serde_json::Value = serde_json::from_str(&listed.stdout).expect("formats JSON must parse");
let extensions: Vec<&str> = formats
.as_array()
.expect("formats output must be a JSON array")
.iter()
.filter_map(|entry| entry.get("extension").and_then(serde_json::Value::as_str))
.collect();
for always_present in ["txt", "md", "csv", "json"] {
assert!(
extensions.contains(&always_present),
"'{always_present}' has an ungated extractor and must always be listed; got {extensions:?}"
);
}
if cfg!(feature = "formats-no-heic") {
assert!(
extensions.contains(&"docx"),
"`formats-no-heic` is enabled, so docx must be listed; got {extensions:?}"
);
} else {
assert!(
!extensions.contains(&"docx"),
"`formats-no-heic` is disabled, so docx must not be advertised; got {extensions:?}"
);
}
}