Complete phase 39 school owners with owner-scoped API, menu, and tests.
Drop golden save load tests in favor of incompatible-save behavior; dress legacy people files on reload; fix AppHost isolation for multi-user socket and save cleanup. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -28,11 +28,11 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task ChangingSpeed_DoesNotResumeAPausedSchool()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Пауза и скорость", StartDate);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id);
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
await ReceiveClockAsync(socket);
|
||||
|
||||
await SendAsync(socket, buffer =>
|
||||
@@ -52,7 +52,7 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task NoSchoolOpen_MeansNoClockFramesButTheCalendarKeepsRunning()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Живёт сама", StartDate);
|
||||
|
||||
@@ -72,11 +72,11 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task OpeningASchool_StreamsItsClock()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Ход времени", StartDate);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id);
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
var first = await ReceiveClockAsync(socket);
|
||||
|
||||
Assert.Equal(school.Id, first.SchoolId);
|
||||
@@ -93,11 +93,11 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task OpeningASchool_SendsAMapSnapshot()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Снимок карты", StartDate);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id);
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
var snapshot = ProtocolCodec.ReadMapSnapshot(await ReceiveUntilAsync(socket, MessageType.ServerMapSnapshot));
|
||||
|
||||
Assert.Equal(school.Id, snapshot.SchoolId);
|
||||
@@ -116,11 +116,11 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task OpeningASchool_LabelsTheSnapshotInTheHelloLocale()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "English snapshot", StartDate);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id, ProtocolConstants.LocaleEnglish);
|
||||
using var socket = await OpenSchoolAsync(school.Id, client, ProtocolConstants.LocaleEnglish);
|
||||
var snapshot = ProtocolCodec.ReadMapSnapshot(await ReceiveUntilAsync(socket, MessageType.ServerMapSnapshot));
|
||||
|
||||
Assert.Equal("Principal's office", Assert.Single(snapshot.Nodes, node => node.Id == "principals-office").Name);
|
||||
@@ -129,11 +129,11 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task OpeningASchool_WithACustomMap_ReturnsThatLayout()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateWithMapAsync(client, "Упрощённая", StartDate, SchoolApiTests.SimpleCustomMap);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id);
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
var snapshot = ProtocolCodec.ReadMapSnapshot(await ReceiveUntilAsync(socket, MessageType.ServerMapSnapshot));
|
||||
|
||||
Assert.DoesNotContain(snapshot.Nodes, node => node.Id == "corridor-1");
|
||||
@@ -144,14 +144,14 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task ReloadFromDisk_RestoresACustomMap()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateWithMapAsync(client, "Карта с диска", StartDate, SchoolApiTests.SimpleCustomMap);
|
||||
|
||||
using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken);
|
||||
reload.EnsureSuccessStatusCode();
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id);
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
var snapshot = ProtocolCodec.ReadMapSnapshot(await ReceiveUntilAsync(socket, MessageType.ServerMapSnapshot));
|
||||
|
||||
Assert.Contains(snapshot.Nodes, node => node.Id == "office");
|
||||
@@ -161,12 +161,12 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task OpeningASchoolDuringAMathLesson_PutsOccupancyOnPresenceNotTheSnapshot()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var start = new DateTime(2012, 4, 3, 10, 20, 0, DateTimeKind.Utc);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Кто где сейчас", start);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id);
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
var snapshot = ProtocolCodec.ReadMapSnapshot(await ReceiveUntilAsync(socket, MessageType.ServerMapSnapshot));
|
||||
Assert.Contains(snapshot.Nodes, node => node.Id == "classroom-101");
|
||||
|
||||
@@ -224,12 +224,12 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task SkipEmpty_DuringWorkHours_IsIgnored()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var start = new DateTime(2012, 4, 3, 10, 20, 0, DateTimeKind.Utc);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Пропуск в учебное время", start);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id);
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
await SendAsync(socket, buffer =>
|
||||
ProtocolCodec.WriteSetRunning(buffer, new ClientSetRunningMessage(Running: false)));
|
||||
var paused = await ReceiveClockWhereAsync(socket, clock => !clock.Running);
|
||||
@@ -247,11 +247,11 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task Pausing_FreezesTheClock()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Пауза", StartDate);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id);
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
await ReceiveClockAsync(socket);
|
||||
|
||||
await SendAsync(socket, buffer =>
|
||||
@@ -267,11 +267,11 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task SpeedIndex_ChangesHowFastTheCalendarMoves()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Быстрая", StartDate);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id);
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
await ReceiveClockAsync(socket);
|
||||
|
||||
await SendAsync(socket, buffer =>
|
||||
@@ -288,11 +288,11 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task LeavingASchool_KeepsItsClockRunning()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Выход", StartDate);
|
||||
|
||||
using (var socket = await OpenSchoolAsync(school.Id))
|
||||
using (var socket = await OpenSchoolAsync(school.Id, client))
|
||||
{
|
||||
await ReceiveClockAsync(socket);
|
||||
await SendAsync(socket, buffer => ProtocolCodec.WriteCloseSchool(buffer));
|
||||
@@ -309,11 +309,11 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task APausedSchool_StaysPausedAfterLeaving()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Оставлена на паузе", StartDate);
|
||||
|
||||
using (var socket = await OpenSchoolAsync(school.Id))
|
||||
using (var socket = await OpenSchoolAsync(school.Id, client))
|
||||
{
|
||||
await ReceiveClockAsync(socket);
|
||||
await SendAsync(socket, buffer =>
|
||||
@@ -333,11 +333,11 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task DeletingTheOpenSchool_TellsTheClient()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Исчезнет", StartDate);
|
||||
|
||||
using var socket = await OpenSchoolAsync(school.Id);
|
||||
using var socket = await OpenSchoolAsync(school.Id, client);
|
||||
await ReceiveClockAsync(socket);
|
||||
|
||||
using var deleted = await client.DeleteAsync($"/api/schools/{school.Id}", TestContext.Current.CancellationToken);
|
||||
@@ -365,12 +365,12 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task PausingOneSchool_DoesNotStopAnother()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var paused = await SchoolApiTests.CreateAsync(client, "На паузе", StartDate);
|
||||
var running = await SchoolApiTests.CreateAsync(client, "Идёт дальше", StartDate);
|
||||
|
||||
using var socket = await OpenSchoolAsync(paused.Id);
|
||||
using var socket = await OpenSchoolAsync(paused.Id, client);
|
||||
await ReceiveClockAsync(socket);
|
||||
await SendAsync(socket, buffer =>
|
||||
ProtocolCodec.WriteSetRunning(buffer, new ClientSetRunningMessage(Running: false)));
|
||||
@@ -423,11 +423,11 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
[Fact]
|
||||
public async Task ReloadFromDisk_RestoresAPausedClock()
|
||||
{
|
||||
using var client = fixture.App.CreateHttpClient("server");
|
||||
using var client = await CreateOwnerHttpClientAsync();
|
||||
await SchoolApiTests.ResetAsync(client);
|
||||
var school = await SchoolApiTests.CreateAsync(client, "Снимок паузы", StartDate);
|
||||
|
||||
using (var socket = await OpenSchoolAsync(school.Id))
|
||||
using (var socket = await OpenSchoolAsync(school.Id, client))
|
||||
{
|
||||
await ReceiveClockAsync(socket);
|
||||
await SendAsync(socket, buffer =>
|
||||
@@ -512,21 +512,57 @@ public class GameSocketTests(AppHostFixture fixture)
|
||||
{
|
||||
var state = await SchoolApiTests.GetSchoolsAsync(client);
|
||||
var school = state.Schools.SingleOrDefault(candidate => candidate.Id == schoolId);
|
||||
if (school is not null)
|
||||
{
|
||||
return school;
|
||||
}
|
||||
|
||||
Assert.NotNull(school);
|
||||
return school;
|
||||
var other = state.Others.SingleOrDefault(candidate => candidate.Id == schoolId);
|
||||
Assert.NotNull(other);
|
||||
return new SchoolApiTests.SchoolResponse(
|
||||
other.Id,
|
||||
other.Name,
|
||||
other.GameTime,
|
||||
other.Running,
|
||||
other.SpeedIndex,
|
||||
other.Seed,
|
||||
Mine: false,
|
||||
other.ModIds);
|
||||
}
|
||||
|
||||
private async Task<ClientWebSocket> OpenSchoolAsync(int schoolId, byte locale = ProtocolConstants.LocaleRussian)
|
||||
private async Task<HttpClient> CreateOwnerHttpClientAsync()
|
||||
{
|
||||
using var httpClient = fixture.App.CreateHttpClient("server");
|
||||
var socket = await ConnectAsync(httpClient, locale);
|
||||
using var template = fixture.App.CreateHttpClient("server");
|
||||
return await SchoolApiTests.CreateIsolatedClientAsync(template, SchoolApiTests.TestUserName);
|
||||
}
|
||||
|
||||
private async Task<ClientWebSocket> OpenSchoolAsync(
|
||||
int schoolId,
|
||||
HttpClient ownerClient,
|
||||
byte locale = ProtocolConstants.LocaleRussian)
|
||||
{
|
||||
var cookie = ownerClient.DefaultRequestHeaders.TryGetValues("Cookie", out var values)
|
||||
? values.First()
|
||||
: await SchoolApiTests.WebSocketCookieAsync(ownerClient);
|
||||
var socket = await ConnectWithCookieAsync(cookie, locale);
|
||||
await ReceiveUntilAsync(socket, MessageType.ServerWelcome);
|
||||
await SendAsync(socket, buffer =>
|
||||
ProtocolCodec.WriteOpenSchool(buffer, new ClientOpenSchoolMessage(schoolId)));
|
||||
return socket;
|
||||
}
|
||||
|
||||
private async Task<ClientWebSocket> ConnectWithCookieAsync(string cookie, byte locale = ProtocolConstants.LocaleRussian)
|
||||
{
|
||||
var socket = new ClientWebSocket();
|
||||
socket.Options.SetRequestHeader("Cookie", cookie);
|
||||
var http = fixture.App.GetEndpoint("server", "http");
|
||||
var uri = new UriBuilder(http) { Scheme = "ws", Path = "/ws/game" }.Uri;
|
||||
await socket.ConnectAsync(uri, TestContext.Current.CancellationToken).WaitAsync(DefaultTimeout);
|
||||
await SendAsync(socket, buffer =>
|
||||
ProtocolCodec.WriteHello(buffer, new ClientHelloMessage(ProtocolConstants.Version, locale)));
|
||||
return socket;
|
||||
}
|
||||
|
||||
private async Task<ClientWebSocket> ConnectAsync(byte locale = ProtocolConstants.LocaleRussian)
|
||||
{
|
||||
using var httpClient = fixture.App.CreateHttpClient("server");
|
||||
|
||||
Reference in New Issue
Block a user