package recruiting import "testing" // The rules that decide a desk move are pure, so they are provable here rather than only reachable by // logging in as a manager and moving a real person's book. Every one of these was planted as a positive // control first: the assertion was inverted, the test was watched to fail, and only then corrected. func TestAnEmptyIncludeMeansTheWholeDesk(t *testing.T) { // A manager whose recruiter has resigned sends no include list. Reading that as "move nothing" would // return a cheerful zero and leave the whole book pointing at somebody who has left. got, err := normalizeKinds(nil) if err != nil { t.Fatalf("nil include should be accepted: %v", err) } if len(got) != len(MovableKinds) { t.Fatalf("nil include should mean every kind, got %v", got) } got, err = normalizeKinds([]string{"", " "}) if err != nil { t.Fatalf("blank include should be accepted: %v", err) } if len(got) != len(MovableKinds) { t.Fatalf("blank include should mean every kind, got %v", got) } } func TestAnUnknownKindIsRefusedRatherThanIgnored(t *testing.T) { // Silently dropping a kind it does not recognise is how a move reports success having skipped the // thing the manager most wanted moved. if _, err := normalizeKinds([]string{"clients", "candidates"}); err == nil { t.Fatal("an unknown kind must be refused, not dropped") } var ve *ValidationError _, err := normalizeKinds([]string{"invoices"}) if !asValidation(err, &ve) { t.Fatalf("want a ValidationError naming the field, got %T", err) } if ve.Field != "include" { t.Fatalf("the error should name include, got %q", ve.Field) } } func TestKindsAreDeduplicatedSoNothingMovesTwice(t *testing.T) { got, err := normalizeKinds([]string{"clients", "clients", "CLIENTS", " clients "}) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(got) != 1 || got[0] != "clients" { t.Fatalf("repeats and casing should collapse to one kind, got %v", got) } } func TestTheCountAndTheMoveDescribeTheSameSet(t *testing.T) { // The ceiling refuses on a number produced by countSQL and the records that would actually change // hands are chosen by moveOwnership. If those two ever describe different sets, a manager is refused // on a number that is not the number, or worse, permitted on one. // // This is the specific trap that made the alert vocabulary drift earlier in this build: two places // holding the same rule, edited one at a time. for _, kind := range MovableKinds { where := whereSQL(kind) if !contains(countSQL(kind), where) { t.Fatalf("%s: the count query must be built from the shared predicate", kind) } } // Closed applications are deliberately excluded from both. appWhere := whereSQL("applications") for _, stage := range []string{"placed", "rejected", "withdrawn"} { if !contains(appWhere, stage) { t.Fatalf("applications should leave %s work where it is, predicate was %q", stage, appWhere) } } } func TestOnlyTheThreeNamedKindsAreMovable(t *testing.T) { // Candidates carry owner_user_id too and are deliberately NOT in this list. A candidate is a person // the firm has a relationship with rather than a unit of work on a desk, and sweeping the entire // candidate database from one recruiter to another as a side effect of a resignation is not what // anybody means by moving a desk. if len(MovableKinds) != 3 { t.Fatalf("the movable vocabulary changed to %v, which needs a deliberate decision", MovableKinds) } for _, want := range []string{"clients", "requisitions", "applications"} { if !isMovableKind(want) { t.Fatalf("%s should be movable", want) } } if isMovableKind("candidates") { t.Fatal("candidates are not desk work, moving them wholesale is a different decision") } } func asValidation(err error, out **ValidationError) bool { ve, ok := err.(*ValidationError) if ok { *out = ve } return ok }