Pause the school on warning notices until the owner dismisses them.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -121,6 +121,177 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
Assert.Null(replayed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PausingNotice_SetsRunningFalse_AndIgnoresSetRunningTrue()
|
||||
{
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Пауза варнингом", StartDate);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
await ReceiveClockWhereAsync(socket, clock => clock.Running);
|
||||
|
||||
var posted = await PostDevNoticeAsync(client, school.Id, "GenerationFailed");
|
||||
var notice = ProtocolCodec.ReadNotice(
|
||||
await ReceiveUntilAsync(socket, MessageType.ServerNotice, TimeSpan.FromSeconds(10)));
|
||||
|
||||
Assert.Equal(posted.Id, notice.Id);
|
||||
Assert.Equal("GenerationFailed", notice.DefName);
|
||||
Assert.True(notice.Pause);
|
||||
Assert.Equal(NoticeSeverity.Error, notice.Severity);
|
||||
|
||||
var paused = await ReceiveClockWhereAsync(socket, clock => !clock.Running);
|
||||
await SendAsync(socket, buffer =>
|
||||
ProtocolCodec.WriteSetRunning(buffer, new ClientSetRunningMessage(Running: true)));
|
||||
var later = await ReceiveClockAfterAsync(socket, TimeSpan.FromSeconds(1));
|
||||
|
||||
Assert.False(later.Running);
|
||||
Assert.Equal(paused.GameTimeUnixMs, later.GameTimeUnixMs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DismissLastPausing_AllowsSetRunningTrue_DoesNotAutoPlay()
|
||||
{
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Dismiss не Play", StartDate);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
await ReceiveClockWhereAsync(socket, clock => clock.Running);
|
||||
|
||||
var posted = await PostDevNoticeAsync(client, school.Id, "GenerationFailed");
|
||||
await ReceiveUntilAsync(socket, MessageType.ServerNotice, TimeSpan.FromSeconds(10));
|
||||
var paused = await ReceiveClockWhereAsync(socket, clock => !clock.Running);
|
||||
|
||||
await SendAsync(socket, buffer =>
|
||||
ProtocolCodec.WriteDismissNotice(buffer, new ClientDismissNoticeMessage(posted.Id)));
|
||||
var stillPaused = await ReceiveClockAfterAsync(socket, TimeSpan.FromSeconds(1));
|
||||
Assert.False(stillPaused.Running);
|
||||
Assert.Equal(paused.GameTimeUnixMs, stillPaused.GameTimeUnixMs);
|
||||
|
||||
await SendAsync(socket, buffer =>
|
||||
ProtocolCodec.WriteSetRunning(buffer, new ClientSetRunningMessage(Running: true)));
|
||||
var resumed = await ReceiveClockWhereAsync(socket, clock => clock.Running);
|
||||
Assert.True(resumed.Running);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveLoad_KeepsSticky_DropsInfo()
|
||||
{
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Sticky сейв", StartDate);
|
||||
|
||||
using (var socket = await OpenSchoolAsync(school.Id, client))
|
||||
{
|
||||
await ReceiveClockAsync(socket);
|
||||
await PostDevNoticeAsync(client, school.Id, "DayStarted");
|
||||
var info = ProtocolCodec.ReadNotice(
|
||||
await ReceiveUntilAsync(socket, MessageType.ServerNotice, TimeSpan.FromSeconds(10)));
|
||||
Assert.Equal("DayStarted", info.DefName);
|
||||
Assert.False(info.Pause);
|
||||
|
||||
await PostDevNoticeAsync(client, school.Id, "GenerationFailed");
|
||||
var sticky = ProtocolCodec.ReadNotice(
|
||||
await ReceiveUntilAsync(socket, MessageType.ServerNotice, TimeSpan.FromSeconds(10)));
|
||||
Assert.Equal("GenerationFailed", sticky.DefName);
|
||||
await ReceiveClockWhereAsync(socket, clock => !clock.Running);
|
||||
}
|
||||
|
||||
using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken);
|
||||
reload.EnsureSuccessStatusCode();
|
||||
|
||||
var restored = await FindAsync(client, school.Id);
|
||||
Assert.False(restored.Running);
|
||||
|
||||
using var reopened = await OpenSchoolAsync(school.Id, client);
|
||||
var replayed = ProtocolCodec.ReadNotice(
|
||||
await ReceiveUntilAsync(reopened, MessageType.ServerNotice, TimeSpan.FromSeconds(10)));
|
||||
Assert.Equal("GenerationFailed", replayed.DefName);
|
||||
Assert.True(replayed.Pause);
|
||||
Assert.False((await ReceiveClockAsync(reopened)).Running);
|
||||
|
||||
var extra = await TryReceiveNoticeAsync(reopened, TimeSpan.FromSeconds(2));
|
||||
Assert.True(extra is not { DefName: "DayStarted" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Guest_Dismiss_DoesNotClearPause_OwnerDoes()
|
||||
{
|
||||
using var ownerClient = await SchoolApiTests.CreateIsolatedClientAsync(fixture.App, "NoticePauseOwner");
|
||||
await SchoolApiTests.WipeAllSavesAsync(ownerClient);
|
||||
var school = await SchoolApiTests.CreateAsync(ownerClient, "Гость не закрывает", StartDate);
|
||||
|
||||
using var ownerSocket = await OpenSchoolAsync(school.Id, ownerClient);
|
||||
await ReceiveClockWhereAsync(ownerSocket, clock => clock.Running);
|
||||
var posted = await PostDevNoticeAsync(ownerClient, school.Id, "GenerationFailed");
|
||||
await ReceiveUntilAsync(ownerSocket, MessageType.ServerNotice, TimeSpan.FromSeconds(10));
|
||||
await ReceiveClockWhereAsync(ownerSocket, clock => !clock.Running);
|
||||
|
||||
using var guestHttp = await SchoolApiTests.CreateIsolatedClientAsync(fixture.App, "NoticePauseGuest");
|
||||
using var forbidden = await guestHttp.PostAsync(
|
||||
$"/api/schools/{school.Id}/notices/{posted.Id}/dismiss",
|
||||
content: null,
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal(System.Net.HttpStatusCode.Forbidden, forbidden.StatusCode);
|
||||
Assert.Equal("not-owner", await SchoolApiTests.ProblemCodeAsync(forbidden));
|
||||
|
||||
var guestCookie = guestHttp.DefaultRequestHeaders.TryGetValues("Cookie", out var guestCookies)
|
||||
? guestCookies.First()
|
||||
: await SchoolApiTests.WebSocketCookieAsync(guestHttp);
|
||||
using var guestSocket = await ConnectWithCookieAsync(guestCookie);
|
||||
await ReceiveUntilAsync(guestSocket, MessageType.ServerWelcome);
|
||||
await SendAsync(guestSocket, buffer =>
|
||||
ProtocolCodec.WriteOpenSchool(buffer, new ClientOpenSchoolMessage(school.Id)));
|
||||
var guestNotice = ProtocolCodec.ReadNotice(
|
||||
await ReceiveUntilAsync(guestSocket, MessageType.ServerNotice, TimeSpan.FromSeconds(10)));
|
||||
Assert.Equal(posted.Id, guestNotice.Id);
|
||||
|
||||
await SendAsync(guestSocket, buffer =>
|
||||
ProtocolCodec.WriteDismissNotice(buffer, new ClientDismissNoticeMessage(posted.Id)));
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken);
|
||||
Assert.False((await FindAsync(ownerClient, school.Id)).Running);
|
||||
|
||||
await SendAsync(ownerSocket, buffer =>
|
||||
ProtocolCodec.WriteDismissNotice(buffer, new ClientDismissNoticeMessage(posted.Id)));
|
||||
await SendAsync(ownerSocket, buffer =>
|
||||
ProtocolCodec.WriteSetRunning(buffer, new ClientSetRunningMessage(Running: true)));
|
||||
var resumed = await ReceiveClockWhereAsync(ownerSocket, clock => clock.Running);
|
||||
Assert.True(resumed.Running);
|
||||
|
||||
using var delete = await ownerClient.DeleteAsync($"/api/schools/{school.Id}", TestContext.Current.CancellationToken);
|
||||
delete.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpenAfterReload_ReplaysSticky_ClockPaused()
|
||||
{
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "F5 sticky", StartDate);
|
||||
|
||||
using (var socket = await OpenSchoolAsync(school.Id, client))
|
||||
{
|
||||
await ReceiveClockAsync(socket);
|
||||
await PostDevNoticeAsync(client, school.Id, "GenerationFailed");
|
||||
await ReceiveUntilAsync(socket, MessageType.ServerNotice, TimeSpan.FromSeconds(10));
|
||||
await ReceiveClockWhereAsync(socket, clock => !clock.Running);
|
||||
}
|
||||
|
||||
using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken);
|
||||
reload.EnsureSuccessStatusCode();
|
||||
|
||||
using var reopened = await OpenSchoolAsync(school.Id, client);
|
||||
var notice = ProtocolCodec.ReadNotice(
|
||||
await ReceiveUntilAsync(reopened, MessageType.ServerNotice, TimeSpan.FromSeconds(10)));
|
||||
var clock = await ReceiveClockWhereAsync(reopened, frame => !frame.Running);
|
||||
|
||||
Assert.Equal("GenerationFailed", notice.DefName);
|
||||
Assert.True(notice.Pause);
|
||||
Assert.False(clock.Running);
|
||||
Assert.False((await FindAsync(client, school.Id)).Running);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpeningASchool_SendsAMapSnapshot()
|
||||
{
|
||||
@@ -539,6 +710,18 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
private static DateTime ToDate(ServerClockMessage clock) =>
|
||||
DateTimeOffset.FromUnixTimeMilliseconds(clock.GameTimeUnixMs).UtcDateTime;
|
||||
|
||||
private static async Task<DevNoticeResponse> PostDevNoticeAsync(HttpClient client, int schoolId, string defName)
|
||||
{
|
||||
using var response = await client.PostAsJsonAsync(
|
||||
$"/api/dev/schools/{schoolId}/notices",
|
||||
new { defName, personId = 0u },
|
||||
TestContext.Current.CancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var body = await response.Content.ReadFromJsonAsync<DevNoticeResponse>(TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(body);
|
||||
return body;
|
||||
}
|
||||
|
||||
private async Task<SchoolApiTests.SchoolResponse> FindAsync(HttpClient client, int schoolId)
|
||||
{
|
||||
var state = await SchoolApiTests.GetSchoolsAsync(client);
|
||||
@@ -784,4 +967,6 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
private sealed record DirectorySnapshot(IReadOnlyList<DirectoryPersonSnapshot> People);
|
||||
|
||||
private sealed record DirectoryPersonSnapshot(string Id, string FullName);
|
||||
|
||||
private sealed record DevNoticeResponse(uint Id, string DefName, bool Pause);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user