Merge branch 'bug/3-users-json-as-school'
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# Баг 3. SchoolStore принимает users.json за сейв школы
|
||||
|
||||
## Симптом
|
||||
|
||||
После первого входа в логе при старте сервера:
|
||||
|
||||
```
|
||||
warn: HSchool.Server.Game.SchoolStore[0]
|
||||
Save D:\Github\h-school\src\HSchool.Server\saves\users.json has id 0; ids start at 1. Leaving the file in place.
|
||||
```
|
||||
|
||||
Ожидали, что список игроков рядом со школами не попадает в загрузку сейвов.
|
||||
|
||||
## Причина
|
||||
|
||||
Фаза 38 положила `saves/users.json` в тот же каталог, что и `{id}.json`. `SchoolStore.LoadAll` обходит все `*.json`, пропуская только `index.json`, `*.people.json` и `*.timetable.json`. У документа пользователей нет поля `id`, десериализация в `SchoolSave` даёт `Id = 0`, срабатывает предупреждение. Файл на месте остаётся, карточки школы из него нет.
|
||||
|
||||
## Путь
|
||||
|
||||
Пропускать `users.json` так же, как `index.json`. Не сужать обход до `{digits}.json` — это другой контракт сканера.
|
||||
|
||||
## Задачи
|
||||
|
||||
- [x] `LoadAll` не читает `users.json` как сейв
|
||||
- [x] Тест: каталог с `1.json` и `users.json` возвращает одну школу
|
||||
|
||||
## Тест, без которого не закрыт
|
||||
|
||||
`HSchool.Server.Tests`: `LoadAll` рядом с валидным `{id}.json` и `users.json` (документ пользователей, без `id`) возвращает только школу.
|
||||
|
||||
```bash
|
||||
dotnet test tests/HSchool.Server.Tests --filter FullyQualifiedName~SchoolStore
|
||||
```
|
||||
|
||||
## Стоп
|
||||
|
||||
Форму `users.json` и HTTP сессии не трогать. `wwwroot` / `UseFileServer` — соседнее предупреждение при старте, не этот баг.
|
||||
@@ -11,3 +11,4 @@
|
||||
| --- | --- | --- |
|
||||
| [1. Окно входа](01-login-layout.md) | ✅ | Форма входа прижата к углу, поле пароля на всю ширину |
|
||||
| [2. Запуск AppHost](02-client-endpoint-proxy.md) | ✅ | `run-aspire` падает: proxy + Port=TargetPort=5173 у client |
|
||||
| [3. users.json как сейв](03-users-json-as-school.md) | ✅ | При старте: `Save …/users.json has id 0; ids start at 1` |
|
||||
|
||||
@@ -119,6 +119,7 @@ internal sealed class SchoolStore
|
||||
{
|
||||
var fileName = Path.GetFileName(path);
|
||||
if (string.Equals(fileName, IndexFileName, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(fileName, UserStore.FileName, StringComparison.OrdinalIgnoreCase)
|
||||
|| fileName.EndsWith(".people.json", StringComparison.OrdinalIgnoreCase)
|
||||
|| fileName.EndsWith(".timetable.json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ internal sealed record UserRecord(string Name);
|
||||
/// <summary>Persistent user list beside school saves.</summary>
|
||||
internal sealed class UserStore
|
||||
{
|
||||
private const string UsersFileName = "users.json";
|
||||
internal const string FileName = "users.json";
|
||||
|
||||
private static readonly JsonSerializerOptions Json = new()
|
||||
{
|
||||
@@ -25,7 +25,7 @@ internal sealed class UserStore
|
||||
public UserStore(SchoolStore schools, ILogger<UserStore> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_path = Path.Combine(schools.DirectoryPath, UsersFileName);
|
||||
_path = Path.Combine(schools.DirectoryPath, FileName);
|
||||
Load();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\HSchool.Server\HSchool.Server.csproj" />
|
||||
<ProjectReference Include="..\..\src\HSchool.People\HSchool.People.csproj" />
|
||||
<ProjectReference Include="..\..\src\HSchool.Simulation\HSchool.Simulation.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using HSchool.Server.Game;
|
||||
using HSchool.Simulation;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace HSchool.Server.Tests;
|
||||
|
||||
public class SchoolStoreTests
|
||||
{
|
||||
[Fact]
|
||||
public void LoadAll_SkipsUsersJsonBesideASchoolSave()
|
||||
{
|
||||
var directory = Directory.CreateTempSubdirectory("h-school-store-");
|
||||
try
|
||||
{
|
||||
File.WriteAllText(
|
||||
Path.Combine(directory.FullName, UserStore.FileName),
|
||||
"""{ "users": [ { "name": "Player" } ] }""");
|
||||
File.WriteAllText(
|
||||
Path.Combine(directory.FullName, "1.json"),
|
||||
"""
|
||||
{
|
||||
"format": 3,
|
||||
"id": 1,
|
||||
"name": "North",
|
||||
"gameTime": "2012-03-31T06:00:00Z",
|
||||
"running": false,
|
||||
"speedIndex": 0
|
||||
}
|
||||
""");
|
||||
|
||||
var store = CreateStore(directory.FullName);
|
||||
var saves = store.LoadAll();
|
||||
|
||||
var save = Assert.Single(saves);
|
||||
Assert.Equal(1, save.Id);
|
||||
Assert.Equal("North", save.Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
directory.Delete(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static SchoolStore CreateStore(string directory)
|
||||
{
|
||||
var options = Options.Create(new SimulationOptions { SavesDirectory = directory });
|
||||
return new SchoolStore(options, new StubHost(), NullLogger<SchoolStore>.Instance);
|
||||
}
|
||||
|
||||
private sealed class StubHost : IHostEnvironment
|
||||
{
|
||||
public string ApplicationName { get; set; } = "HSchool.Server.Tests";
|
||||
public string EnvironmentName { get; set; } = "Development";
|
||||
public string ContentRootPath { get; set; } = Path.GetTempPath();
|
||||
public IFileProvider ContentRootFileProvider { get; set; } = new NullFileProvider();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user