Returns all pack definitions with id, name, description, cost_coins, and total_cards so the dashboard can render a purchasable pack store without needing to know the definitions at compile time. Two new tests: store listing shape and buy-then-open round-trip. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -152,6 +152,7 @@ pub async fn build(pool: Pool, cfg: Config) -> Result<Router> {
|
|||||||
get(routes::upgrades::get_chemistry_styles),
|
get(routes::upgrades::get_chemistry_styles),
|
||||||
)
|
)
|
||||||
.route("/packs", get(routes::packs::get_packs))
|
.route("/packs", get(routes::packs::get_packs))
|
||||||
|
.route("/packs/store", get(routes::packs::get_pack_store))
|
||||||
.route("/packs/history", get(routes::packs::get_pack_history))
|
.route("/packs/history", get(routes::packs::get_pack_history))
|
||||||
.route("/packs/buy", post(routes::packs::post_buy_pack))
|
.route("/packs/buy", post(routes::packs::post_buy_pack))
|
||||||
.route("/packs/open/:pack_id", post(routes::packs::post_open_pack))
|
.route("/packs/open/:pack_id", post(routes::packs::post_open_pack))
|
||||||
|
|||||||
@@ -35,6 +35,25 @@ pub async fn post_buy_pack(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List all purchasable pack definitions with their coin costs.
|
||||||
|
pub async fn get_pack_store(State(state): State<AppState>) -> AppResult<Json<Value>> {
|
||||||
|
let store: Vec<Value> = state
|
||||||
|
.pack_defs
|
||||||
|
.iter()
|
||||||
|
.map(|d| {
|
||||||
|
let total_cards: u8 = d.slots.iter().map(|s| s.count).sum();
|
||||||
|
json!({
|
||||||
|
"id": d.id,
|
||||||
|
"name": d.name,
|
||||||
|
"description": d.description,
|
||||||
|
"cost_coins": d.cost_coins,
|
||||||
|
"total_cards": total_cards,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(Json(json!({ "packs": store })))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_packs(State(state): State<AppState>) -> AppResult<Json<Value>> {
|
pub async fn get_packs(State(state): State<AppState>) -> AppResult<Json<Value>> {
|
||||||
let profile = profile_svc::get_active_profile(&state.pool).await?;
|
let profile = profile_svc::get_active_profile(&state.pool).await?;
|
||||||
let club = club_svc::get_club_by_profile(&state.pool, &profile.id).await?;
|
let club = club_svc::get_club_by_profile(&state.pool, &profile.id).await?;
|
||||||
|
|||||||
@@ -1409,3 +1409,43 @@ async fn test_rivals_reward_increments_week_counter() {
|
|||||||
assert_eq!(s, StatusCode::OK, "{json}");
|
assert_eq!(s, StatusCode::OK, "{json}");
|
||||||
assert_eq!(json["week_number"], 2, "second claim should be week 2");
|
assert_eq!(json["week_number"], 2, "second claim should be week 2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Phase 15: Pack Store ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_pack_store_lists_definitions() {
|
||||||
|
let app = build_test_app().await;
|
||||||
|
auth(&app, "StorePlayer").await;
|
||||||
|
|
||||||
|
let (status, json) = json_get(&app, "/packs/store").await;
|
||||||
|
assert_eq!(status, StatusCode::OK, "{json}");
|
||||||
|
let packs = json["packs"].as_array().expect("packs array");
|
||||||
|
assert!(!packs.is_empty(), "store should have at least one pack definition");
|
||||||
|
let first = &packs[0];
|
||||||
|
assert!(first["id"].is_string(), "id missing");
|
||||||
|
assert!(first["name"].is_string(), "name missing");
|
||||||
|
assert!(first["cost_coins"].is_number(), "cost_coins missing");
|
||||||
|
assert!(first["total_cards"].is_number(), "total_cards missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_pack_store_buy_then_opens() {
|
||||||
|
let app = build_test_app().await;
|
||||||
|
auth(&app, "StoreBuyer").await;
|
||||||
|
|
||||||
|
// Get cheapest pack from store
|
||||||
|
let (_, store) = json_get(&app, "/packs/store").await;
|
||||||
|
let packs = store["packs"].as_array().unwrap();
|
||||||
|
let cheapest = packs.iter().min_by_key(|p| p["cost_coins"].as_i64().unwrap_or(i64::MAX)).unwrap();
|
||||||
|
let def_id = cheapest["id"].as_str().unwrap();
|
||||||
|
|
||||||
|
// Buy it
|
||||||
|
let (status, json) = json_post(&app, "/packs/buy", serde_json::json!({ "pack_definition_id": def_id })).await;
|
||||||
|
assert_eq!(status, StatusCode::OK, "buy failed: {json}");
|
||||||
|
let pack_id = json["pack"]["id"].as_str().expect("pack id").to_string();
|
||||||
|
|
||||||
|
// Open it
|
||||||
|
let (status, json) = json_post(&app, &format!("/packs/open/{pack_id}"), serde_json::json!({})).await;
|
||||||
|
assert_eq!(status, StatusCode::OK, "open failed: {json}");
|
||||||
|
assert!(json["cards"].as_array().map(|a| !a.is_empty()).unwrap_or(false), "opened pack should contain cards");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user