use super::*;
use crate::framing::{Framing, framing};
use crate::http_head::parse_response;
fn message_of(body: &str) -> String {
let start = body.find(r#""message":""#).expect("a message field") + 11;
let rest = &body[start..];
let end = rest.find('"').expect("a closing quote");
rest[..end].to_owned()
}
#[test]
fn every_refusal_is_well_formed_and_declares_its_own_length() {
for (name, bytes, status) in [
("unauthorized", unauthorized(), 401),
("bad request", bad_request(), 400),
("bad gateway", bad_gateway(), 502),
("backend unreachable", backend_unreachable(), 502),
("tunnel unavailable", tunnel_unavailable(), 502),
("incomplete request", incomplete_request(), 400),
] {
let (head, consumed) = parse_response(&bytes)
.unwrap_or_else(|e| panic!("{name} must parse: {e:?}"))
.unwrap_or_else(|| panic!("{name} must be complete"));
assert_eq!(head.status, status, "{name}");
assert_eq!(
framing(&head.headers, true),
Ok(Framing::Length((bytes.len() - consumed) as u64)),
"{name} declares the body it wrote"
);
}
}
#[test]
fn the_unauthorized_response_advertises_the_scheme() {
let bytes = unauthorized();
let (head, _) = parse_response(&bytes).unwrap().unwrap();
assert!(
head.headers
.iter()
.any(|(n, v)| n.eq_ignore_ascii_case("www-authenticate") && v.contains("Bearer"))
);
}
#[test]
fn a_backend_failure_is_not_reported_as_a_client_error() {
let (head, _) = parse_response(&bad_gateway()).unwrap().unwrap();
assert!((500..600).contains(&head.status), "5xx, not 4xx");
}
#[test]
fn a_client_failure_is_not_reported_as_a_backend_error() {
let (head, _) = parse_response(&incomplete_request()).unwrap().unwrap();
assert!((400..500).contains(&head.status), "4xx, not 5xx");
}
#[test]
fn the_three_gateway_failures_say_three_different_things() {
let bodies = [
("bad_gateway", bad_gateway()),
("backend_unreachable", backend_unreachable()),
("tunnel_unavailable", tunnel_unavailable()),
];
let mut messages = Vec::new();
for (code, bytes) in &bodies {
let text = String::from_utf8(bytes.clone()).expect("ascii");
assert!(
text.contains(&format!(r#""code":"{code}""#)),
"{code} must name itself so a program can match on it: {text}"
);
let (head, _consumed) = parse_response(bytes).unwrap().unwrap();
assert_eq!(head.status, 502, "{code}");
messages.push(message_of(&text));
}
messages.sort();
let distinct = {
let mut m = messages.clone();
m.dedup();
m.len()
};
assert_eq!(
distinct, 3,
"two of the three still say the same thing: {messages:?}"
);
}
#[test]
fn telling_them_apart_did_not_change_what_a_client_does() {
for bytes in [bad_gateway(), backend_unreachable(), tunnel_unavailable()] {
let (head, _) = parse_response(&bytes).unwrap().unwrap();
assert_eq!(head.status, 502);
let text = String::from_utf8(bytes).expect("ascii");
assert!(text.contains("Connection: close"), "{text}");
assert!(text.contains("Content-Type: application/json"), "{text}");
}
}
#[test]
fn no_refusal_names_an_address() {
for bytes in [
unauthorized(),
bad_request(),
bad_gateway(),
backend_unreachable(),
tunnel_unavailable(),
incomplete_request(),
] {
let text = String::from_utf8(bytes).expect("ascii");
for leak in ["127.0.0.1", "localhost", "http://", ":11434"] {
assert!(!text.contains(leak), "{leak} appears in: {text}");
}
}
}
#[test]
fn the_message_extractor_returns_only_the_prose() {
let m = message_of(&String::from_utf8(bad_gateway()).unwrap());
assert_eq!(
m,
"the backend did not return a response this tunnel could read"
);
assert!(!m.contains("code"), "the code leaked into the message: {m}");
}