Files
h-school/tests/HSchool.Simulation.Tests/PersonDayLogTests.cs
T
Leonid PershinandCursor 6be4fa75ab Split the person card into overview, clothes, carry and now tabs.
Today's history is a paged HTTP log on the worker, not on the card or in people.json.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 04:00:38 +03:00

229 lines
8.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Arch.Core;
using HSchool.Ai;
using HSchool.Content;
using HSchool.People;
namespace HSchool.Simulation.Tests;
public class PersonDayLogTests
{
private static readonly DateTime TuesdayFive = new(2012, 4, 3, 5, 0, 0, DateTimeKind.Utc);
private static readonly DateTime TuesdayNoon = new(2012, 4, 3, 12, 0, 0, DateTimeKind.Utc);
private static readonly DateTime TuesdayEvening = new(2012, 4, 3, 22, 0, 0, DateTimeKind.Utc);
[Fact]
public void TryStartAndFinish_WriteStartAndEndRows()
{
using var school = OpenCafeteria(TuesdayNoon);
Spawn(school, "a", hunger: 0.3f);
Assert.True(school.TryStartAction("a", "EatLunch"));
Assert.Contains(
school.DayLog,
row => row.PersonId == "a"
&& row.Type == PersonLogTypes.ActionStarted
&& row.ThingDef == "EatLunch");
TickMinutes(school, 16);
Assert.Contains(
school.DayLog,
row => row.PersonId == "a"
&& row.Type == PersonLogTypes.ActionEnded
&& row.ThingDef == "EatLunch");
Assert.Equal(
"начал: Обед",
school.DayLog.First(row => row.Type == PersonLogTypes.ActionStarted).Caption(school.Catalog!, "ru"));
}
[Fact]
public void AfterSix_YesterdaysRowsAreGone()
{
using var school = OpenCafeteria(TuesdayFive);
Spawn(school, "a", hunger: 0.3f);
Assert.True(school.TryStartAction("a", "EatLunch"));
Assert.NotEmpty(school.DayLog);
TickMinutes(school, 61);
Assert.True(school.Clock.Time >= new DateTime(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc));
Assert.Empty(school.DayLog);
}
[Fact]
public void SkipEmpty_ClearsYesterdaysRows()
{
using var school = OpenEmpty(TuesdayEvening);
school.AppendDayLog(new PersonLogEvent("f0.c0", TuesdayEvening, PersonLogTypes.ActionStarted, "EatLunch"));
Assert.NotEmpty(school.DayLog);
var result = school.TrySkipEmpty();
Assert.True(result.Succeeded);
Assert.Empty(school.DayLog);
}
[Fact]
public void Pages_DoNotOverlap()
{
var time = TuesdayNoon;
var log = new List<PersonLogEvent>();
for (var i = 0; i < 25; i++)
{
log.Add(new PersonLogEvent("p", time.AddMinutes(i), PersonLogTypes.ActionStarted, "EatLunch"));
}
var first = PersonLogBrowser.Apply(log, "p", new PersonLogQuery(null, Descending: false, Page: 1, PageSize: 10), catalog: null, "ru");
var second = PersonLogBrowser.Apply(log, "p", new PersonLogQuery(null, Descending: false, Page: 2, PageSize: 10), catalog: null, "ru");
Assert.Equal(25, first.Total);
Assert.Equal(10, first.Entries.Count);
Assert.Equal(10, second.Entries.Count);
Assert.Empty(first.Entries.Select(row => row.Time).Intersect(second.Entries.Select(row => row.Time)));
Assert.Equal(time, first.Entries[0].Time);
Assert.Equal(time.AddMinutes(10), second.Entries[0].Time);
}
[Fact]
public void Search_FiltersByCaption()
{
var log = new[]
{
new PersonLogEvent("p", TuesdayNoon, PersonLogTypes.ActionStarted, "EatLunch"),
new PersonLogEvent("p", TuesdayNoon.AddMinutes(1), PersonLogTypes.ActionStarted, "UseToilet"),
};
var page = PersonLogBrowser.Apply(log, "p", new PersonLogQuery("toilet", Descending: false, Page: 1, PageSize: 20), catalog: null, "en");
Assert.Equal(1, page.Total);
Assert.Equal("action-started", page.Entries[0].Type);
Assert.Equal("UseToilet", page.Entries[0].ThingDef);
}
private static void TickMinutes(School school, int minutes)
{
for (var i = 0; i < minutes; i++)
{
school.Tick(0.2d, 5d);
}
}
private static School OpenCafeteria(DateTime start)
{
var catalog = new CatalogLoader().Load(
[CatalogLoader.CorePackId],
[
new ContentDocument("core", "defs/actions/sit.jsonc", """{ "defName": "Sit", "abstract": true }"""),
new ContentDocument(
"core",
"defs/actions/eat.jsonc",
"""
{
"defName": "EatLunch",
"room": "Cafeteria",
"thing": "Chair",
"minutes": 15,
"need": "Hunger",
"needGain": 0.5,
"roles": ["student"]
}
"""),
new ContentDocument(
"core",
"defs/needs/hunger.jsonc",
"""{ "defName": "Hunger", "initial": 1, "decayPerHour": 0, "min": 0, "max": 1 }"""),
new ContentDocument("core", "defs/things/chair.jsonc", """{ "defName": "Chair", "actions": ["Sit"] }"""),
new ContentDocument("core", "defs/territories/yard.jsonc", """{ "defName": "Yard", "travelMinutes": 1 }"""),
new ContentDocument("core", "defs/buildings/main.jsonc", """{ "defName": "Main" }"""),
new ContentDocument("core", "defs/floors/floor.jsonc", """{ "defName": "Floor" }"""),
new ContentDocument(
"core",
"defs/rooms/cafeteria.jsonc",
"""
{
"defName": "Cafeteria",
"slots": [{ "key": "seats", "thing": "Chair" }],
"travelMinutes": 1
}
"""),
new ContentDocument("core", "localizations/ru.jsonc", """{ "EatLunch": "Обед", "ActionStarted": "начал: {0}", "ActionEnded": "закончил: {0}" }"""),
]);
var map = new MapLayout
{
Territory = new TerritoryNode { Id = "yard", Def = "Yard" },
Buildings = [new BuildingNode { Id = "main", Def = "Main" }],
Floors = [new FloorNode { Id = "floor-1", Def = "Floor", Building = "main" }],
Rooms =
[
new RoomNode
{
Id = "cafe",
Def = "Cafeteria",
Building = "main",
Floor = "floor-1",
Slots = [new SlotFill { Key = "seats", Thing = "Chair", Count = 8 }],
},
],
Links = [new MapLink { A = "yard", B = "cafe" }],
};
MapValidator.Validate(map, catalog);
return School.Create(1, "Столовая", start, catalog, map);
}
private static void Spawn(School school, string id, float hunger)
{
school.World.Create(
new PersonIdentity(id, "f", false, TuesdayNoon, DummyName()),
new PersonRoles(true, false, false, null, null, null),
new PersonNeeds(new Dictionary<string, float>(StringComparer.Ordinal) { ["Hunger"] = hunger }),
new Presence("cafe", 0f, "cafe", false, []),
PersonActivity.Idle);
}
private static School OpenEmpty(DateTime start)
{
var (catalog, map) = Vanilla();
var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 1, "Russia", start);
var pool = ApplicantPool.Create(catalog, roster, schoolSeed: 1, "Russia", start);
var school = School.Create(1, "Пустая", start, catalog, map);
school.InstallPeople(roster, seed: 1, "Russia", pool);
school.ConfigurePresence(weekDays: 5, maxDecisionsPerTick: 10_000);
return school;
}
private static (DefCatalog Catalog, MapLayout Map) Vanilla()
{
var root = Path.Combine(AppContext.BaseDirectory, "vanilla");
var documents = new List<ContentDocument>();
foreach (var path in Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories))
{
if (!path.EndsWith(".jsonc", StringComparison.OrdinalIgnoreCase)
&& !path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var relative = Path.GetRelativePath(root, path).Replace('\\', '/');
documents.Add(new ContentDocument(CatalogLoader.CorePackId, relative, File.ReadAllText(path)));
}
var catalog = new CatalogLoader().Load([CatalogLoader.CorePackId], documents);
var map = CatalogLoader.LastDefaultMap([CatalogLoader.CorePackId], documents);
Assert.NotNull(map);
return (catalog, map);
}
private static PersonName DummyName()
{
var cases = new CaseTable
{
Nom = "А",
Gen = "А",
Dat = "А",
Acc = "А",
Ins = "А",
Pre = "А",
};
return new PersonName("А", "А", "А", cases, cases, cases);
}
}