host: claim GET ut/%s/item (FutViewCards), which fell through to Python
Fifth instance of this project's recurring dead-route defect: a handler exists and
is correct, but classify() never produces the route, so every request falls
through to the Python upstream. Invisible in production, where the oracle answers;
on staging, where the upstream is deliberately dead, it is a 502.
GET ut/%s/item is FutViewCards (deser 0x1801293d0, top-level itemData via the
shared card element 0x18013fe00). The oracle answers it with defs_route - the SAME
handler already wired for item/resource and defid: pull every integer out of the
query (idList=a,b,c / definitionId= / resourceId=) and return one definition per
id, or {"itemData": []} when there are none (tools/utas_server.py:1103). So
claiming it is byte-identical parity, not new behaviour.
The query handling is the substance of the fix, not decoration. ut_tail does NOT
strip the query string, so an equality-only arm (`Some("item")`) misses every real
request while passing a no-query unit test - which is precisely how the route
stayed unclaimed. The same latent bug applied to the two arms that were already
there: item/resource and defid only matched with no query, so
`item/resource?resourceId=` and `defid?definitionId=` were ALSO falling through.
All three now mirror the oracle's own `item(\?|$)` pattern.
Verified on staging, all 200 where they were 502:
GET /item -> itemData 0
GET /item?idList=100000003,100000004 -> itemData 2
GET /ut/v2/game/fifa17/item -> itemData 0
GET /item/resource?resourceId=5003012 -> itemData 1
GET /defid?definitionId=200389 -> itemData 1
Does NOT by itself fix the kit selector - GET /item is a definition lookup keyed
by ids the client already holds, not the thing that seeds the club collection, and
the observed session never requested it. It is a real production-masked gap and a
prerequisite for testing anything else on staging.
get_item_is_claimed_and_the_other_item_verbs_are_unaffected pins the claim plus
the three verbs that share the prefix: PUT item stays FutMoveCard on the economy
path and DELETE item/<id> stays QuickSellPath.
cargo test -p openfut-utas-host: 190 passed, 0 failed. clippy -D warnings clean.
This commit is contained in:
@@ -292,8 +292,32 @@ pub fn classify(method: &str, path: &str) -> Route {
|
||||
Some("champion") if get => Route::FeatureOffEmpty,
|
||||
Some("clubUser") if get => Route::FeatureOffEmpty,
|
||||
Some("user/list") if get => Route::FeatureOffEmpty,
|
||||
Some("item/resource") if get => Route::ItemDefs,
|
||||
Some("defid") if get => Route::ItemDefs,
|
||||
Some(t) if get && (t == "item/resource" || t.starts_with("item/resource?")) => {
|
||||
Route::ItemDefs
|
||||
}
|
||||
Some(t) if get && (t == "defid" || t.starts_with("defid?")) => Route::ItemDefs,
|
||||
// `GET ut/%s/item` is FutViewCards (deser `0x1801293d0`, top-level
|
||||
// `itemData` via the shared card element `0x18013fe00`). The oracle
|
||||
// answers it with `defs_route` — the SAME handler as `item/resource` and
|
||||
// `defid`: parse every integer out of the query (`idList=a,b,c`,
|
||||
// `definitionId=`, `resourceId=`) and return one definition per id, or
|
||||
// `{"itemData": []}` when the query carries none
|
||||
// (`tools/utas_server.py:1103`). Claiming it is byte-identical parity.
|
||||
//
|
||||
// It was unclaimed, so it fell through to the Python upstream. That is
|
||||
// invisible in production, where the oracle answers, and shows up only on
|
||||
// staging as a 502 — the same dead-route class already found four times
|
||||
// (season/list, watchList, the handle_static_ack tails, tournament/user).
|
||||
//
|
||||
// The `?` forms are not decoration: `ut_tail` does NOT strip the query, so
|
||||
// a bare equality arm silently misses every real request, which is exactly
|
||||
// how this route stayed unclaimed. The oracle's own pattern is
|
||||
// `item(\?|$)` (`tools/utas_server.py:1419`) and this mirrors it.
|
||||
//
|
||||
// MUST stay below `item/resource` (matched first) and must not swallow
|
||||
// `item/<id>`, which is DELETE-only Quick Sell, nor PUT `item`, which is
|
||||
// FutMoveCard on the economy path.
|
||||
Some(t) if get && (t == "item" || t.starts_with("item?")) => Route::ItemDefs,
|
||||
Some(t) if get && (t == "marketdata" || t.starts_with("marketdata/")) => Route::MarketData,
|
||||
_ => Route::Passthrough,
|
||||
}
|
||||
@@ -7045,6 +7069,52 @@ mod tests {
|
||||
Route::Passthrough
|
||||
);
|
||||
}
|
||||
/// `GET ut/%s/item` (FutViewCards) MUST be claimed, and claiming it must not
|
||||
/// disturb the three other verbs that share the `item` prefix.
|
||||
///
|
||||
/// This route was unclaimed and fell through to the Python upstream, which is
|
||||
/// invisible in production (the oracle answers) and appears only on staging as
|
||||
/// a 502. That is the recurring dead-route defect in this project, so the
|
||||
/// point of this test is that a handler existing is not the same as a request
|
||||
/// reaching it.
|
||||
#[test]
|
||||
fn get_item_is_claimed_and_the_other_item_verbs_are_unaffected() {
|
||||
// FutViewCards, with and without the definition query the client builds.
|
||||
assert_eq!(classify("GET", "/ut/game/fifa17/item"), Route::ItemDefs);
|
||||
assert_eq!(
|
||||
classify("GET", "/ut/game/fifa17/item?idList=100000003,100000004"),
|
||||
Route::ItemDefs
|
||||
);
|
||||
// v2 sku form resolves identically.
|
||||
assert_eq!(classify("GET", "/ut/v2/game/fifa17/item"), Route::ItemDefs);
|
||||
// The more specific definition routes still win, and — the bug this test
|
||||
// exists for — they must survive a query string too. `ut_tail` does not
|
||||
// strip the query, so an equality-only arm misses every real request while
|
||||
// looking correct in a no-query unit test.
|
||||
assert_eq!(
|
||||
classify("GET", "/ut/game/fifa17/item/resource"),
|
||||
Route::ItemDefs
|
||||
);
|
||||
assert_eq!(
|
||||
classify("GET", "/ut/game/fifa17/item/resource?resourceId=5003012"),
|
||||
Route::ItemDefs
|
||||
);
|
||||
assert_eq!(
|
||||
classify("GET", "/ut/game/fifa17/defid?definitionId=200389"),
|
||||
Route::ItemDefs
|
||||
);
|
||||
// PUT `item` is FutMoveCard on the economy path, NOT a definition read.
|
||||
assert_eq!(classify("PUT", "/ut/game/fifa17/item"), Route::Passthrough);
|
||||
assert_eq!(
|
||||
classify_economy("PUT", "/ut/game/fifa17/item"),
|
||||
Some(EconomyRoute::MoveItems)
|
||||
);
|
||||
// DELETE `item/<id>` is single-card Quick Sell and must not be swallowed.
|
||||
assert_eq!(
|
||||
classify_economy("DELETE", "/ut/game/fifa17/item/100000240"),
|
||||
Some(EconomyRoute::QuickSellPath)
|
||||
);
|
||||
}
|
||||
|
||||
/// Retail FIFA 17 issues part of the item family under `/ut/v2/game/`, so the
|
||||
/// same three verbs must land identically under both prefixes: `ut_tail`
|
||||
|
||||
Reference in New Issue
Block a user