test(core,sync,server): add EmptySource, ConflictReport, and roundtrip coverage

- core/game_state.rs: move_from_empty_pile_returns_empty_source covers the
  EmptySource error path in move_cards() that had no test
- sync/merge.rs: four new tests verifying ConflictReport field/value content
  for win_streak_current and daily_challenge_streak divergence, plus negative
  cases asserting no report is generated when values are equal
- server/tests: register_login_push_pull_full_roundtrip drives the full
  register → login → push → pull sequence through the test router, confirming
  that a login-derived JWT can push stats and retrieve them unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-04-28 22:34:57 +00:00
parent 2407686e13
commit 8f957d919f
2 changed files with 246 additions and 46 deletions
+89
View File
@@ -643,6 +643,95 @@ mod tests {
assert_eq!(merged.progress.weekly_goal_progress.get("weekly_5_wins"), Some(&1));
}
// -----------------------------------------------------------------------
// ConflictReport field population
// -----------------------------------------------------------------------
#[test]
fn conflict_report_win_streak_current_contains_correct_field_and_values() {
// Verify that the ConflictReport for win_streak_current carries the exact
// field name and the string representations of the diverging values.
let mut local = default_payload();
local.stats.win_streak_current = 7;
let mut remote = default_payload();
remote.stats.win_streak_current = 2;
let (_, conflicts) = merge(&local, &remote);
let report = conflicts
.iter()
.find(|c| c.field == "win_streak_current")
.expect("ConflictReport for win_streak_current must be present");
assert_eq!(
report.local_value, "7",
"local_value in ConflictReport must be the local streak as a string"
);
assert_eq!(
report.remote_value, "2",
"remote_value in ConflictReport must be the remote streak as a string"
);
}
#[test]
fn conflict_report_daily_challenge_streak_contains_correct_field_and_values() {
// daily_challenge_streak divergence must also produce a ConflictReport with
// the correct field name and human-readable values.
let mut local = default_payload();
local.progress.daily_challenge_streak = 10;
let mut remote = default_payload();
remote.progress.daily_challenge_streak = 4;
let (merged, conflicts) = merge(&local, &remote);
let report = conflicts
.iter()
.find(|c| c.field == "daily_challenge_streak")
.expect("ConflictReport for daily_challenge_streak must be present");
assert_eq!(
report.local_value, "10",
"local_value must equal the local streak string"
);
assert_eq!(
report.remote_value, "4",
"remote_value must equal the remote streak string"
);
// Best-effort resolution: the higher value is retained.
assert_eq!(
merged.progress.daily_challenge_streak, 10,
"merged streak must take the higher value"
);
}
#[test]
fn no_conflict_report_when_win_streak_current_is_equal() {
// Identical win_streak_current must not generate any ConflictReport.
let mut local = default_payload();
local.stats.win_streak_current = 5;
let mut remote = default_payload();
remote.stats.win_streak_current = 5;
let (_, conflicts) = merge(&local, &remote);
assert!(
!conflicts.iter().any(|c| c.field == "win_streak_current"),
"equal streaks must produce no conflict"
);
}
#[test]
fn no_conflict_report_when_daily_challenge_streak_is_equal() {
// Identical daily_challenge_streak must not generate any ConflictReport.
let mut local = default_payload();
local.progress.daily_challenge_streak = 3;
let mut remote = default_payload();
remote.progress.daily_challenge_streak = 3;
let (_, conflicts) = merge(&local, &remote);
assert!(
!conflicts.iter().any(|c| c.field == "daily_challenge_streak"),
"equal daily challenge streaks must produce no conflict"
);
}
#[test]
fn fastest_win_both_max_sentinel_stays_max() {
// Both sides have u64::MAX (no wins recorded on either) — result must remain MAX,