mod common;
use common::{
animated_gif_bytes, gif_bytes, png_bytes, send_metrics_request, send_raw_request,
send_transform_request, send_transform_request_to, spawn_fixture_server, spawn_server,
split_response, temp_dir,
};
use serial_test::serial;
use std::fs;
use truss::{MediaType, RawArtifact, ServerConfig, sniff_artifact};
#[test]
fn serve_once_transforms_a_path_source_over_http() {
let storage_root = temp_dir("success");
fs::write(storage_root.join("image.png"), png_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request(
addr,
r#"{"source":{"kind":"path","path":"/image.png"},"options":{"format":"jpeg"}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
let artifact = sniff_artifact(RawArtifact::new(body, None)).expect("sniff transformed output");
assert!(header.starts_with("HTTP/1.1 200 OK"));
assert_eq!(content_type, "image/jpeg");
assert_eq!(artifact.media_type, MediaType::Jpeg);
assert_eq!(artifact.metadata.width, Some(4));
assert_eq!(artifact.metadata.height, Some(3));
}
#[test]
fn serve_once_accepts_optimize_options_in_json_body() {
let storage_root = temp_dir("optimize-json");
fs::write(storage_root.join("image.png"), png_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request(
addr,
r#"{"source":{"kind":"path","path":"/image.png"},"options":{"format":"jpeg","optimize":"lossy","targetQuality":"ssim:0.98"}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
let artifact = sniff_artifact(RawArtifact::new(body, None)).expect("sniff transformed output");
assert!(header.starts_with("HTTP/1.1 200 OK"));
assert_eq!(content_type, "image/jpeg");
assert_eq!(artifact.media_type, MediaType::Jpeg);
}
#[test]
fn serve_once_applies_grayscale_from_the_json_body() {
let storage_root = temp_dir("grayscale-json");
fs::write(storage_root.join("image.png"), png_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request(
addr,
r#"{"source":{"kind":"path","path":"/image.png"},"options":{"format":"png","grayscale":true}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
assert!(header.starts_with("HTTP/1.1 200 OK"));
assert_eq!(content_type, "image/png");
let output = image::load_from_memory_with_format(&body, image::ImageFormat::Png)
.expect("decode transformed output")
.to_rgba8();
let pixel = output.get_pixel(0, 0);
assert!(
pixel[0] == pixel[1] && pixel[1] == pixel[2],
"expected a neutral gray pixel, got {pixel:?}"
);
}
#[test]
fn serve_once_transforms_a_static_gif_source() {
let storage_root = temp_dir("gif-source");
fs::write(storage_root.join("image.gif"), gif_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request(
addr,
r#"{"source":{"kind":"path","path":"/image.gif"},"options":{"format":"png"}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
let artifact = sniff_artifact(RawArtifact::new(body, None)).expect("sniff transformed output");
assert!(header.starts_with("HTTP/1.1 200 OK"));
assert_eq!(content_type, "image/png");
assert_eq!(artifact.media_type, MediaType::Png);
assert_eq!(artifact.metadata.width, Some(4));
assert_eq!(artifact.metadata.height, Some(3));
}
#[test]
fn serve_once_serves_a_gif_source_as_png_when_no_format_is_requested() {
let storage_root = temp_dir("gif-default-format");
fs::write(storage_root.join("image.gif"), gif_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request(
addr,
r#"{"source":{"kind":"path","path":"/image.gif"}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, _body) = split_response(&response);
assert!(header.starts_with("HTTP/1.1 200 OK"));
assert_eq!(content_type, "image/png");
}
#[test]
fn serve_once_rejects_an_animated_gif_source() {
let storage_root = temp_dir("gif-animated");
fs::write(storage_root.join("image.gif"), animated_gif_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request(
addr,
r#"{"source":{"kind":"path","path":"/image.gif"},"options":{"format":"png"}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _content_type, body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 415"),
"an animated GIF should be an unsupported-media-type error, got: {header}"
);
let text = String::from_utf8_lossy(&body);
assert!(
text.contains("animated GIF is not supported"),
"the response should say why, got: {text}"
);
}
#[test]
fn serve_once_rejects_gif_as_an_output_format() {
let storage_root = temp_dir("gif-output");
fs::write(storage_root.join("image.png"), png_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request(
addr,
r#"{"source":{"kind":"path","path":"/image.png"},"options":{"format":"gif"}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _content_type, _body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 415"),
"gif output should be an unsupported-media-type error, got: {header}"
);
}
#[test]
fn serve_once_honours_fit_inside_and_without_enlargement() {
let storage_root = temp_dir("fit-inside");
fs::write(storage_root.join("image.png"), png_bytes()).expect("write source fixture");
let request = |body: &str| {
let (addr, handle) = spawn_server(ServerConfig::new(
storage_root.clone(),
Some("secret".to_string()),
));
let response = send_transform_request(addr, body, Some("secret"));
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _content_type, body) = split_response(&response);
assert!(header.starts_with("HTTP/1.1 200 OK"), "{header}");
sniff_artifact(RawArtifact::new(body, None)).expect("sniff transformed output")
};
let enlarged = request(
r#"{"source":{"kind":"path","path":"/image.png"},"options":{"format":"png","width":8,"height":8,"fit":"inside"}}"#,
);
assert_eq!(enlarged.metadata.width, Some(8));
assert_eq!(enlarged.metadata.height, Some(6));
let kept = request(
r#"{"source":{"kind":"path","path":"/image.png"},"options":{"format":"png","width":8,"height":8,"fit":"inside","withoutEnlargement":true}}"#,
);
assert_eq!(kept.metadata.width, Some(4));
assert_eq!(kept.metadata.height, Some(3));
}
#[test]
fn serve_once_rejects_private_url_sources_by_default() {
let storage_root = temp_dir("url-blocked");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request(
addr,
r#"{"source":{"kind":"url","url":"http://127.0.0.1:8080/image.png"}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
let body = String::from_utf8(body).expect("utf8 response body");
assert!(header.starts_with("HTTP/1.1 403 Forbidden"));
assert_eq!(content_type, "application/problem+json");
assert!(body.contains("port is not allowed"));
}
#[test]
fn serve_once_transforms_a_url_source_when_insecure_allowance_is_enabled() {
let storage_root = temp_dir("url-success");
let (url, fixture) = spawn_fixture_server(vec![(
"200 OK".to_string(),
vec![("Content-Type".to_string(), "image/png".to_string())],
png_bytes(),
)]);
let (addr, handle) = spawn_server(
ServerConfig::new(storage_root, Some("secret".to_string())).with_insecure_url_sources(true),
);
let body = format!(
"{{\"source\":{{\"kind\":\"url\",\"url\":\"{url}\"}},\"options\":{{\"format\":\"jpeg\"}}}}"
);
let response = send_transform_request(addr, &body, Some("secret"));
handle
.join()
.expect("join server thread")
.expect("serve one request");
fixture.join().expect("join fixture server");
let (header, content_type, body) = split_response(&response);
let artifact = sniff_artifact(RawArtifact::new(body, None)).expect("sniff transformed output");
assert!(header.starts_with("HTTP/1.1 200 OK"));
assert_eq!(content_type, "image/jpeg");
assert_eq!(artifact.media_type, MediaType::Jpeg);
}
#[test]
fn serve_once_follows_remote_redirects_for_url_sources() {
let storage_root = temp_dir("url-redirect");
let (url, fixture) = spawn_fixture_server(vec![
(
"302 Found".to_string(),
vec![("Location".to_string(), "/final-image".to_string())],
Vec::new(),
),
(
"200 OK".to_string(),
vec![("Content-Type".to_string(), "image/png".to_string())],
png_bytes(),
),
]);
let (addr, handle) = spawn_server(
ServerConfig::new(storage_root, Some("secret".to_string())).with_insecure_url_sources(true),
);
let response = send_transform_request(
addr,
&format!(r#"{{"source":{{"kind":"url","url":"{url}"}},"options":{{"format":"jpeg"}}}}"#),
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
fixture.join().expect("join fixture server");
let (header, content_type, body) = split_response(&response);
let artifact = sniff_artifact(RawArtifact::new(body, None)).expect("sniff transformed output");
assert!(header.starts_with("HTTP/1.1 200 OK"));
assert_eq!(content_type, "image/jpeg");
assert_eq!(artifact.media_type, MediaType::Jpeg);
}
#[test]
fn serve_once_rejects_unsupported_remote_content_encoding() {
let storage_root = temp_dir("encoding");
let (url, fixture) = spawn_fixture_server(vec![(
"200 OK".to_string(),
vec![
("Content-Type".to_string(), "image/png".to_string()),
("Content-Encoding".to_string(), "compress".to_string()),
],
png_bytes(),
)]);
let (addr, handle) = spawn_server(
ServerConfig::new(storage_root, Some("secret".to_string())).with_insecure_url_sources(true),
);
let body = format!(
"{{\"source\":{{\"kind\":\"url\",\"url\":\"{url}\"}},\"options\":{{\"format\":\"jpeg\"}}}}"
);
let response = send_transform_request(addr, &body, Some("secret"));
handle
.join()
.expect("join server thread")
.expect("serve one request");
fixture.join().expect("join fixture server");
let (header, content_type, body) = split_response(&response);
let body = String::from_utf8(body).expect("utf8 response body");
assert!(header.starts_with("HTTP/1.1 502 Bad Gateway"));
assert_eq!(content_type, "application/problem+json");
assert!(body.contains("unsupported content-encoding"));
}
#[test]
fn serve_once_rejects_oversized_output_with_413() {
let storage_root = temp_dir("limit-exceeded");
fs::write(storage_root.join("image.png"), png_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request(
addr,
r#"{"source":{"kind":"path","path":"/image.png"},"options":{"width":8193,"height":8192}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
let body = String::from_utf8(body).expect("utf8 response body");
assert!(header.starts_with("HTTP/1.1 413 Payload Too Large"));
assert_eq!(content_type, "application/problem+json");
assert!(body.contains("output image"));
assert!(body.contains("limit"));
}
#[test]
fn serve_once_exposes_metrics_with_bearer_auth() {
let storage_root = temp_dir("metrics");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_metrics_request(addr, Some("secret"));
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
let body = String::from_utf8(body).expect("utf8 response body");
assert!(header.starts_with("HTTP/1.1 200 OK"));
assert_eq!(content_type, "text/plain; version=0.0.4; charset=utf-8");
assert!(body.contains("truss_http_requests_total"));
assert!(body.contains("truss_http_responses_total"));
}
#[test]
fn serve_once_rejects_redirect_without_location_header() {
let storage_root = temp_dir("redirect-no-location");
let (url, fixture) = spawn_fixture_server(vec![("302 Found".to_string(), vec![], Vec::new())]);
let (addr, handle) = spawn_server(
ServerConfig::new(storage_root, Some("secret".to_string())).with_insecure_url_sources(true),
);
let body = format!(
"{{\"source\":{{\"kind\":\"url\",\"url\":\"{url}\"}},\"options\":{{\"format\":\"jpeg\"}}}}"
);
let response = send_transform_request(addr, &body, Some("secret"));
handle
.join()
.expect("join server thread")
.expect("serve one request");
fixture.join().expect("join fixture server");
let (header, content_type, body) = split_response(&response);
let body = String::from_utf8(body).expect("utf8 response body");
assert!(header.starts_with("HTTP/1.1 502 Bad Gateway"));
assert_eq!(content_type, "application/problem+json");
assert!(body.contains("Location"));
}
#[test]
fn serve_once_rejects_redirect_limit_exceeded() {
let storage_root = temp_dir("redirect-limit");
let mut responses: Vec<common::FixtureResponse> = Vec::new();
for _ in 0..6 {
responses.push((
"302 Found".to_string(),
vec![("Location".to_string(), "/image".to_string())],
Vec::new(),
));
}
responses.push((
"200 OK".to_string(),
vec![("Content-Type".to_string(), "image/png".to_string())],
png_bytes(),
));
let (url, fixture) = spawn_fixture_server(responses);
let (addr, handle) = spawn_server(
ServerConfig::new(storage_root, Some("secret".to_string())).with_insecure_url_sources(true),
);
let body = format!(
"{{\"source\":{{\"kind\":\"url\",\"url\":\"{url}\"}},\"options\":{{\"format\":\"jpeg\"}}}}"
);
let response = send_transform_request(addr, &body, Some("secret"));
handle
.join()
.expect("join server thread")
.expect("serve one request");
fixture.join().expect("join fixture server");
let (header, content_type, body) = split_response(&response);
let body = String::from_utf8(body).expect("utf8 response body");
assert!(header.starts_with("HTTP/1.1 508 Loop Detected"));
assert_eq!(content_type, "application/problem+json");
assert!(body.to_lowercase().contains("redirect"));
}
#[test]
#[serial]
fn serve_once_health_endpoint_returns_200() {
let storage = temp_dir("health-200");
let config = ServerConfig::new(storage, None);
let (addr, handle) = spawn_server(config);
let response = send_raw_request(
addr,
"GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n",
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 200"),
"expected 200 from /health, got: {header}"
);
assert_eq!(content_type, "application/json");
let body_str = String::from_utf8_lossy(&body);
assert!(
body_str.contains("\"status\":\"ok\""),
"expected JSON health body, got: {body_str}"
);
}
#[test]
#[serial]
fn serve_once_health_token_rejects_unauthenticated() {
let storage = temp_dir("health-auth-reject");
let mut config = ServerConfig::new(storage, None);
config.health_token = Some("health-secret".to_string());
let (addr, handle) = spawn_server(config);
let response = send_raw_request(
addr,
"GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n",
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 401"),
"expected 401 from /health without token, got: {header}"
);
assert_eq!(content_type, "application/problem+json");
assert!(
header.contains("WWW-Authenticate: Bearer"),
"expected WWW-Authenticate header in: {header}"
);
let body_str = String::from_utf8_lossy(&body);
assert!(
body_str.contains("health endpoint requires authentication"),
"expected auth error message, got: {body_str}"
);
}
#[test]
#[serial]
fn serve_once_health_token_rejects_wrong_token() {
let storage = temp_dir("health-auth-wrong");
let mut config = ServerConfig::new(storage, None);
config.health_token = Some("health-secret".to_string());
let (addr, handle) = spawn_server(config);
let response = send_raw_request(
addr,
"GET /health HTTP/1.1\r\nHost: localhost\r\nAuthorization: Bearer wrong-token\r\nConnection: close\r\n\r\n",
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, _body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 401"),
"expected 401 from /health with wrong token, got: {header}"
);
assert_eq!(content_type, "application/problem+json");
}
#[test]
#[serial]
fn serve_once_health_token_accepts_valid_token() {
let storage = temp_dir("health-auth-ok");
let mut config = ServerConfig::new(storage, None);
config.health_token = Some("health-secret".to_string());
let (addr, handle) = spawn_server(config);
let response = send_raw_request(
addr,
"GET /health HTTP/1.1\r\nHost: localhost\r\nAuthorization: Bearer health-secret\r\nConnection: close\r\n\r\n",
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 200"),
"expected 200 from /health with valid token, got: {header}"
);
assert_eq!(content_type, "application/json");
let body_str = String::from_utf8_lossy(&body);
assert!(
body_str.contains("\"status\":\"ok\""),
"expected JSON health body, got: {body_str}"
);
}
#[test]
#[serial]
fn serve_once_health_live_unaffected_by_health_token() {
let storage = temp_dir("health-live-no-auth");
let mut config = ServerConfig::new(storage, None);
config.health_token = Some("health-secret".to_string());
let (addr, handle) = spawn_server(config);
let response = send_raw_request(
addr,
"GET /health/live HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n",
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _content_type, _body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 200"),
"expected 200 from /health/live without token, got: {header}"
);
}
#[test]
#[serial]
fn serve_once_health_ready_unaffected_by_health_token() {
let storage = temp_dir("health-ready-no-auth");
let mut config = ServerConfig::new(storage, None);
config.health_token = Some("health-secret".to_string());
let (addr, handle) = spawn_server(config);
let response = send_raw_request(
addr,
"GET /health/ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n",
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _content_type, _body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 200"),
"expected 200 from /health/ready without token, got: {header}"
);
}
#[test]
#[serial]
fn serve_once_returns_404_for_unknown_path() {
let storage = temp_dir("not-found-404");
let config = ServerConfig::new(storage, Some("secret".to_string()));
let (addr, handle) = spawn_server(config);
let response = send_raw_request(
addr,
"GET /nonexistent HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n",
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _content_type, _body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 404"),
"expected 404 for unknown path, got: {header}"
);
}
#[test]
#[serial]
fn serve_once_strips_crlf_from_x_request_id() {
let storage = temp_dir("crlf-strip");
fs::create_dir_all(storage.join("images")).expect("create images dir");
fs::write(storage.join("images/test.png"), png_bytes()).expect("write test image");
let config = ServerConfig::new(storage, Some("secret".to_string()));
let (addr, handle) = spawn_server(config);
let body = r#"{"source":{"kind":"path","path":"images/test.png"}}"#;
let request = format!(
"POST /images:transform HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nAuthorization: Bearer secret\r\nX-Request-Id: evil\r\ninjected-header: yes\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}",
body.len()
);
let response = send_raw_request(addr, &request);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _content_type, _body) = split_response(&response);
assert!(
!header.contains("injected-header"),
"CRLF injection should not produce new headers in response"
);
assert!(
!header.contains("X-Request-Id: evil\r\ninjected-header"),
"X-Request-Id with CRLF should be stripped"
);
}
#[test]
#[serial]
fn serve_once_rejects_post_transform_without_content_type() {
let storage = temp_dir("no-content-type");
let config = ServerConfig::new(storage, Some("secret".to_string()));
let (addr, handle) = spawn_server(config);
let body = r#"{"source":{"kind":"path","path":"test.png"}}"#;
let request = format!(
"POST /images:transform HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nAuthorization: Bearer secret\r\nContent-Length: {}\r\n\r\n{body}",
body.len()
);
let response = send_raw_request(addr, &request);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _content_type, _body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 415"),
"expected 415 for missing Content-Type, got: {header}"
);
}
#[test]
#[serial]
fn serve_once_rejects_invalid_json_body() {
let storage = temp_dir("invalid-json");
let config = ServerConfig::new(storage, Some("secret".to_string()));
let (addr, handle) = spawn_server(config);
let body = "not valid json at all";
let request = format!(
"POST /images:transform HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nAuthorization: Bearer secret\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}",
body.len()
);
let response = send_raw_request(addr, &request);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _content_type, _body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 400"),
"expected 400 for invalid JSON body, got: {header}"
);
}
#[test]
#[serial]
fn serve_once_returns_404_for_missing_source_path() {
let storage = temp_dir("missing-source");
let config = ServerConfig::new(storage, Some("secret".to_string()));
let (addr, handle) = spawn_server(config);
let body = r#"{"source":{"kind":"path","path":"does-not-exist.png"}}"#;
let response = send_transform_request(addr, body, Some("secret"));
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _content_type, _body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 404"),
"expected 404 for missing source file, got: {header}"
);
}
#[test]
#[serial]
fn serve_once_accepts_preserve_exif_without_a_second_metadata_field() {
let storage_root = temp_dir("preserve-exif-alone");
fs::write(storage_root.join("image.png"), png_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request(
addr,
r#"{"source":{"kind":"path","path":"/image.png"},"options":{"format":"png","preserveExif":true}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 200 OK"),
"preserveExif on its own is a complete request, got: {header}"
);
assert_eq!(content_type, "image/png");
let artifact = sniff_artifact(RawArtifact::new(body, None)).expect("sniff transformed output");
assert_eq!(artifact.media_type, MediaType::Png);
}
#[test]
#[serial]
fn serve_once_rejects_preserve_exif_only_when_stripping_is_asked_for_explicitly() {
let storage_root = temp_dir("preserve-exif-conflict");
fs::write(storage_root.join("image.png"), png_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request(
addr,
r#"{"source":{"kind":"path","path":"/image.png"},"options":{"format":"png","preserveExif":true,"stripMetadata":true}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _content_type, _body) = split_response(&response);
assert!(
header.starts_with("HTTP/1.1 200 OK"),
"the pair resolves the way the CLI resolves it, got: {header}"
);
}
#[test]
fn serve_once_rejects_a_json_transform_that_carries_a_query_string() {
let storage_root = temp_dir("json-query-string");
fs::write(storage_root.join("image.png"), png_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request_to(
addr,
"/images:transform?width=64&format=png",
r#"{"source":{"kind":"path","path":"/image.png"}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, content_type, body) = split_response(&response);
let body = String::from_utf8(body).expect("utf8 response body");
assert!(header.starts_with("HTTP/1.1 400 Bad Request"));
assert_eq!(content_type, "application/problem+json");
assert!(
body.contains("width"),
"the refusal names what was sent: {body}"
);
}
#[test]
fn serve_once_rejects_a_json_transform_that_carries_an_unknown_query_name() {
let storage_root = temp_dir("json-unknown-query");
fs::write(storage_root.join("image.png"), png_bytes()).expect("write source fixture");
let (addr, handle) = spawn_server(ServerConfig::new(storage_root, Some("secret".to_string())));
let response = send_transform_request_to(
addr,
"/images:transform?bogusParam=1",
r#"{"source":{"kind":"path","path":"/image.png"}}"#,
Some("secret"),
);
handle
.join()
.expect("join server thread")
.expect("serve one request");
let (header, _, body) = split_response(&response);
let body = String::from_utf8(body).expect("utf8 response body");
assert!(header.starts_with("HTTP/1.1 400 Bad Request"));
assert!(
body.contains("bogusParam"),
"the refusal names what was sent: {body}"
);
}