//! Deployment-address audit: the configured address must reach every //! client-visible endpoint, and nothing may quietly substitute its own. //! //! OpenFUT has to run on arbitrary addresses. The development topology is //! deployment configuration, not architecture, so no crate may contain a //! production destination, an advertised address, or a hidden localhost //! fallback. //! //! Two TEST-NET addresses are used throughout (RFC 5737), deliberately not the //! lab's real LAN addresses: a test that passes only because its constant //! happens to match the current lab proves nothing about relocatability. use openfut_adapter_fifa17::blaze::{client_config, AdapterConfig, Endpoints}; use openfut_adapter_fifa17::redirector; /// TEST-NET-2 and TEST-NET-3. Never routable, never ours, and obviously not a /// lab address to anyone reading a failure. const ADDR_A: &str = "198.51.100.7"; const ADDR_B: &str = "203.0.113.42"; fn cfg(advertise: &str) -> AdapterConfig { AdapterConfig::advertising(advertise) } /// Every client-visible string a config produces, for wholesale comparison. fn client_visible_surface(cfg: &AdapterConfig) -> Vec { let mut out = vec![ cfg.utas_base(), cfg.nucleus_base(), cfg.pow_content_url(), String::from_utf8(redirector::redirect_response(cfg)).unwrap(), ]; for section in client_config::known_sections() { for (k, v) in client_config::rows_for(section, cfg) { out.push(format!("{section}/{k}={v}")); } } out } /// (5) Changing the advertised host must update every applicable generated URL, /// with no recompilation and no leftovers. #[test] fn changing_the_advertised_host_updates_every_client_visible_url() { let a = client_visible_surface(&cfg(ADDR_A)); let b = client_visible_surface(&cfg(ADDR_B)); assert_eq!(a.len(), b.len(), "the surface itself must not change shape"); let a_has = a.iter().filter(|s| s.contains(ADDR_A)).count(); let b_has = b.iter().filter(|s| s.contains(ADDR_B)).count(); assert!(a_has > 200, "expected the address throughout, saw {a_has}"); assert_eq!(a_has, b_has, "the same entries must carry the new address"); // Nothing may retain the old address after reconfiguration. let stragglers: Vec<&String> = b.iter().filter(|s| s.contains(ADDR_A)).collect(); assert!( stragglers.is_empty(), "these kept the previous address: {:?}", &stragglers[..stragglers.len().min(5)] ); } /// (1) A remote configuration must not silently become localhost anywhere. #[test] fn remote_configuration_never_silently_becomes_localhost() { let c = cfg(ADDR_A); // Allowlisted: two OAuth redirect targets that are literals in the oracle // and are never dialled (see the compatibility exceptions in the vault). const ALLOWED_LOOPBACK_KEYS: [&str; 2] = ["identityRedirectUri", "redirect_uri"]; for entry in client_visible_surface(&c) { if entry.contains("127.0.0.1") || entry.contains("localhost") { assert!( ALLOWED_LOOPBACK_KEYS.iter().any(|k| entry.contains(k)), "unexpected loopback in a remote configuration: {entry}" ); } } // POW hosts in particular must derive from advertise, not fall back alone. assert!(c.endpoints.pow_content_host.starts_with(ADDR_A)); assert!(c.endpoints.pow_host.starts_with(ADDR_A)); assert!(c.pow_content_url().contains(ADDR_A)); } /// (3) Bind and advertise are different concepts and must never be conflated. #[test] fn bind_can_differ_from_advertise() { let mut c = cfg(ADDR_A); c.endpoints.bind = "0.0.0.0".into(); assert_eq!(c.endpoints.advertise, ADDR_A); assert_eq!(c.endpoints.bind, "0.0.0.0"); // The advertised surface follows advertise, not bind. assert!(c.utas_base().contains(ADDR_A)); assert!(!c.utas_base().contains("0.0.0.0")); // COMPATIBILITY EXCEPTION, reproduced deliberately: nucleusConnect follows // BIND in the oracle. Instrumentation showed the client never dials it, so // this is cosmetic on the observed path. Asserted so the exception cannot // be "fixed" by accident without this test failing and forcing the decision // to be made explicitly. assert_eq!(c.nucleus_base(), "http://0.0.0.0:42131"); } /// (4) The advertised Blaze port must reach the redirect result. #[test] fn changing_the_blaze_port_changes_the_redirect() { let mut c = cfg(ADDR_A); let before = String::from_utf8(redirector::redirect_response(&c)).unwrap(); assert!(before.contains("42130")); c.endpoints.blaze_port = 42999; let after = String::from_utf8(redirector::redirect_response(&c)).unwrap(); assert!(after.contains("42999"), "{after}"); assert!(!after.contains("42130")); // And the advertised host still follows config. assert!(after.contains(&format!("{ADDR_A}"))); } /// The UTAS port is deployment configuration too, not a constant we own. #[test] fn changing_the_utas_port_changes_every_rs4_url() { let mut c = cfg(ADDR_A); assert!(c.utas_base().contains(":8099/")); c.endpoints.utas_port = 9099; assert_eq!(c.utas_base(), format!("http://{ADDR_A}:9099/")); let rows = client_config::rows_for("BlazeSDK", &c); let base = rows.iter().find(|(k, _)| k == "FUT_RS4_BASE_URL").unwrap(); assert_eq!(base.1, format!("http://{ADDR_A}:9099/")); assert!(!rows.iter().any(|(_, v)| v.contains(":8099"))); } /// (6) No service-specific helper may construct an endpoint from a different /// source of truth than the central configuration. #[test] fn no_helper_bypasses_the_central_configuration() { // bind MUST differ from advertise here. With them equal, a helper that // wrongly reads `bind` is indistinguishable from one that reads // `advertise` — and reading `bind` is the single most likely bypass, // because the oracle really does it for nucleusConnect. Mutation-tested: // with bind == advertise this test could not detect that substitution. let mut c = cfg(ADDR_B); c.endpoints.bind = "0.0.0.0".into(); // Every URL-shaped helper resolves through the same Endpoints. assert!(c.utas_base().contains(ADDR_B)); assert!(c.pow_content_url().contains(ADDR_B)); let ep = redirector::BlazeEndpoint::from_config(&c); assert_eq!( ep.host, c.endpoints.advertise, "the redirector must advertise the ADVERTISED host, not the bind address" ); assert_ne!( ep.host, c.endpoints.bind, "bind must not leak into the wire" ); let xml = String::from_utf8(redirector::redirect_response(&c)).unwrap(); assert!(xml.contains(ADDR_B)); assert!( !xml.contains("0.0.0.0"), "the bind address must never reach the client" ); assert_eq!(ep.port, c.endpoints.blaze_port); // And the config table's URL tokens resolve through those same helpers, // rather than re-deriving a URL shape of their own. let rows = client_config::rows_for("BlazeSDK", &c); let base = rows.iter().find(|(k, _)| k == "FUT_RS4_BASE_URL").unwrap(); assert_eq!(base.1, c.utas_base()); let nucleus = rows.iter().find(|(k, _)| k == "nucleusConnect").unwrap(); assert_eq!(nucleus.1, c.nucleus_base()); } /// (7) Mutating the configuration must make these tests fail — a suite that /// passes regardless of the configured address would prove nothing. #[test] fn configuration_mutations_are_detectable() { let a = cfg(ADDR_A); let b = cfg(ADDR_B); // Each of these is what a mutation would have to defeat. assert_ne!(a.utas_base(), b.utas_base()); assert_ne!(a.pow_content_url(), b.pow_content_url()); assert_ne!( redirector::redirect_response(&a), redirector::redirect_response(&b) ); assert_ne!( client_config::rows_for("BlazeSDK", &a), client_config::rows_for("BlazeSDK", &b) ); let mut port_changed = a.clone(); port_changed.endpoints.blaze_port += 1; assert_ne!( redirector::redirect_response(&a), redirector::redirect_response(&port_changed) ); } /// Loopback must be a named, deliberate choice — not something a caller can /// reach by omission. #[test] fn loopback_is_explicit_not_a_default() { let l = Endpoints::loopback(); assert_eq!(l.advertise, "127.0.0.1"); assert!(l.pow_content_host.starts_with("127.0.0.1")); // `Endpoints::default()` and `AdapterConfig::default()` deliberately do not // exist; this test documents that, and the crate would not compile if they // were reintroduced and used by accident elsewhere. let explicit = AdapterConfig::loopback(); assert_eq!(explicit.endpoints.advertise, "127.0.0.1"); }