#![cfg(all(test, feature = "transforms"))]
#![allow(clippy::unwrap_used)] #![allow(clippy::indexing_slicing)] #![allow(clippy::cast_precision_loss)] #![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_sign_loss)] #![allow(missing_docs)]
use std::io::Cursor;
use image::{ImageBuffer, ImageFormat, RgbImage, Rgba, RgbaImage};
use super::*;
fn create_test_image_1000x800() -> Vec<u8> {
let img: RgbImage = ImageBuffer::from_fn(1000, 800, |x, y| {
image::Rgb([
((x % 256) as u8),
((y % 256) as u8),
(((x + y) % 256) as u8),
])
});
let dyn_img = image::DynamicImage::ImageRgb8(img);
let mut bytes = Vec::new();
dyn_img
.write_to(&mut Cursor::new(&mut bytes), ImageFormat::Jpeg)
.expect("Failed to encode test JPEG");
bytes
}
fn create_test_png_with_alpha() -> Vec<u8> {
let img: RgbaImage = ImageBuffer::from_fn(800, 600, |x, y| {
let alpha = if (x + y) % 2 == 0 { 255 } else { 128 };
Rgba([
((x % 256) as u8),
((y % 256) as u8),
(((x + y) % 256) as u8),
alpha,
])
});
let dyn_img = image::DynamicImage::ImageRgba8(img);
let mut bytes = Vec::new();
dyn_img
.write_to(&mut Cursor::new(&mut bytes), ImageFormat::Png)
.expect("Failed to encode test PNG");
bytes
}
fn create_test_pdf() -> Vec<u8> {
b"%PDF-1.4\n1 0 obj\n<< /Type /Catalog >>\nendobj\n".to_vec()
}
#[test]
fn test_resize_jpeg_to_width() {
let input = create_test_image_1000x800();
let params = TransformParams {
width: Some(500),
height: None,
format: None,
quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.width <= 500);
let expected_height = (500 * 800) / 1000;
assert!(output.height <= expected_height + 1); }
#[test]
fn test_resize_with_height() {
let input = create_test_image_1000x800();
let params = TransformParams {
width: None,
height: Some(200),
format: None,
quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.height <= 200);
let expected_width = (200 * 1000) / 800;
assert!(output.width <= expected_width + 1);
}
#[test]
fn test_resize_with_both_dimensions() {
let input = create_test_image_1000x800();
let params = TransformParams {
width: Some(300),
height: Some(300),
format: None,
quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.width <= 300);
assert!(output.height <= 300);
}
#[test]
fn test_convert_jpeg_to_webp() {
let input = create_test_image_1000x800();
let params = TransformParams {
width: None,
height: None,
format: Some(OutputFormat::Webp),
quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_ok());
let output = result.unwrap();
assert_eq!(output.content_type, "image/webp");
assert!(output.body.starts_with(b"RIFF"));
}
#[test]
fn test_convert_png_to_jpeg() {
let input = create_test_png_with_alpha();
let params = TransformParams {
width: None,
height: None,
format: Some(OutputFormat::Jpeg),
quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_ok());
let output = result.unwrap();
assert_eq!(output.content_type, "image/jpeg");
assert_eq!(&output.body[0..2], &[0xFF, 0xD8]);
}
#[test]
fn test_unsupported_format_returns_error() {
let input = create_test_image_1000x800();
let params = TransformParams {
width: None,
height: None,
format: Some(OutputFormat::Bmp), quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_err());
}
#[test]
fn test_non_image_file_returns_error() {
let input = create_test_pdf();
let params = TransformParams {
width: None,
height: None,
format: None,
quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_err());
}
#[test]
fn test_transform_with_quality_parameter() {
let input = create_test_image_1000x800();
let params_low_quality = TransformParams {
width: Some(500),
height: None,
format: Some(OutputFormat::Jpeg),
quality: Some(50),
};
let params_high_quality = TransformParams {
width: Some(500),
height: None,
format: Some(OutputFormat::Jpeg),
quality: Some(95),
};
let result_low = ImageTransformer::transform(&input, ¶ms_low_quality);
let result_high = ImageTransformer::transform(&input, ¶ms_high_quality);
assert!(result_low.is_ok());
assert!(result_high.is_ok());
let low_output = result_low.unwrap();
let high_output = result_high.unwrap();
assert_eq!(low_output.content_type, "image/jpeg");
assert_eq!(high_output.content_type, "image/jpeg");
assert_eq!(&low_output.body[0..2], &[0xFF, 0xD8]);
assert_eq!(&high_output.body[0..2], &[0xFF, 0xD8]);
}
#[test]
fn test_transform_default_quality() {
let input = create_test_image_1000x800();
let params = TransformParams {
width: Some(500),
height: None,
format: Some(OutputFormat::Jpeg),
quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_ok());
}
#[test]
fn test_invalid_dimensions_returns_error() {
let input = create_test_image_1000x800();
let params = TransformParams {
width: Some(0), height: None,
format: None,
quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_err());
}
#[test]
fn test_resize_maintains_aspect_ratio() {
let input = create_test_image_1000x800();
let params = TransformParams {
width: Some(250),
height: None,
format: None,
quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_ok());
let output = result.unwrap();
let output_ratio = output.width as f32 / output.height as f32;
let original_ratio = 1000.0 / 800.0;
assert!((output_ratio - original_ratio).abs() < 0.05);
}
#[test]
fn test_transform_output_has_correct_dimensions() {
let input = create_test_image_1000x800();
let params = TransformParams {
width: Some(500),
height: None,
format: None,
quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_ok());
let output = result.unwrap();
assert_eq!(output.width, 500);
assert_eq!(output.height, 400); }
#[test]
fn test_transform_empty_input_returns_error() {
let input = vec![];
let params = TransformParams {
width: Some(500),
height: None,
format: None,
quality: None,
};
let result = ImageTransformer::transform(&input, ¶ms);
assert!(result.is_err());
}
#[test]
fn test_apply_preset_thumbnail() {
use crate::config::TransformPreset;
let presets = vec![
TransformPreset {
name: "thumbnail".to_string(),
width: Some(150),
height: Some(150),
format: Some("webp".to_string()),
quality: Some(80),
},
TransformPreset {
name: "medium".to_string(),
width: Some(800),
height: Some(600),
format: Some("jpeg".to_string()),
quality: Some(85),
},
];
let params = ImageTransformer::apply_preset("thumbnail", Some(&presets));
assert!(params.is_some());
let p = params.unwrap();
assert_eq!(p.width, Some(150));
assert_eq!(p.height, Some(150));
assert_eq!(p.format, Some(OutputFormat::Webp));
assert_eq!(p.quality, Some(80));
}
#[test]
fn test_apply_preset_not_found() {
use crate::config::TransformPreset;
let presets = vec![TransformPreset {
name: "thumbnail".to_string(),
width: Some(150),
height: Some(150),
format: Some("webp".to_string()),
quality: Some(80),
}];
let params = ImageTransformer::apply_preset("nonexistent", Some(&presets));
assert!(params.is_none());
}
#[test]
fn test_apply_preset_none_presets() {
let params = ImageTransformer::apply_preset("any", None);
assert!(params.is_none());
}
#[test]
fn test_apply_preset_format_conversion() {
use crate::config::TransformPreset;
let presets = vec![
TransformPreset {
name: "png".to_string(),
width: None,
height: None,
format: Some("png".to_string()),
quality: None,
},
TransformPreset {
name: "jpg".to_string(),
width: None,
height: None,
format: Some("jpg".to_string()),
quality: None,
},
TransformPreset {
name: "avif".to_string(),
width: None,
height: None,
format: Some("avif".to_string()),
quality: None,
},
];
let png_params = ImageTransformer::apply_preset("png", Some(&presets)).unwrap();
assert_eq!(png_params.format, Some(OutputFormat::Png));
let jpg_params = ImageTransformer::apply_preset("jpg", Some(&presets)).unwrap();
assert_eq!(jpg_params.format, Some(OutputFormat::Jpeg));
let avif_params = ImageTransformer::apply_preset("avif", Some(&presets)).unwrap();
assert_eq!(avif_params.format, Some(OutputFormat::Avif));
}
#[tokio::test]
async fn test_render_cache_stores_and_retrieves_transform() {
use std::sync::Arc;
use tempfile::TempDir;
use crate::{backend::LocalBackend, transforms::cache::TransformCache};
let temp_dir = TempDir::new().unwrap();
let backend = Arc::new(LocalBackend::new(temp_dir.path().to_str().unwrap()));
let original = create_test_image_1000x800();
let _key = backend.upload("test.jpg", &original, "image/jpeg").await.unwrap();
let cache = TransformCache::new(backend.clone());
let params = TransformParams {
width: Some(500),
height: None,
format: None,
quality: None,
};
let result = cache.get_or_transform("test.jpg", &original, ¶ms).await.unwrap();
assert!(result.is_some());
let output = result.unwrap();
assert_eq!(output.width, 500);
let cached_result = cache.get_or_transform("test.jpg", &original, ¶ms).await.unwrap();
assert!(cached_result.is_some());
assert_eq!(cached_result.unwrap().body, output.body);
}
#[tokio::test]
async fn test_render_cache_invalidated_on_source_change() {
use std::sync::Arc;
use tempfile::TempDir;
use crate::{backend::LocalBackend, transforms::cache::TransformCache};
let temp_dir = TempDir::new().unwrap();
let backend = Arc::new(LocalBackend::new(temp_dir.path().to_str().unwrap()));
let original_1 = create_test_image_1000x800();
let original_2 = create_test_png_with_alpha();
backend.upload("test.jpg", &original_1, "image/jpeg").await.unwrap();
let cache = TransformCache::new(backend.clone());
let params = TransformParams {
width: Some(500),
height: None,
format: None,
quality: None,
};
let result_1 = cache.get_or_transform("test.jpg", &original_1, ¶ms).await.unwrap().unwrap();
cache.invalidate("test.jpg").await.unwrap();
let result_2 = cache.get_or_transform("test.jpg", &original_2, ¶ms).await.unwrap().unwrap();
assert_ne!(result_1.body, result_2.body);
}
#[test]
fn test_render_cache_key_format() {
use crate::transforms::cache::TransformCache;
let params = TransformParams {
width: Some(500),
height: Some(400),
format: Some(OutputFormat::Webp),
quality: Some(85),
};
let key = TransformCache::build_cache_key("my-image.jpg", ¶ms);
assert!(key.contains("500"));
assert!(key.contains("400"));
assert!(key.contains("webp"));
assert!(key.contains("85"));
}
#[tokio::test]
async fn test_render_cache_with_preset_lookup() {
use std::sync::Arc;
use tempfile::TempDir;
use crate::{
backend::LocalBackend, config::TransformPreset, transforms::cache::TransformCache,
};
let temp_dir = TempDir::new().unwrap();
let backend = Arc::new(LocalBackend::new(temp_dir.path().to_str().unwrap()));
let original = create_test_image_1000x800();
backend.upload("test.jpg", &original, "image/jpeg").await.unwrap();
let cache = TransformCache::new(backend.clone());
let presets = vec![TransformPreset {
name: "thumbnail".to_string(),
width: Some(150),
height: Some(150),
format: Some("webp".to_string()),
quality: Some(80),
}];
let preset_params = ImageTransformer::apply_preset("thumbnail", Some(&presets)).unwrap();
let result = cache
.get_or_transform("test.jpg", &original, &preset_params)
.await
.unwrap()
.unwrap();
assert_eq!(result.width, 150);
assert_eq!(result.height, 120);
assert_eq!(result.content_type, "image/webp");
}