213 lines
10 KiB
C#
213 lines
10 KiB
C#
namespace HSchool.People.Tests;
|
|
|
|
public class StaffingTests
|
|
{
|
|
private const float Cap = 10_000f;
|
|
|
|
[Fact]
|
|
public void MonthlyPay_AddsSurchargeAfterTheFirstSubject()
|
|
{
|
|
var rules = Fixtures.Catalog().StaffingRules!;
|
|
|
|
Assert.Equal(4_000f, Staffing.MonthlyBase(rules, 50f));
|
|
Assert.Equal(4_000f, Staffing.MonthlyPay(rules, 50f, 0));
|
|
Assert.Equal(4_000f, Staffing.MonthlyPay(rules, 50f, 1));
|
|
Assert.Equal(5_000f, Staffing.MonthlyPay(rules, 50f, 2));
|
|
Assert.Equal(6_000f, Staffing.MonthlyPay(rules, 50f, 3));
|
|
}
|
|
|
|
[Fact]
|
|
public void Hire_TwoPeopleAtThreeAndSevenThousand_RejectsTheThird()
|
|
{
|
|
var (catalog, map, roster, pool) = Fresh();
|
|
var cheap = WithAsk(pool.Applicants[0], 37.5f);
|
|
var expensive = WithAsk(pool.Applicants[1], 87.5f);
|
|
var extra = WithAsk(pool.Applicants[2], 40f);
|
|
pool = pool with { Applicants = [cheap, expensive, extra] };
|
|
|
|
var first = Staffing.Hire(catalog, map, roster, pool, cheap.Person.Id, Staffing.TeacherPosition, Cap);
|
|
Assert.Equal(StaffingError.None, first.Error);
|
|
Assert.Equal(3_000f, first.Payroll);
|
|
|
|
var second = Staffing.Hire(catalog, map, first.Roster, first.Pool, expensive.Person.Id, Staffing.TeacherPosition, Cap);
|
|
Assert.Equal(StaffingError.None, second.Error);
|
|
Assert.Equal(10_000f, second.Payroll);
|
|
Assert.Equal(0f, second.Remaining);
|
|
|
|
var third = Staffing.Hire(catalog, map, second.Roster, second.Pool, extra.Person.Id, Staffing.TeacherPosition, Cap);
|
|
Assert.Equal(StaffingError.PayrollExceeded, third.Error);
|
|
Assert.Equal(10_000f, third.Payroll);
|
|
Assert.True(third.Attempted > Cap);
|
|
Assert.Equal(2, second.Roster.People.Count(person => person.IsStaff));
|
|
Assert.DoesNotContain(third.Roster.People, person => person.Id == extra.Person.Id && person.IsStaff);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnassignSubject_LowersPayrollSoAnotherHireFits()
|
|
{
|
|
var (catalog, map, roster, pool) = Fresh();
|
|
var first = WithAsk(pool.Applicants[0], 37.5f);
|
|
var second = WithAsk(pool.Applicants[1], 50f);
|
|
pool = pool with { Applicants = [first, second, .. pool.Applicants.Skip(2)] };
|
|
|
|
var hired = Staffing.Hire(catalog, map, roster, pool, first.Person.Id, Staffing.TeacherPosition, Cap);
|
|
Assert.Equal(StaffingError.None, hired.Error);
|
|
|
|
var loaded = hired;
|
|
foreach (var subject in new[] { "Mathematics", "RussianLanguage", "Literature", "History", "Biology", "ForeignLanguage" })
|
|
{
|
|
loaded = Staffing.AssignSubject(catalog, loaded.Roster, loaded.Pool, first.Person.Id, subject, Cap);
|
|
Assert.Equal(StaffingError.None, loaded.Error);
|
|
}
|
|
|
|
Assert.Equal(6_750f, loaded.Payroll);
|
|
|
|
var blocked = Staffing.Hire(catalog, map, loaded.Roster, loaded.Pool, second.Person.Id, Staffing.TeacherPosition, Cap);
|
|
Assert.Equal(StaffingError.PayrollExceeded, blocked.Error);
|
|
|
|
var dropped = Staffing.UnassignSubject(catalog, loaded.Roster, loaded.Pool, first.Person.Id, "Biology", Cap);
|
|
Assert.Equal(StaffingError.None, dropped.Error);
|
|
Assert.Equal(6_000f, dropped.Payroll);
|
|
|
|
var fitted = Staffing.Hire(catalog, map, dropped.Roster, dropped.Pool, second.Person.Id, Staffing.TeacherPosition, Cap);
|
|
Assert.Equal(StaffingError.None, fitted.Error);
|
|
Assert.Equal(10_000f, fitted.Payroll);
|
|
}
|
|
|
|
[Fact]
|
|
public void AssignSubject_ThatWouldExceedTheCap_IsRejected()
|
|
{
|
|
var (catalog, map, roster, pool) = Fresh();
|
|
var applicant = WithAsk(pool.Applicants[0], 50f);
|
|
pool = pool with { Applicants = [applicant, .. pool.Applicants.Skip(1)] };
|
|
|
|
var hired = Staffing.Hire(catalog, map, roster, pool, applicant.Person.Id, Staffing.TeacherPosition, Cap);
|
|
var loaded = hired;
|
|
foreach (var subject in new[] { "Mathematics", "RussianLanguage", "Literature", "History", "Biology", "Physics", "Chemistry" })
|
|
{
|
|
loaded = Staffing.AssignSubject(catalog, loaded.Roster, loaded.Pool, applicant.Person.Id, subject, Cap);
|
|
Assert.Equal(StaffingError.None, loaded.Error);
|
|
}
|
|
|
|
Assert.Equal(10_000f, loaded.Payroll);
|
|
|
|
var rejected = Staffing.AssignSubject(catalog, loaded.Roster, loaded.Pool, applicant.Person.Id, "Geography", Cap);
|
|
Assert.Equal(StaffingError.PayrollExceeded, rejected.Error);
|
|
Assert.Equal(10_000f, rejected.Payroll);
|
|
Assert.Equal(11_000f, rejected.Attempted);
|
|
Assert.Equal(0f, rejected.Remaining);
|
|
Assert.DoesNotContain(
|
|
rejected.Roster.People.Single(person => person.Id == applicant.Person.Id).Subjects,
|
|
name => name == "Geography");
|
|
}
|
|
|
|
[Fact]
|
|
public void Hire_ParentKeepsTheSameId_GeneratedJoinsTheRoster()
|
|
{
|
|
var catalog = Fixtures.Catalog();
|
|
var map = Fixtures.VanillaMap();
|
|
Roster? roster = null;
|
|
Applicant? parent = null;
|
|
Applicant? generated = null;
|
|
ApplicantPool? pool = null;
|
|
for (var seed = 1; seed <= 30 && pool is null; seed++)
|
|
{
|
|
var candidate = Fixtures.Generate(map, seed);
|
|
var created = ApplicantPool.Create(catalog, candidate, seed, "Slavic", Fixtures.AsOf);
|
|
var rosterIds = candidate.People.Select(person => person.Id).ToHashSet(StringComparer.Ordinal);
|
|
var parentHere = created.Applicants.FirstOrDefault(applicant => rosterIds.Contains(applicant.Person.Id));
|
|
var generatedHere = created.Applicants.FirstOrDefault(applicant => applicant.Person.Id.StartsWith('a'));
|
|
if (parentHere is not null && generatedHere is not null)
|
|
{
|
|
roster = candidate;
|
|
pool = created;
|
|
parent = parentHere;
|
|
generated = generatedHere;
|
|
}
|
|
}
|
|
|
|
Assert.NotNull(roster);
|
|
Assert.NotNull(pool);
|
|
Assert.NotNull(parent);
|
|
Assert.NotNull(generated);
|
|
|
|
var afterParent = Staffing.Hire(catalog, map, roster, pool, parent.Person.Id, Staffing.TeacherPosition, 50_000f);
|
|
Assert.Equal(StaffingError.None, afterParent.Error);
|
|
var staffParent = afterParent.Roster.People.Single(person => person.Id == parent.Person.Id);
|
|
Assert.True(staffParent.IsParent);
|
|
Assert.True(staffParent.IsStaff);
|
|
Assert.Equal(1, afterParent.Roster.People.Count(person => person.Id == parent.Person.Id));
|
|
Assert.DoesNotContain(afterParent.Pool.Applicants, applicant => applicant.Person.Id == parent.Person.Id);
|
|
|
|
var afterGenerated = Staffing.Hire(catalog, map, afterParent.Roster, afterParent.Pool, generated.Person.Id, Staffing.TeacherPosition, 50_000f);
|
|
Assert.Equal(StaffingError.None, afterGenerated.Error);
|
|
Assert.Contains(afterGenerated.Roster.People, person => person.Id == generated.Person.Id && person.IsStaff && !person.IsParent);
|
|
Assert.Contains(afterGenerated.Roster.Families, family => family.Id == generated.Person.FamilyId);
|
|
|
|
var again = Staffing.Hire(catalog, map, afterGenerated.Roster, afterGenerated.Pool, parent.Person.Id, Staffing.TeacherPosition, 50_000f);
|
|
Assert.Equal(StaffingError.AlreadyHired, again.Error);
|
|
}
|
|
|
|
[Fact]
|
|
public void Uncovered_ListsSubjectsTaughtInExistingYearsWithoutATeacher()
|
|
{
|
|
var (catalog, map, roster, pool) = Fresh();
|
|
var uncovered = Staffing.Uncovered(catalog, roster);
|
|
Assert.Contains(uncovered, subject => subject.DefName == "PrimarySchool");
|
|
Assert.Contains(uncovered, subject => subject.DefName == "Mathematics");
|
|
Assert.Contains(uncovered, subject => subject.DefName == "PhysicalEducation");
|
|
Assert.Equal(catalog.Subjects.Values.Count(subject => !subject.Abstract), uncovered.Count);
|
|
|
|
var applicant = pool.Applicants[0];
|
|
var hired = Staffing.Hire(catalog, map, roster, pool, applicant.Person.Id, Staffing.TeacherPosition, Cap);
|
|
var assigned = Staffing.AssignSubject(catalog, hired.Roster, hired.Pool, applicant.Person.Id, "Mathematics", Cap);
|
|
|
|
Assert.DoesNotContain(Staffing.Uncovered(catalog, assigned.Roster), subject => subject.DefName == "Mathematics");
|
|
Assert.Contains(Staffing.Uncovered(catalog, assigned.Roster), subject => subject.DefName == "PrimarySchool");
|
|
}
|
|
|
|
[Fact]
|
|
public void PeopleJson_RoundTripsHiredStaffAndSubjects()
|
|
{
|
|
var (catalog, map, roster, pool) = Fresh();
|
|
var applicant = pool.Applicants[0];
|
|
var hired = Staffing.Hire(catalog, map, roster, pool, applicant.Person.Id, Staffing.TeacherPosition, Cap);
|
|
var assigned = Staffing.AssignSubject(catalog, hired.Roster, hired.Pool, applicant.Person.Id, "Mathematics", Cap);
|
|
|
|
var json = RosterJson.Serialize(RosterDocument.From(Fixtures.SchoolSeed, assigned.Roster, assigned.Pool));
|
|
var loaded = RosterJson.Parse(json);
|
|
var person = loaded.People.Single(member => member.Id == applicant.Person.Id);
|
|
|
|
Assert.True(person.IsStaff);
|
|
Assert.Equal(Staffing.TeacherPosition, person.Position);
|
|
Assert.Equal(applicant.HourlyWageAsk, person.HourlyWageAsk);
|
|
Assert.Equal(["Mathematics"], person.Subjects);
|
|
Assert.DoesNotContain(loaded.Applicants!.Applicants, candidate => candidate.Person.Id == applicant.Person.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void Hire_UnknownApplicantAndUnknownPosition()
|
|
{
|
|
var (catalog, map, roster, pool) = Fresh();
|
|
|
|
Assert.Equal(
|
|
StaffingError.UnknownApplicant,
|
|
Staffing.Hire(catalog, map, roster, pool, "nobody", Staffing.TeacherPosition, Cap).Error);
|
|
Assert.Equal(
|
|
StaffingError.UnknownPosition,
|
|
Staffing.Hire(catalog, map, roster, pool, pool.Applicants[0].Person.Id, "Janitor", Cap).Error);
|
|
}
|
|
|
|
private static (DefCatalog Catalog, MapLayout Map, Roster Roster, ApplicantPool Pool) Fresh()
|
|
{
|
|
var catalog = Fixtures.Catalog();
|
|
var map = Fixtures.VanillaMap();
|
|
var roster = Fixtures.Generate(map);
|
|
var pool = ApplicantPool.Create(catalog, roster, Fixtures.SchoolSeed, "Slavic", Fixtures.AsOf);
|
|
return (catalog, map, roster, pool);
|
|
}
|
|
|
|
private static Applicant WithAsk(Applicant applicant, float hourlyAsk) =>
|
|
applicant with { HourlyWageAsk = hourlyAsk };
|
|
}
|