Merge branch 'review/slice-2-recheck'
This commit is contained in:
@@ -42,3 +42,55 @@
|
|||||||
и проверяет диапазон.
|
и проверяет диапазон.
|
||||||
- `List_SortsBySurname` требовал ordinal-порядок, сервер ставит Ё на место Е. Совпадали, пока
|
- `List_SortsBySurname` требовал ordinal-порядок, сервер ставит Ё на место Е. Совпадали, пока
|
||||||
в `core` не было фамилий на Ё.
|
в `core` не было фамилий на Ё.
|
||||||
|
|
||||||
|
## Срез 2. Люди — перепроверка
|
||||||
|
|
||||||
|
- **Фазы:** 5–9
|
||||||
|
- **Проверен на:** `c2e01f0`, 2026-08-20
|
||||||
|
- **Пути:** `src/HSchool.People`, `src/HSchool.Content/PeopleDefs.cs`,
|
||||||
|
`src/HSchool.Simulation/RosterSpawner.cs`, `src/HSchool.Simulation/School.cs`,
|
||||||
|
`src/HSchool.Server/Api/SchoolEndpoints.cs`, `src/HSchool.Server/Game/PersonCardReader.cs`,
|
||||||
|
`src/HSchool.Server/Game/SchoolWorker.cs`,
|
||||||
|
`src/HSchool.Client/src/ui/peoplePanel.ts`, `src/HSchool.Client/src/ui/personCard.ts`,
|
||||||
|
`tests/HSchool.People.Tests`, `tests/HSchool.Content.Tests/PeopleDefTests.cs`,
|
||||||
|
`tests/HSchool.Simulation.Tests/PeopleInSchoolTests.cs`,
|
||||||
|
`tests/HSchool.AppHost.Tests/PeopleApiTests.cs`,
|
||||||
|
`tests/HSchool.AppHost.Tests/SchoolSeedTests.cs`
|
||||||
|
- **Итог:** перепроверка после дрейфа с `bdb1253` (страна, одежда, вкладки карточки, мнения,
|
||||||
|
речь). Обещания фаз 5–9 целы; код не чинился; дописаны тесты фильтров списка и работающего
|
||||||
|
родителя на наборе
|
||||||
|
|
||||||
|
Что подтверждено:
|
||||||
|
|
||||||
|
- Дефы, наследование, патч, неизвестная ссылка, склонение, мод со своей страной и чертами.
|
||||||
|
`GET /api/catalog` отдаёт страны, не наборы имён — так решила фаза 29, не регрессия среза 2.
|
||||||
|
- Генератор: тот же сид → тот же ростер; 13-я семья не двигает первые двенадцать; отчества
|
||||||
|
отцовские; полное телосложение зажимает ловкость; 1 и 11 кабинетов собираются; неполные
|
||||||
|
семьи 5–10% и не все материнские. Arch / ASP.NET / `DateTime.Now` в `HSchool.People` нет.
|
||||||
|
- Одиннадцать кабинетов-номеров, `Homeroom` не компьютерный класс, сущности в `World`,
|
||||||
|
`PersistPeople` не с таймера часов. Штат при создании пуст — фаза 11. Нужды в `core` уже
|
||||||
|
с ненулевым `decayPerHour` — срез 5.
|
||||||
|
- Список читает опубликованный снимок (`FindPeople`), карточка идёт в мейлбокс с
|
||||||
|
`TaskCompletionSource`. Фильтры роли/параллели/литеры/должности/пола/возраста живут в
|
||||||
|
`RosterBrowser`; панель — вкладка рядом с картой, строки через `t(...)`.
|
||||||
|
- Выпуск старшей имеющейся, вместимость, младший брат в том же роде, reload после набора
|
||||||
|
(тест прошлого прохода на месте).
|
||||||
|
|
||||||
|
Дописано:
|
||||||
|
|
||||||
|
- `RosterBrowserTests.FilterBySex_KeepsOnlyThatSex`
|
||||||
|
- `RosterBrowserTests.FilterByLetter_KeepsOnlyThatClassLetter`
|
||||||
|
- `RosterBrowserTests.FilterByAgeRange_KeepsAgesInside`
|
||||||
|
- `RosterBrowserTests.FilterByPosition_KeepsThatJob` — фаза 8 обещает эти фильтры; после
|
||||||
|
дрейфа их гонял только клиентский «отправил query».
|
||||||
|
- `YearlyIntakeTests.StaffParentOfOnlyGraduates_StaysAfterIntake` — ветка «родитель
|
||||||
|
выпускников остаётся, если работает здесь» после фазы 11 была пустой: генератор больше
|
||||||
|
не ставит штат, и старый тест её не касался.
|
||||||
|
|
||||||
|
Исправлено: ничего в коде среза.
|
||||||
|
|
||||||
|
Открытое:
|
||||||
|
|
||||||
|
- Хостовые `PeopleApiTests` / `SchoolSeedTests` в этом проходе не гонялись (Testing policy:
|
||||||
|
HTTP не менялся). Симуляционный переход 1 сентября и библиотечный набор зелёные.
|
||||||
|
- `people.json` теперь ещё пишется после разговора и ссоры — это срез 9, не состав среза 2.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
| Срез | Проверен на | Журнал |
|
| Срез | Проверен на | Журнал |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Срез 1. Оболочка и карта | `d087486` | [01-shell](01-shell/reviewed.md) |
|
| Срез 1. Оболочка и карта | `d087486` | [01-shell](01-shell/reviewed.md) |
|
||||||
| Срез 2. Люди | `bdb1253` | [02-people](02-people/reviewed.md) |
|
| Срез 2. Люди | `c2e01f0` | [02-people](02-people/reviewed.md) |
|
||||||
| Срез 3. Штат | `bdb1253` | [03-staffing](03-staffing/reviewed.md) |
|
| Срез 3. Штат | `bdb1253` | [03-staffing](03-staffing/reviewed.md) |
|
||||||
| Срез 4. Расписание | `bdb1253` | [04-schedule](04-schedule/reviewed.md) |
|
| Срез 4. Расписание | `bdb1253` | [04-schedule](04-schedule/reviewed.md) |
|
||||||
| Срез 5. Жизнь школы | `cbe739a` | [05-ai](05-ai/reviewed.md) |
|
| Срез 5. Жизнь школы | `cbe739a` | [05-ai](05-ai/reviewed.md) |
|
||||||
|
|||||||
@@ -65,10 +65,82 @@ public class RosterBrowserTests
|
|||||||
Assert.All(page.People, person => Assert.True(person.IsParent));
|
Assert.All(page.People, person => Assert.True(person.IsParent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterBySex_KeepsOnlyThatSex()
|
||||||
|
{
|
||||||
|
var roster = Fixtures.Generate(Fixtures.Classrooms(4));
|
||||||
|
var page = RosterBrowser.Apply(
|
||||||
|
roster,
|
||||||
|
Fixtures.AsOf,
|
||||||
|
Query(female: true, pageSize: RosterBrowser.MaxPageSize));
|
||||||
|
|
||||||
|
Assert.NotEmpty(page.People);
|
||||||
|
Assert.True(page.Total < roster.People.Count);
|
||||||
|
Assert.All(page.People, person => Assert.True(person.Female));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterByLetter_KeepsOnlyThatClassLetter()
|
||||||
|
{
|
||||||
|
var roster = Fixtures.Generate(Fixtures.Classrooms(4));
|
||||||
|
var letter = roster.Classes[0].Letter;
|
||||||
|
var page = RosterBrowser.Apply(
|
||||||
|
roster,
|
||||||
|
Fixtures.AsOf,
|
||||||
|
Query(letter: letter, pageSize: RosterBrowser.MaxPageSize));
|
||||||
|
var classes = roster.Classes.ToDictionary(schoolClass => schoolClass.Id, StringComparer.Ordinal);
|
||||||
|
|
||||||
|
Assert.NotEmpty(page.People);
|
||||||
|
Assert.All(page.People, person =>
|
||||||
|
Assert.Equal(letter, person.ClassId is { } classId ? classes[classId].Letter : null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterByAgeRange_KeepsAgesInside()
|
||||||
|
{
|
||||||
|
var roster = Fixtures.Generate(Fixtures.Classrooms(4));
|
||||||
|
var pupil = roster.People.First(person => person.IsStudent);
|
||||||
|
var age = pupil.AgeOn(Fixtures.AsOf);
|
||||||
|
var page = RosterBrowser.Apply(
|
||||||
|
roster,
|
||||||
|
Fixtures.AsOf,
|
||||||
|
Query(ageMin: age, ageMax: age, pageSize: RosterBrowser.MaxPageSize));
|
||||||
|
|
||||||
|
Assert.Contains(page.People, person => person.Id == pupil.Id);
|
||||||
|
Assert.All(page.People, person => Assert.Equal(age, person.AgeOn(Fixtures.AsOf)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FilterByPosition_KeepsThatJob()
|
||||||
|
{
|
||||||
|
var roster = Fixtures.Generate(Fixtures.Classrooms(4));
|
||||||
|
var parent = roster.People.First(person => person.IsParent);
|
||||||
|
roster = roster with
|
||||||
|
{
|
||||||
|
People = roster.People
|
||||||
|
.Select(person => person.Id.Equals(parent.Id, StringComparison.Ordinal)
|
||||||
|
? person with { IsStaff = true, Position = "Librarian" }
|
||||||
|
: person)
|
||||||
|
.ToArray(),
|
||||||
|
};
|
||||||
|
|
||||||
|
var page = RosterBrowser.Apply(
|
||||||
|
roster,
|
||||||
|
Fixtures.AsOf,
|
||||||
|
Query(position: "Librarian", pageSize: RosterBrowser.MaxPageSize));
|
||||||
|
|
||||||
|
Assert.Equal(parent.Id, Assert.Single(page.People).Id);
|
||||||
|
}
|
||||||
|
|
||||||
private static RosterQuery Query(
|
private static RosterQuery Query(
|
||||||
string? role = null,
|
string? role = null,
|
||||||
int? year = null,
|
int? year = null,
|
||||||
|
string? letter = null,
|
||||||
|
string? position = null,
|
||||||
|
bool? female = null,
|
||||||
|
int? ageMin = null,
|
||||||
|
int? ageMax = null,
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int pageSize = RosterBrowser.DefaultPageSize) =>
|
int pageSize = RosterBrowser.DefaultPageSize) =>
|
||||||
new(role, year, Letter: null, Position: null, Female: null, AgeMin: null, AgeMax: null, PersonSort.Surname, Descending: false, page, pageSize);
|
new(role, year, letter, position, female, ageMin, ageMax, PersonSort.Surname, Descending: false, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,43 @@ public class YearlyIntakeTests
|
|||||||
person => Assert.False(person.IsParent));
|
person => Assert.False(person.IsParent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Slice 3 stopped generating staff, so the "unless they work here" branch of the test above
|
||||||
|
/// is vacuously empty. Hire still marks a parent <c>IsStaff</c>; intake must not drop them
|
||||||
|
/// with the graduates' civilian co-parent.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void StaffParentOfOnlyGraduates_StaysAfterIntake()
|
||||||
|
{
|
||||||
|
var map = Fixtures.Classrooms(4);
|
||||||
|
var catalog = Fixtures.Catalog();
|
||||||
|
var generated = Fixtures.Generate(map);
|
||||||
|
var graduating = generated.Classes
|
||||||
|
.Where(schoolClass => schoolClass.Year == 4)
|
||||||
|
.SelectMany(schoolClass => schoolClass.PupilIds)
|
||||||
|
.ToHashSet(StringComparer.Ordinal);
|
||||||
|
var family = generated.Families.First(candidate =>
|
||||||
|
candidate.ChildIds.Count > 0
|
||||||
|
&& candidate.ChildIds.All(id => graduating.Contains(id)));
|
||||||
|
var staffId = family.ParentIds[0];
|
||||||
|
var civilians = family.ParentIds.Skip(1).ToArray();
|
||||||
|
var before = generated with
|
||||||
|
{
|
||||||
|
People = generated.People
|
||||||
|
.Select(person => person.Id.Equals(staffId, StringComparison.Ordinal)
|
||||||
|
? person with { IsStaff = true, Position = "Librarian" }
|
||||||
|
: person)
|
||||||
|
.ToArray(),
|
||||||
|
};
|
||||||
|
|
||||||
|
var after = YearlyIntake.Apply(catalog, before, Fixtures.SchoolSeed, "Russia", September);
|
||||||
|
var stayed = Assert.Single(after.People, person => person.Id == staffId);
|
||||||
|
|
||||||
|
Assert.True(stayed.IsStaff);
|
||||||
|
Assert.False(stayed.IsParent);
|
||||||
|
Assert.All(civilians, id => Assert.DoesNotContain(after.People, person => person.Id == id));
|
||||||
|
}
|
||||||
|
|
||||||
private static string Snapshot(Roster roster) =>
|
private static string Snapshot(Roster roster) =>
|
||||||
string.Join('\n', roster.People.Select(person =>
|
string.Join('\n', roster.People.Select(person =>
|
||||||
$"{person.Id}|{person.FamilyId}|{person.IsStudent}|{person.IsStaff}|{person.IsParent}|{person.ClassId}|{person.Name.Full}|{Skills(person)}"));
|
$"{person.Id}|{person.FamilyId}|{person.IsStudent}|{person.IsStaff}|{person.IsParent}|{person.ClassId}|{person.Name.Full}|{Skills(person)}"));
|
||||||
|
|||||||
Reference in New Issue
Block a user