This commit is contained in:
Leonid Pershin
2026-08-18 23:51:10 +03:00
parent d8f8db6a48
commit 65feda3756
50 changed files with 1485 additions and 224 deletions
@@ -109,7 +109,7 @@ public class GameSocketTests(AppHostFixture fixture)
var classroom = Assert.Single(snapshot.Nodes, node => node.Id == "classroom-101");
Assert.Equal(16, classroom.PupilSlots);
Assert.Contains(classroom.Items, item => item.Name == "Парта" && item.Count == 16);
Assert.Contains(classroom.Items, item => item.Name == "Стул" && item.Count == 1);
Assert.DoesNotContain(classroom.Items, item => item.Name == "Стул");
}
[Fact]
+20 -2
View File
@@ -9,7 +9,7 @@ namespace HSchool.AppHost.Tests;
[Collection(AppHostCollection.Name)]
public class SchoolApiTests(AppHostFixture fixture)
{
private static readonly DateTime ExpectedDefaultStart = new(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc);
private static readonly DateTime ExpectedDefaultStart = new(2012, 3, 31, 6, 0, 0, DateTimeKind.Utc);
[Fact]
public async Task Schools_ReportTheConfiguredLimitAndDefaultStartDate()
@@ -159,6 +159,14 @@ public class SchoolApiTests(AppHostFixture fixture)
Assert.Equal("yard", ru.DefaultMap.Territory?.Id);
Assert.Equal("Славянский", Assert.Single(ru.NameSets, set => set.DefName == "Slavic").Label);
Assert.Equal("Slavic", Assert.Single(en.NameSets, set => set.DefName == "Slavic").Label);
Assert.Equal("Начальные классы", Assert.Single(ru.Subjects, subject => subject.DefName == "PrimarySchool").Label);
Assert.Equal("Primary", Assert.Single(en.Subjects, subject => subject.DefName == "PrimarySchool").Label);
var classroom = Assert.Single(ru.Rooms, room => room.DefName == "Classroom");
Assert.True(classroom.Homeroom);
Assert.Equal("StudentDesk", classroom.SeatThing);
Assert.Equal(16, classroom.DefaultSeats);
Assert.Empty(classroom.Slots);
Assert.Empty(classroom.Positions);
}
[Fact]
@@ -358,14 +366,24 @@ public class SchoolApiTests(AppHostFixture fixture)
IReadOnlyList<RoomInfoResponse> Rooms,
IReadOnlyList<DefInfoResponse> Things,
IReadOnlyList<DefInfoResponse> NameSets,
IReadOnlyList<SubjectInfoResponse> Subjects,
MapLayoutResponse DefaultMap);
private sealed record DefInfoResponse(string DefName, string Label);
private sealed record RoomInfoResponse(string DefName, string Label, IReadOnlyList<RoomSlotResponse> Slots, IReadOnlyList<string> Positions);
private sealed record RoomInfoResponse(
string DefName,
string Label,
bool Homeroom,
string? SeatThing,
int DefaultSeats,
IReadOnlyList<RoomSlotResponse> Slots,
IReadOnlyList<string> Positions);
private sealed record RoomSlotResponse(string Key, string Thing);
private sealed record SubjectInfoResponse(string DefName, string Label);
private sealed record MapLayoutResponse(TerritoryResponse? Territory);
private sealed record TerritoryResponse(string Id, string Def);
+2 -9
View File
@@ -43,16 +43,9 @@ public class MapViewTests
var classroom = ru.Single(node => node.Id == "classroom-101");
Assert.Equal("Класс 101", classroom.Name);
Assert.Equal(
[
new MapViewItem("Доска", 1),
new MapViewItem("Стол", 1),
new MapViewItem("Стул", 1),
new MapViewItem("Парта", 16),
],
classroom.Items);
Assert.Equal([new MapViewItem("Парта", 16)], classroom.Items);
Assert.Equal(16, classroom.PupilSlots);
Assert.Equal(["Учитель"], classroom.Positions);
Assert.Empty(classroom.Positions);
Assert.Equal("Classroom 101", en.Single(node => node.Id == "classroom-101").Name);
}
@@ -23,6 +23,9 @@ public class PeopleDefTests
Assert.Equal("Славянский", catalog.Label("ru", catalog.NameSets["Slavic"]));
Assert.Equal("Slavic", catalog.Label("en", catalog.NameSets["Slavic"]));
Assert.Equal("Усидчивый", catalog.Label("ru", catalog.Traits["Diligent"]));
Assert.True(catalog.Subjects.ContainsKey("PrimarySchool"));
Assert.Equal("Начальные классы", catalog.Label("ru", catalog.Subjects["PrimarySchool"]));
Assert.Equal("Primary", catalog.Label("en", catalog.Subjects["PrimarySchool"]));
}
[Fact]
@@ -147,6 +150,30 @@ public class PeopleDefTests
Assert.Contains("Ghost", ex.Message);
}
[Fact]
public void Subject_UnknownSkill_FailsTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(CatalogLoader.CorePackId, "skills", "math", """{ "defName": "Mathematics" }"""),
PackDocuments.Def(
CatalogLoader.CorePackId,
"subjects",
"ghost",
"""
{
"defName": "GhostSubject",
"grades": { "min": 1, "max": 4 },
"hoursPerWeek": 2,
"skills": [{ "skill": "Missing", "share": 1 }]
}
"""),
]));
Assert.Contains("Missing", ex.Message);
}
[Fact]
public void OneWayIncompatibility_IsMutual()
{
@@ -26,24 +26,80 @@ public class VanillaCoreTests
Assert.True(catalog.Rooms["Classroom"].Homeroom);
Assert.False(catalog.Rooms["ComputerLab"].Homeroom);
Assert.Equal("Класс", catalog.Label("ru", catalog.Rooms["Classroom"]));
Assert.Equal(["Teacher"], catalog.PositionsFor(DefKind.Room, "Classroom"));
Assert.Empty(catalog.PositionsFor(DefKind.Room, "Classroom"));
Assert.Empty(catalog.PositionsFor(DefKind.Room, "ComputerLab"));
Assert.Empty(catalog.PositionsFor(DefKind.Room, "GymHall"));
Assert.False(catalog.Positions.ContainsKey("PETeacher"));
Assert.Equal("StudentDesk", catalog.Rooms["Classroom"].SeatThing);
Assert.Equal(16, catalog.Rooms["Classroom"].DefaultSeats);
Assert.Empty(catalog.Rooms["Classroom"].Slots);
Assert.Equal(1, catalog.Things["StudentDesk"].PupilSlots);
Assert.Equal(1, catalog.Things["Computer"].PupilSlots);
Assert.Equal(0, catalog.Things["Desk"].PupilSlots);
Assert.Equal(0, catalog.Things["Chair"].PupilSlots);
Assert.Contains(catalog.Rooms["Classroom"].Slots, slot => slot.Key == "teacherChair" && slot.Thing == "Chair");
Assert.Equal(16, catalog.Rooms["Classroom"].Slots.Single(slot => slot.Key == "studentDesks").Count);
Assert.True(catalog.Subjects.ContainsKey("PrimarySchool"));
Assert.Equal(1, catalog.Subjects["PrimarySchool"].Grades.Min);
Assert.Equal(4, catalog.Subjects["PrimarySchool"].Grades.Max);
Assert.True(catalog.Subjects.ContainsKey("PhysicalEducation"));
Assert.Equal(2, map.Buildings.Count);
var homerooms = map.Rooms.Where(room => room.Def == "Classroom").ToList();
Assert.Equal(11, homerooms.Count);
Assert.Contains(map.Rooms, room => room.Id == "classroom-101" && room.Label == "101");
Assert.Contains(map.Rooms, room => room.Id == "classroom-207" && room.Label == "207");
Assert.DoesNotContain(map.Rooms, room => room.Label is "1A" or "1B" or "2A" or "2B");
Assert.Equal(
16,
map.Rooms.Single(room => room.Id == "classroom-101").Slots.Single(slot => slot.Key == "studentDesks").Count);
Assert.Contains(
map.Rooms.Single(room => room.Id == "classroom-101").Slots,
slot => slot.Key == "teacherChair" && slot.Thing == "Chair");
Assert.Equal(16, map.Rooms.Single(room => room.Id == "classroom-101").Seats);
Assert.Empty(map.Rooms.Single(room => room.Id == "classroom-101").Slots);
}
/// <summary>
/// Every name a card can print has to exist in both locales. Missing ones do not fail — they
/// fall through to the raw key, which reads as a label in English and as a leak in Russian:
/// the build row showed "Build: Обычное" for exactly this reason.
/// </summary>
[Fact]
public void EveryVanillaLabel_ExistsInBothLocales()
{
var root = Path.Combine(AppContext.BaseDirectory, "vanilla");
var catalog = new CatalogLoader().Load(
[CatalogLoader.CorePackId],
PackDocuments.FromDirectory(CatalogLoader.CorePackId, root));
var keys = new List<string>();
keys.AddRange(Names(catalog.Actions.Values));
keys.AddRange(Names(catalog.Things.Values));
keys.AddRange(Names(catalog.Positions.Values));
keys.AddRange(Names(catalog.Works.Values));
keys.AddRange(Names(catalog.Rooms.Values));
keys.AddRange(Names(catalog.Buildings.Values));
keys.AddRange(Names(catalog.Floors.Values));
keys.AddRange(Names(catalog.Territories.Values));
keys.AddRange(Names(catalog.Skills.Values));
keys.AddRange(Names(catalog.Traits.Values));
keys.AddRange(Names(catalog.BodyAttributes.Values));
keys.AddRange(Names(catalog.Needs.Values));
keys.AddRange(Names(catalog.NameSets.Values));
keys.AddRange(Names(catalog.Subjects.Values));
// Derived in code, so no def carries them.
keys.Add(BodyBuilds.Attribute);
keys.AddRange(BodyBuilds.Values);
foreach (var value in catalog.BodyAttributes.Values.SelectMany(def => def.Options))
{
keys.Add(value.Value);
}
var missing = keys
.Distinct(StringComparer.Ordinal)
.SelectMany(key => new[] { ("ru", key), ("en", key) })
.Where(pair => !catalog.HasText(pair.Item1, pair.Item2))
.Select(pair => $"{pair.Item1}:{pair.Item2}")
.Order(StringComparer.Ordinal)
.ToArray();
Assert.Empty(missing);
}
private static IEnumerable<string> Names(IEnumerable<Def> defs) =>
defs.Where(def => !def.Abstract).Select(def => def.DefName);
}
+16 -5
View File
@@ -47,10 +47,7 @@ internal static class Fixtures
Building = "main",
Floor = "floor-1",
Label = $"{101 + i}",
Slots =
[
new SlotFill { Key = "studentDesks", Thing = "StudentDesk", Count = desks },
],
Seats = desks,
};
}
@@ -72,7 +69,7 @@ internal static class Fixtures
Building = "main",
Floor = "floor-1",
Label = "101",
Slots = [new SlotFill { Key = "studentDesks", Thing = "StudentDesk", Count = 1 }],
Seats = 1,
},
};
@@ -84,6 +81,20 @@ internal static class Fixtures
return new MapLayout { Rooms = rooms };
}
public static MapLayout VanillaMap()
{
var root = Path.Combine(AppContext.BaseDirectory, "vanilla");
var map = CatalogLoader.LastDefaultMap(
[CatalogLoader.CorePackId],
PackDocuments.FromDirectory(CatalogLoader.CorePackId, root));
if (map is null)
{
throw new InvalidOperationException("Vanilla default map is missing.");
}
return map;
}
public static Roster Generate(MapLayout map, int seed = SchoolSeed) =>
RosterGenerator.Generate(Catalog(), map, seed, "Slavic", AsOf);
@@ -55,7 +55,7 @@ public class RosterBrowserTests
[Fact]
public void ParentFilter_IncludesStaffWhoAreAlsoParents()
{
var roster = Fixtures.Generate(Fixtures.Classrooms(4));
var roster = Fixtures.Generate(Fixtures.VanillaMap());
var page = RosterBrowser.Apply(
roster,
Fixtures.AsOf,
@@ -104,7 +104,7 @@ public class RosterGeneratorTests
[Fact]
public void SomeStaffAreAlsoParents()
{
var roster = Fixtures.Generate(Fixtures.Classrooms(4));
var roster = Fixtures.Generate(Fixtures.VanillaMap());
Assert.Contains(roster.People, person => person.IsStaff && person.IsParent);
}
@@ -122,7 +122,9 @@ public class RosterGeneratorTests
Assert.Equal(11, demand.Classes.Count);
Assert.Equal(11 * 16, demand.Seats.Count);
Assert.DoesNotContain(demand.Classes, schoolClass => schoolClass.RoomId == "computer-lab");
Assert.Contains(demand.Staff, opening => opening.RoomId == "computer-lab" && opening.Position == "Teacher");
Assert.DoesNotContain(demand.Staff, opening => opening.Position == "Teacher");
Assert.DoesNotContain(demand.Staff, opening => opening.RoomId == "computer-lab");
Assert.Contains(demand.Staff, opening => opening.RoomId == "library" && opening.Position == "Librarian");
}
[Fact]
@@ -163,10 +163,7 @@ public class PeopleInSchoolTests
Building = "main",
Floor = "floor-1",
Label = $"{101 + i}",
Slots =
[
new SlotFill { Key = "studentDesks", Thing = "StudentDesk", Count = desks },
],
Seats = desks,
};
}