- Updated `PeopleApiTests` to correctly handle family structures, ensuring tests account for single-parent scenarios and sorting behavior for surnames. - Added a new test to validate roster integrity after the yearly intake, confirming that the composition reflects changes post-intake. - Revised assertions to ensure accurate comparisons of student and family data, improving test reliability. - Enhanced documentation within tests to clarify the purpose and expected outcomes of new functionalities. - Addressed discrepancies between design and implementation regarding the layout of the people management interface.
144 lines
5.7 KiB
C#
144 lines
5.7 KiB
C#
namespace HSchool.People.Tests;
|
|
|
|
public class ApplicantPoolTests
|
|
{
|
|
[Fact]
|
|
public void SameSeedAndWeek_YieldTheSamePool()
|
|
{
|
|
var roster = Fixtures.Generate(Fixtures.VanillaMap());
|
|
var a = Create(roster);
|
|
var b = Create(roster);
|
|
|
|
Assert.Equal(Snapshot(a), Snapshot(b));
|
|
}
|
|
|
|
[Fact]
|
|
public void Advance_KeepsSomePeopleWithTheSameSkillsAndAsk()
|
|
{
|
|
var roster = Fixtures.Generate(Fixtures.VanillaMap());
|
|
var first = Create(roster);
|
|
var next = first.Advance(Fixtures.Catalog(), roster, Fixtures.SchoolSeed, "Slavic", Fixtures.AsOf.AddDays(7));
|
|
|
|
Assert.NotEqual(first.Week, next.Week);
|
|
var stayed = first.Applicants
|
|
.Join(
|
|
next.Applicants,
|
|
applicant => applicant.Person.Id,
|
|
applicant => applicant.Person.Id,
|
|
(before, after) => (before, after),
|
|
StringComparer.Ordinal)
|
|
.ToArray();
|
|
|
|
Assert.NotEmpty(stayed);
|
|
foreach (var (before, after) in stayed)
|
|
{
|
|
Assert.Equal(before.HourlyWageAsk, after.HourlyWageAsk);
|
|
Assert.Equal(Skills(before.Person), Skills(after.Person));
|
|
Assert.Equal(before.Person.Name.Full, after.Person.Name.Full);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void FiftyWeeks_StayAtPoolSize()
|
|
{
|
|
var catalog = Fixtures.Catalog();
|
|
var roster = Fixtures.Generate(Fixtures.VanillaMap());
|
|
var size = catalog.StaffingRules!.PoolSize;
|
|
var asOf = Fixtures.AsOf;
|
|
var pool = ApplicantPool.Create(catalog, roster, Fixtures.SchoolSeed, "Slavic", asOf);
|
|
|
|
for (var week = 0; week < 50; week++)
|
|
{
|
|
asOf = asOf.AddDays(7);
|
|
pool = pool.Advance(catalog, roster, Fixtures.SchoolSeed, "Slavic", asOf);
|
|
Assert.Equal(size, pool.Applicants.Count);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void StrongerSkills_AskForMore()
|
|
{
|
|
var catalog = Fixtures.Catalog();
|
|
var person = Fixtures.Generate(Fixtures.Classrooms(1)).People.First(candidate => candidate.IsParent);
|
|
var weak = person with { Skills = person.Skills.ToDictionary(pair => pair.Key, _ => 15) };
|
|
var strong = person with { Skills = person.Skills.ToDictionary(pair => pair.Key, _ => 90) };
|
|
|
|
Assert.True(ApplicantPool.HourlyAsk(catalog, strong) > ApplicantPool.HourlyAsk(catalog, weak));
|
|
}
|
|
|
|
/// <summary>
|
|
/// design/staffing.md prices an hour from skills *and* traits — the self-assured ask for more.
|
|
/// Skills are covered above; this pins the trait half, which core sets on Leader (+10),
|
|
/// HotTempered (+6) and Quiet (-8).
|
|
/// </summary>
|
|
[Fact]
|
|
public void ConfidentTraits_AskForMoreAndQuietOnesForLess()
|
|
{
|
|
var catalog = Fixtures.Catalog();
|
|
var person = Fixtures.Generate(Fixtures.Classrooms(1)).People.First(candidate => candidate.IsParent);
|
|
var plain = person with { Traits = [] };
|
|
var leader = person with { Traits = ["Leader"] };
|
|
var quiet = person with { Traits = ["Quiet"] };
|
|
|
|
var plainAsk = ApplicantPool.HourlyAsk(catalog, plain);
|
|
|
|
Assert.Equal(plainAsk + 10f, ApplicantPool.HourlyAsk(catalog, leader), 0.01f);
|
|
Assert.Equal(plainAsk - 8f, ApplicantPool.HourlyAsk(catalog, quiet), 0.01f);
|
|
}
|
|
|
|
[Fact]
|
|
public void GeneratedApplicants_AreNotOnTheRoster_ParentsKeepTheirId()
|
|
{
|
|
var roster = Fixtures.Generate(Fixtures.VanillaMap());
|
|
ApplicantPool? pool = null;
|
|
for (var seed = 1; seed <= 20 && pool is null; seed++)
|
|
{
|
|
var candidate = Fixtures.Generate(Fixtures.VanillaMap(), seed);
|
|
var created = ApplicantPool.Create(Fixtures.Catalog(), candidate, seed, "Slavic", Fixtures.AsOf);
|
|
if (created.Applicants.Any(applicant => candidate.People.Any(person => person.Id == applicant.Person.Id)))
|
|
{
|
|
roster = candidate;
|
|
pool = created;
|
|
}
|
|
}
|
|
|
|
Assert.NotNull(pool);
|
|
var rosterIds = roster.People.Select(person => person.Id).ToHashSet(StringComparer.Ordinal);
|
|
foreach (var applicant in pool.Applicants)
|
|
{
|
|
if (rosterIds.Contains(applicant.Person.Id))
|
|
{
|
|
Assert.Contains(roster.People, person => person.Id == applicant.Person.Id && person.IsParent && !person.IsStaff);
|
|
}
|
|
else
|
|
{
|
|
Assert.StartsWith("a", applicant.Person.Id, StringComparison.Ordinal);
|
|
Assert.False(applicant.Person.IsStaff);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void PeopleJson_RoundTripsThePool()
|
|
{
|
|
var roster = Fixtures.Generate(Fixtures.Classrooms(1));
|
|
var pool = Create(roster);
|
|
var json = RosterJson.Serialize(RosterDocument.From(Fixtures.SchoolSeed, roster, pool));
|
|
var loaded = RosterJson.Parse(json);
|
|
|
|
Assert.Equal(Snapshot(pool), Snapshot(loaded.Applicants!));
|
|
Assert.DoesNotContain(loaded.People, person => pool.Applicants.Any(applicant =>
|
|
applicant.Person.Id.StartsWith('a') && applicant.Person.Id == person.Id));
|
|
}
|
|
|
|
private static ApplicantPool Create(Roster roster) =>
|
|
ApplicantPool.Create(Fixtures.Catalog(), roster, Fixtures.SchoolSeed, "Slavic", Fixtures.AsOf);
|
|
|
|
private static string Snapshot(ApplicantPool pool) =>
|
|
string.Join('\n', pool.Applicants.Select(applicant =>
|
|
$"{pool.Week}|{pool.NextIndex}|{applicant.Person.Id}|{applicant.HourlyWageAsk:0.00}|{applicant.Person.Name.Full}|{Skills(applicant.Person)}"));
|
|
|
|
private static string Skills(Person person) =>
|
|
string.Join(',', person.Skills.OrderBy(pair => pair.Key, StringComparer.Ordinal).Select(pair => $"{pair.Key}={pair.Value}"));
|
|
}
|