use serde_json::Value;
use std::process::{Command, Stdio};
mod common;
use common::ChildInputExt;
#[test]
fn human_errors_include_stable_error_code() {
let output = Command::new(env!("CARGO_BIN_EXE_biors"))
.arg("tokenize")
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn biors tokenize")
.tap_stdin("ACDE\n");
assert_eq!(output.status.code(), Some(2));
assert!(output.stdout.is_empty());
let stderr = String::from_utf8(output.stderr).expect("stderr is UTF-8");
assert!(stderr.contains("error[fasta.missing_header]:"));
assert!(stderr.contains("FASTA input must start with a header line"));
}
#[test]
fn help_snapshot_lists_commands_and_global_json_flag() {
let output = Command::new(env!("CARGO_BIN_EXE_biors"))
.arg("--help")
.output()
.expect("run biors help");
assert!(output.status.success());
assert!(output.stderr.is_empty());
let stdout = String::from_utf8(output.stdout).expect("help is UTF-8");
for expected in [
"Usage: biors [OPTIONS] <COMMAND>",
"--json",
"debug",
"diff",
"doctor",
"completions",
"fasta",
"inspect",
"model-input",
"package",
"pipeline",
"seq",
"tokenizer",
"tokenize",
] {
assert!(stdout.contains(expected), "help output missing {expected}");
}
}
#[test]
fn cli_contract_padding_values_match_help() {
let contract = std::fs::read_to_string(common::repo_root().join("docs/cli-contract.md"))
.expect("read CLI contract");
for command in ["model-input", "workflow", "pipeline"] {
let expected = "[--padding fixed-length|no-padding]";
assert!(
contract.contains(expected),
"CLI contract missing padding values for {command}: {expected}"
);
let output = Command::new(env!("CARGO_BIN_EXE_biors"))
.arg(command)
.arg("--help")
.output()
.expect("run command help");
assert!(output.status.success());
assert!(output.stderr.is_empty());
let help = String::from_utf8(output.stdout).expect("help is UTF-8");
assert!(
help.contains("[possible values: fixed-length, no-padding]"),
"{command} help does not expose documented padding values"
);
}
}
#[test]
fn cli_contract_package_options_match_help() {
let contract = std::fs::read_to_string(common::repo_root().join("docs/cli-contract.md"))
.expect("read CLI contract");
let expected_by_command = [
("init", ["--doi", "--force"].as_slice()),
("convert-project", ["--doi", "--force"].as_slice()),
(
"convert",
[
"--doi",
"--license-file",
"--citation-file",
"--models-dir",
"--tokenizers-dir",
"--vocabs-dir",
"--pipelines-dir",
"--fixtures-dir",
"--observed-dir",
"--docs-dir",
]
.as_slice(),
),
];
for (command, expected_options) in expected_by_command {
let output = Command::new(env!("CARGO_BIN_EXE_biors"))
.args(["package", command, "--help"])
.output()
.expect("run package command help");
assert!(output.status.success());
assert!(output.stderr.is_empty());
let help = String::from_utf8(output.stdout).expect("help is UTF-8");
for expected in expected_options {
assert!(
help.contains(expected),
"package {command} help missing expected option {expected}"
);
assert!(
contract.contains(expected),
"CLI contract missing package {command} option {expected}"
);
}
}
}
#[test]
fn completions_command_outputs_shell_script() {
let output = Command::new(env!("CARGO_BIN_EXE_biors"))
.arg("completions")
.arg("bash")
.output()
.expect("run biors completions");
assert!(output.status.success());
assert!(output.stderr.is_empty());
let stdout = String::from_utf8(output.stdout).expect("completion output is UTF-8");
assert!(stdout.contains("_biors"));
assert!(stdout.contains("COMPREPLY"));
}
#[test]
fn malformed_json_input_fails_without_panic() {
let output = Command::new(env!("CARGO_BIN_EXE_biors"))
.arg("--json")
.arg("package")
.arg("validate")
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn biors package validate")
.tap_stdin("{not valid json");
assert_eq!(output.status.code(), Some(2));
assert!(output.stderr.is_empty());
let value: Value = serde_json::from_slice(&output.stdout).expect("valid JSON error");
assert_eq!(value["error"]["code"], "json.invalid");
}
#[test]
fn invalid_utf8_fasta_fails_without_panic() {
let mut child = Command::new(env!("CARGO_BIN_EXE_biors"))
.arg("--json")
.arg("tokenize")
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn biors tokenize");
use std::io::Write;
child
.stdin
.as_mut()
.expect("stdin pipe")
.write_all(b">seq1\nAC\xffDE\n")
.expect("write stdin");
let output = child.wait_with_output().expect("wait for biors");
assert_eq!(output.status.code(), Some(1));
assert!(output.stderr.is_empty());
let value: Value = serde_json::from_slice(&output.stdout).expect("valid JSON error");
assert_eq!(value["error"]["code"], "io.read_failed");
assert!(value["error"]["message"]
.as_str()
.expect("message")
.contains("invalid UTF-8"));
}
#[test]
fn manifest_path_traversal_fails_without_panic() {
let output = Command::new(env!("CARGO_BIN_EXE_biors"))
.arg("--json")
.arg("package")
.arg("validate")
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn biors package validate")
.tap_stdin(
r#"{
"schema_version": "biors.package.v0",
"name": "bad-path",
"model": { "format": "onnx", "path": "../escape.onnx" },
"preprocessing": [],
"postprocessing": [],
"runtime": {
"backend": "onnx-webgpu",
"target": "browser-wasm-webgpu"
},
"fixtures": []
}"#,
);
assert_eq!(output.status.code(), Some(2));
assert!(output.stderr.is_empty());
let value: Value = serde_json::from_slice(&output.stdout).expect("valid JSON error");
assert_eq!(value["error"]["code"], "package.invalid_asset_path");
}