//! The roster response, held against bytes captured from the live oracle. //! //! The fixture masks the two volatile fields (`Date:`, `Server:`) and records //! the observed `Server` string separately, so this can assert the full byte //! layout while still failing loudly if the oracle's Python version drifts away //! from the adapter's `ORACLE_SERVER` constant. use openfut_adapter_fifa17::roster::{self, Method}; const MASK: &str = ""; fn fixture() -> String { let path = format!("{}/fixtures/roster.json", env!("CARGO_MANIFEST_DIR")); std::fs::read_to_string(&path) .unwrap_or_else(|e| panic!("roster fixtures missing at {path}: {e}")) } /// Minimal extraction: the fixture is written by our own generator and is a /// flat object, so a JSON dependency would be overkill in a crate that has none. fn field(text: &str, key: &str) -> String { let needle = format!("\"{key}\""); let at = text .find(&needle) .unwrap_or_else(|| panic!("fixture has no {key}")); let rest = &text[at + needle.len()..]; let colon = rest.find(':').expect("key: value"); let open = rest[colon..].find('"').expect("value opens") + colon; let close = rest[open + 1..].find('"').expect("value closes"); rest[open + 1..open + 1 + close].to_string() } fn expected(method: &str) -> Vec { // Scope the search to the responses object so a key never matches elsewhere. let text = fixture(); let at = text.find("\"responses\"").expect("responses"); let hex = field(&text[at..], method); (0..hex.len()) .step_by(2) .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).expect("hex")) .collect() } /// Re-apply the generator's masking so the comparison is like-for-like. fn mask(raw: &[u8]) -> Vec { let text = String::from_utf8_lossy(raw); let masked: String = text .split("\r\n") .map(|line| { if line.starts_with("Date: ") { format!("Date: {MASK}") } else if line.starts_with("Server: ") { format!("Server: {MASK}") } else { line.to_string() } }) .collect::>() .join("\r\n"); masked.into_bytes() } fn check(method: Method, name: &str) { let got = roster::roster_response( method, roster::ORACLE_SERVER, "Tue, 11 Aug 2026 05:15:13 GMT", ); assert_eq!( String::from_utf8_lossy(&mask(&got)), String::from_utf8_lossy(&expected(name)), "{name} differs from the oracle" ); } #[test] fn get_matches_the_oracle() { check(Method::Get, "GET"); } #[test] fn head_matches_the_oracle() { check(Method::Head, "HEAD"); } /// Including the quirk: headers advertising a body that never arrives. #[test] fn post_matches_the_oracle() { check(Method::Post, "POST"); } /// If the container's Python changes, `ORACLE_SERVER` is stale and every /// response this adapter builds is wrong in a byte the oracle would have got /// right. The fixture records what was actually observed so that drift is a /// test failure rather than a silent divergence. #[test] fn the_server_constant_still_matches_the_observed_oracle() { let observed = field(&fixture(), "observed_server"); assert_eq!( roster::ORACLE_SERVER, observed, "roster::ORACLE_SERVER is stale — the oracle now sends {observed:?}. \ Regenerate fixtures and update the constant." ); } /// The masking must not be able to hide a real difference. If `Date:` were /// dropped rather than masked, a response missing it entirely would still pass. #[test] fn masking_does_not_hide_a_missing_header() { let good = roster::roster_response(Method::Get, roster::ORACLE_SERVER, "X"); let without_date: Vec = String::from_utf8_lossy(&good) .split("\r\n") .filter(|l| !l.starts_with("Date: ")) .collect::>() .join("\r\n") .into_bytes(); assert_ne!( mask(&good), mask(&without_date), "masking collapsed a missing Date into a match" ); }