Phase 24: daily check-in system + club milestones endpoint
CI / Build, lint & test (push) Failing after 28s
CI / Build, lint & test (push) Failing after 28s
- migrations/0012_daily_checkin.sql: daily_checkins table tracking streak, coins awarded, pack granted, timestamp per profile - services/checkin.rs: get_status() (available, streak_day, next reward), claim() (idempotent same-day guard, streak logic: continue if yesterday or today, else reset; 7-day cycle with STREAK_COINS array, day-7 pack) - routes/club.rs: GET /club/checkin, POST /club/checkin, GET /club/milestones (computed from statistics, season_history, owned_cards, sbc_submissions, daily_checkins tables; no new DB tables needed) - 4 new integration tests: checkin available initially, claim awards coins, idempotent same-day, milestones endpoint structure (93 → 93+4=97 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1789,3 +1789,64 @@ async fn test_division_response_has_zone_fields() {
|
||||
assert_eq!(json["relegation_pts"], 3);
|
||||
assert_eq!(json["season_length"], 10);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_checkin_available_initially() {
|
||||
let app = build_test_app().await;
|
||||
auth(&app, "CheckinFirst").await;
|
||||
|
||||
let (status, json) = json_get(&app, "/club/checkin").await;
|
||||
assert_eq!(status, StatusCode::OK, "{json}");
|
||||
assert_eq!(json["available"], true);
|
||||
assert_eq!(json["streak_day"], 0);
|
||||
assert!(json["next_reward_coins"].is_number());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_checkin_claim_awards_coins() {
|
||||
let app = build_test_app().await;
|
||||
auth(&app, "CheckinClaim").await;
|
||||
|
||||
let (_, before) = json_get(&app, "/club").await;
|
||||
let coins_before = before["coins"].as_i64().unwrap_or(0);
|
||||
|
||||
let (status, json) = json_post(&app, "/club/checkin", serde_json::json!({})).await;
|
||||
assert_eq!(status, StatusCode::OK, "{json}");
|
||||
assert_eq!(json["already_claimed"], false);
|
||||
assert!(json["coins_awarded"].as_i64().unwrap_or(0) > 0);
|
||||
assert_eq!(json["new_streak"], 1);
|
||||
|
||||
let (_, after) = json_get(&app, "/club").await;
|
||||
let coins_after = after["coins"].as_i64().unwrap_or(0);
|
||||
assert!(coins_after > coins_before, "coins should increase after checkin");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_checkin_idempotent_same_day() {
|
||||
let app = build_test_app().await;
|
||||
auth(&app, "CheckinIdempotent").await;
|
||||
|
||||
let (_, r1) = json_post(&app, "/club/checkin", serde_json::json!({})).await;
|
||||
assert_eq!(r1["already_claimed"], false);
|
||||
|
||||
let (_, r2) = json_post(&app, "/club/checkin", serde_json::json!({})).await;
|
||||
assert_eq!(r2["already_claimed"], true);
|
||||
assert_eq!(r2["coins_awarded"], 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_milestones_endpoint_structure() {
|
||||
let app = build_test_app().await;
|
||||
auth(&app, "MilestonePlayer").await;
|
||||
|
||||
let (status, json) = json_get(&app, "/club/milestones").await;
|
||||
assert_eq!(status, StatusCode::OK, "{json}");
|
||||
assert!(json["total_wins"].is_number());
|
||||
assert!(json["total_goals_scored"].is_number());
|
||||
assert!(json["best_win_streak"].is_number());
|
||||
assert!(json["seasons_completed"].is_number());
|
||||
assert!(json["highest_division_reached"].is_number());
|
||||
assert!(json["cards_owned"].is_number());
|
||||
assert!(json["sbcs_completed"].is_number());
|
||||
assert!(json["total_checkins"].is_number());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user