Files
OpenFUT-Bridge/src/mapper.rs
T
funman300 77af7ce3c9 Phase 25: leaderboard table + trade history in dashboard
- Division tab: leaderboard table (10 clubs, color-coded promo/relegation rows,
  player row highlighted, sorted by pts)
- Market tab: trade history table (last 30 buy/sell events, card name/OVR/price/date)
  loaded alongside market listings and my listings
- Mapper: added GET /division/leaderboard and GET /trade/history exact routes
- Mapper assertion bumped to ≥61

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 19:13:33 -07:00

809 lines
34 KiB
Rust

/// 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<CoreMapping> {
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<CoreMapping> {
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());
}
}