This commit is contained in:
Mateusz Gruszczyński committed 2026-10-04 11:15:58 +02:00
1 parent 87e571c170
commit fa7abc9e53
308 files changed
+49746 -50237

No files matched your search

+395
View File
@@ -0,0 +1,395 @@
mod tests {
use super::*;
#[test]
fn identity_decode_borrows_the_collected_body() {
let body = b"const value = 1;";
let decoded = decode(body, None, 1024).unwrap();
assert!(matches!(decoded, Cow::Borrowed(_)));
}
#[test]
fn transform_memory_budget_accounts_for_identity_and_decoded_buffers() {
assert_eq!(
transform_memory_required_bytes(2 * 1024 * 1024, None, 2 * 1024 * 1024),
Some(4 * 1024 * 1024)
);
assert_eq!(
transform_memory_required_bytes(
2 * 1024 * 1024,
Some("gzip"),
2 * 1024 * 1024
),
Some(6 * 1024 * 1024)
);
assert_eq!(
transform_memory_required_bytes(
DEFAULT_MEMORY_BUDGET_BYTES,
Some("gzip"),
DEFAULT_MEMORY_BUDGET_BYTES
),
Some(3 * DEFAULT_MEMORY_BUDGET_BYTES)
);
}
#[test]
fn transform_memory_reservation_obeys_runtime_budget() {
let counter = AtomicUsize::new(0);
let required = 4 * 1024 * 1024;
let reservation = try_reserve_memory_from(&counter, required, required).unwrap();
assert!(try_reserve_memory_from(&counter, required, required).is_none());
drop(reservation);
assert!(try_reserve_memory_from(&counter, required, required).is_some());
}
#[test]
fn transform_inflight_reservation_obeys_runtime_limit() {
let counter = AtomicUsize::new(0);
let slot = try_reserve_slot_from(&counter, 1).unwrap();
assert!(try_reserve_slot_from(&counter, 1).is_none());
drop(slot);
assert!(try_reserve_slot_from(&counter, 1).is_some());
}
#[test]
fn resource_usage_exposes_configured_limits() {
let settings = Settings {
memory_budget_bytes: 512 * 1024 * 1024,
max_inflight: 64,
..Default::default()
};
let usage = resource_usage(&settings);
assert_eq!(usage.memory_budget_bytes, 512 * 1024 * 1024);
assert_eq!(usage.max_inflight, 64);
}
#[test]
fn resource_preset_metadata_round_trips() {
let mut settings = Settings::default();
settings.preset = Some(Preset::Uber);
settings.memory_budget_bytes = 1024 * 1024 * 1024;
settings.max_body_bytes = 32 * 1024 * 1024;
settings.max_inflight = 128;
let encoded = serde_json::to_value(&settings).unwrap();
assert_eq!(encoded["preset"], "uber");
let decoded: Settings = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded.preset, Some(Preset::Uber));
assert_eq!(decoded.memory_budget_bytes, 1024 * 1024 * 1024);
assert_eq!(decoded.max_inflight, 128);
}
#[test]
fn legacy_transform_settings_receive_resource_defaults() {
let decoded: Settings = serde_json::from_str(r#"{"enabled":true}"#).unwrap();
assert!(decoded.enabled);
assert_eq!(decoded.preset, None);
assert_eq!(decoded.memory_budget_bytes, DEFAULT_MEMORY_BUDGET_BYTES);
assert_eq!(decoded.max_body_bytes, DEFAULT_MAX_BODY_BYTES);
assert_eq!(decoded.max_inflight, DEFAULT_MAX_INFLIGHT);
}
#[test]
fn bounded_decoder_rejects_expansion_past_limit() {
use std::io::Write as _;
let identity = vec![b'x'; 128 * 1024];
let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
encoder.write_all(&identity).unwrap();
let encoded = encoder.finish().unwrap();
assert!(decode(&encoded, Some("gzip"), 64 * 1024).is_err());
}
#[tokio::test]
async fn aggregation_is_bounded_even_when_content_length_is_false() {
let settings = Settings {
enabled: true,
json: true,
max_body_bytes: 1024,
..Default::default()
};
let mut response = Response::new(Body::from(vec![b' '; 2048]));
response
.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
response
.headers_mut()
.insert("content-length", HeaderValue::from_static("128"));
assert!(apply(response, "/data.json", &settings, false).await.is_err());
}
#[test]
fn metrics_track_applied_skipped_and_cached_transformed_responses() {
let metrics = Metrics::default();
let mut applied = Response::new(Body::empty());
applied.extensions_mut().insert(Applied {
kind: Kind::Css,
original_bytes: 100,
transformed_bytes: 70,
});
metrics.record_response("app", &applied, Duration::from_micros(500));
let mut skipped = Response::new(Body::empty());
skipped.extensions_mut().insert(Skipped {
kind: Some(Kind::Css),
reason: "response_no_transform",
});
metrics.record_response("app", &skipped, Duration::from_micros(100));
metrics.record_cache_hit("app");
let total = metrics.total();
assert_eq!(total.applied, 1);
assert_eq!(total.cache_hits, 1);
let view = metrics.view();
assert_eq!(view.total.examined, 2);
assert_eq!(view.total.applied, 1);
assert_eq!(view.total.skipped, 1);
assert_eq!(view.total.cache_hits, 1);
assert_eq!(view.total.saved_bytes, 30);
assert_eq!(view.total.by_kind.get("css"), Some(&1));
assert_eq!(
view.total.reasons.get("response_no_transform"),
Some(&1)
);
assert_eq!(view.scopes.len(), 1);
assert_eq!(view.scopes[0].vhost, "app");
}
#[test]
fn per_vhost_is_opt_in_even_when_global_is_enabled() {
let global = Settings {
enabled: true,
..Default::default()
};
assert!(!Overrides::default().resolve(&global).unwrap().enabled);
let enabled = Overrides {
enabled: true,
..Default::default()
};
assert!(enabled.resolve(&global).unwrap().enabled);
}
#[tokio::test]
async fn json_is_compacted_and_gets_a_new_validator() {
let settings = Settings {
enabled: true,
json: true,
..Default::default()
};
let mut response = Response::new(Body::from("{ \"a\": 1, \"b\": [ true ] }"));
response
.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
response
.headers_mut()
.insert("content-length", HeaderValue::from_static("30"));
response
.headers_mut()
.insert("etag", HeaderValue::from_static("\"origin\""));
response
.headers_mut()
.insert("vary", HeaderValue::from_static("Accept-Encoding"));
let response = apply(response, "/data.json", &settings, false).await.unwrap();
assert!(response.headers()["etag"].to_str().unwrap().starts_with("\"rustproxy-"));
assert!(!response.headers().contains_key("vary"));
assert!(response.extensions().get::<CanonicalIdentity>().is_some());
let validators = response.extensions().get::<OriginValidators>().unwrap();
assert_eq!(validators.etag.as_ref().unwrap(), "\"origin\"");
let body = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&body[..], br#"{"a":1,"b":[true]}"#);
}
#[tokio::test]
async fn unchanged_body_keeps_origin_validator() {
let settings = Settings {
enabled: true,
json: true,
..Default::default()
};
let body = br#"{"a":1}"#;
let mut response = Response::new(Body::from(body.as_slice()));
response
.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
response
.headers_mut()
.insert("content-length", HeaderValue::from_static("7"));
response
.headers_mut()
.insert("etag", HeaderValue::from_static("\"origin\""));
response.headers_mut().insert(
"vary",
HeaderValue::from_static("Accept-Encoding, X-Theme"),
);
let response = apply(response, "/data.json", &settings, false).await.unwrap();
assert_eq!(response.headers()["etag"], "\"origin\"");
assert_eq!(response.headers()["vary"], "X-Theme");
assert!(response.extensions().get::<CanonicalIdentity>().is_some());
assert!(response.extensions().get::<Applied>().is_none());
}
#[tokio::test]
async fn unchanged_compressed_body_is_canonicalized_to_identity() {
use std::io::Write as _;
let settings = Settings {
enabled: true,
json: true,
..Default::default()
};
let identity = br#"{"a":1}"#;
let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
encoder.write_all(identity).unwrap();
let encoded = encoder.finish().unwrap();
let mut response = Response::new(Body::from(encoded.clone()));
response
.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
response.headers_mut().insert(
"content-length",
HeaderValue::from_str(&encoded.len().to_string()).unwrap(),
);
response
.headers_mut()
.insert("content-encoding", HeaderValue::from_static("gzip"));
response
.headers_mut()
.insert("etag", HeaderValue::from_static("\"origin\""));
let response = apply(response, "/data.json", &settings, false).await.unwrap();
assert!(!response.headers().contains_key("content-encoding"));
assert_eq!(
response.headers()["content-length"].to_str().unwrap(),
identity.len().to_string()
);
assert!(response.headers()["etag"].to_str().unwrap().starts_with("\"rustproxy-"));
let validators = response.extensions().get::<OriginValidators>().unwrap();
assert_eq!(validators.etag.as_ref().unwrap(), "\"origin\"");
let applied = response.extensions().get::<Applied>().unwrap();
assert_eq!(applied.original_bytes, identity.len());
assert_eq!(applied.transformed_bytes, identity.len());
let body = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&body[..], identity);
}
#[tokio::test]
async fn origin_no_transform_blocks_body_changes_and_keeps_validator() {
let settings = Settings {
enabled: true,
json: true,
..Default::default()
};
let body = br#"{ "a": 1 }"#;
let mut response = Response::new(Body::from(body.as_slice()));
response
.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
response.headers_mut().insert(
"content-length",
HeaderValue::from_str(&body.len().to_string()).unwrap(),
);
response
.headers_mut()
.insert("etag", HeaderValue::from_static("\"origin\""));
let response = apply(response, "/data.json", &settings, true).await.unwrap();
assert_eq!(response.headers()["etag"], "\"origin\"");
assert!(response.extensions().get::<Applied>().is_none());
let received = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&received[..], body);
}
#[tokio::test]
async fn final_no_transform_blocks_body_changes() {
let settings = Settings {
enabled: true,
json: true,
..Default::default()
};
let body = br#"{ "a": 1 }"#;
let mut response = Response::new(Body::from(body.as_slice()));
response
.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
response.headers_mut().insert(
"content-length",
HeaderValue::from_str(&body.len().to_string()).unwrap(),
);
response.headers_mut().insert(
"cache-control",
HeaderValue::from_static("public, max-age=60, no-transform"),
);
let response = apply(response, "/data.json", &settings, false).await.unwrap();
assert!(response.extensions().get::<Applied>().is_none());
let received = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&received[..], body);
}
#[tokio::test]
async fn empty_head_body_with_origin_length_keeps_origin_representation_headers() {
let settings = Settings {
enabled: true,
json: true,
..Default::default()
};
let mut response = Response::new(Body::empty());
response
.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
response
.headers_mut()
.insert("content-length", HeaderValue::from_static("76"));
response
.headers_mut()
.insert("etag", HeaderValue::from_static("\"origin-head\""));
let response = apply(response, "/data.json", &settings, false).await.unwrap();
assert_eq!(response.headers()["content-length"], "76");
assert_eq!(response.headers()["etag"], "\"origin-head\"");
assert!(response.extensions().get::<Applied>().is_none());
assert!(response.extensions().get::<CanonicalIdentity>().is_none());
let body = response.into_body().collect().await.unwrap().to_bytes();
assert!(body.is_empty());
}
#[tokio::test]
async fn compressed_no_transform_preserves_origin_encoding_and_bytes() {
use std::io::Write as _;
let settings = Settings {
enabled: true,
json: true,
..Default::default()
};
let identity = br#"{ "a": 1 }"#;
let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
encoder.write_all(identity).unwrap();
let encoded = encoder.finish().unwrap();
let mut response = Response::new(Body::from(encoded.clone()));
response
.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
response.headers_mut().insert(
"content-length",
HeaderValue::from_str(&encoded.len().to_string()).unwrap(),
);
response
.headers_mut()
.insert("content-encoding", HeaderValue::from_static("gzip"));
response.headers_mut().insert(
"cache-control",
HeaderValue::from_static("public, max-age=60, no-transform"),
);
response
.headers_mut()
.insert("vary", HeaderValue::from_static("Accept-Encoding"));
response
.headers_mut()
.insert("etag", HeaderValue::from_static("\"origin\""));
let response = apply(response, "/data.json", &settings, false).await.unwrap();
assert_eq!(response.headers()["content-encoding"], "gzip");
assert_eq!(response.headers()["etag"], "\"origin\"");
assert_eq!(response.headers()["vary"], "Accept-Encoding");
assert!(response.extensions().get::<CanonicalIdentity>().is_none());
let received = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&received[..], encoded.as_slice());
}
}