XP thresholds (500→1200→2000→…→11000→+2500/level) drive automatic level increases. add_xp_with_levelup() replaces bare add_xp() in match processing: for each level gained it grants level×500 coins and milestone packs (bronze@5, silver@10, gold@15, rare_gold@20, gold every 5 after). GET /profile now returns computed level (recalculated from XP so it stays consistent), xp_to_next_level, and xp_for_next_level so the dashboard can render a progress bar without a second call. POST /matches/result response gains level_ups array (empty when no level-up occurred) with new_level, coins_granted, pack_granted per event. Four new tests: profile level fields, level_for_xp boundary checks, level-up event in match result, milestone pack unit test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1449,3 +1449,67 @@ async fn test_pack_store_buy_then_opens() {
|
||||
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");
|
||||
}
|
||||
|
||||
// ── Phase 17: Level-up system ─────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_profile_returns_level_info() {
|
||||
let app = build_test_app().await;
|
||||
auth(&app, "LevelInfoPlayer").await;
|
||||
|
||||
let (status, json) = json_get(&app, "/profile").await;
|
||||
assert_eq!(status, StatusCode::OK, "{json}");
|
||||
assert!(json["level"].as_i64().unwrap_or(0) >= 1, "level missing");
|
||||
assert!(json["xp"].is_number(), "xp missing");
|
||||
assert!(json["xp_to_next_level"].is_number(), "xp_to_next_level missing");
|
||||
assert!(json["xp_for_next_level"].is_number(), "xp_for_next_level missing");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_level_for_xp_thresholds() {
|
||||
use openfut_core::models::profile::level_for_xp;
|
||||
assert_eq!(level_for_xp(0), 1);
|
||||
assert_eq!(level_for_xp(499), 1);
|
||||
assert_eq!(level_for_xp(500), 2);
|
||||
assert_eq!(level_for_xp(1199), 2);
|
||||
assert_eq!(level_for_xp(1200), 3);
|
||||
assert_eq!(level_for_xp(11000), 10);
|
||||
// Beyond table: level 11 at 11000 + 2500 = 13500
|
||||
assert_eq!(level_for_xp(13500), 11);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_match_result_includes_level_ups_on_first_win() {
|
||||
let app = build_test_app().await;
|
||||
auth(&app, "LevelUpWinner").await;
|
||||
|
||||
// A single win awards ~300 XP (beginner win); fresh profile is at 0 XP.
|
||||
// With enough wins we cross the 500 XP threshold (level 2).
|
||||
let mut level_ups_seen = false;
|
||||
for _ in 0..5 {
|
||||
let (status, json) = json_post(&app, "/matches/result", serde_json::json!({
|
||||
"squad_id": "dummy", "opponent_name": "Bot",
|
||||
"goals_for": 3, "goals_against": 0, "mode": "squad_battles"
|
||||
})).await;
|
||||
assert_eq!(status, StatusCode::OK, "{json}");
|
||||
if let Some(arr) = json["level_ups"].as_array() {
|
||||
if !arr.is_empty() {
|
||||
level_ups_seen = true;
|
||||
let ev = &arr[0];
|
||||
assert!(ev["new_level"].as_i64().unwrap_or(0) >= 2, "should be at least level 2");
|
||||
assert!(ev["coins_granted"].as_i64().unwrap_or(0) > 0, "coins granted on level up");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(level_ups_seen, "expected a level-up event within 5 wins");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_level_up_grants_milestone_pack_at_level_5() {
|
||||
use openfut_core::models::profile::{level_for_xp, pack_for_level, XP_THRESHOLDS};
|
||||
// Level 5 requires 3000 XP total
|
||||
assert_eq!(level_for_xp(3000), 5);
|
||||
assert_eq!(pack_for_level(5), Some("bronze_pack"));
|
||||
assert_eq!(pack_for_level(10), Some("silver_pack"));
|
||||
assert_eq!(pack_for_level(7), None);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user