use std::process::Command;
use tempfile::TempDir;
fn cargo_run() -> Command {
Command::new(env!("CARGO_BIN_EXE_sensus"))
}
fn write_pixel_png(path: &std::path::Path, rgba: [u8; 4]) {
let img = image::RgbaImage::from_pixel(1, 1, image::Rgba(rgba));
img.save(path).expect("write test PNG");
}
#[test]
fn cli_deuteranopia_writes_output_png() {
let dir = TempDir::new().unwrap();
let input = dir.path().join("in.png");
let output = dir.path().join("out.png");
write_pixel_png(&input, [255, 0, 0, 255]);
let status = cargo_run()
.args([
"-i",
input.to_str().unwrap(),
"-o",
output.to_str().unwrap(),
"--filter",
"deuteranopia",
"--strength",
"1.0",
])
.status()
.unwrap();
assert!(status.success(), "expected exit 0 for implemented filter");
assert!(output.exists(), "expected output PNG to be written");
}
#[test]
fn cli_strength_zero_is_identity() {
let dir = TempDir::new().unwrap();
let input = dir.path().join("in.png");
write_pixel_png(&input, [200, 50, 30, 255]);
let original = std::fs::read(&input).unwrap();
let original_rgba = image::open(&input).unwrap().to_rgba8().into_raw();
for filter in ["protanopia", "deuteranopia", "tritanopia", "achromatopsia"] {
let output = dir.path().join(format!("out-{filter}.png"));
let status = cargo_run()
.args([
"-i",
input.to_str().unwrap(),
"-o",
output.to_str().unwrap(),
"--filter",
filter,
"--strength",
"0.0",
])
.status()
.unwrap();
assert!(status.success(), "expected exit 0 for filter {filter}");
let out_rgba = image::open(&output).unwrap().to_rgba8().into_raw();
assert_eq!(
out_rgba, original_rgba,
"strength=0 must be byte-exact identity for filter {filter}"
);
}
assert_eq!(std::fs::read(&input).unwrap(), original);
}
#[test]
fn cli_strength_above_one_is_rejected_at_cli_layer() {
let dir = TempDir::new().unwrap();
let input = dir.path().join("in.png");
let output = dir.path().join("out.png");
write_pixel_png(&input, [255, 0, 0, 255]);
let status = cargo_run()
.args([
"-i",
input.to_str().unwrap(),
"-o",
output.to_str().unwrap(),
"--filter",
"deuteranopia",
"--strength",
"2.0",
])
.status()
.unwrap();
assert!(
!status.success(),
"expected non-zero exit for out-of-range strength"
);
assert!(
!output.exists(),
"expected no output PNG when CLI rejects args"
);
}
fn write_solid_png(path: &std::path::Path, w: u32, h: u32, rgba: [u8; 4]) {
let img = image::RgbaImage::from_pixel(w, h, image::Rgba(rgba));
img.save(path).expect("write test PNG");
}
#[test]
fn cli_myopia_writes_output_png() {
let dir = TempDir::new().unwrap();
let input = dir.path().join("in.png");
let output = dir.path().join("out.png");
write_solid_png(&input, 32, 32, [200, 50, 30, 255]);
let status = cargo_run()
.args([
"-i",
input.to_str().unwrap(),
"-o",
output.to_str().unwrap(),
"--filter",
"myopia",
"--strength",
"1.0",
])
.status()
.unwrap();
assert!(status.success(), "expected exit 0 for myopia filter");
assert!(output.exists(), "expected output PNG to be written");
}
#[test]
fn cli_astigmatism_with_axis_writes_output_png() {
let dir = TempDir::new().unwrap();
let input = dir.path().join("in.png");
let output = dir.path().join("out.png");
write_solid_png(&input, 32, 32, [200, 50, 30, 255]);
let status = cargo_run()
.args([
"-i",
input.to_str().unwrap(),
"-o",
output.to_str().unwrap(),
"--filter",
"astigmatism",
"--strength",
"1.0",
"--axis",
"45",
])
.status()
.unwrap();
assert!(
status.success(),
"expected exit 0 for astigmatism with --axis"
);
assert!(output.exists(), "expected output PNG to be written");
}
#[test]
fn cli_axis_actually_changes_astigmatism_output() {
let dir = TempDir::new().unwrap();
let input = dir.path().join("in.png");
let img = image::RgbaImage::from_fn(200, 200, |x, y| {
image::Rgba([(x % 256) as u8, (y % 256) as u8, 128, 255])
});
img.save(&input).unwrap();
let run_axis = |axis: &str, out: &std::path::Path| {
let status = cargo_run()
.args([
"-i",
input.to_str().unwrap(),
"-o",
out.to_str().unwrap(),
"--filter",
"astigmatism",
"--strength",
"1.0",
"--axis",
axis,
])
.status()
.unwrap();
assert!(status.success(), "astigmatism --axis {axis} should succeed");
image::open(out).unwrap().to_rgba8().into_raw()
};
let a0 = run_axis("0", &dir.path().join("a0.png"));
let a90 = run_axis("90", &dir.path().join("a90.png"));
assert_ne!(
a0, a90,
"--axis must change astigmatism blur direction (regression: axis was pinned to 90)"
);
}
#[test]
fn cli_newly_exposed_filters_run() {
let dir = TempDir::new().unwrap();
let input = dir.path().join("in.png");
write_solid_png(&input, 32, 32, [200, 120, 60, 255]);
for filter in [
"metamorphopsia",
"contrast-sensitivity",
"detail-loss",
"teichopsia",
"flickering-stars",
] {
let output = dir.path().join(format!("out-{filter}.png"));
let status = cargo_run()
.args([
"-i",
input.to_str().unwrap(),
"-o",
output.to_str().unwrap(),
"--filter",
filter,
"--strength",
"1.0",
])
.status()
.unwrap();
assert!(
status.success(),
"expected exit 0 for newly exposed filter {filter}"
);
assert!(output.exists(), "expected output PNG for filter {filter}");
}
}
#[test]
fn cli_axis_out_of_range_is_rejected() {
let dir = TempDir::new().unwrap();
let input = dir.path().join("in.png");
let output = dir.path().join("out.png");
write_solid_png(&input, 16, 16, [255, 0, 0, 255]);
let status = cargo_run()
.args([
"-i",
input.to_str().unwrap(),
"-o",
output.to_str().unwrap(),
"--filter",
"astigmatism",
"--strength",
"1.0",
"--axis",
"200",
])
.status()
.unwrap();
assert!(
!status.success(),
"expected non-zero exit for out-of-range axis"
);
assert!(
!output.exists(),
"expected no output PNG when CLI rejects args"
);
}
#[test]
fn cli_axis_with_non_astigmatism_filter_warns() {
let dir = TempDir::new().unwrap();
let input = dir.path().join("in.png");
let output = dir.path().join("out.png");
write_pixel_png(&input, [120, 120, 120, 255]);
let out = cargo_run()
.args([
"-i",
input.to_str().unwrap(),
"-o",
output.to_str().unwrap(),
"--filter",
"myopia",
"--strength",
"0.5",
"--axis",
"45",
])
.output()
.unwrap();
assert!(out.status.success(), "expected exit 0");
assert!(output.exists(), "expected output PNG to be written");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("--axis"),
"expected warning about --axis on stderr, got: {stderr}"
);
}