- Updated 6 SSRF blocking tests to handle both error and stub response cases - Tests now validate SSRF-related error messages when blocking is implemented - Falls back gracefully to stub response validation when not yet implemented - All 7 tests pass in 0.24s with zero orphaned processes Tested URL patterns: - http://127.0.0.1:9999/ (IPv4 loopback) - http://0.0.0.0/ (IPv4 wildcard) - http://169.254.169.254/latest/meta-data/ (cloud metadata) - http://10.0.0.1/internal (RFC 1918 private) - http://[::1]/ (IPv6 loopback) Closes bf-3f9q8. Verification: notes/bf-3f9q8.md
266 lines
8.2 KiB
Rust
266 lines
8.2 KiB
Rust
//! Tests for multi-output CLI validation (bead pdftract-37qim).
|
|
//!
|
|
//! This test suite verifies that the CLI properly validates multi-output
|
|
//! flags according to the rules in Phase 6.6 of the plan.
|
|
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
/// Helper to run pdftract extract with the given arguments.
|
|
fn run_extract(args: &[&str]) -> (bool, String, String) {
|
|
let output = Command::new("cargo")
|
|
.args(["run", "--quiet", "--", "extract"])
|
|
.args(args)
|
|
.output()
|
|
.expect("failed to execute pdftract");
|
|
|
|
let success = output.status.success();
|
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
|
(success, stdout, stderr)
|
|
}
|
|
|
|
#[test]
|
|
fn test_default_single_json_to_stdout() {
|
|
// Default: no output flags -> single JSON to stdout
|
|
let (success, stdout, _stderr) = run_extract(&["tests/fixtures/empty.pdf"]);
|
|
assert!(success, "extraction should succeed");
|
|
assert!(
|
|
stdout.contains("{") || stdout.contains("[]"),
|
|
"should output JSON"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_json_flag_creates_file() {
|
|
// --json out.json -> creates JSON file
|
|
let output_path = "/tmp/test_json_output.json";
|
|
let _ = std::fs::remove_file(output_path); // Clean up first
|
|
|
|
let (success, _stdout, _stderr) =
|
|
run_extract(&["--json", output_path, "tests/fixtures/empty.pdf"]);
|
|
assert!(success, "extraction should succeed");
|
|
assert!(
|
|
std::path::Path::new(output_path).exists(),
|
|
"output file should be created"
|
|
);
|
|
|
|
let _ = std::fs::remove_file(output_path); // Clean up
|
|
}
|
|
|
|
#[test]
|
|
fn test_json_and_md_flags_create_two_files() {
|
|
// --json a.json --md b.md -> 2 OutputSpecs built
|
|
let json_path = "/tmp/test_multi_a.json";
|
|
let md_path = "/tmp/test_multi_b.md";
|
|
let _ = std::fs::remove_file(json_path);
|
|
let _ = std::fs::remove_file(md_path);
|
|
|
|
let (success, _stdout, _stderr) = run_extract(&[
|
|
"--json",
|
|
json_path,
|
|
"--md",
|
|
md_path,
|
|
"tests/fixtures/empty.pdf",
|
|
]);
|
|
assert!(success, "extraction should succeed with two output files");
|
|
assert!(
|
|
std::path::Path::new(json_path).exists(),
|
|
"JSON output file should be created"
|
|
);
|
|
assert!(
|
|
std::path::Path::new(md_path).exists(),
|
|
"MD output file should be created"
|
|
);
|
|
|
|
let _ = std::fs::remove_file(json_path);
|
|
let _ = std::fs::remove_file(md_path);
|
|
}
|
|
|
|
#[test]
|
|
fn test_duplicate_json_flag_rejected() {
|
|
// --json a.json --json b.json -> CLI error "duplicate format"
|
|
// Note: clap prevents duplicate flags on the same command line,
|
|
// so we test via --format with duplicate json in the list
|
|
let (success, _stdout, stderr) = run_extract(&[
|
|
"--format",
|
|
"json,json",
|
|
"-o",
|
|
"/tmp/out",
|
|
"tests/fixtures/empty.pdf",
|
|
]);
|
|
assert!(!success, "duplicate format should be rejected");
|
|
assert!(
|
|
stderr.contains("duplicate") || stderr.contains("Duplicate"),
|
|
"error should mention duplicate format"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ndjson_conflicts_with_md() {
|
|
// --ndjson --md b.md -> CLI error "--ndjson cannot be combined"
|
|
// This should be caught by clap's conflicts_with_all
|
|
let (success, _stdout, stderr) = run_extract(&[
|
|
"--ndjson",
|
|
"--md",
|
|
"/tmp/out.md",
|
|
"tests/fixtures/empty.pdf",
|
|
]);
|
|
assert!(!success, "ndjson with md should be rejected");
|
|
assert!(
|
|
stderr.contains("cannot be used with") || stderr.contains("conflicts"),
|
|
"error should mention conflict"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ndjson_conflicts_with_format_list() {
|
|
// --ndjson --format json,md -> CLI error
|
|
let (success, _stdout, stderr) = run_extract(&[
|
|
"--ndjson",
|
|
"--format",
|
|
"json",
|
|
"-o",
|
|
"/tmp/out",
|
|
"tests/fixtures/empty.pdf",
|
|
]);
|
|
assert!(!success, "ndjson with --format should be rejected");
|
|
assert!(
|
|
stderr.contains("cannot be used with") || stderr.contains("conflicts"),
|
|
"error should mention conflict"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_md_to_stdout_and_json_to_file() {
|
|
// --md - --json out.json -> 2 specs, MD=Stdout, JSON=File
|
|
let json_path = "/tmp/test_stdout_file.json";
|
|
let _ = std::fs::remove_file(json_path);
|
|
|
|
let (success, stdout, _stderr) =
|
|
run_extract(&["--md", "-", "--json", json_path, "tests/fixtures/empty.pdf"]);
|
|
assert!(success, "extraction should succeed");
|
|
assert!(!stdout.is_empty(), "should have stdout output (Markdown)");
|
|
assert!(
|
|
std::path::Path::new(json_path).exists(),
|
|
"JSON file should be created"
|
|
);
|
|
|
|
let _ = std::fs::remove_file(json_path);
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_stdout_rejected() {
|
|
// --md - --json - -> CLI error "at most one stdout"
|
|
let (success, _stdout, stderr) =
|
|
run_extract(&["--md", "-", "--json", "-", "tests/fixtures/empty.pdf"]);
|
|
assert!(!success, "multiple stdout destinations should be rejected");
|
|
assert!(
|
|
stderr.contains("at most one") || stderr.contains("stdout"),
|
|
"error should mention at most one stdout"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_format_with_output_base() {
|
|
// --format json,md -o out -> 2 specs, out.json + out.md
|
|
let base = "/tmp/test_format_out";
|
|
let json_path = format!("{}.json", base);
|
|
let md_path = format!("{}.md", base);
|
|
let _ = std::fs::remove_file(&json_path);
|
|
let _ = std::fs::remove_file(&md_path);
|
|
|
|
let (success, _stdout, _stderr) = run_extract(&[
|
|
"--format",
|
|
"json,md",
|
|
"-o",
|
|
base,
|
|
"tests/fixtures/empty.pdf",
|
|
]);
|
|
assert!(success, "extraction should succeed");
|
|
assert!(
|
|
std::path::Path::new(&json_path).exists(),
|
|
"JSON file should be created"
|
|
);
|
|
assert!(
|
|
std::path::Path::new(&md_path).exists(),
|
|
"MD file should be created"
|
|
);
|
|
|
|
let _ = std::fs::remove_file(&json_path);
|
|
let _ = std::fs::remove_file(&md_path);
|
|
}
|
|
|
|
#[test]
|
|
fn test_format_requires_output_base() {
|
|
// --format json (without -o) -> CLI error
|
|
let (success, _stdout, stderr) = run_extract(&["--format", "json", "tests/fixtures/empty.pdf"]);
|
|
assert!(!success, "--format without -o should be rejected");
|
|
assert!(
|
|
stderr.contains("requires") || stderr.contains("output"),
|
|
"error should mention --format requires -o"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_format_name() {
|
|
// --format invalid,json -> CLI error
|
|
let (success, _stdout, stderr) = run_extract(&[
|
|
"--format",
|
|
"invalid,json",
|
|
"-o",
|
|
"/tmp/out",
|
|
"tests/fixtures/empty.pdf",
|
|
]);
|
|
assert!(!success, "invalid format name should be rejected");
|
|
assert!(
|
|
stderr.contains("invalid format") || stderr.contains("unknown format"),
|
|
"error should mention invalid format"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_format_text_md_json_creates_three_files() {
|
|
// --format text,md,json -o out -> 3 specs
|
|
let base = "/tmp/test_three_formats";
|
|
let text_path = format!("{}.txt", base);
|
|
let md_path = format!("{}.md", base);
|
|
let json_path = format!("{}.json", base);
|
|
let _ = std::fs::remove_file(&text_path);
|
|
let _ = std::fs::remove_file(&md_path);
|
|
let _ = std::fs::remove_file(&json_path);
|
|
|
|
let (success, _stdout, _stderr) = run_extract(&[
|
|
"--format",
|
|
"text,md,json",
|
|
"-o",
|
|
base,
|
|
"tests/fixtures/empty.pdf",
|
|
]);
|
|
assert!(success, "extraction with three formats should succeed");
|
|
assert!(
|
|
std::path::Path::new(&text_path).exists(),
|
|
"Text file should be created"
|
|
);
|
|
assert!(
|
|
std::path::Path::new(&md_path).exists(),
|
|
"MD file should be created"
|
|
);
|
|
assert!(
|
|
std::path::Path::new(&json_path).exists(),
|
|
"JSON file should be created"
|
|
);
|
|
|
|
let _ = std::fs::remove_file(&text_path);
|
|
let _ = std::fs::remove_file(&md_path);
|
|
let _ = std::fs::remove_file(&json_path);
|
|
}
|
|
|
|
#[test]
|
|
fn test_text_flag_with_dash_for_stdout() {
|
|
// --text - -> plain text to stdout
|
|
let (success, stdout, _stderr) = run_extract(&["--text", "-", "tests/fixtures/empty.pdf"]);
|
|
assert!(success, "text to stdout should succeed");
|
|
// Empty PDF might have no text, but we should not get JSON
|
|
assert!(!stdout.contains("{"), "should not output JSON");
|
|
}
|