feat: map objectives, SBC, squad battles, and match endpoints

Adds exact and prefix routes for endpoints observed in FIFA 23 FUT that
were not yet covered:
- GET /ut/game/fut/user/dynamicobjectives → GET /objectives
- POST /ut/game/fut/user/dynamicobjectives/{id}/milestones/{m}/claim
    → POST /objectives/{id}/claim
- GET /ut/game/fut/sbs/challenges[/{id}] → GET /sbc[/{id}]
- POST /ut/game/fut/sbs/challenges/{id}/sets/{setId}/trade → POST /sbc/submit
- POST /ut/game/fut/squadbattle/result → POST /matches/result
- POST /ut/game/fut/game/r → POST /matches/result
- GET /ut/game/fut/squads/user → GET /squad

Also fixes prefix-route method forwarding bug: prefix routes were
unconditionally returning method "GET" instead of the matched EA method.

Closes #3 #4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-25 22:11:11 -07:00
parent 77af7ce3c9
commit d4bab06826
+88 -3
View File
@@ -381,6 +381,36 @@ const EXACT: &[ExactRoute] = &[
core_method: "GET", core_path: "/notifications",
notes: "FUT active messages → Core notifications (mapped to nearest equivalent)",
},
// ── Dynamic objectives (FIFA 23 actual path) ──────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/user/dynamicobjectives",
core_method: "GET", core_path: "/objectives",
notes: "FIFA 23 dynamic objectives list → Core objectives",
},
// ── SBS challenges (FIFA 23 uses /sbs/ not /sbc/) ────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/sbs/challenges",
core_method: "GET", core_path: "/sbc",
notes: "FIFA 23 SBS challenges list → Core SBC list",
},
// ── Squad Battles result (separate from rivals/result) ───────────────────
ExactRoute {
ea_method: "POST", ea_path: "/ut/game/fut/squadbattle/result",
core_method: "POST", core_path: "/matches/result",
notes: "Squad Battles match result → Core match result",
},
// ── Match submission (FIFA 23 uses /game/r) ───────────────────────────────
ExactRoute {
ea_method: "POST", ea_path: "/ut/game/fut/game/r",
core_method: "POST", core_path: "/matches/result",
notes: "FIFA 23 match result (short path /game/r) → Core match result",
},
// ── Squad list ────────────────────────────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/squads/user",
core_method: "GET", core_path: "/squad",
notes: "FUT squad list for user → Core squad",
},
];
// ── Prefix mappings (dynamic path segments after a known prefix) ───────────────
@@ -431,6 +461,36 @@ fn prefix_routes() -> &'static [PrefixRoute] {
core_path_fn: |_| "/sbc/submit".to_string(),
notes: "FUT SBC set submission → Core SBC submit",
},
// /ut/game/fut/sbs/challenges/{id} → GET /sbc/{id}
PrefixRoute {
ea_method: "GET",
ea_prefix: "/ut/game/fut/sbs/challenges/",
core_path_fn: |suffix| {
// suffix is "{id}" or "{id}/sets/{setId}" — strip extra segments
let id = suffix.split('/').next().unwrap_or(suffix);
format!("/sbc/{id}")
},
notes: "FIFA 23 SBS challenge detail → Core SBC by ID",
},
// /ut/game/fut/sbs/challenges/{id}/sets/{setId}/trade → POST /sbc/submit
PrefixRoute {
ea_method: "POST",
ea_prefix: "/ut/game/fut/sbs/challenges/",
core_path_fn: |_| "/sbc/submit".to_string(),
notes: "FIFA 23 SBS challenge trade → Core SBC submit",
},
// /ut/game/fut/user/dynamicobjectives/{id}/milestones/{m}/claim
// → POST /objectives/{id}/claim
PrefixRoute {
ea_method: "POST",
ea_prefix: "/ut/game/fut/user/dynamicobjectives/",
core_path_fn: |suffix| {
// suffix is "{id}/milestones/{m}/claim" — extract just the obj id
let obj_id = suffix.split('/').next().unwrap_or(suffix);
format!("/objectives/{obj_id}/claim")
},
notes: "FIFA 23 dynamic objective claim → Core objective claim by ID",
},
// /ut/game/fut/trade/{trade_id} (DELETE) → DELETE /market/listings/{trade_id}
PrefixRoute {
ea_method: "DELETE",
@@ -523,7 +583,7 @@ pub fn map_to_core(method: &str, path: &str) -> Option<CoreMapping> {
if !suffix.is_empty() {
let core_path = (r.core_path_fn)(suffix);
return Some(CoreMapping {
method: "GET", // overridden per-route below
method: r.ea_method,
core_path,
notes: r.notes,
});
@@ -720,14 +780,39 @@ mod tests {
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");
let m = m.unwrap();
assert_eq!(m.core_path, "/collection/owned-111");
assert_eq!(m.method, "DELETE");
}
#[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");
let m = m.unwrap();
assert_eq!(m.core_path, "/market/listings/trade-222");
assert_eq!(m.method, "DELETE");
}
#[test]
fn test_prefix_routes_preserve_method() {
let cases = [
("POST", "/ut/game/fut/draft/abc-123/pick", "POST"),
("POST", "/ut/game/fut/champs/sess-456/result", "POST"),
("DELETE", "/ut/game/fut/trade/trade-222", "DELETE"),
("DELETE", "/ut/game/fut/item/owned-111", "DELETE"),
("PUT", "/ut/game/fut/item/owned-999", "PUT"),
("POST", "/ut/game/fut/store/pack/pack-789/open", "POST"),
];
for (ea_method, path, expected_method) in cases {
let m = map_to_core(ea_method, path)
.unwrap_or_else(|| panic!("no mapping for {ea_method} {path}"));
assert_eq!(
m.method, expected_method,
"{ea_method} {path} → expected method {expected_method}, got {}",
m.method
);
}
}
#[test]