Critical: undo toast now trashes only batch output files (not entire dir), JPEG scanline write errors propagated, selective metadata write result returned. High: zero-dimension guards in ResizeConfig/fit_within, negative aspect ratio rejection, FM integration toggle infinite recursion guard, saturating counter arithmetic in executor. Medium: PNG compression level passed to oxipng, pct mode updates job_config, external file loading updates step indicator, CLI undo removes history entries, watch config write failures reported, fast-copy path reads image dimensions for rename templates, discovery excludes unprocessable formats (heic/svg/ico/jxl), CLI warns on invalid algorithm/overwrite values, resolve_collision trailing dot fix, generation guards on all preview threads to cancel stale results, default DPI aligned to 0, watermark text width uses char count not byte length. Low: binary path escaped in Nautilus extension, file dialog filter aligned with discovery, reset_wizard clears preset_mode and output_dir.
126 lines
4.2 KiB
Rust
126 lines
4.2 KiB
Rust
use pixstrip_core::operations::MetadataConfig;
|
|
use pixstrip_core::operations::metadata::strip_metadata;
|
|
use std::path::Path;
|
|
|
|
fn create_test_jpeg(path: &Path) {
|
|
let img = image::RgbImage::from_fn(100, 80, |x, y| {
|
|
image::Rgb([(x % 256) as u8, (y % 256) as u8, 128])
|
|
});
|
|
img.save_with_format(path, image::ImageFormat::Jpeg).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn strip_all_metadata_produces_file() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let input = dir.path().join("test.jpg");
|
|
let output = dir.path().join("stripped.jpg");
|
|
create_test_jpeg(&input);
|
|
|
|
strip_metadata(&input, &output, &MetadataConfig::StripAll).unwrap();
|
|
assert!(output.exists());
|
|
assert!(std::fs::metadata(&output).unwrap().len() > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn keep_all_metadata_copies_file() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let input = dir.path().join("test.jpg");
|
|
let output = dir.path().join("kept.jpg");
|
|
create_test_jpeg(&input);
|
|
|
|
strip_metadata(&input, &output, &MetadataConfig::KeepAll).unwrap();
|
|
assert!(output.exists());
|
|
}
|
|
|
|
#[test]
|
|
fn privacy_mode_strips_gps() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let input = dir.path().join("test.jpg");
|
|
let output = dir.path().join("privacy.jpg");
|
|
create_test_jpeg(&input);
|
|
|
|
strip_metadata(&input, &output, &MetadataConfig::Privacy).unwrap();
|
|
assert!(output.exists());
|
|
}
|
|
|
|
fn create_test_png(path: &Path) {
|
|
let img = image::RgbaImage::from_fn(100, 80, |x, y| {
|
|
image::Rgba([(x % 256) as u8, (y % 256) as u8, 128, 255])
|
|
});
|
|
img.save_with_format(path, image::ImageFormat::Png).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn strip_png_metadata_produces_valid_png() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let input = dir.path().join("test.png");
|
|
let output = dir.path().join("stripped.png");
|
|
create_test_png(&input);
|
|
|
|
strip_metadata(&input, &output, &MetadataConfig::StripAll).unwrap();
|
|
assert!(output.exists());
|
|
// Output must be a valid PNG that can be opened
|
|
let img = image::open(&output).unwrap();
|
|
assert_eq!(img.width(), 100);
|
|
assert_eq!(img.height(), 80);
|
|
}
|
|
|
|
#[test]
|
|
fn strip_png_removes_text_chunks() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let input = dir.path().join("test.png");
|
|
let output = dir.path().join("stripped.png");
|
|
create_test_png(&input);
|
|
|
|
strip_metadata(&input, &output, &MetadataConfig::StripAll).unwrap();
|
|
// Read output and verify no tEXt chunks remain
|
|
let data = std::fs::read(&output).unwrap();
|
|
let mut pos = 8; // skip PNG signature
|
|
while pos + 12 <= data.len() {
|
|
let chunk_len = u32::from_be_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]) as usize;
|
|
let chunk_type = &data[pos+4..pos+8];
|
|
assert_ne!(chunk_type, b"tEXt", "tEXt chunk should be stripped");
|
|
assert_ne!(chunk_type, b"iTXt", "iTXt chunk should be stripped");
|
|
assert_ne!(chunk_type, b"zTXt", "zTXt chunk should be stripped");
|
|
pos += 12 + chunk_len;
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn strip_png_output_smaller_or_equal() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let input = dir.path().join("test.png");
|
|
let output = dir.path().join("stripped.png");
|
|
create_test_png(&input);
|
|
|
|
let input_size = std::fs::metadata(&input).unwrap().len();
|
|
strip_metadata(&input, &output, &MetadataConfig::StripAll).unwrap();
|
|
let output_size = std::fs::metadata(&output).unwrap().len();
|
|
assert!(output_size <= input_size);
|
|
}
|
|
|
|
#[test]
|
|
fn strip_jpeg_removes_app1_exif() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let input = dir.path().join("test.jpg");
|
|
let output = dir.path().join("stripped.jpg");
|
|
create_test_jpeg(&input);
|
|
|
|
strip_metadata(&input, &output, &MetadataConfig::StripAll).unwrap();
|
|
// Verify no APP1 (0xFFE1) markers remain
|
|
let data = std::fs::read(&output).unwrap();
|
|
let mut i = 2; // skip SOI
|
|
while i + 1 < data.len() {
|
|
if data[i] != 0xFF { break; }
|
|
let marker = data[i + 1];
|
|
if marker == 0xDA { break; } // SOS - rest is image data
|
|
assert_ne!(marker, 0xE1, "APP1/EXIF marker should be stripped");
|
|
if i + 3 < data.len() {
|
|
let len = ((data[i + 2] as usize) << 8) | (data[i + 3] as usize);
|
|
i += 2 + len;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|