#[cfg(test)]
#[cfg(feature = "media-thumbnail")]
mod tests {
use crate::media::CasStore;
use crate::runtime::builtin::media::context::MediaToolContext;
use crate::runtime::builtin::media::{MediaOp, MediaOpResult};
use std::sync::Arc;
async fn setup() -> (tempfile::TempDir, Arc<MediaToolContext>) {
let dir = tempfile::tempdir().unwrap();
let ctx = Arc::new(MediaToolContext::new(CasStore::new(dir.path())));
(dir, ctx)
}
async fn store(ctx: &MediaToolContext, data: &[u8]) -> String {
ctx.cas.store(data).await.unwrap().hash
}
fn encode_rgba(img: &image::ImageBuffer<image::Rgba<u8>, Vec<u8>>) -> Vec<u8> {
let (w, h) = img.dimensions();
let mut buf = Vec::new();
let enc = image::codecs::png::PngEncoder::new(&mut buf);
image::ImageEncoder::write_image(enc, img.as_raw(), w, h, image::ExtendedColorType::Rgba8)
.unwrap();
buf
}
fn encode_rgb(img: &image::ImageBuffer<image::Rgb<u8>, Vec<u8>>) -> Vec<u8> {
let (w, h) = img.dimensions();
let mut buf = Vec::new();
let enc = image::codecs::png::PngEncoder::new(&mut buf);
image::ImageEncoder::write_image(enc, img.as_raw(), w, h, image::ExtendedColorType::Rgb8)
.unwrap();
buf
}
fn solid_rgba(w: u32, h: u32, r: u8, g: u8, b: u8, a: u8) -> Vec<u8> {
let img = image::ImageBuffer::from_pixel(w, h, image::Rgba([r, g, b, a]));
encode_rgba(&img)
}
fn solid_rgb(w: u32, h: u32, r: u8, g: u8, b: u8) -> Vec<u8> {
let img = image::ImageBuffer::from_pixel(w, h, image::Rgb([r, g, b]));
encode_rgb(&img)
}
fn gradient_rgba(w: u32, h: u32) -> Vec<u8> {
let img = image::ImageBuffer::from_fn(w, h, |x, y| {
image::Rgba([
(x * 255 / w.max(1)) as u8,
(y * 255 / h.max(1)) as u8,
128,
255,
])
});
encode_rgba(&img)
}
fn checkerboard(w: u32, h: u32, cell: u32) -> Vec<u8> {
let img = image::ImageBuffer::from_fn(w, h, |x, y| {
if (x / cell + y / cell) % 2 == 0 {
image::Rgba([0u8, 0, 0, 255])
} else {
image::Rgba([255u8, 255, 255, 255])
}
});
encode_rgba(&img)
}
fn make_jpeg(w: u32, h: u32, r: u8, g: u8, b: u8) -> Vec<u8> {
let img = image::ImageBuffer::from_pixel(w, h, image::Rgb([r, g, b]));
let mut buf = Vec::new();
let enc = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 90);
image::ImageEncoder::write_image(enc, img.as_raw(), w, h, image::ExtendedColorType::Rgb8)
.unwrap();
buf
}
use crate::runtime::builtin::media::color::DominantColorOp;
use crate::runtime::builtin::media::convert::ConvertOp;
use crate::runtime::builtin::media::dimensions::DimensionsOp;
use crate::runtime::builtin::media::strip::StripOp;
use crate::runtime::builtin::media::thumbhash_tool::ThumbhashOp;
use crate::runtime::builtin::media::thumbnail::ThumbnailOp;
#[tokio::test]
async fn t01_thumbnail_solid_blue_pixels_are_blue() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(80, 80, 0, 0, 255, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 40}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap().to_rgba8();
for pixel in img.pixels() {
assert!(
pixel[2] > 200,
"blue channel should be > 200, got {}",
pixel[2]
);
assert!(
pixel[0] < 50,
"red channel should be < 50, got {}",
pixel[0]
);
assert!(
pixel[1] < 50,
"green channel should be < 50, got {}",
pixel[1]
);
}
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t02_convert_roundtrip_pixel_fidelity() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(20, 20, 255, 0, 0, 255)).await;
let op = ConvertOp;
let jpeg_result = op
.execute(
serde_json::json!({"hash": hash, "format": "jpeg", "quality": 100}),
&ctx,
)
.await
.unwrap();
let jpeg_hash = if let MediaOpResult::Binary { data, .. } = &jpeg_result {
store(&ctx, data).await
} else {
panic!("expected Binary")
};
let png_result = op
.execute(
serde_json::json!({"hash": jpeg_hash, "format": "png"}),
&ctx,
)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = png_result {
let img = image::load_from_memory(&data).unwrap().to_rgba8();
let center = img.get_pixel(10, 10);
assert!(
center[0] > 240,
"red channel after roundtrip should be > 240, got {}",
center[0]
);
assert!(
center[1] < 30,
"green channel after roundtrip should be < 30, got {}",
center[1]
);
assert!(
center[2] < 30,
"blue channel after roundtrip should be < 30, got {}",
center[2]
);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t03_strip_pixel_perfect_lossless() {
let (_dir, ctx) = setup().await;
let original = solid_rgba(15, 15, 123, 45, 67, 255);
let hash = store(&ctx, &original).await;
let op = StripOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let orig_img = image::load_from_memory(&original).unwrap().to_rgba8();
let stripped_img = image::load_from_memory(&data).unwrap().to_rgba8();
assert_eq!(orig_img.dimensions(), stripped_img.dimensions());
for (o, s) in orig_img.pixels().zip(stripped_img.pixels()) {
assert_eq!(o, s, "pixel mismatch: original {:?} != stripped {:?}", o, s);
}
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t04_thumbnail_checkerboard_2to1_pattern_integrity() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &checkerboard(100, 100, 10)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 50}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap().to_rgba8();
assert_eq!(img.width(), 50);
assert_eq!(img.height(), 50);
let tl = img.get_pixel(0, 0);
assert!(
tl[0] < 50 && tl[1] < 50 && tl[2] < 50,
"top-left should be dark, got {:?}",
tl
);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t05_convert_transparent_png_to_jpeg_white_composite() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(20, 20, 255, 0, 0, 0)).await;
let op = ConvertOp;
let result = op
.execute(serde_json::json!({"hash": hash, "format": "jpeg"}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap().to_rgb8();
assert_eq!(img.width(), 20);
assert_eq!(img.height(), 20);
let _center = img.get_pixel(10, 10);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t06_thumbnail_preserves_alpha_values() {
let (_dir, ctx) = setup().await;
let img = image::ImageBuffer::from_fn(40, 40, |_, _| image::Rgba([0u8, 255, 0, 128]));
let hash = store(&ctx, &encode_rgba(&img)).await;
let op = ThumbnailOp;
let result = op
.execute(
serde_json::json!({"hash": hash, "width": 20, "format": "png"}),
&ctx,
)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let out = image::load_from_memory(&data).unwrap().to_rgba8();
let center = out.get_pixel(10, 10);
assert!(
(100..160).contains(¢er[3]),
"alpha should be ~128 (semi-transparent), got {}",
center[3]
);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t07_thumbnail_jpeg_input_pixel_check() {
let (_dir, ctx) = setup().await;
let jpeg = make_jpeg(60, 30, 0, 200, 0); let hash = store(&ctx, &jpeg).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 30}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap().to_rgba8();
assert_eq!(img.width(), 30);
assert_eq!(img.height(), 15);
let center = img.get_pixel(15, 7);
assert!(
center[1] > 150,
"green channel should be dominant, got {}",
center[1]
);
} else {
panic!("expected Binary");
}
}
#[cfg(feature = "media-optimize")]
#[tokio::test]
async fn t08_optimize_pixel_equivalence_lossless() {
let (_dir, ctx) = setup().await;
let original = solid_rgb(30, 30, 100, 200, 50);
let hash = store(&ctx, &original).await;
let op = crate::runtime::builtin::media::optimize::OptimizeOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let orig_img = image::load_from_memory(&original).unwrap().to_rgb8();
let opt_img = image::load_from_memory(&data).unwrap().to_rgb8();
assert_eq!(orig_img.dimensions(), opt_img.dimensions());
for (o, s) in orig_img.pixels().zip(opt_img.pixels()) {
assert_eq!(
o, s,
"optimize should be lossless: original {:?} != optimized {:?}",
o, s
);
}
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t09_thumbnail_gradient_direction_preserved() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(200, 200)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 50}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap().to_rgba8();
let tl = img.get_pixel(0, 0); let br = img.get_pixel(49, 49); assert!(
br[0] > tl[0],
"bottom-right red ({}) should be > top-left red ({})",
br[0],
tl[0]
);
} else {
panic!("expected Binary");
}
}
#[cfg(feature = "media-svg")]
#[tokio::test]
async fn t10_svg_render_pixel_at_fill_region() {
let (_dir, ctx) = setup().await;
let svg = r##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" fill="#ff0000"/>
</svg>"##;
let hash = store(&ctx, svg.as_bytes()).await;
let op = crate::runtime::builtin::media::svg::SvgRenderOp;
let result = op
.execute(
serde_json::json!({"hash": hash, "width": 100, "height": 100}),
&ctx,
)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap().to_rgba8();
let center = img.get_pixel(50, 50);
assert!(
center[0] > 240,
"red fill should be visible at center, R={}",
center[0]
);
assert!(
center[3] > 240,
"alpha should be opaque at center, A={}",
center[3]
);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t11_thumbnail_odd_ratio_7x3_to_5() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(7, 3, 128, 128, 128, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 5}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, metadata, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 5);
let h = img.height();
assert!(
h == 2 || h == 3,
"7:3 ratio at width=5 -> height ~2, got {h}"
);
assert_eq!(metadata["width"], 5);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t12_thumbnail_prime_dimensions_13x11_to_7() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(13, 11, 64, 64, 64, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 7}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 7);
let h = img.height();
assert!(
(5..=7).contains(&h),
"expected height ~6 for 13:11 at w=7, got {h}"
);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t13_thumbnail_extreme_landscape_to_1px() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(1000, 1, 200, 100, 50, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 1}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, metadata, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 1);
assert_eq!(img.height(), 1, "height must clamp to 1, not 0");
assert_eq!(metadata["height"], 1);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t14_thumbnail_extreme_portrait_height_at_limit() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(1, 1000, 50, 200, 100, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 10}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, metadata, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 10);
let h = metadata["height"].as_u64().unwrap();
assert!(h <= 10_000, "height must not exceed 10000, got {h}");
assert!(
h >= 1000,
"height should be ~10000 for 1:1000 at w=10, got {h}"
);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t15_thumbnail_explicit_dims_ignore_ratio() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(200, 100)).await;
let op = ThumbnailOp;
let result = op
.execute(
serde_json::json!({"hash": hash, "width": 30, "height": 30}),
&ctx,
)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 30, "explicit width should be respected");
assert_eq!(img.height(), 30, "explicit height should be respected");
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t16_thumbnail_same_width_as_original() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(50, 25, 10, 20, 30, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 50}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 50);
assert_eq!(img.height(), 25);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t17_thumbnail_3x3_to_2() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(3, 3, 255, 128, 0, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 2}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 2);
assert_eq!(img.height(), 2, "3:3 at w=2 should give h=2");
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t18_thumbnail_non_round_ratio_500x250_to_333() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(500, 250)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 333}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 333);
let h = img.height();
assert!(
(166..=167).contains(&h),
"500:250 at w=333 -> h~166.5, got {h}"
);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t19_thumbnail_upscale_2x2_to_100() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(2, 2, 255, 255, 0, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 100}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 100);
assert_eq!(img.height(), 100, "2:2 square upscaled to 100:100");
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t20_thumbnail_large_downscale_5000_to_10() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(5000, 2500, 64, 128, 192, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 10}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 10);
assert_eq!(img.height(), 5, "5000:2500 at w=10 -> h=5");
let p = *img.to_rgba8().get_pixel(5, 2);
assert!((40..90).contains(&p[0]), "R ~64, got {}", p[0]);
assert!((100..160).contains(&p[1]), "G ~128, got {}", p[1]);
assert!((160..220).contains(&p[2]), "B ~192, got {}", p[2]);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t21_convert_webp_magic_bytes_structure() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(10, 10, 0, 0, 255, 255)).await;
let op = ConvertOp;
let result = op
.execute(serde_json::json!({"hash": hash, "format": "webp"}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
assert_eq!(&data[0..4], b"RIFF", "WebP starts with RIFF");
assert_eq!(&data[8..12], b"WEBP", "bytes 8-12 should be WEBP");
assert!(data.len() > 12, "WebP should have content after header");
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t22_convert_jpeg_soi_eoi_markers() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(20, 20, 0, 255, 0, 255)).await;
let op = ConvertOp;
let result = op
.execute(serde_json::json!({"hash": hash, "format": "jpeg"}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
assert_eq!(
&data[..2],
&[0xFF, 0xD8],
"JPEG should start with SOI marker"
);
assert_eq!(
&data[data.len() - 2..],
&[0xFF, 0xD9],
"JPEG should end with EOI marker"
);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t23_thumbnail_png_signature_and_ihdr() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(40, 40, 128, 128, 128, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 20}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
assert_eq!(
&data[..8],
&[137, 80, 78, 71, 13, 10, 26, 10],
"PNG signature"
);
assert_eq!(&data[12..16], b"IHDR", "first chunk should be IHDR");
let w = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
assert_eq!(w, 20, "IHDR width should be 20");
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t24_thumbnail_webp_decodable_and_correct_dims() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(200, 100)).await;
let op = ThumbnailOp;
let result = op
.execute(
serde_json::json!({"hash": hash, "width": 60, "format": "webp"}),
&ctx,
)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 60);
assert_eq!(img.height(), 30, "200:100 -> 60:30");
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t25_convert_jpeg_to_webp_decodable() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &make_jpeg(50, 30, 255, 128, 0)).await;
let op = ConvertOp;
let result = op
.execute(serde_json::json!({"hash": hash, "format": "webp"}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 50);
assert_eq!(img.height(), 30);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t26_convert_webp_to_png_chain() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(10, 10, 0, 0, 255, 255)).await;
let op = ConvertOp;
let webp = op
.execute(serde_json::json!({"hash": hash, "format": "webp"}), &ctx)
.await
.unwrap();
let webp_hash = if let MediaOpResult::Binary { data, .. } = &webp {
store(&ctx, data).await
} else {
panic!()
};
let png = op
.execute(
serde_json::json!({"hash": webp_hash, "format": "png"}),
&ctx,
)
.await
.unwrap();
if let MediaOpResult::Binary {
data, mime_type, ..
} = png
{
assert_eq!(mime_type, "image/png");
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 10);
assert_eq!(img.height(), 10);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t27_convert_three_format_chain_dimensions_stable() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(25, 15, 128, 64, 32, 255)).await;
let op = ConvertOp;
let r1 = op
.execute(serde_json::json!({"hash": hash, "format": "jpeg"}), &ctx)
.await
.unwrap();
let h1 = if let MediaOpResult::Binary { data, .. } = &r1 {
store(&ctx, data).await
} else {
panic!()
};
let r2 = op
.execute(serde_json::json!({"hash": h1, "format": "webp"}), &ctx)
.await
.unwrap();
let h2 = if let MediaOpResult::Binary { data, .. } = &r2 {
store(&ctx, data).await
} else {
panic!()
};
let r3 = op
.execute(serde_json::json!({"hash": h2, "format": "png"}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = r3 {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 25, "width stable through 3 conversions");
assert_eq!(img.height(), 15, "height stable through 3 conversions");
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t28_strip_jpeg_autodetect_output_valid() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &make_jpeg(25, 25, 100, 100, 100)).await;
let op = StripOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary {
data, mime_type, ..
} = result
{
assert_eq!(mime_type, "image/jpeg");
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 25);
assert_eq!(img.height(), 25);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t29_strip_format_override_jpeg_to_png() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &make_jpeg(15, 15, 200, 50, 100)).await;
let op = StripOp;
let result = op
.execute(serde_json::json!({"hash": hash, "format": "png"}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary {
data, mime_type, ..
} = result
{
assert_eq!(mime_type, "image/png");
assert_eq!(&data[..4], &[137, 80, 78, 71], "should be PNG");
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 15);
assert_eq!(img.height(), 15);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t30_thumbnail_jpeg_vs_png_different_sizes() {
let (_dir, ctx) = setup().await;
let img = image::ImageBuffer::from_fn(200, 200, |x, y| {
let noise = ((x * 17 + y * 31) % 256) as u8;
image::Rgba([
((x * 255 / 200) as u8).wrapping_add(noise),
((y * 255 / 200) as u8).wrapping_add(noise),
noise,
255,
])
});
let data = encode_rgba(&img);
let hash = store(&ctx, &data).await;
let op = ThumbnailOp;
let png_result = op
.execute(
serde_json::json!({"hash": hash, "width": 100, "format": "png"}),
&ctx,
)
.await
.unwrap();
let jpeg_result = op
.execute(
serde_json::json!({"hash": hash, "width": 100, "format": "jpeg"}),
&ctx,
)
.await
.unwrap();
let png_size = if let MediaOpResult::Binary { data, .. } = png_result {
data.len()
} else {
0
};
let jpeg_size = if let MediaOpResult::Binary { data, .. } = jpeg_result {
data.len()
} else {
0
};
assert_ne!(
jpeg_size, png_size,
"JPEG and PNG should produce different file sizes"
);
assert!(png_size > 0 && jpeg_size > 0);
}
#[tokio::test]
async fn t31_thumbnail_idempotent_binary_output() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(80, 40, 0, 0, 255, 255)).await;
let op = ThumbnailOp;
let r1 = op
.execute(serde_json::json!({"hash": hash, "width": 40}), &ctx)
.await
.unwrap();
let r2 = op
.execute(serde_json::json!({"hash": hash, "width": 40}), &ctx)
.await
.unwrap();
let d1 = if let MediaOpResult::Binary { data, .. } = r1 {
data
} else {
panic!()
};
let d2 = if let MediaOpResult::Binary { data, .. } = r2 {
data
} else {
panic!()
};
assert_eq!(
d1, d2,
"identical thumbnail operations must produce identical binary output"
);
}
#[tokio::test]
async fn t32_convert_idempotent_binary_output() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(10, 10, 255, 0, 0, 255)).await;
let op = ConvertOp;
let r1 = op
.execute(
serde_json::json!({"hash": hash, "format": "jpeg", "quality": 80}),
&ctx,
)
.await
.unwrap();
let r2 = op
.execute(
serde_json::json!({"hash": hash, "format": "jpeg", "quality": 80}),
&ctx,
)
.await
.unwrap();
let d1 = if let MediaOpResult::Binary { data, .. } = r1 {
data
} else {
panic!()
};
let d2 = if let MediaOpResult::Binary { data, .. } = r2 {
data
} else {
panic!()
};
assert_eq!(
d1, d2,
"identical convert operations must produce identical binary output"
);
}
#[tokio::test]
async fn t33_dimensions_consistent_repeated_calls() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(317, 211)).await;
let op = DimensionsOp;
for _ in 0..10 {
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
assert_eq!(v["width"], 317);
assert_eq!(v["height"], 211);
} else {
panic!("expected Metadata");
}
}
}
#[tokio::test]
async fn t34_strip_idempotent() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(10, 10, 128, 128, 128, 255)).await;
let op = StripOp;
let r1 = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
let d1 = if let MediaOpResult::Binary { data, .. } = r1 {
data
} else {
panic!()
};
let h1 = store(&ctx, &d1).await;
let r2 = op
.execute(serde_json::json!({"hash": h1}), &ctx)
.await
.unwrap();
let d2 = if let MediaOpResult::Binary { data, .. } = r2 {
data
} else {
panic!()
};
assert_eq!(d1, d2, "strip(strip(img)) should equal strip(img)");
}
#[tokio::test]
async fn t35_thumbnail_cas_hash_deterministic() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(50, 50, 0, 255, 0, 255)).await;
let op = ThumbnailOp;
let r1 = op
.execute(serde_json::json!({"hash": hash, "width": 25}), &ctx)
.await
.unwrap();
let r2 = op
.execute(serde_json::json!({"hash": hash, "width": 25}), &ctx)
.await
.unwrap();
let d1 = if let MediaOpResult::Binary { data, .. } = &r1 {
data.clone()
} else {
panic!()
};
let d2 = if let MediaOpResult::Binary { data, .. } = &r2 {
data.clone()
} else {
panic!()
};
let h1 = store(&ctx, &d1).await;
let h2 = store(&ctx, &d2).await;
assert_eq!(h1, h2, "same thumbnail should produce same CAS hash");
}
#[tokio::test]
async fn t36_thumbnail_different_widths_different_hashes() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(100, 50)).await;
let op = ThumbnailOp;
let r1 = op
.execute(serde_json::json!({"hash": hash, "width": 30}), &ctx)
.await
.unwrap();
let r2 = op
.execute(serde_json::json!({"hash": hash, "width": 50}), &ctx)
.await
.unwrap();
let d1 = if let MediaOpResult::Binary { data, .. } = r1 {
data
} else {
panic!()
};
let d2 = if let MediaOpResult::Binary { data, .. } = r2 {
data
} else {
panic!()
};
assert_ne!(d1, d2, "different widths should produce different outputs");
}
#[tokio::test]
async fn t37_convert_different_formats_different_output() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(10, 10, 128, 0, 128, 255)).await;
let op = ConvertOp;
let r_png = op
.execute(serde_json::json!({"hash": hash, "format": "png"}), &ctx)
.await
.unwrap();
let r_jpeg = op
.execute(serde_json::json!({"hash": hash, "format": "jpeg"}), &ctx)
.await
.unwrap();
let r_webp = op
.execute(serde_json::json!({"hash": hash, "format": "webp"}), &ctx)
.await
.unwrap();
let d_png = if let MediaOpResult::Binary { data, .. } = r_png {
data
} else {
panic!()
};
let d_jpeg = if let MediaOpResult::Binary { data, .. } = r_jpeg {
data
} else {
panic!()
};
let d_webp = if let MediaOpResult::Binary { data, .. } = r_webp {
data
} else {
panic!()
};
assert_ne!(d_png, d_jpeg, "PNG != JPEG");
assert_ne!(d_png, d_webp, "PNG != WebP");
assert_ne!(d_jpeg, d_webp, "JPEG != WebP");
}
#[cfg(feature = "media-optimize")]
#[tokio::test]
async fn t38_optimize_different_levels_different_sizes() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(100, 100)).await;
let op = crate::runtime::builtin::media::optimize::OptimizeOp;
let r1 = op
.execute(serde_json::json!({"hash": hash, "level": 1}), &ctx)
.await
.unwrap();
let r6 = op
.execute(serde_json::json!({"hash": hash, "level": 6}), &ctx)
.await
.unwrap();
let s1 = if let MediaOpResult::Binary { data, .. } = r1 {
data.len()
} else {
0
};
let s6 = if let MediaOpResult::Binary { data, .. } = r6 {
data.len()
} else {
0
};
assert!(
s6 <= s1,
"level 6 ({s6}B) should be <= level 1 ({s1}B) for same image"
);
}
#[tokio::test]
async fn t39_thumbnail_output_smaller_than_input() {
let (_dir, ctx) = setup().await;
let original = gradient_rgba(500, 250);
let original_size = original.len();
let hash = store(&ctx, &original).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 50}), &ctx)
.await
.unwrap();
let output_size = if let MediaOpResult::Binary { data, .. } = result {
data.len()
} else {
0
};
assert!(
output_size < original_size,
"50px thumbnail ({output_size}B) should be smaller than 500px original ({original_size}B)"
);
}
#[cfg(feature = "media-optimize")]
#[tokio::test]
async fn t40_optimize_output_never_larger() {
let (_dir, ctx) = setup().await;
let original = gradient_rgba(80, 80);
let original_size = original.len();
let hash = store(&ctx, &original).await;
let op = crate::runtime::builtin::media::optimize::OptimizeOp;
let result = op
.execute(serde_json::json!({"hash": hash, "level": 2}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, metadata, .. } = result {
assert!(
data.len() <= original_size,
"optimized ({}) should not be larger than original ({original_size})",
data.len()
);
assert_eq!(
metadata["original_size"].as_u64().unwrap(),
original_size as u64
);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t41_color_pure_white_hex_ffffff() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(20, 20, 255, 255, 255, 255)).await;
let op = DominantColorOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
let colors = v["colors"].as_array().unwrap();
let first = &colors[0];
assert!(first["r"].as_u64().unwrap() > 240, "white R > 240");
assert!(first["g"].as_u64().unwrap() > 240, "white G > 240");
assert!(first["b"].as_u64().unwrap() > 240, "white B > 240");
let hex = first["hex"].as_str().unwrap();
assert!(hex.starts_with('#'));
assert_eq!(hex.len(), 7);
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t42_color_pure_black_hex_000000() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(20, 20, 0, 0, 0, 255)).await;
let op = DominantColorOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
let colors = v["colors"].as_array().unwrap();
let first = &colors[0];
assert!(first["r"].as_u64().unwrap() < 15, "black R < 15");
assert!(first["g"].as_u64().unwrap() < 15, "black G < 15");
assert!(first["b"].as_u64().unwrap() < 15, "black B < 15");
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t43_color_hex_always_lowercase_7chars() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(20, 20, 171, 205, 239, 255)).await;
let op = DominantColorOp;
let result = op
.execute(serde_json::json!({"hash": hash, "count": 5}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
for color in v["colors"].as_array().unwrap() {
let hex = color["hex"].as_str().unwrap();
assert_eq!(hex.len(), 7, "hex should be 7 chars: {hex}");
assert!(hex.starts_with('#'), "hex should start with #: {hex}");
assert!(
hex[1..]
.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()),
"hex digits should be lowercase: {hex}"
);
}
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t44_color_rgb_matches_hex_encoding() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(30, 30, 100, 150, 200, 255)).await;
let op = DominantColorOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
let first = &v["colors"].as_array().unwrap()[0];
let r = first["r"].as_u64().unwrap() as u8;
let g = first["g"].as_u64().unwrap() as u8;
let b = first["b"].as_u64().unwrap() as u8;
let hex = first["hex"].as_str().unwrap();
let expected_hex = format!("#{:02x}{:02x}{:02x}", r, g, b);
assert_eq!(hex, expected_hex, "hex should encode RGB values exactly");
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t45_color_count_matches_array_length() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &checkerboard(50, 50, 5)).await;
let op = DominantColorOp;
let result = op
.execute(serde_json::json!({"hash": hash, "count": 8}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
let colors = v["colors"].as_array().unwrap();
let count = v["count"].as_u64().unwrap();
assert_eq!(
count,
colors.len() as u64,
"count field should match array length"
);
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t46_thumbhash_solid_vs_gradient_differ() {
let (_dir, ctx) = setup().await;
let solid_hash = store(&ctx, &solid_rgba(50, 50, 255, 0, 0, 255)).await;
let grad_hash = store(&ctx, &gradient_rgba(50, 50)).await;
let op = ThumbhashOp;
let r1 = op
.execute(serde_json::json!({"hash": solid_hash}), &ctx)
.await
.unwrap();
let r2 = op
.execute(serde_json::json!({"hash": grad_hash}), &ctx)
.await
.unwrap();
let h1 = if let MediaOpResult::Metadata(v) = r1 {
v["thumbhash"].as_str().unwrap().to_string()
} else {
panic!()
};
let h2 = if let MediaOpResult::Metadata(v) = r2 {
v["thumbhash"].as_str().unwrap().to_string()
} else {
panic!()
};
assert_ne!(
h1, h2,
"solid and gradient should have different thumbhashes"
);
}
#[tokio::test]
async fn t47_thumbhash_size_within_spec_range() {
let images = [
solid_rgba(1, 1, 255, 0, 0, 255),
solid_rgba(100, 100, 0, 255, 0, 255),
gradient_rgba(200, 100),
checkerboard(80, 80, 4),
];
let (_dir, ctx) = setup().await;
let op = ThumbhashOp;
for (i, img) in images.iter().enumerate() {
let hash = store(&ctx, img).await;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
let size = v["size_bytes"].as_u64().unwrap();
assert!(
(3..=28).contains(&size),
"image {i}: thumbhash size {size} outside spec range 3..28"
);
} else {
panic!("expected Metadata for image {i}");
}
}
}
#[tokio::test]
async fn t48_thumbhash_of_thumbnail_deterministic() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(300, 150)).await;
let thumb_op = ThumbnailOp;
let r = thumb_op
.execute(serde_json::json!({"hash": hash, "width": 80}), &ctx)
.await
.unwrap();
let thumb_hash = if let MediaOpResult::Binary { data, .. } = &r {
store(&ctx, data).await
} else {
panic!()
};
let th_op = ThumbhashOp;
let r1 = th_op
.execute(serde_json::json!({"hash": thumb_hash}), &ctx)
.await
.unwrap();
let r2 = th_op
.execute(serde_json::json!({"hash": thumb_hash}), &ctx)
.await
.unwrap();
let h1 = if let MediaOpResult::Metadata(v) = r1 {
v["thumbhash"].as_str().unwrap().to_string()
} else {
panic!()
};
let h2 = if let MediaOpResult::Metadata(v) = r2 {
v["thumbhash"].as_str().unwrap().to_string()
} else {
panic!()
};
assert_eq!(h1, h2);
}
#[tokio::test]
async fn t49_color_quality_extremes_both_valid() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &checkerboard(40, 40, 4)).await;
let op = DominantColorOp;
for q in [1, 5, 10] {
let result = op
.execute(serde_json::json!({"hash": hash, "quality": q}), &ctx)
.await;
assert!(result.is_ok(), "quality={q} should produce valid result");
if let Ok(MediaOpResult::Metadata(v)) = result {
assert!(
!v["colors"].as_array().unwrap().is_empty(),
"quality={q} should return colors"
);
}
}
}
#[tokio::test]
async fn t50_thumbhash_jpeg_input() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &make_jpeg(60, 30, 0, 128, 255)).await;
let op = ThumbhashOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
assert!(v["thumbhash"].is_string());
assert!(v["size_bytes"].as_u64().unwrap() > 0);
} else {
panic!("expected Metadata");
}
}
#[test]
fn t51_schema_dimensions_requires_hash() {
let op = DimensionsOp;
let schema = op.parameters_schema();
let required = schema["required"].as_array().unwrap();
assert!(
required.iter().any(|v| v == "hash"),
"dimensions schema must require 'hash'"
);
}
#[test]
fn t52_schema_thumbnail_requires_hash_and_width() {
let op = ThumbnailOp;
let schema = op.parameters_schema();
let required = schema["required"].as_array().unwrap();
let req_strs: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
assert!(req_strs.contains(&"hash"), "thumbnail must require 'hash'");
assert!(
req_strs.contains(&"width"),
"thumbnail must require 'width'"
);
}
#[test]
fn t53_schema_convert_requires_hash_and_format() {
let op = ConvertOp;
let schema = op.parameters_schema();
let required = schema["required"].as_array().unwrap();
let req_strs: Vec<&str> = required.iter().filter_map(|v| v.as_str()).collect();
assert!(req_strs.contains(&"hash"));
assert!(req_strs.contains(&"format"));
}
#[test]
fn t54_schema_thumbnail_format_enum() {
let op = ThumbnailOp;
let schema = op.parameters_schema();
let format_enum = schema["properties"]["format"]["enum"].as_array().unwrap();
let vals: Vec<&str> = format_enum.iter().filter_map(|v| v.as_str()).collect();
assert!(vals.contains(&"png"), "should include png");
assert!(vals.contains(&"jpeg"), "should include jpeg");
assert!(vals.contains(&"webp"), "should include webp");
}
#[test]
fn t55_schema_convert_format_enum() {
let op = ConvertOp;
let schema = op.parameters_schema();
let format_enum = schema["properties"]["format"]["enum"].as_array().unwrap();
let vals: Vec<&str> = format_enum.iter().filter_map(|v| v.as_str()).collect();
assert!(vals.contains(&"png"));
assert!(vals.contains(&"jpeg"));
assert!(vals.contains(&"webp"));
}
#[test]
fn t56_schema_strip_requires_only_hash() {
let op = StripOp;
let schema = op.parameters_schema();
let required = schema["required"].as_array().unwrap();
assert_eq!(required.len(), 1, "strip should require only 'hash'");
assert_eq!(required[0], "hash");
}
#[cfg(feature = "media-optimize")]
#[test]
fn t57_schema_optimize_level_bounds() {
let op = crate::runtime::builtin::media::optimize::OptimizeOp;
let schema = op.parameters_schema();
assert_eq!(schema["properties"]["level"]["minimum"], 1);
assert_eq!(schema["properties"]["level"]["maximum"], 6);
}
#[test]
fn t58_schema_color_count_bounds() {
let op = DominantColorOp;
let schema = op.parameters_schema();
assert_eq!(schema["properties"]["count"]["minimum"], 2);
assert_eq!(schema["properties"]["count"]["maximum"], 20);
}
#[test]
fn t59_schema_color_quality_bounds() {
let op = DominantColorOp;
let schema = op.parameters_schema();
assert_eq!(schema["properties"]["quality"]["minimum"], 1);
assert_eq!(schema["properties"]["quality"]["maximum"], 10);
}
#[test]
fn t60_schema_thumbnail_width_bounds() {
let op = ThumbnailOp;
let schema = op.parameters_schema();
assert_eq!(schema["properties"]["width"]["minimum"], 1);
assert_eq!(schema["properties"]["width"]["maximum"], 10000);
}
#[tokio::test]
async fn t61_thumbnail_metadata_has_width_height() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(100, 80)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 50}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { metadata, .. } = result {
assert!(
metadata.get("width").is_some(),
"metadata should have width"
);
assert!(
metadata.get("height").is_some(),
"metadata should have height"
);
assert_eq!(metadata["width"].as_u64().unwrap(), 50);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t62_convert_metadata_has_converted_to() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(10, 10, 0, 0, 0, 255)).await;
let op = ConvertOp;
for fmt in ["png", "jpeg", "webp"] {
let result = op
.execute(serde_json::json!({"hash": hash, "format": fmt}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { metadata, .. } = result {
assert_eq!(
metadata["converted_to"], fmt,
"converted_to should be {fmt}"
);
} else {
panic!("expected Binary for format {fmt}");
}
}
}
#[tokio::test]
async fn t63_strip_metadata_has_stripped_true() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(10, 10, 0, 0, 0, 255)).await;
let op = StripOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { metadata, .. } = result {
assert_eq!(metadata["stripped"], true);
} else {
panic!("expected Binary");
}
}
#[cfg(feature = "media-optimize")]
#[tokio::test]
async fn t64_optimize_metadata_all_fields() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(50, 50)).await;
let op = crate::runtime::builtin::media::optimize::OptimizeOp;
let result = op
.execute(serde_json::json!({"hash": hash, "level": 3}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { metadata, .. } = result {
assert!(metadata.get("original_size").is_some());
assert!(metadata.get("optimized_size").is_some());
assert!(metadata.get("savings_pct").is_some());
assert!(metadata.get("level").is_some());
assert_eq!(metadata["level"], 3);
let savings = metadata["savings_pct"].as_f64().unwrap();
assert!(
(0.0..=100.0).contains(&savings),
"savings_pct should be 0-100, got {savings}"
);
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t65_dimensions_metadata_has_orientation() {
let (_dir, ctx) = setup().await;
for (w, h, expected) in [
(200, 100, "landscape"),
(100, 200, "portrait"),
(50, 50, "square"),
] {
let hash = store(&ctx, &solid_rgba(w, h, 0, 0, 0, 255)).await;
let op = DimensionsOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
assert_eq!(
v["orientation"], expected,
"{}x{} should be {expected}",
w, h
);
} else {
panic!("expected Metadata");
}
}
}
#[cfg(feature = "media-svg")]
#[tokio::test]
async fn t66_svg_render_metadata_source_format() {
let (_dir, ctx) = setup().await;
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><rect width="10" height="10" fill="red"/></svg>"#;
let hash = store(&ctx, svg.as_bytes()).await;
let op = crate::runtime::builtin::media::svg::SvgRenderOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { metadata, .. } = result {
assert_eq!(metadata["source_format"], "svg");
} else {
panic!("expected Binary");
}
}
#[tokio::test]
async fn t67_thumbhash_response_fields() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(20, 20, 0, 0, 0, 255)).await;
let op = ThumbhashOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
assert!(v.get("thumbhash").is_some(), "should have thumbhash field");
assert!(
v.get("size_bytes").is_some(),
"should have size_bytes field"
);
assert!(v["thumbhash"].is_string());
assert!(v["size_bytes"].is_u64());
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t68_color_response_structure() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &checkerboard(30, 30, 5)).await;
let op = DominantColorOp;
let result = op
.execute(serde_json::json!({"hash": hash, "count": 4}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
assert!(v["colors"].is_array(), "should have colors array");
assert!(v["count"].is_u64(), "should have numeric count");
for color in v["colors"].as_array().unwrap() {
assert!(color.get("r").is_some(), "color should have r");
assert!(color.get("g").is_some(), "color should have g");
assert!(color.get("b").is_some(), "color should have b");
assert!(color.get("hex").is_some(), "color should have hex");
}
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t69_media_op_result_variant_distinguishable() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(10, 10, 0, 0, 0, 255)).await;
let dim_result = DimensionsOp
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
assert!(matches!(dim_result, MediaOpResult::Metadata(_)));
let thumb_result = ThumbnailOp
.execute(serde_json::json!({"hash": hash, "width": 5}), &ctx)
.await
.unwrap();
assert!(matches!(thumb_result, MediaOpResult::Binary { .. }));
}
#[tokio::test]
async fn t70_metadata_result_is_valid_json() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(40, 20)).await;
let op = DimensionsOp;
let result = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = result {
let json_str = serde_json::to_string(&v).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(parsed["width"], 40);
assert_eq!(parsed["height"], 20);
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t71_chain_thumb_strip_convert_dimensions() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(300, 150)).await;
let r1 = ThumbnailOp
.execute(serde_json::json!({"hash": hash, "width": 60}), &ctx)
.await
.unwrap();
let h1 = if let MediaOpResult::Binary { data, .. } = &r1 {
store(&ctx, data).await
} else {
panic!()
};
let r2 = StripOp
.execute(serde_json::json!({"hash": h1}), &ctx)
.await
.unwrap();
let h2 = if let MediaOpResult::Binary { data, .. } = &r2 {
store(&ctx, data).await
} else {
panic!()
};
let r3 = ConvertOp
.execute(serde_json::json!({"hash": h2, "format": "jpeg"}), &ctx)
.await
.unwrap();
let h3 = if let MediaOpResult::Binary { data, .. } = &r3 {
store(&ctx, data).await
} else {
panic!()
};
let r4 = DimensionsOp
.execute(serde_json::json!({"hash": h3}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = r4 {
assert_eq!(v["width"], 60);
assert_eq!(v["height"], 30);
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t72_chain_convert_jpeg_thumb_color() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(80, 80, 0, 200, 0, 255)).await;
let r1 = ConvertOp
.execute(serde_json::json!({"hash": hash, "format": "jpeg"}), &ctx)
.await
.unwrap();
let h1 = if let MediaOpResult::Binary { data, .. } = &r1 {
store(&ctx, data).await
} else {
panic!()
};
let r2 = ThumbnailOp
.execute(serde_json::json!({"hash": h1, "width": 20}), &ctx)
.await
.unwrap();
let h2 = if let MediaOpResult::Binary { data, .. } = &r2 {
store(&ctx, data).await
} else {
panic!()
};
let r3 = DominantColorOp
.execute(serde_json::json!({"hash": h2}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = r3 {
let first = &v["colors"].as_array().unwrap()[0];
assert!(
first["g"].as_u64().unwrap() > 150,
"green should be dominant after chain"
);
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t73_chain_thumbnail_thumbhash_valid() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &checkerboard(200, 200, 20)).await;
let r1 = ThumbnailOp
.execute(serde_json::json!({"hash": hash, "width": 50}), &ctx)
.await
.unwrap();
let h1 = if let MediaOpResult::Binary { data, .. } = &r1 {
store(&ctx, data).await
} else {
panic!()
};
let r2 = ThumbhashOp
.execute(serde_json::json!({"hash": h1}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = r2 {
let encoded = v["thumbhash"].as_str().unwrap();
let decoded =
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, encoded)
.unwrap();
assert!((3..=28).contains(&decoded.len()));
} else {
panic!("expected Metadata");
}
}
#[cfg(feature = "media-optimize")]
#[tokio::test]
async fn t74_chain_optimize_thumbnail_dimensions() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(100, 100)).await;
let r1 = crate::runtime::builtin::media::optimize::OptimizeOp
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
let h1 = if let MediaOpResult::Binary { data, .. } = &r1 {
store(&ctx, data).await
} else {
panic!()
};
let r2 = ThumbnailOp
.execute(serde_json::json!({"hash": h1, "width": 30}), &ctx)
.await
.unwrap();
let h2 = if let MediaOpResult::Binary { data, .. } = &r2 {
store(&ctx, data).await
} else {
panic!()
};
let r3 = DimensionsOp
.execute(serde_json::json!({"hash": h2}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = r3 {
assert_eq!(v["width"], 30);
assert_eq!(v["height"], 30);
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t75_chain_double_strip_idempotent() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(30, 20)).await;
let op = StripOp;
let r1 = op
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
let d1 = if let MediaOpResult::Binary { data, .. } = r1 {
data
} else {
panic!()
};
let h1 = store(&ctx, &d1).await;
let r2 = op
.execute(serde_json::json!({"hash": h1}), &ctx)
.await
.unwrap();
let d2 = if let MediaOpResult::Binary { data, .. } = r2 {
data
} else {
panic!()
};
assert_eq!(
d1, d2,
"strip(strip(img)) should be identical to strip(img)"
);
}
#[cfg(feature = "media-svg")]
#[tokio::test]
async fn t76_chain_svg_render_thumb_color() {
let (_dir, ctx) = setup().await;
let svg = r##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" fill="#0000ff"/>
</svg>"##;
let hash = store(&ctx, svg.as_bytes()).await;
let r1 = crate::runtime::builtin::media::svg::SvgRenderOp
.execute(
serde_json::json!({"hash": hash, "width": 80, "height": 80}),
&ctx,
)
.await
.unwrap();
let h1 = if let MediaOpResult::Binary { data, .. } = &r1 {
store(&ctx, data).await
} else {
panic!()
};
let r2 = ThumbnailOp
.execute(serde_json::json!({"hash": h1, "width": 20}), &ctx)
.await
.unwrap();
let h2 = if let MediaOpResult::Binary { data, .. } = &r2 {
store(&ctx, data).await
} else {
panic!()
};
let r3 = DominantColorOp
.execute(serde_json::json!({"hash": h2}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = r3 {
let first = &v["colors"].as_array().unwrap()[0];
assert!(
first["b"].as_u64().unwrap() > 150,
"blue SVG -> blue dominant color"
);
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t77_chain_thumbnail_webp_then_dimensions() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(100, 50)).await;
let r1 = ThumbnailOp
.execute(
serde_json::json!({"hash": hash, "width": 40, "format": "webp"}),
&ctx,
)
.await
.unwrap();
let h1 = if let MediaOpResult::Binary { data, .. } = &r1 {
store(&ctx, data).await
} else {
panic!()
};
let r2 = DimensionsOp
.execute(serde_json::json!({"hash": h1}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = r2 {
assert_eq!(v["width"], 40);
assert_eq!(v["height"], 20);
} else {
panic!("expected Metadata");
}
}
#[cfg(feature = "media-optimize")]
#[tokio::test]
async fn t78_chain_jpeg_low_quality_to_optimized_png() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(60, 60)).await;
let r1 = ConvertOp
.execute(
serde_json::json!({"hash": hash, "format": "jpeg", "quality": 5}),
&ctx,
)
.await
.unwrap();
let h1 = if let MediaOpResult::Binary { data, .. } = &r1 {
store(&ctx, data).await
} else {
panic!()
};
let r2 = ConvertOp
.execute(serde_json::json!({"hash": h1, "format": "png"}), &ctx)
.await
.unwrap();
let h2 = if let MediaOpResult::Binary { data, .. } = &r2 {
store(&ctx, data).await
} else {
panic!()
};
let r3 = crate::runtime::builtin::media::optimize::OptimizeOp
.execute(serde_json::json!({"hash": h2}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = r3 {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 60);
assert_eq!(img.height(), 60);
} else {
panic!("expected Binary");
}
}
#[cfg(feature = "media-metadata")]
#[tokio::test]
async fn t79_chain_every_binary_tool_metadata_has_image_mime() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(20, 20, 128, 128, 128, 255)).await;
let meta_op = crate::runtime::builtin::media::metadata::MetadataOp;
let r = ThumbnailOp
.execute(serde_json::json!({"hash": hash, "width": 10}), &ctx)
.await
.unwrap();
let h = if let MediaOpResult::Binary { data, .. } = &r {
store(&ctx, data).await
} else {
panic!()
};
let m = meta_op
.execute(serde_json::json!({"hash": h}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = m {
assert!(
v["mime_type"].as_str().unwrap().starts_with("image/"),
"thumbnail output should be image/*"
);
}
let r = ConvertOp
.execute(serde_json::json!({"hash": hash, "format": "jpeg"}), &ctx)
.await
.unwrap();
let h = if let MediaOpResult::Binary { data, .. } = &r {
store(&ctx, data).await
} else {
panic!()
};
let m = meta_op
.execute(serde_json::json!({"hash": h}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = m {
assert!(
v["mime_type"].as_str().unwrap().starts_with("image/"),
"convert output should be image/*"
);
}
let r = StripOp
.execute(serde_json::json!({"hash": hash}), &ctx)
.await
.unwrap();
let h = if let MediaOpResult::Binary { data, .. } = &r {
store(&ctx, data).await
} else {
panic!()
};
let m = meta_op
.execute(serde_json::json!({"hash": h}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = m {
assert!(
v["mime_type"].as_str().unwrap().starts_with("image/"),
"strip output should be image/*"
);
}
}
#[tokio::test]
async fn t80_all_tools_share_same_hash_without_interference() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(60, 30)).await;
let dim = DimensionsOp
.execute(serde_json::json!({"hash": hash}), &ctx)
.await;
let thumb = ThumbnailOp
.execute(serde_json::json!({"hash": hash, "width": 30}), &ctx)
.await;
let conv = ConvertOp
.execute(serde_json::json!({"hash": hash, "format": "jpeg"}), &ctx)
.await;
let strip = StripOp
.execute(serde_json::json!({"hash": hash}), &ctx)
.await;
let color = DominantColorOp
.execute(serde_json::json!({"hash": hash}), &ctx)
.await;
let thash = ThumbhashOp
.execute(serde_json::json!({"hash": hash}), &ctx)
.await;
assert!(dim.is_ok(), "dimensions should work");
assert!(thumb.is_ok(), "thumbnail should work");
assert!(conv.is_ok(), "convert should work");
assert!(strip.is_ok(), "strip should work");
assert!(color.is_ok(), "color should work");
assert!(thash.is_ok(), "thumbhash should work");
let data = ctx.read_media(&hash).await.unwrap();
assert!(!data.is_empty(), "original data should still be intact");
}
#[tokio::test]
async fn t81_concurrent_thumbnails_correct_dimensions() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(200, 100)).await;
let widths: Vec<u32> = vec![10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
let handles: Vec<_> = widths
.iter()
.map(|&w| {
let ctx = Arc::clone(&ctx);
let hash = hash.clone();
tokio::spawn(async move {
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": w}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { data, .. } = result {
let img = image::load_from_memory(&data).unwrap();
(w, img.width(), img.height())
} else {
panic!()
}
})
})
.collect();
let results: Vec<_> = futures::future::join_all(handles).await;
for r in results {
let (requested_w, actual_w, actual_h) = r.unwrap();
assert_eq!(
actual_w, requested_w,
"width mismatch for requested {requested_w}"
);
let expected_h = (requested_w as f64 * 100.0 / 200.0).round() as u32;
assert_eq!(
actual_h, expected_h,
"height mismatch for width {requested_w}"
);
}
}
#[tokio::test]
async fn t82_concurrent_mixed_metadata_tools() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(50, 50, 200, 100, 50, 255)).await;
let ctx1 = Arc::clone(&ctx);
let ctx2 = Arc::clone(&ctx);
let ctx3 = Arc::clone(&ctx);
let h1 = hash.clone();
let h2 = hash.clone();
let h3 = hash.clone();
let (r1, r2, r3) = tokio::join!(
tokio::spawn(async move {
DimensionsOp
.execute(serde_json::json!({"hash": h1}), &ctx1)
.await
}),
tokio::spawn(async move {
DominantColorOp
.execute(serde_json::json!({"hash": h2}), &ctx2)
.await
}),
tokio::spawn(async move {
ThumbhashOp
.execute(serde_json::json!({"hash": h3}), &ctx3)
.await
}),
);
assert!(r1.unwrap().is_ok());
assert!(r2.unwrap().is_ok());
assert!(r3.unwrap().is_ok());
}
#[tokio::test]
async fn t83_concurrent_converts_different_formats() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(20, 20, 0, 128, 255, 255)).await;
let formats = ["png", "jpeg", "webp"];
let handles: Vec<_> = formats
.iter()
.map(|&fmt| {
let ctx = Arc::clone(&ctx);
let hash = hash.clone();
let fmt = fmt.to_string();
tokio::spawn(async move {
let op = ConvertOp;
let result = op
.execute(serde_json::json!({"hash": hash, "format": fmt}), &ctx)
.await
.unwrap();
if let MediaOpResult::Binary { mime_type, .. } = result {
mime_type
} else {
panic!()
}
})
})
.collect();
let results: Vec<_> = futures::future::join_all(handles).await;
let mimes: Vec<String> = results.into_iter().map(|r| r.unwrap()).collect();
assert!(mimes.contains(&"image/png".to_string()));
assert!(mimes.contains(&"image/jpeg".to_string()));
assert!(mimes.contains(&"image/webp".to_string()));
}
#[tokio::test]
async fn t84_concurrent_stores_unique_hashes() {
let (_dir, ctx) = setup().await;
let images: Vec<Vec<u8>> = (0..10u8)
.map(|i| solid_rgba(10, 10, i * 25, 0, 0, 255))
.collect();
let handles: Vec<_> = images
.iter()
.map(|img| {
let ctx = Arc::clone(&ctx);
let img = img.clone();
tokio::spawn(async move { ctx.cas.store(&img).await.unwrap().hash })
})
.collect();
let hashes: Vec<String> = futures::future::join_all(handles)
.await
.into_iter()
.map(|r| r.unwrap())
.collect();
let unique: std::collections::HashSet<&String> = hashes.iter().collect();
assert_eq!(
unique.len(),
10,
"10 different images should have 10 unique hashes"
);
}
#[tokio::test]
async fn t85_concurrent_thumbnail_and_strip_no_corruption() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &gradient_rgba(100, 50)).await;
let ctx1 = Arc::clone(&ctx);
let ctx2 = Arc::clone(&ctx);
let h1 = hash.clone();
let h2 = hash.clone();
let (r1, r2) = tokio::join!(
tokio::spawn(async move {
ThumbnailOp
.execute(serde_json::json!({"hash": h1, "width": 30}), &ctx1)
.await
}),
tokio::spawn(async move {
StripOp
.execute(serde_json::json!({"hash": h2}), &ctx2)
.await
}),
);
let thumb_data = if let MediaOpResult::Binary { data, .. } = r1.unwrap().unwrap() {
data
} else {
panic!()
};
let strip_data = if let MediaOpResult::Binary { data, .. } = r2.unwrap().unwrap() {
data
} else {
panic!()
};
let thumb_img = image::load_from_memory(&thumb_data).unwrap();
assert_eq!(thumb_img.width(), 30, "thumbnail should be 30px wide");
let strip_img = image::load_from_memory(&strip_data).unwrap();
assert_eq!(
strip_img.width(),
100,
"strip should preserve original 100px width"
);
}
#[tokio::test]
async fn t86_concurrent_reads_identical() {
let (_dir, ctx) = setup().await;
let data = gradient_rgba(40, 20);
let hash = store(&ctx, &data).await;
let handles: Vec<_> = (0..20)
.map(|_| {
let ctx = Arc::clone(&ctx);
let hash = hash.clone();
tokio::spawn(async move { ctx.read_media(&hash).await.unwrap() })
})
.collect();
let results: Vec<Vec<u8>> = futures::future::join_all(handles)
.await
.into_iter()
.map(|r| r.unwrap())
.collect();
for r in &results {
assert_eq!(r, &data, "concurrent reads should return identical data");
}
}
#[tokio::test]
async fn t87_compute_pool_thread_name_verified() {
let (_dir, ctx) = setup().await;
let name = ctx
.compute
.compute(|| {
std::thread::current()
.name()
.unwrap_or("unknown")
.to_string()
})
.await
.unwrap();
assert!(
name.starts_with("nika-media"),
"compute should run on nika-media-N thread, got: {name}"
);
}
#[tokio::test]
async fn t88_compute_pool_parallel_execution() {
let (_dir, ctx) = setup().await;
let start = std::time::Instant::now();
let handles: Vec<_> = (0..4)
.map(|i| {
let compute = Arc::clone(&ctx.compute);
tokio::spawn(async move {
compute
.compute(move || {
std::thread::sleep(std::time::Duration::from_millis(50));
i
})
.await
.unwrap()
})
})
.collect();
let results: Vec<_> = futures::future::join_all(handles).await;
let elapsed = start.elapsed();
assert_eq!(results.len(), 4);
assert!(
elapsed < std::time::Duration::from_millis(200),
"4 parallel 50ms tasks should complete in < 200ms, took {:?}",
elapsed
);
}
#[test]
fn t89_working_memory_guard_drop_releases() {
use crate::runtime::builtin::media::context::WorkingMemoryBudget;
let budget = WorkingMemoryBudget::with_max(2048);
assert_eq!(budget.current(), 0);
let g1 = budget.acquire(512).unwrap();
assert_eq!(budget.current(), 512);
let g2 = budget.acquire(512).unwrap();
assert_eq!(budget.current(), 1024);
drop(g2);
assert_eq!(budget.current(), 512);
let g3 = budget.acquire(1536).unwrap(); assert_eq!(budget.current(), 2048);
drop(g1);
assert_eq!(budget.current(), 1536);
drop(g3);
assert_eq!(budget.current(), 0);
}
#[test]
fn t90_working_memory_interleaved_accounting() {
use crate::runtime::builtin::media::context::WorkingMemoryBudget;
let budget = WorkingMemoryBudget::with_max(100);
let g1 = budget.acquire(30).unwrap();
let g2 = budget.acquire(30).unwrap();
assert_eq!(budget.current(), 60);
drop(g1);
assert_eq!(budget.current(), 30);
let g3 = budget.acquire(70).unwrap();
assert_eq!(budget.current(), 100);
assert!(budget.acquire(1).is_err()); drop(g2);
assert_eq!(budget.current(), 70);
let g4 = budget.acquire(30).unwrap();
assert_eq!(budget.current(), 100);
drop(g3);
drop(g4);
assert_eq!(budget.current(), 0);
}
#[tokio::test]
async fn t91_thumbnail_width_at_max_10000_accepted() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(10, 10, 0, 0, 0, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(serde_json::json!({"hash": hash, "width": 10000}), &ctx)
.await;
assert!(result.is_ok(), "width=10000 (at max) should be accepted");
if let Ok(MediaOpResult::Binary { data, .. }) = result {
let img = image::load_from_memory(&data).unwrap();
assert_eq!(img.width(), 10000);
}
}
#[tokio::test]
async fn t92_thumbnail_explicit_height_at_max() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(10, 10, 0, 0, 0, 255)).await;
let op = ThumbnailOp;
let result = op
.execute(
serde_json::json!({"hash": hash, "width": 1, "height": 10000}),
&ctx,
)
.await;
assert!(result.is_ok(), "height=10000 (at max) should be accepted");
}
#[tokio::test]
async fn t93_dimensions_webp_image() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &solid_rgba(35, 17, 128, 64, 32, 255)).await;
let op = ConvertOp;
let r = op
.execute(serde_json::json!({"hash": hash, "format": "webp"}), &ctx)
.await
.unwrap();
let webp_hash = if let MediaOpResult::Binary { data, .. } = &r {
store(&ctx, data).await
} else {
panic!()
};
let dim = DimensionsOp
.execute(serde_json::json!({"hash": webp_hash}), &ctx)
.await
.unwrap();
if let MediaOpResult::Metadata(v) = dim {
assert_eq!(v["width"], 35);
assert_eq!(v["height"], 17);
} else {
panic!("expected Metadata");
}
}
#[tokio::test]
async fn t94_color_jpeg_input() {
let (_dir, ctx) = setup().await;
let hash = store(&ctx, &make_jpeg(30, 30, 255, 128, 0)).await;
let op = DominantColorOp;
let result = op.execute(serde_json::json!({"hash": hash}), &ctx).await;
assert!(result.is_ok(), "color should work on JPEG input");
if let Ok(MediaOpResult::Metadata(v)) = result {
assert!(!v["colors"].as_array().unwrap().is_empty());
}
}
#[test]
fn t95_dimensions_op_name_and_description() {
let op = DimensionsOp;
assert_eq!(op.name(), "dimensions");
assert!(!op.description().is_empty());
assert!(op.description().to_lowercase().contains("dimension"));
}
#[test]
fn t96_thumbnail_op_name_and_description() {
let op = ThumbnailOp;
assert_eq!(op.name(), "thumbnail");
assert!(!op.description().is_empty());
}
#[test]
fn t97_convert_op_name() {
let op = ConvertOp;
assert_eq!(op.name(), "convert");
}
#[test]
fn t98_strip_op_name() {
let op = StripOp;
assert_eq!(op.name(), "strip");
}
#[test]
fn t99_color_op_name() {
let op = DominantColorOp;
assert_eq!(op.name(), "dominant_color");
}
#[test]
fn t100_thumbhash_op_name() {
let op = ThumbhashOp;
assert_eq!(op.name(), "thumbhash");
}
}