/// Mapping layer: translates known FIFA 23 API paths to OpenFUT Core endpoints. /// Unknown paths return None and will be recorded for reverse engineering. #[derive(Debug, Clone)] pub struct CoreMapping { pub method: &'static str, pub core_path: String, #[allow(dead_code)] pub notes: &'static str, } // ── Exact mappings (static path, no dynamic segments) ───────────────────────── struct ExactRoute { ea_method: &'static str, ea_path: &'static str, core_method: &'static str, core_path: &'static str, notes: &'static str, } const EXACT: &[ExactRoute] = &[ // ── Auth ───────────────────────────────────────────────────────────────── ExactRoute { ea_method: "POST", ea_path: "/ut/auth", core_method: "POST", core_path: "/auth/local", notes: "FUT login → Core local auth", }, // ── Profile / Settings ─────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/user/settings", core_method: "GET", core_path: "/profile", notes: "FUT user settings → Core profile", }, ExactRoute { ea_method: "PUT", ea_path: "/ut/game/fut/user/settings", core_method: "PUT", core_path: "/settings", notes: "FUT update settings → Core settings", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/user/accountinfo", core_method: "GET", core_path: "/profile", notes: "FUT account info → Core profile", }, // ── Club / Mass info ────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/usermassinfo", core_method: "GET", core_path: "/club", notes: "FUT mass info (club + coins) → Core club", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/club", core_method: "GET", core_path: "/club", notes: "FUT club info → Core club", }, ExactRoute { ea_method: "PUT", ea_path: "/ut/game/fut/club", core_method: "PUT", core_path: "/club", notes: "FUT update club → Core update club", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/club/stats", core_method: "GET", core_path: "/statistics", notes: "FUT club stats → Core statistics", }, // ── Cards / Collection ──────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/item", core_method: "GET", core_path: "/collection", notes: "FUT collection → Core owned cards", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/item/search", core_method: "GET", core_path: "/cards", notes: "FUT item search → Core card catalogue (query params forwarded)", }, // ── Squad ───────────────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/squad/active", core_method: "GET", core_path: "/squad", notes: "FUT active squad → Core squad", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/squad/0", core_method: "GET", core_path: "/squad", notes: "FUT squad by slot 0 → Core squad (first squad)", }, ExactRoute { ea_method: "PUT", ea_path: "/ut/game/fut/squad/active", core_method: "POST", core_path: "/squad", notes: "FUT save squad → Core save squad", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/squad/chemistry", core_method: "GET", core_path: "/squad", notes: "FUT squad chemistry → Core squad (chemistry included in response)", }, // ── Packs ───────────────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/store/packdetails", core_method: "GET", core_path: "/packs", notes: "FUT pack store → Core pack list", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/store/purchase", core_method: "POST", core_path: "/packs/buy", notes: "FUT pack purchase → Core pack buy", }, // ── Transfer Market ─────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/transfermarket", core_method: "GET", core_path: "/market", notes: "FUT transfer market search → Core NPC market", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/trade/bid", core_method: "POST", core_path: "/market/buy", notes: "FUT bid/buy now → Core market buy", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/trade/watchlist", core_method: "GET", core_path: "/market", notes: "FUT watchlist → Core market (approximation)", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/trade/tradepile", core_method: "GET", core_path: "/market/my-listings", notes: "FUT trade pile (my listings) → Core my-listings", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/auctionhouse", core_method: "POST", core_path: "/market/sell", notes: "FUT list card on AH → Core sell card", }, // ── Objectives ──────────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/objectives", core_method: "GET", core_path: "/objectives", notes: "FUT objectives → Core objectives list", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/objectives/claim", core_method: "POST", core_path: "/objectives/claim", notes: "FUT claim objective → Core claim", }, // ── Events ──────────────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/events", core_method: "GET", core_path: "/events", notes: "FUT events → Core events list", }, // ── Matches / Squad Battles ─────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/squadbattle/opponent", core_method: "GET", core_path: "/matches/opponent", notes: "Squad battles opponent → Core match opponent generator", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/result", core_method: "POST", core_path: "/matches/result", notes: "FUT match result submit → Core match result", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/matches", core_method: "GET", core_path: "/matches", notes: "FUT match history → Core match list", }, // ── SBC ────────────────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/sbc", core_method: "GET", core_path: "/sbc", notes: "FUT SBC list → Core SBC list", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/sbc/challenges", core_method: "GET", core_path: "/sbc", notes: "FUT SBC challenges → Core SBC list", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/sbc/submission", core_method: "POST", core_path: "/sbc/submit", notes: "FUT SBC submission → Core SBC submit", }, // ── Draft ───────────────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/draft", core_method: "GET", core_path: "/draft/squad", notes: "FUT draft view → Core draft squad", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/draft/new", core_method: "POST", core_path: "/draft/start", notes: "FUT new draft → Core draft start", }, // ── Division / Season ───────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/division/rivals", core_method: "GET", core_path: "/division", notes: "FUT Division Rivals status → Core division", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/division/rivals/claim", core_method: "POST", core_path: "/rivals/claim-weekly", notes: "FUT rivals weekly claim → Core rivals claim", }, // ── FUT Champions ───────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/champs", core_method: "GET", core_path: "/fut-champs", notes: "FUT Champions status → Core fut-champs", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/champs/start", core_method: "POST", core_path: "/fut-champs/start", notes: "FUT Champions start week → Core fut-champs start", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/champs/history", core_method: "GET", core_path: "/fut-champs/history", notes: "FUT Champions history → Core history", }, // ── Card Upgrades ───────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/chemistry", core_method: "GET", core_path: "/chemistry-styles", notes: "FUT chemistry styles → Core chemistry styles list", }, // ── Notifications ───────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/notification", core_method: "GET", core_path: "/notifications", notes: "FUT notifications → Core notifications", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/notifications", core_method: "GET", core_path: "/notifications", notes: "FUT notifications (plural form) → Core notifications", }, // ── Squad list ──────────────────────────────────────────────────────────── // ── Division / Season history / Leaderboard ─────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/division/history", core_method: "GET", core_path: "/division/history", notes: "FUT division history → Core season history", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/division/leaderboard", core_method: "GET", core_path: "/division/leaderboard", notes: "FUT division leaderboard → Core seeded NPC leaderboard", }, // ── Market trade history ────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/trade/history", core_method: "GET", core_path: "/market/trade-history", notes: "FUT trade history → Core market trade history", }, // ── Daily check-in ──────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/dailyObjective", core_method: "GET", core_path: "/club/checkin", notes: "FUT daily objective status → Core check-in status", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/dailyObjective/claim", core_method: "POST", core_path: "/club/checkin", notes: "FUT daily objective claim → Core check-in claim", }, // ── Club milestones ─────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/milestones", core_method: "GET", core_path: "/club/milestones", notes: "FUT milestones → Core club milestones", }, // ── Squad list ──────────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/squad/list", core_method: "GET", core_path: "/squads", notes: "FUT squad list → Core all squads", }, ExactRoute { ea_method: "DELETE", ea_path: "/ut/game/fut/squad/active", core_method: "DELETE", core_path: "/squad", notes: "FUT delete active squad → Core delete squad (best-effort)", }, // ── Achievements / Trophies ─────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/trophies", core_method: "GET", core_path: "/achievements", notes: "FUT trophies → Core achievements", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/trophy", core_method: "GET", core_path: "/achievements", notes: "FUT trophy (singular) → Core achievements", }, // ── Rivals extra endpoints ──────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/rivals/rank", core_method: "GET", core_path: "/division", notes: "FUT rivals rank → Core division", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/rivals/result", core_method: "POST", core_path: "/matches/result", notes: "FUT rivals match result → Core match result", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/rivals/leaderboard", core_method: "GET", core_path: "/statistics", notes: "FUT rivals leaderboard → Core statistics (offline approximation)", }, // ── Objectives sub-groups ───────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/objectives/group", core_method: "GET", core_path: "/objectives", notes: "FUT objectives group → Core objectives", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/objectives/daily", core_method: "GET", core_path: "/objectives", notes: "FUT daily objectives → Core objectives", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/objectives/weekly", core_method: "GET", core_path: "/objectives", notes: "FUT weekly objectives → Core objectives", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/objectives/group/claim", core_method: "POST", core_path: "/objectives/claim", notes: "FUT objectives group claim → Core objectives claim", }, // ── Catalogue / Card search ─────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/catalogue/item", core_method: "GET", core_path: "/cards", notes: "FUT catalogue items → Core card catalogue", }, ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/catalogue", core_method: "GET", core_path: "/cards", notes: "FUT catalogue → Core card catalogue", }, // ── Store / Pricing ─────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/store/pricetiers", core_method: "GET", core_path: "/packs/store", notes: "FUT price tiers → Core pack store definitions", }, // ── Consumables / Chemistry / Fitness ───────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/consumables", core_method: "GET", core_path: "/chemistry-styles", notes: "FUT consumables → Core chemistry styles (approximation)", }, ExactRoute { ea_method: "POST", ea_path: "/ut/game/fut/fitness", core_method: "GET", core_path: "/club", notes: "FUT fitness apply → Core club (placeholder, fitness not tracked)", }, // ── Loan items ──────────────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/loanitems", core_method: "GET", core_path: "/collection", notes: "FUT loan items → Core collection (client filters is_loan)", }, // ── Customization / Kit ─────────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/customization", core_method: "GET", core_path: "/settings", notes: "FUT customization → Core settings", }, ExactRoute { ea_method: "PUT", ea_path: "/ut/game/fut/customization", core_method: "PUT", core_path: "/settings", notes: "FUT save customization → Core save settings", }, // ── Active messages / MOTD ──────────────────────────────────────────────── ExactRoute { ea_method: "GET", ea_path: "/ut/game/fut/activeMessage", core_method: "GET", core_path: "/notifications", notes: "FUT active messages → Core notifications (mapped to nearest equivalent)", }, ]; // ── Prefix mappings (dynamic path segments after a known prefix) ─────────────── struct PrefixRoute { ea_method: &'static str, ea_prefix: &'static str, /// Generates the core path given the path suffix after ea_prefix. core_path_fn: fn(&str) -> String, notes: &'static str, } fn prefix_routes() -> &'static [PrefixRoute] { &[ // /ut/game/fut/draft/{session_id} → GET /draft/sessions/{session_id} PrefixRoute { ea_method: "GET", ea_prefix: "/ut/game/fut/draft/", core_path_fn: |suffix| format!("/draft/sessions/{suffix}"), notes: "FUT draft session → Core draft session", }, // /ut/game/fut/draft/{id}/pick → POST /draft/sessions/{id}/pick PrefixRoute { ea_method: "POST", ea_prefix: "/ut/game/fut/draft/", core_path_fn: |suffix| { // suffix is "{id}/pick" or "{id}/abandon" let parts: Vec<&str> = suffix.splitn(2, '/').collect(); if parts.len() == 2 { format!("/draft/sessions/{}/{}", parts[0], parts[1]) } else { format!("/draft/sessions/{suffix}") } }, notes: "FUT draft pick/abandon → Core draft action", }, // /ut/game/fut/champs/{session_id}/result → POST /fut-champs/{session_id}/result PrefixRoute { ea_method: "POST", ea_prefix: "/ut/game/fut/champs/", core_path_fn: |suffix| format!("/fut-champs/{suffix}"), notes: "FUT Champions match result / claim → Core fut-champs action", }, // /ut/game/fut/sbc/set/{set_id}/submission → POST /sbc/submit PrefixRoute { ea_method: "POST", ea_prefix: "/ut/game/fut/sbc/set/", core_path_fn: |_| "/sbc/submit".to_string(), notes: "FUT SBC set submission → Core SBC submit", }, // /ut/game/fut/trade/{trade_id} (DELETE) → DELETE /market/listings/{trade_id} PrefixRoute { ea_method: "DELETE", ea_prefix: "/ut/game/fut/trade/", core_path_fn: |suffix| format!("/market/listings/{suffix}"), notes: "FUT cancel trade listing → Core delete market listing", }, // /ut/game/fut/item/{owned_id} (DELETE) → DELETE /collection/{owned_id} PrefixRoute { ea_method: "DELETE", ea_prefix: "/ut/game/fut/item/", core_path_fn: |suffix| format!("/collection/{suffix}"), notes: "FUT quick-sell item → Core delete owned card", }, // /ut/game/fut/item/{owned_id} (PUT) → PUT /collection/{owned_id}/chemistry PrefixRoute { ea_method: "PUT", ea_prefix: "/ut/game/fut/item/", core_path_fn: |suffix| { // suffix is just the owned_card_id; game sends chemistry style in body format!("/collection/{suffix}/chemistry") }, notes: "FUT apply chemistry style → Core update card chemistry", }, // /ut/game/fut/squad/{id} (GET) → GET /squads/{id} // Note: must come after exact matches for /squad/active, /squad/0, etc. PrefixRoute { ea_method: "GET", ea_prefix: "/ut/game/fut/squad/", core_path_fn: |suffix| { // Skip known non-ID suffixes already handled as exact routes match suffix { "active" | "chemistry" | "list" => "/squad".to_string(), _ => format!("/squads/{suffix}"), } }, notes: "FUT squad by ID → Core squad by ID", }, // /ut/game/fut/squad/{id} (DELETE) → DELETE /squads/{id} PrefixRoute { ea_method: "DELETE", ea_prefix: "/ut/game/fut/squad/", core_path_fn: |suffix| format!("/squads/{suffix}"), notes: "FUT delete squad by ID → Core delete squad", }, // /ut/game/fut/sbc/set/{set_id} (GET) → GET /sbc/{set_id} PrefixRoute { ea_method: "GET", ea_prefix: "/ut/game/fut/sbc/set/", core_path_fn: |suffix| format!("/sbc/{suffix}"), notes: "FUT SBC set details → Core individual SBC", }, // /ut/game/fut/store/pack/{pack_id}/open → POST /packs/open/{pack_id} PrefixRoute { ea_method: "POST", ea_prefix: "/ut/game/fut/store/pack/", core_path_fn: |suffix| { // suffix: "{pack_id}/open" or just "{pack_id}" let id = suffix.trim_end_matches("/open"); format!("/packs/open/{id}") }, notes: "FUT open pack → Core open pack", }, ] } // ── Public API ──────────────────────────────────────────────────────────────── /// Attempt to map an incoming FIFA 23 request to an OpenFUT Core call. /// Returns Some(mapping) if the route is known, None otherwise. pub fn map_to_core(method: &str, path: &str) -> Option { let m = method.to_uppercase(); let p = path.trim_end_matches('/'); // 1. Exact match for r in EXACT { if r.ea_method == m && r.ea_path == p { return Some(CoreMapping { method: r.core_method, core_path: r.core_path.to_string(), notes: r.notes, }); } } // 2. Prefix match — first prefix that fits wins for r in prefix_routes() { if r.ea_method == m { if let Some(suffix) = p.strip_prefix(r.ea_prefix) { if !suffix.is_empty() { let core_path = (r.core_path_fn)(suffix); return Some(CoreMapping { method: "GET", // overridden per-route below core_path, notes: r.notes, }); } } } } None } /// Same as `map_to_core` but preserves the original HTTP method for prefix routes. /// /// The simple `map_to_core` hardcodes "GET" for prefix routes because the method /// is already known from the match condition. This helper re-derives it. pub fn map_to_core_with_method(method: &str, path: &str) -> Option { let m = method.to_uppercase(); let p = path.trim_end_matches('/'); for r in EXACT { if r.ea_method == m && r.ea_path == p { return Some(CoreMapping { method: r.core_method, core_path: r.core_path.to_string(), notes: r.notes, }); } } for r in prefix_routes() { if r.ea_method == m { if let Some(suffix) = p.strip_prefix(r.ea_prefix) { if !suffix.is_empty() { return Some(CoreMapping { method: r.ea_method, // use the matched method core_path: (r.core_path_fn)(suffix), notes: r.notes, }); } } } } None } /// Return a safe placeholder response for unknown endpoints. pub fn placeholder_response(method: &str, path: &str) -> serde_json::Value { tracing::warn!( "UNKNOWN ENDPOINT: {} {} — returning placeholder", method, path ); serde_json::json!({ "status": "ok", "openfut_note": "This endpoint has not been mapped yet. Check captures/ for details.", "method": method, "path": path, }) } #[cfg(test)] mod tests { use super::*; #[test] fn test_known_auth_maps() { let m = map_to_core("POST", "/ut/auth"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/auth/local"); } #[test] fn test_known_market_maps() { let m = map_to_core("GET", "/ut/game/fut/transfermarket"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/market"); } #[test] fn test_events_maps() { let m = map_to_core("GET", "/ut/game/fut/events"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/events"); } #[test] fn test_squad_put_maps() { let m = map_to_core("PUT", "/ut/game/fut/squad/active"); assert!(m.is_some()); assert_eq!(m.unwrap().method, "POST"); } #[test] fn test_unknown_returns_none() { assert!(map_to_core("GET", "/ut/game/fut/some/unknown/path").is_none()); } #[test] fn test_trailing_slash_trimmed() { let m = map_to_core("POST", "/ut/auth/"); assert!(m.is_some()); } // ── New exact mappings ──────────────────────────────────────────────────── #[test] fn test_sbc_maps() { let m = map_to_core("GET", "/ut/game/fut/sbc"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/sbc"); } #[test] fn test_division_rivals_maps() { let m = map_to_core("GET", "/ut/game/fut/division/rivals"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/division"); } #[test] fn test_fut_champs_start_maps() { let m = map_to_core("POST", "/ut/game/fut/champs/start"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/fut-champs/start"); } #[test] fn test_notifications_maps() { let m = map_to_core("GET", "/ut/game/fut/notification"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/notifications"); } #[test] fn test_settings_put_maps() { let m = map_to_core("PUT", "/ut/game/fut/user/settings"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/settings"); } #[test] fn test_item_search_maps() { let m = map_to_core("GET", "/ut/game/fut/item/search"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/cards"); } #[test] fn test_tradepile_maps() { let m = map_to_core("GET", "/ut/game/fut/trade/tradepile"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/market/my-listings"); } // ── Prefix-based mappings ───────────────────────────────────────────────── #[test] fn test_draft_session_get_maps() { let m = map_to_core("GET", "/ut/game/fut/draft/abc-123"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/draft/sessions/abc-123"); } #[test] fn test_draft_pick_maps() { let m = map_to_core("POST", "/ut/game/fut/draft/abc-123/pick"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/draft/sessions/abc-123/pick"); } #[test] fn test_draft_abandon_maps() { let m = map_to_core("POST", "/ut/game/fut/draft/abc-123/abandon"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/draft/sessions/abc-123/abandon"); } #[test] fn test_champs_result_maps() { let m = map_to_core("POST", "/ut/game/fut/champs/sess-456/result"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/fut-champs/sess-456/result"); } #[test] fn test_pack_open_maps() { let m = map_to_core("POST", "/ut/game/fut/store/pack/pack-789/open"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/packs/open/pack-789"); } #[test] fn test_quick_sell_maps() { let m = map_to_core("DELETE", "/ut/game/fut/item/owned-111"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/collection/owned-111"); } #[test] fn test_cancel_listing_maps() { let m = map_to_core("DELETE", "/ut/game/fut/trade/trade-222"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/market/listings/trade-222"); } #[test] fn test_total_exact_routes_count() { assert!(EXACT.len() >= 61, "expected at least 61 exact mappings, got {}", EXACT.len()); } // ── Phase 22 new mappings ───────────────────────────────────────────────── #[test] fn test_squad_list_maps() { let m = map_to_core("GET", "/ut/game/fut/squad/list"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/squads"); } #[test] fn test_squad_by_id_get_maps() { let m = map_to_core_with_method("GET", "/ut/game/fut/squad/abc-squad-id"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/squads/abc-squad-id"); } #[test] fn test_squad_by_id_delete_maps() { let m = map_to_core_with_method("DELETE", "/ut/game/fut/squad/abc-squad-id"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/squads/abc-squad-id"); } #[test] fn test_item_put_chemistry_maps() { let m = map_to_core_with_method("PUT", "/ut/game/fut/item/owned-999"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/collection/owned-999/chemistry"); } #[test] fn test_sbc_set_get_maps() { let m = map_to_core_with_method("GET", "/ut/game/fut/sbc/set/sbc-123"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/sbc/sbc-123"); } #[test] fn test_trophies_maps() { let m = map_to_core("GET", "/ut/game/fut/trophies"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/achievements"); } #[test] fn test_rivals_result_maps() { let m = map_to_core("POST", "/ut/game/fut/rivals/result"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/matches/result"); } #[test] fn test_catalogue_item_maps() { let m = map_to_core("GET", "/ut/game/fut/catalogue/item"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/cards"); } #[test] fn test_objectives_daily_maps() { let m = map_to_core("GET", "/ut/game/fut/objectives/daily"); assert!(m.is_some()); assert_eq!(m.unwrap().core_path, "/objectives"); } #[test] fn test_active_message_maps() { let m = map_to_core("GET", "/ut/game/fut/activeMessage"); assert!(m.is_some()); } }