#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use base64::Engine;
use serde_json::json;
use barbacane_plugin_sdk::types::{base64_body, Request, Response};
use crate::http_client::HttpResponse;
use crate::instance::PluginHttpRequest;
#[test]
fn plugin_http_request_compat_with_host() {
let binary_body: Vec<u8> = vec![0x89, 0x50, 0x4E, 0x47, 0xFF, 0x00, 0x01];
let b64 = base64::engine::general_purpose::STANDARD.encode(&binary_body);
let plugin_json = json!({
"method": "POST",
"url": "https://upstream.example.com/api",
"headers": {"content-type": "application/octet-stream"},
"body": b64,
"timeout_ms": 5000_u64,
});
let host_parsed: PluginHttpRequest =
serde_json::from_value(plugin_json).expect("host should parse plugin HttpRequest");
assert_eq!(host_parsed.method, "POST");
assert_eq!(host_parsed.url, "https://upstream.example.com/api");
assert_eq!(host_parsed.body, Some(binary_body));
assert_eq!(host_parsed.timeout_ms, Some(5000));
}
#[test]
fn plugin_http_request_null_body_compat() {
let plugin_json = json!({
"method": "GET",
"url": "https://example.com",
});
let host_parsed: PluginHttpRequest =
serde_json::from_value(plugin_json).expect("host should parse null-body request");
assert!(host_parsed.body.is_none());
}
#[test]
fn host_http_response_compat_with_plugin() {
let binary_body: Vec<u8> = vec![0x00, 0x01, 0x80, 0xFF, 0xFE];
let host_resp = HttpResponse {
status: 200,
headers: {
let mut h = std::collections::HashMap::new();
h.insert(
"content-type".to_string(),
"application/octet-stream".to_string(),
);
h
},
body: Some(binary_body.clone()),
};
let json = serde_json::to_value(&host_resp).expect("host should serialize");
#[derive(serde::Deserialize)]
struct PluginHttpResponse {
status: u16,
headers: BTreeMap<String, String>,
#[serde(default, with = "base64_body")]
body: Option<Vec<u8>>,
}
let plugin_parsed: PluginHttpResponse =
serde_json::from_value(json).expect("plugin should parse host HttpResponse");
assert_eq!(plugin_parsed.status, 200);
assert_eq!(plugin_parsed.body, Some(binary_body));
assert_eq!(
plugin_parsed
.headers
.get("content-type")
.map(|s| s.as_str()),
Some("application/octet-stream")
);
}
#[test]
fn request_body_absent_from_json() {
let req = Request {
method: "POST".into(),
path: "/upload".into(),
query: Some("fmt=raw".into()),
headers: {
let mut h = BTreeMap::new();
h.insert("content-type".into(), "application/octet-stream".into());
h
},
body: Some((0..=255).collect()),
client_ip: "10.0.0.1".into(),
path_params: BTreeMap::new(),
};
let json_bytes = serde_json::to_vec(&req).unwrap();
let json_str = std::str::from_utf8(&json_bytes).unwrap();
assert!(
!json_str.contains("body"),
"body should not appear in Request JSON"
);
let parsed: Request = serde_json::from_slice(&json_bytes).unwrap();
assert_eq!(parsed.method, "POST");
assert_eq!(parsed.query, Some("fmt=raw".into()));
assert_eq!(parsed.body, None); }
#[test]
fn response_body_absent_from_json() {
let resp = Response {
status: 200,
headers: {
let mut h = BTreeMap::new();
h.insert("content-type".into(), "image/png".into());
h
},
body: Some(vec![0x89, 0x50, 0x4E, 0x47]),
};
let json_bytes = serde_json::to_vec(&resp).unwrap();
let json_str = std::str::from_utf8(&json_bytes).unwrap();
assert!(
!json_str.contains("body"),
"body should not appear in Response JSON"
);
assert_eq!(resp.status, 200);
}
#[test]
fn middleware_continue_metadata_survives_value_extraction() {
let req = Request {
method: "POST".into(),
path: "/api/data".into(),
query: None,
headers: {
let mut h = BTreeMap::new();
h.insert("x-consumer-id".into(), "user-42".into());
h
},
body: None,
client_ip: "127.0.0.1".into(),
path_params: BTreeMap::new(),
};
let output = json!({"action": 0, "data": req});
let output_bytes = serde_json::to_vec(&output).unwrap();
let parsed: serde_json::Value = serde_json::from_slice(&output_bytes).unwrap();
let data_bytes = serde_json::to_vec(&parsed["data"]).unwrap();
let dispatch_req: Request = serde_json::from_slice(&data_bytes).unwrap();
assert_eq!(dispatch_req.method, "POST");
assert_eq!(dispatch_req.path, "/api/data");
assert_eq!(
dispatch_req
.headers
.get("x-consumer-id")
.map(|s| s.as_str()),
Some("user-42")
);
assert_eq!(dispatch_req.body, None);
}
#[test]
fn middleware_shortcircuit_metadata_survives_value_extraction() {
let resp = Response {
status: 403,
headers: {
let mut h = BTreeMap::new();
h.insert("content-type".into(), "application/json".into());
h
},
body: None,
};
let output = json!({"action": 1, "data": resp});
let output_bytes = serde_json::to_vec(&output).unwrap();
let parsed: serde_json::Value = serde_json::from_slice(&output_bytes).unwrap();
let data_bytes = serde_json::to_vec(&parsed["data"]).unwrap();
let host_resp: Response = serde_json::from_slice(&data_bytes).unwrap();
assert_eq!(host_resp.status, 403);
assert_eq!(host_resp.body, None);
}
#[test]
fn large_body_does_not_inflate_json() {
let body: Vec<u8> = vec![0xAA; 1_000_000]; let req = Request {
method: "POST".into(),
path: "/upload".into(),
query: None,
headers: BTreeMap::new(),
body: Some(body),
client_ip: "127.0.0.1".into(),
path_params: BTreeMap::new(),
};
let json_bytes = serde_json::to_vec(&req).unwrap();
assert!(
json_bytes.len() < 200,
"JSON should be small (was {} bytes); body must not be in JSON",
json_bytes.len()
);
}
#[test]
fn large_body_middleware_output_stays_small() {
let req = Request {
method: "POST".into(),
path: "/upload".into(),
query: None,
headers: BTreeMap::new(),
body: Some(vec![0xBB; 1_000_000]),
client_ip: "127.0.0.1".into(),
path_params: BTreeMap::new(),
};
let output = json!({"action": 0, "data": req});
let output_bytes = serde_json::to_vec(&output).unwrap();
assert!(
output_bytes.len() < 300,
"middleware output JSON should be small (was {} bytes)",
output_bytes.len()
);
}
}