test: move image-converter tests to tests/

This commit is contained in:
2026-10-25 22:48:29 +01:00
parent 74674b157c
commit 760acb1f9f
8 changed files with 27 additions and 27 deletions

View File

@@ -49,7 +49,3 @@ impl PeriodicJob for ConversionBackfillJob {
Ok(())
}
}
#[cfg(test)]
#[path = "tests/backfill.rs"]
mod tests;

View File

@@ -37,7 +37,3 @@ impl ConversionConfig {
Ok(Some(Self { format }))
}
}
#[cfg(test)]
#[path = "tests/config.rs"]
mod tests;

View File

@@ -99,7 +99,3 @@ fn convert(bytes: Vec<u8>, format: Format) -> Result<Vec<u8>, String> {
}
}
}
#[cfg(test)]
#[path = "tests/handler.rs"]
mod tests;

View File

@@ -1,85 +0,0 @@
use super::*;
use std::sync::Mutex;
struct MockImageRef {
keys: Vec<String>,
}
#[async_trait::async_trait]
impl ImageRefQuery for MockImageRef {
async fn list_keys(&self) -> Result<Vec<String>, DomainError> {
Ok(self.keys.clone())
}
}
struct MockPublisher {
emitted: Mutex<Vec<String>>,
}
impl MockPublisher {
fn new() -> Arc<Self> {
Arc::new(Self {
emitted: Mutex::new(vec![]),
})
}
fn emitted(&self) -> Vec<String> {
self.emitted.lock().unwrap().clone()
}
}
#[async_trait::async_trait]
impl EventPublisher for MockPublisher {
async fn publish(&self, event: &DomainEvent) -> Result<(), DomainError> {
if let DomainEvent::ImageStored { key } = event {
self.emitted.lock().unwrap().push(key.clone());
}
Ok(())
}
}
#[tokio::test]
async fn emits_image_stored_for_unconverted_keys() {
let image_ref = Arc::new(MockImageRef {
keys: vec!["avatars/u1".into(), "posters/m1".into()],
});
let publisher = MockPublisher::new();
let job =
ConversionBackfillJob::new(image_ref, Arc::clone(&publisher) as Arc<dyn EventPublisher>);
job.run().await.unwrap();
let mut emitted = publisher.emitted();
emitted.sort();
assert_eq!(emitted, vec!["avatars/u1", "posters/m1"]);
}
#[tokio::test]
async fn skips_already_converted_keys() {
let image_ref = Arc::new(MockImageRef {
keys: vec![
"avatars/u1.avif".into(),
"posters/m1".into(),
"avatars/u2.webp".into(),
],
});
let publisher = MockPublisher::new();
let job =
ConversionBackfillJob::new(image_ref, Arc::clone(&publisher) as Arc<dyn EventPublisher>);
job.run().await.unwrap();
assert_eq!(publisher.emitted(), vec!["posters/m1"]);
}
#[tokio::test]
async fn empty_keys_emits_nothing() {
let image_ref = Arc::new(MockImageRef { keys: vec![] });
let publisher = MockPublisher::new();
let job =
ConversionBackfillJob::new(image_ref, Arc::clone(&publisher) as Arc<dyn EventPublisher>);
job.run().await.unwrap();
assert!(publisher.emitted().is_empty());
}

View File

@@ -1,54 +0,0 @@
use super::*;
fn section(enabled: bool, format: Option<&str>) -> ::config::ImageConversionConfig {
::config::ImageConversionConfig {
enabled,
format: format.map(str::to_owned),
}
}
fn convert(enabled: bool, format: Option<&str>) -> anyhow::Result<Option<ConversionConfig>> {
ConversionConfig::from_config(&section(enabled, format))
}
#[test]
fn disabled_by_default() {
assert!(
ConversionConfig::from_config(&::config::ImageConversionConfig::default())
.unwrap()
.is_none()
);
assert!(convert(false, Some("webp")).unwrap().is_none());
}
#[test]
fn enabled_avif() {
let cfg = convert(true, Some("avif")).unwrap().unwrap();
assert_eq!(cfg.format, Format::Avif);
}
#[test]
fn enabled_webp() {
let cfg = convert(true, Some("webp")).unwrap().unwrap();
assert_eq!(cfg.format, Format::Webp);
}
#[test]
fn unknown_format_is_error() {
assert!(convert(true, Some("gif")).is_err());
}
#[test]
fn missing_format_when_enabled_is_error() {
assert!(convert(true, None).is_err());
}
#[test]
fn avif_extension() {
assert_eq!(Format::Avif.extension(), ".avif");
}
#[test]
fn webp_extension() {
assert_eq!(Format::Webp.extension(), ".webp");
}

View File

