fix(arena): match prod get_challenge_info wire shape; stub ranking_history

Prod /arena/get_challenge_info capture (Season 26):
- reward_step_info.reward_step_list is a Dict<string,string>
  ({"5":"5","10":"10","15":"15"}), not the List<int> I'd assumed
- max_reward_step is stringified

The previous stub would have parsed at the client (LitJson tolerates the
shape via indexed iteration), but cleaning to match prod exactly.

Also stubs /arena/get_challenge_ranking_history (new endpoint observed
in the same capture). Prod ships {two_pick: [], sealed: []} with no
history populated — empty lists match. Routing smoke added.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-05-31 13:21:44 -04:00
parent 1af56b4ec4
commit ac2f31103d
4 changed files with 47 additions and 4 deletions

View File

@@ -43,6 +43,14 @@ public class ArenaController : SVSimController
catch { /* fall back to defaults */ } catch { /* fall back to defaults */ }
} }
// Default Challenge Master reward steps from prod capture: 3 milestones at 5/10/15 wins.
var rewardSteps = new Dictionary<string, string>
{
["5"] = "5",
["10"] = "10",
["15"] = "15",
};
return Ok(new GetChallengeInfoResponseDto return Ok(new GetChallengeInfoResponseDto
{ {
ChallengeName = name, ChallengeName = name,
@@ -51,9 +59,17 @@ public class ArenaController : SVSimController
TwoPickAllWinCount = 0, TwoPickAllWinCount = 0,
RewardStepInfo = new RewardStepInfoDto RewardStepInfo = new RewardStepInfoDto
{ {
MaxRewardStep = 0, MaxRewardStep = 15,
RewardStepList = new List<int>(), RewardStepList = rewardSteps,
}, },
}); });
} }
[HttpPost("get_challenge_ranking_history")]
public IActionResult GetChallengeRankingHistory([FromBody] GetChallengeInfoRequest req)
{
if (!TryGetViewerId(out _)) return Unauthorized();
// Prod returns {two_pick: [], sealed: []}. Stub matches.
return Ok(new GetChallengeRankingHistoryResponseDto());
}
} }

View File

@@ -1,5 +1,6 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using MessagePack; using MessagePack;
using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Arena; namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Arena;
@@ -32,9 +33,15 @@ public class GetChallengeInfoResponseDto
[MessagePackObject] [MessagePackObject]
public class RewardStepInfoDto public class RewardStepInfoDto
{ {
[JsonPropertyName("max_reward_step")] [Key("max_reward_step")] [JsonPropertyName("max_reward_step")] [JsonConverter(typeof(StringifiedIntConverter))] [Key("max_reward_step")]
public int MaxRewardStep { get; set; } public int MaxRewardStep { get; set; }
/// <summary>
/// Wire shape: dict keyed by stringified-int win count → stringified-int reward step
/// (prod capture: <c>{"5":"5","10":"10","15":"15"}</c>). Client parser at
/// ChallangeHistoryInfoTask.cs:43 iterates by Count + indexed value access, which works
/// for both arrays and LitJson object-iteration order — but prod always ships the dict.
/// </summary>
[JsonPropertyName("reward_step_list")] [Key("reward_step_list")] [JsonPropertyName("reward_step_list")] [Key("reward_step_list")]
public List<int> RewardStepList { get; set; } = new(); public Dictionary<string, string> RewardStepList { get; set; } = new();
} }

View File

@@ -0,0 +1,19 @@
using System.Text.Json.Serialization;
using MessagePack;
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Arena;
/// <summary>
/// Wire shape for /arena/get_challenge_ranking_history. Prod returns two empty lists
/// (two_pick + sealed) per the season-26 capture. Populated history is per-viewer + per-season
/// ranking snapshots; not tracked locally yet.
/// </summary>
[MessagePackObject]
public class GetChallengeRankingHistoryResponseDto
{
[JsonPropertyName("two_pick")] [Key("two_pick")]
public List<object> TwoPick { get; set; } = new();
[JsonPropertyName("sealed")] [Key("sealed")]
public List<object> Sealed { get; set; } = new();
}

View File

@@ -110,6 +110,7 @@ public class RoutingSmokeTests
[TestCase("/arena_two_pick_battle/finish")] [TestCase("/arena_two_pick_battle/finish")]
[TestCase("/arena_colosseum/get_fee_info")] [TestCase("/arena_colosseum/get_fee_info")]
[TestCase("/arena/get_challenge_info")] [TestCase("/arena/get_challenge_info")]
[TestCase("/arena/get_challenge_ranking_history")]
public async Task Authenticated_route_resolves(string path) public async Task Authenticated_route_resolves(string path)
{ {
using var factory = new TestFactory(); using var factory = new TestFactory();