fix(arena-tk2): park returns 3002 RETRY + empty node_server_url
Two client-crash bugs in the do_matching response when no partner is waiting: 1. matching_state was 3001 (RC_BATTLE_MATCHING_ILLEGAL); the client's Matching.OnFinishedDoMatching switch maps that to an error dialog, not a retry. The retry state is 3002 (RC_BATTLE_MATCHING_RETRY). 2. node_server_url was omitted entirely. The client's DoMatchingBase.SettingDoMatchingData reads it via data["node_server_url"].ToString() with no Keys.Contains guard, so absence throws KeyNotFoundException out of NetworkManager.Connect before the matching_state switch is even reached. Prod RETRY captures send "" while waiting and the real URL only on SUCCEEDED; match that. battle_id stays absent; its accessor IS guarded. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -62,10 +62,16 @@ public class ArenaTwoPickBattleController : SVSimController
|
|||||||
ct);
|
ct);
|
||||||
if (paired is null)
|
if (paired is null)
|
||||||
{
|
{
|
||||||
|
// 3002 = RC_BATTLE_MATCHING_RETRY: client polls again. 3001 is ILLEGAL
|
||||||
|
// and shows an error dialog on the client side. node_server_url must be
|
||||||
|
// present (the client's DoMatchingBase.SettingDoMatchingData calls
|
||||||
|
// .ToString() on it without a Keys.Contains guard); prod sends "" while
|
||||||
|
// waiting and the real URL only on SUCCEEDED. battle_id stays absent
|
||||||
|
// (its accessor IS guarded).
|
||||||
return Ok(new DoMatchingResponseDto
|
return Ok(new DoMatchingResponseDto
|
||||||
{
|
{
|
||||||
MatchingState = 3001,
|
MatchingState = 3002,
|
||||||
// BattleId / NodeServerUrl null — client polls again.
|
NodeServerUrl = "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class ArenaTwoPickBattleControllerTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task DoMatching_solo_poller_returns_3001_RETRY_with_no_BattleId()
|
public async Task DoMatching_solo_poller_returns_3002_RETRY_with_no_BattleId_but_empty_NodeServerUrl()
|
||||||
{
|
{
|
||||||
using var factory = new SVSimTestFactory();
|
using var factory = new SVSimTestFactory();
|
||||||
var vid = await factory.SeedViewerAsync();
|
var vid = await factory.SeedViewerAsync();
|
||||||
@@ -58,11 +58,17 @@ public class ArenaTwoPickBattleControllerTests
|
|||||||
using var doc = JsonDocument.Parse(body);
|
using var doc = JsonDocument.Parse(body);
|
||||||
var root = doc.RootElement;
|
var root = doc.RootElement;
|
||||||
|
|
||||||
Assert.That(root.GetProperty("matching_state").GetInt32(), Is.EqualTo(3001));
|
// 3002 = RC_BATTLE_MATCHING_RETRY (client polls again). 3001 is ILLEGAL and
|
||||||
// BattleId should be ABSENT from the JSON (WhenWritingNull) — TryGetProperty
|
// pops an error dialog on the client.
|
||||||
// returns false when the key isn't present.
|
Assert.That(root.GetProperty("matching_state").GetInt32(), Is.EqualTo(3002));
|
||||||
|
// battle_id must be ABSENT from the JSON; the client's accessor IS guarded with
|
||||||
|
// Keys.Contains so absence is the safe shape (matches prod RETRY captures).
|
||||||
Assert.That(root.TryGetProperty("battle_id", out _), Is.False,
|
Assert.That(root.TryGetProperty("battle_id", out _), Is.False,
|
||||||
"battle_id must be absent from the wire when matching_state==3001 RETRY.");
|
"battle_id must be absent from the wire when matching_state==3002 RETRY.");
|
||||||
|
// node_server_url MUST be present (empty string while waiting, the real URL on
|
||||||
|
// SUCCEEDED). Client's DoMatchingBase.SettingDoMatchingData calls .ToString() on
|
||||||
|
// it without a Keys.Contains guard, so absence throws KeyNotFoundException.
|
||||||
|
Assert.That(root.GetProperty("node_server_url").GetString(), Is.EqualTo(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -107,8 +113,8 @@ public class ArenaTwoPickBattleControllerTests
|
|||||||
// A polls first (parks).
|
// A polls first (parks).
|
||||||
var respA1 = await clientA.PostAsync("/arena_two_pick_battle/do_matching", JsonContent.Create(req));
|
var respA1 = await clientA.PostAsync("/arena_two_pick_battle/do_matching", JsonContent.Create(req));
|
||||||
using var docA1 = JsonDocument.Parse(await respA1.Content.ReadAsStringAsync());
|
using var docA1 = JsonDocument.Parse(await respA1.Content.ReadAsStringAsync());
|
||||||
Assert.That(docA1.RootElement.GetProperty("matching_state").GetInt32(), Is.EqualTo(3001),
|
Assert.That(docA1.RootElement.GetProperty("matching_state").GetInt32(), Is.EqualTo(3002),
|
||||||
"A's first poll parks.");
|
"A's first poll parks (3002 = RETRY).");
|
||||||
|
|
||||||
// B polls (pairs).
|
// B polls (pairs).
|
||||||
var respB = await clientB.PostAsync("/arena_two_pick_battle/do_matching", JsonContent.Create(req));
|
var respB = await clientB.PostAsync("/arena_two_pick_battle/do_matching", JsonContent.Create(req));
|
||||||
|
|||||||
Reference in New Issue
Block a user