fix(fifa17): preserve owned-pack reveal state for GET /purchased
Closes the reveal contract gap: POST /purchased opens a pack and returns metadata; the client then polls GET /purchased for the opened items. Store BUY returns items inline, but owned reward-pack (e.g. pack 70) opens had no reveal read path, so a real FIFA session would show nothing after opening. Faithful to the Python oracle (fut_store.last_pack / purchased pile): the reveal is the set of owned items currently in the FIFA "purchased" pile — durable, idempotent on repeat GET, cleared per-item when a card is moved to the club, and appended-to by each open. Not a replay cache; presentation state derived from the durable pile store + Core inventory. - pile_store.rs: `list_by_pile(pile) -> Vec<core_item_id>` (reveal membership). - economy_store.rs: `PurchasedPileSink` trait + optional `StoreDeps.purchased`; handle_store_buy/handle_pack_open record each minted item into the "purchased" pile. `shape_purchased_reveal` (pure): filter Core inventory to the purchased pile, shape with the SAME `shape_club_response` /club uses. Grants nothing, consumes no entitlement, allocates no id, moves no coins. - lib.rs: EconomyRoute::PackReveal + classify_economy (GET purchased); BridgedPurchasedSink (records via the runtime bridge from the sync dispatch thread); dispatch reads the pile async + Core inventory sync + pure-shapes. Scoping: single fifa17 profile/club (like the Python oracle), so all sessions share one purchased pile — DIFFERENT-BY-DESIGN vs a per-SID cache, matching the oracle's single-profile model. Tests: pile_store::list_by_pile_filters_and_reflects_moves; and the dispatch E2E now opens pack 70 (entitlement seeded via the Core economy API) and asserts GET /purchased reveals the opened items and is idempotent on repeat. host 71 lib + 2 integration + 24 host_test green; clippy -D warnings + fmt clean.
This commit is contained in:
@@ -550,6 +550,61 @@ fn economy_sequence(base: &str, dir: &std::path::Path) -> SeqResult {
|
||||
"buying a cancelled listing does not debit"
|
||||
);
|
||||
|
||||
// 6b) Owned-pack (70) open + GET /purchased reveal (Part 8 + 0B). Seed an
|
||||
// unopened pack-70 entitlement via the Core economy API (cost 0), open it,
|
||||
// and prove the reveal screen (GET /purchased) shows the freshly opened items
|
||||
// and is idempotent on repeat (presentation state, not a second grant).
|
||||
let http = reqwest::blocking::Client::new();
|
||||
post(
|
||||
&http,
|
||||
base,
|
||||
"/economy/purchase-entitlement",
|
||||
json!({ "cost": 0, "definition_id": "70" }),
|
||||
);
|
||||
let reveal_before = {
|
||||
let r = server
|
||||
.try_handle_economy("GET", "/ut/game/fifa17/purchased", &[], b"", None)
|
||||
.expect("reveal routed");
|
||||
serde_json::from_slice::<Value>(&r.body).unwrap()["itemData"]
|
||||
.as_array()
|
||||
.map(|a| a.len())
|
||||
.unwrap_or(0)
|
||||
};
|
||||
let open = server
|
||||
.try_handle_economy(
|
||||
"POST",
|
||||
"/ut/game/fifa17/purchased",
|
||||
&[],
|
||||
br#"{"packId":70}"#,
|
||||
None,
|
||||
)
|
||||
.expect("pack open routed");
|
||||
assert_eq!(open.status, 200, "pack-70 open 200");
|
||||
let ov: Value = serde_json::from_slice(&open.body).unwrap();
|
||||
assert_eq!(ov["packId"], 70, "open echoes the pack id");
|
||||
let reveal = server
|
||||
.try_handle_economy("GET", "/ut/game/fifa17/purchased", &[], b"", None)
|
||||
.expect("reveal routed");
|
||||
let reveal_items = serde_json::from_slice::<Value>(&reveal.body).unwrap()["itemData"]
|
||||
.as_array()
|
||||
.expect("reveal itemData")
|
||||
.len();
|
||||
assert!(
|
||||
reveal_items > reveal_before,
|
||||
"GET /purchased reveals the opened items ({reveal_before} -> {reveal_items})"
|
||||
);
|
||||
// Idempotent: a repeat GET does not re-grant or clear (same reveal).
|
||||
let reveal2 = server
|
||||
.try_handle_economy("GET", "/ut/game/fifa17/purchased", &[], b"", None)
|
||||
.unwrap();
|
||||
let reveal2_items = serde_json::from_slice::<Value>(&reveal2.body).unwrap()["itemData"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.len();
|
||||
assert_eq!(
|
||||
reveal2_items, reveal_items,
|
||||
"repeated GET /purchased is idempotent"
|
||||
);
|
||||
// 7) Move a still-owned minted card to the trade pile (durable pile metadata).
|
||||
let move_wire = items[1]["id"].as_i64().unwrap();
|
||||
let mv = server
|
||||
|
||||
Reference in New Issue
Block a user