Merge branch 'phase/59-dump-nodes'

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-20 13:08:19 +03:00
co-authored by Cursor
3 changed files with 44 additions and 13 deletions
@@ -89,16 +89,17 @@ public class ServiceabilityTests(AppHostFixture fixture)
using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Дамп", TuesdayMorning);
// Create starts the clock. Presence still goes out while paused (often empty); waiting
// for people then hangs. Hire, keep time moving, then dump.
Assert.True(school.Running);
await HireMathAsync(client, school.Id);
var dump = await client.GetFromJsonAsync<SchoolDumpResponse>(
$"/api/dev/schools/{school.Id}/dump",
TestContext.Current.CancellationToken);
var dump = await WaitUntilSomeoneIsOnANodeAsync(client, school.Id);
Assert.NotNull(dump);
Assert.Equal(school.Id, dump.Id);
Assert.True(dump.Running);
Assert.NotEmpty(dump.People);
Assert.Contains(dump.People, person => person.NodeId is not null || person.Needs.Count > 0);
Assert.Contains(dump.People, person => !string.IsNullOrWhiteSpace(person.NodeId));
Assert.All(dump.People, person =>
{
Assert.False(string.IsNullOrWhiteSpace(person.Id));
@@ -119,6 +120,36 @@ public class ServiceabilityTests(AppHostFixture fixture)
Assert.Equal("unknown-school", await ProblemCodeAsync(response));
}
/// <summary>
/// Polls the dump while the clock is moving. A paused school would keep answering with empty
/// occupancy; fail immediately instead of waiting that out.
/// </summary>
private static async Task<SchoolDumpResponse> WaitUntilSomeoneIsOnANodeAsync(HttpClient client, int schoolId)
{
SchoolDumpResponse? dump = null;
for (var attempt = 0; attempt < 120; attempt++)
{
dump = await client.GetFromJsonAsync<SchoolDumpResponse>(
$"/api/dev/schools/{schoolId}/dump",
TestContext.Current.CancellationToken);
Assert.NotNull(dump);
if (!dump.Running)
{
Assert.Fail("Clock is paused; occupancy stays empty and this wait would hang.");
}
if (dump.People.Any(person => !string.IsNullOrWhiteSpace(person.NodeId)))
{
return dump;
}
await Task.Delay(250, TestContext.Current.CancellationToken);
}
throw new InvalidOperationException(
$"Nobody had a NodeId after hire with the clock running at {dump?.GameTime:HH:mm}.");
}
private static async Task HireMathAsync(HttpClient client, int schoolId)
{
var staffing = await client.GetFromJsonAsync<StaffingResponse>(