@@ -1,158 +0,0 @@
use super::*;
use object_storage::ObjectStorageAdapter;
use object_store::memory::InMemory;
use std::sync::Mutex;
struct MockImageRef {
swaps: Mutex<Vec<(String, String)>>,
}
impl MockImageRef {
fn new() -> Arc<Self> {
Arc::new(Self {
swaps: Mutex::new(vec![]),
})
}
fn swaps(&self) -> Vec<(String, String)> {
self.swaps.lock().unwrap().clone()
}
}
#[async_trait::async_trait]
impl ImageRefCommand for MockImageRef {
async fn swap(&self, old: &str, new: &str) -> Result<(), DomainError> {
self.swaps.lock().unwrap().push((old.into(), new.into()));
Ok(())
}
}
fn in_memory_storage() -> Arc<ObjectStorageAdapter> {
Arc::new(ObjectStorageAdapter::new(Arc::new(InMemory::new())))
}
fn tiny_jpeg() -> Vec<u8> {
use image::{DynamicImage, ImageBuffer, Rgb};
let img = DynamicImage::ImageRgb8(ImageBuffer::from_pixel(4, 4, Rgb([200u8, 100, 50])));
let mut buf = std::io::Cursor::new(Vec::new());
img.write_to(&mut buf, image::ImageFormat::Jpeg).unwrap();
buf.into_inner()
}
#[tokio::test]
async fn ignores_non_image_stored_events() {
let storage = in_memory_storage();
let image_ref = MockImageRef::new();
let handler = ImageConversionHandler::new(
Arc::clone(&storage) as Arc<dyn ObjectStorage>,
Arc::clone(&image_ref) as Arc<dyn ImageRefCommand>,
Format::Avif,
);
handler
.handle(&DomainEvent::UserUpdated {
user_id: domain::value_objects::UserId::from_uuid(uuid::Uuid::new_v4()),
})
.await
.unwrap();
assert!(image_ref.swaps().is_empty());
}
#[tokio::test]
async fn skips_already_converted_avif_key() {
let storage = in_memory_storage();
storage
.store("avatars/u1.avif", &tiny_jpeg())
.await
.unwrap();
let image_ref = MockImageRef::new();
let handler = ImageConversionHandler::new(
Arc::clone(&storage) as Arc<dyn ObjectStorage>,
Arc::clone(&image_ref) as Arc<dyn ImageRefCommand>,
Format::Avif,
);
handler
.handle(&DomainEvent::ImageStored {
key: "avatars/u1.avif".into(),
})
.await
.unwrap();
assert!(image_ref.swaps().is_empty());
}
#[tokio::test]
async fn skips_already_converted_webp_key() {
let storage = in_memory_storage();
storage
.store("posters/m1.webp", &tiny_jpeg())
.await
.unwrap();
let image_ref = MockImageRef::new();
let handler = ImageConversionHandler::new(
Arc::clone(&storage) as Arc<dyn ObjectStorage>,
Arc::clone(&image_ref) as Arc<dyn ImageRefCommand>,
Format::Webp,
);
handler
.handle(&DomainEvent::ImageStored {
key: "posters/m1.webp".into(),
})
.await
.unwrap();
assert!(image_ref.swaps().is_empty());
}
#[tokio::test]
async fn converts_jpeg_to_avif_and_swaps_key() {
let storage = in_memory_storage();
storage.store("avatars/u1", &tiny_jpeg()).await.unwrap();
let image_ref = MockImageRef::new();
let handler = ImageConversionHandler::new(
Arc::clone(&storage) as Arc<dyn ObjectStorage>,
Arc::clone(&image_ref) as Arc<dyn ImageRefCommand>,
Format::Avif,
);
handler
.handle(&DomainEvent::ImageStored {
key: "avatars/u1".into(),
})
.await
.unwrap();
assert_eq!(
image_ref.swaps(),
vec![("avatars/u1".into(), "avatars/u1.avif".into())]
);
assert!(storage.get("avatars/u1.avif").await.is_ok());
}
#[tokio::test]
async fn converts_jpeg_to_webp_and_swaps_key() {
let storage = in_memory_storage();
storage.store("avatars/u1", &tiny_jpeg()).await.unwrap();
let image_ref = MockImageRef::new();
let handler = ImageConversionHandler::new(
Arc::clone(&storage) as Arc<dyn ObjectStorage>,
Arc::clone(&image_ref) as Arc<dyn ImageRefCommand>,
Format::Webp,
);
handler
.handle(&DomainEvent::ImageStored {
key: "avatars/u1".into(),
})
.await
.unwrap();
assert_eq!(
image_ref.swaps(),
vec![("avatars/u1".into(), "avatars/u1.webp".into())]
);
assert!(storage.get("avatars/u1.webp").await.is_ok());
}