Phase 39 dropped GoldenSaveTests when format 2 saves became unstartable. Recheck restores the missing-field and seed-equals-id assertions against a stamped copy, and records the recheck. Co-authored-by: Cursor <cursoragent@cursor.com>
171 lines
6.6 KiB
C#
171 lines
6.6 KiB
C#
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace HSchool.AppHost.Tests;
|
|
|
|
/// <summary>
|
|
/// Phase 25 goldens. The folders stay historic (format 2, no country): that shape must not start.
|
|
/// Load tests stamp today's format, country and owner onto a copy so missing-field defaults and
|
|
/// seed-equals-id still have a host assertion after later slices raised <c>CanStart</c>.
|
|
/// </summary>
|
|
[Collection(AppHostCollection.Name)]
|
|
public class GoldenSaveTests(AppHostFixture fixture)
|
|
{
|
|
[Fact]
|
|
public async Task HistoricFormat2Golden_LeavesTheSchoolUnstartedAndTheFileUntouched()
|
|
{
|
|
using var client = fixture.App.CreateHttpClient("server");
|
|
await SchoolApiTests.ResetAsync(client);
|
|
var directory = await SchoolApiTests.SavesDirectoryAsync(client);
|
|
|
|
try
|
|
{
|
|
InstallFolder(directory, "current");
|
|
var path = Path.Combine(directory, "1.json");
|
|
var before = File.ReadAllBytes(path);
|
|
|
|
using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken);
|
|
reload.EnsureSuccessStatusCode();
|
|
|
|
var state = await SchoolApiTests.GetSchoolsAsync(client);
|
|
Assert.Empty(state.Schools);
|
|
var broken = Assert.Single(state.Others);
|
|
Assert.Equal(1, broken.Id);
|
|
Assert.Equal("Золотая", broken.Name);
|
|
Assert.Equal(1, broken.Seed);
|
|
|
|
using var people = await client.GetAsync(
|
|
$"/api/schools/{broken.Id}/people?pageSize=1",
|
|
TestContext.Current.CancellationToken);
|
|
Assert.Equal(HttpStatusCode.NotFound, people.StatusCode);
|
|
|
|
Assert.Equal(before, File.ReadAllBytes(path));
|
|
}
|
|
finally
|
|
{
|
|
DeleteSchoolFiles(directory, 1);
|
|
using var cleanup = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken);
|
|
cleanup.EnsureSuccessStatusCode();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SaveWithoutNativeLanguage_LoadsWithoutReshuffling()
|
|
{
|
|
using var client = fixture.App.CreateHttpClient("server");
|
|
await SchoolApiTests.ResetAsync(client);
|
|
var directory = await SchoolApiTests.SavesDirectoryAsync(client);
|
|
|
|
try
|
|
{
|
|
await InstallRunnableAsync(client, directory, "legacy-no-native", includeNative: false);
|
|
|
|
var state = await SchoolApiTests.GetSchoolsAsync(client);
|
|
var school = Assert.Single(state.Schools);
|
|
Assert.Equal(1, school.Id);
|
|
Assert.Equal(1, school.Seed);
|
|
Assert.Equal("Золотая", school.Name);
|
|
|
|
var people = await PeopleAsync(client, school.Id);
|
|
Assert.Equal(ExpectedNames(), people.Select(row => row.FullName).OrderBy(name => name, StringComparer.Ordinal).ToArray());
|
|
Assert.Contains(people, person => person.Roles.Contains("student"));
|
|
Assert.Equal(new DateTime(2012, 3, 31, 6, 0, 0, DateTimeKind.Utc), school.GameTime);
|
|
}
|
|
finally
|
|
{
|
|
DeleteSchoolFiles(directory, 1);
|
|
using var cleanup = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken);
|
|
cleanup.EnsureSuccessStatusCode();
|
|
}
|
|
}
|
|
|
|
private static async Task InstallRunnableAsync(
|
|
HttpClient client,
|
|
string directory,
|
|
string folder,
|
|
bool includeNative)
|
|
{
|
|
InstallFolder(directory, folder);
|
|
StampRunnable(Path.Combine(directory, "1.json"), includeNative);
|
|
|
|
using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken);
|
|
reload.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
private static void InstallFolder(string directory, string folder)
|
|
{
|
|
var source = Path.Combine(AppContext.BaseDirectory, "golden", folder);
|
|
foreach (var file in Directory.EnumerateFiles(source))
|
|
{
|
|
File.Copy(file, Path.Combine(directory, Path.GetFileName(file)), overwrite: true);
|
|
}
|
|
}
|
|
|
|
private static void StampRunnable(string path, bool includeNative)
|
|
{
|
|
var node = JsonNode.Parse(File.ReadAllText(path))
|
|
?? throw new InvalidOperationException($"Save {path} parsed to nothing.");
|
|
node["format"] = 3;
|
|
node["countryId"] = "Russia";
|
|
node["climatePresetId"] = "TemperateContinental";
|
|
node["owner"] = SchoolApiTests.TestUserName;
|
|
node.AsObject().Remove("nameSetId");
|
|
if (includeNative)
|
|
{
|
|
node["nativeLanguage"] = "RussianLanguage";
|
|
}
|
|
else
|
|
{
|
|
node.AsObject().Remove("nativeLanguage");
|
|
}
|
|
|
|
File.WriteAllText(path, node.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
|
|
}
|
|
|
|
private static void DeleteSchoolFiles(string directory, int id)
|
|
{
|
|
foreach (var name in new[] { $"{id}.json", $"{id}.people.json", $"{id}.timetable.json" })
|
|
{
|
|
var path = Path.Combine(directory, name);
|
|
if (File.Exists(path))
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static async Task<IReadOnlyList<PersonRow>> PeopleAsync(HttpClient client, int schoolId)
|
|
{
|
|
var page = await client.GetFromJsonAsync<PeoplePage>(
|
|
$"/api/schools/{schoolId}/people?pageSize=100",
|
|
TestContext.Current.CancellationToken);
|
|
Assert.NotNull(page);
|
|
return page.People
|
|
.OrderBy(row => row.Id, StringComparer.Ordinal)
|
|
.ToArray();
|
|
}
|
|
|
|
private static string[] ExpectedNames()
|
|
{
|
|
var json = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "golden", "current", "1.people.json"));
|
|
using var document = JsonDocument.Parse(json);
|
|
return document.RootElement.GetProperty("people")
|
|
.EnumerateArray()
|
|
.Select(person =>
|
|
{
|
|
var name = person.GetProperty("name");
|
|
var surname = name.GetProperty("surname").GetString() ?? "";
|
|
var given = name.GetProperty("given").GetString() ?? "";
|
|
var patronymic = name.GetProperty("patronymic").GetString() ?? "";
|
|
return string.Join(' ', new[] { surname, given, patronymic }.Where(part => part.Length > 0));
|
|
})
|
|
.OrderBy(full => full, StringComparer.Ordinal)
|
|
.ToArray();
|
|
}
|
|
|
|
private sealed record PeoplePage(IReadOnlyList<PersonRow> People);
|
|
|
|
private sealed record PersonRow(string Id, string FullName, IReadOnlyList<string> Roles);
|
|
}
|