220 lines
9.4 KiB
C#
220 lines
9.4 KiB
C#
namespace HSchool.People.Tests;
|
|
|
|
public class RosterGeneratorTests
|
|
{
|
|
[Fact]
|
|
public void SameSeedMapAndNameSet_YieldTheSameRoster()
|
|
{
|
|
var map = Fixtures.Classrooms(4);
|
|
var a = Fixtures.Generate(map);
|
|
var b = Fixtures.Generate(map);
|
|
|
|
Assert.Equal(Snapshot(a), Snapshot(b));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Each family draws from its own seeded stream, so a bigger map does not reshuffle the
|
|
/// families already planned: the first twelve keep their surname, their size and every given
|
|
/// name. What a bigger map does move is which classroom a child sits in — seats are dealt
|
|
/// across the whole school so siblings are not automatically classmates — and with the
|
|
/// classroom comes the year, hence the birth year. That is the one thing not asserted here.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ThirteenthFamily_DoesNotChangeHowTheFirstTwelveAreDrawn()
|
|
{
|
|
var twelve = FamilyIdentities(Fixtures.Generate(Fixtures.Classrooms(4)), take: 12);
|
|
var thirteen = FamilyIdentities(Fixtures.Generate(Fixtures.Classrooms(5)), take: 12);
|
|
|
|
Assert.Equal(12, twelve.Count);
|
|
Assert.Equal(twelve, thirteen);
|
|
}
|
|
|
|
[Fact]
|
|
public void FamilyNames_ShareSurnameAndPatronymicFromTheFather()
|
|
{
|
|
var roster = Fixtures.Generate(Fixtures.Classrooms(4));
|
|
var people = roster.People.ToDictionary(person => person.Id, StringComparer.Ordinal);
|
|
|
|
foreach (var family in roster.Families)
|
|
{
|
|
if (family.ChildIds.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// One parent may be absent, so the father's name comes off the family, not off a person.
|
|
var parents = family.ParentIds.Select(id => people[id]).ToArray();
|
|
Assert.NotEmpty(parents);
|
|
Assert.Equal(parents.Length, parents.DistinctBy(parent => parent.Female).Count());
|
|
Assert.NotEmpty(family.FatherGiven);
|
|
|
|
foreach (var parent in parents)
|
|
{
|
|
Assert.Equal(parent.Name.SurnameCases.Nom, parent.Name.Surname);
|
|
}
|
|
|
|
foreach (var childId in family.ChildIds)
|
|
{
|
|
var child = people[childId];
|
|
Assert.Equal(
|
|
NameGrammar.Patronymic(family.FatherGiven, child.Female, NameGrammar.SlavicPatronymic),
|
|
child.Name.Patronymic);
|
|
|
|
var sameSexParent = parents.FirstOrDefault(parent => parent.Female == child.Female);
|
|
if (sameSexParent is not null)
|
|
{
|
|
Assert.Equal(sameSexParent.Name.Surname, child.Name.Surname);
|
|
}
|
|
|
|
Assert.Equal(child.Name.SurnameCases.Nom, child.Name.Surname);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ObesePupil_CannotHaveHighAgility()
|
|
{
|
|
var agility = Fixtures.Catalog().Skills["Agility"];
|
|
var clamped = PersonSampler.ApplyBodyLimits(
|
|
80,
|
|
agility,
|
|
new Dictionary<string, string> { [BodyBuilds.Attribute] = BodyBuilds.Obese });
|
|
Assert.Equal(25, clamped);
|
|
|
|
var roster = Fixtures.Generate(Fixtures.Classrooms(11));
|
|
var obesePupils = roster.People.Where(person =>
|
|
person.IsStudent
|
|
&& person.Choices.TryGetValue(BodyBuilds.Attribute, out var build)
|
|
&& build == BodyBuilds.Obese);
|
|
|
|
Assert.All(
|
|
obesePupils,
|
|
person => Assert.True(
|
|
person.Skills["Agility"] <= 25,
|
|
$"{person.Name.Surname} {person.Name.Given} is obese with agility {person.Skills["Agility"]}."));
|
|
}
|
|
|
|
[Fact]
|
|
public void OneClassroomAndEleven_BothFillSeatsAndLeaveJobsEmpty()
|
|
{
|
|
AssertFilled(Fixtures.Generate(Fixtures.Classrooms(1)), classrooms: 1);
|
|
AssertFilled(Fixtures.Generate(Fixtures.Classrooms(11)), classrooms: 11);
|
|
}
|
|
|
|
[Fact]
|
|
public void NewSchool_HasNoStaff()
|
|
{
|
|
var roster = Fixtures.Generate(Fixtures.VanillaMap());
|
|
Assert.DoesNotContain(roster.People, person => person.IsStaff);
|
|
Assert.Equal(11 * 16, roster.People.Count(person => person.IsStudent));
|
|
Assert.Contains(roster.People, person => person.IsParent);
|
|
}
|
|
|
|
[Fact]
|
|
public void VanillaMap_HasElevenHomeroomsNotTheComputerLab()
|
|
{
|
|
var catalog = Fixtures.Catalog();
|
|
var root = Path.Combine(AppContext.BaseDirectory, "vanilla");
|
|
var map = CatalogLoader.LastDefaultMap(
|
|
[CatalogLoader.CorePackId],
|
|
PackDocuments.FromDirectory(CatalogLoader.CorePackId, root));
|
|
Assert.NotNull(map);
|
|
|
|
var demand = SchoolDemand.From(catalog, map);
|
|
Assert.Equal(11, demand.Classes.Count);
|
|
Assert.Equal(11 * 16, demand.Seats.Count);
|
|
Assert.DoesNotContain(demand.Classes, schoolClass => schoolClass.RoomId == "computer-lab");
|
|
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]
|
|
public void RosterJson_RoundTripsAGeneratedRoster()
|
|
{
|
|
var roster = Fixtures.Generate(Fixtures.Classrooms(1));
|
|
var json = RosterJson.Serialize(RosterDocument.From(7, roster));
|
|
var loaded = RosterJson.Parse(json).ToRoster();
|
|
|
|
Assert.Equal(roster.People.Count, loaded.People.Count);
|
|
Assert.Equal(roster.People[0].Name.Given, loaded.People[0].Name.Given);
|
|
Assert.Equal(roster.People[0].Name.SurnameCases.Gen, loaded.People[0].Name.SurnameCases.Gen);
|
|
Assert.True(RosterFit.Matches(loaded, SchoolDemand.From(Fixtures.Catalog(), Fixtures.Classrooms(1))));
|
|
Assert.False(RosterFit.Matches(loaded, SchoolDemand.From(Fixtures.Catalog(), Fixtures.Classrooms(2))));
|
|
}
|
|
|
|
[Fact]
|
|
public void Assembly_DoesNotReferenceArchAspNetOrSockets()
|
|
{
|
|
var names = typeof(RosterGenerator).Assembly.GetReferencedAssemblies().Select(assembly => assembly.Name!);
|
|
Assert.DoesNotContain(names, name => name.StartsWith("Arch", StringComparison.OrdinalIgnoreCase));
|
|
Assert.DoesNotContain(names, name => name.Contains("AspNet", StringComparison.OrdinalIgnoreCase));
|
|
Assert.DoesNotContain(names, name => name.Contains("Sockets", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
[Fact]
|
|
public void Sources_DoNotUseWallClock()
|
|
{
|
|
var root = Path.Combine(Fixtures.RepoRoot(), "src", "HSchool.People");
|
|
foreach (var path in Directory.EnumerateFiles(root, "*.cs"))
|
|
{
|
|
var text = File.ReadAllText(path);
|
|
Assert.DoesNotContain("DateTime.Now", text, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("DateTime.UtcNow", text, StringComparison.Ordinal);
|
|
}
|
|
}
|
|
|
|
private static void AssertFilled(Roster roster, int classrooms)
|
|
{
|
|
Assert.Equal(classrooms, roster.Classes.Count);
|
|
Assert.Equal(classrooms * 16, roster.People.Count(person => person.IsStudent));
|
|
Assert.All(roster.Classes, schoolClass =>
|
|
{
|
|
Assert.Equal(schoolClass.Capacity, schoolClass.PupilIds.Count);
|
|
Assert.InRange(schoolClass.Year, 1, 11);
|
|
});
|
|
|
|
var demand = SchoolDemand.From(Fixtures.Catalog(), Fixtures.Classrooms(classrooms));
|
|
Assert.Equal(0, roster.People.Count(person => person.IsStaff));
|
|
Assert.True(RosterFit.Matches(roster, demand));
|
|
}
|
|
|
|
private static string Snapshot(Roster roster) =>
|
|
string.Join('\n', roster.People.Select(person =>
|
|
$"{person.Id}|{person.FamilyId}|{person.Female}|{person.BirthDate:O}|{person.Name.Given}|{person.Name.Surname}|{person.Name.Patronymic}|{person.IsStudent}|{person.IsStaff}|{person.IsParent}|{person.ClassId}|{person.Position}|{person.Choices[BodyBuilds.Attribute]}|{Skills(person)}|{string.Join(',', person.Traits)}"));
|
|
|
|
private static string Skills(Person person) =>
|
|
string.Join(',', person.Skills.OrderBy(pair => pair.Key, StringComparer.Ordinal).Select(pair => $"{pair.Key}={pair.Value}"));
|
|
|
|
/// <summary>Who a family is, without where its children ended up sitting.</summary>
|
|
private static List<string> FamilyIdentities(Roster roster, int take)
|
|
{
|
|
var people = roster.People.ToDictionary(person => person.Id, StringComparer.Ordinal);
|
|
return roster.Families
|
|
.OrderBy(family => family.Id, StringComparer.Ordinal)
|
|
.Take(take)
|
|
.Select(family =>
|
|
{
|
|
var members = family.ParentIds.Concat(family.ChildIds).Select(id => people[id]);
|
|
return string.Join(';', members.Select(person =>
|
|
$"{person.Id}:{person.Name.Surname} {person.Name.Given} {person.Name.Patronymic}:{person.Female}"));
|
|
})
|
|
.ToList();
|
|
}
|
|
|
|
private static List<string> FamilySnapshots(Roster roster, int take)
|
|
{
|
|
var people = roster.People.ToDictionary(person => person.Id, StringComparer.Ordinal);
|
|
return roster.Families
|
|
.OrderBy(family => family.Id, StringComparer.Ordinal)
|
|
.Take(take)
|
|
.Select(family =>
|
|
{
|
|
var members = family.ParentIds.Concat(family.ChildIds).Select(id => people[id]);
|
|
return string.Join(';', members.Select(person =>
|
|
$"{person.Id}:{person.Name.Surname} {person.Name.Given} {person.Name.Patronymic}:{person.BirthDate:O}:{person.Female}:{Skills(person)}"));
|
|
})
|
|
.ToList();
|
|
}
|
|
}
|