package engine import ( "context" "reflect" "strings" "testing" "github.com/shopspring/decimal" ) // ---------- test fixtures ---------- // perfectCandidate / perfectRequirement form a 100/100 Green match. Individual tests degrade one // dimension at a time so each sub-score is isolated. func perfectRequirement() Requirement { return Requirement{ ID: "req-1", RequiredLevel: ClearanceSecret, RequiredPoly: PolyNone, RequiredSkills: []RequiredSkill{ {SkillID: "sk-aws", Name: "AWS", Mandatory: true}, {SkillID: "sk-go", Name: "Go", Mandatory: true}, }, RequiredCerts: []string{"Security+"}, CustomerAgency: "DISA", RequiredYears: 5, WorkLocation: "Fort Meade, MD", RemoteAllowed: false, } } func perfectCandidate() Candidate { return Candidate{ ID: "cand-1", Clearance: Clearance{Level: ClearanceSecret, Poly: PolyNone, Status: StatusActive}, Skills: []CandidateSkill{ {SkillID: "sk-aws", Name: "AWS"}, {SkillID: "sk-go", Name: "Go"}, }, Certifications: []string{"Security+"}, CustomerExperience: []string{"DISA"}, YearsExperience: 7, Location: "Fort Meade, MD", Available: true, } } func dec(s string) decimal.Decimal { return decimal.RequireFromString(s) } func eq(t *testing.T, got decimal.Decimal, want string) { t.Helper() if !got.Equal(dec(want)) { t.Fatalf("got %s, want %s", got.String(), want) } } // ---------- happy path ---------- func TestPerfectMatchScores100Green(t *testing.T) { res := ScoreCandidate(context.Background(), perfectCandidate(), perfectRequirement(), nil) eq(t, res.Total, "100") if res.Band != BandGreen { t.Fatalf("band = %v, want Green", res.Band) } if len(res.GapSummary) != 0 { t.Fatalf("expected no gaps, got %v", res.GapSummary) } if len(res.Trace) != 6 { t.Fatalf("expected 6 trace rows, got %d", len(res.Trace)) } } // ---------- clearance hierarchy ---------- func TestClearanceHigherSatisfiesLower(t *testing.T) { c := perfectCandidate() c.Clearance.Level = ClearanceTSSCI // TS/SCI must satisfy a Secret requirement res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil) eq(t, res.Clearance, "100") } func TestClearanceLowerDoesNotSatisfyHigher(t *testing.T) { r := perfectRequirement() r.RequiredLevel = ClearanceTSSCI // require TS/SCI c := perfectCandidate() // holds only Secret res := ScoreCandidate(context.Background(), c, r, nil) eq(t, res.Clearance, "0") assertGapContains(t, res, "TS/SCI") } func TestExpiredClearanceScoresZero(t *testing.T) { c := perfectCandidate() c.Clearance.Status = StatusExpired res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil) eq(t, res.Clearance, "0") assertGapContains(t, res, "expired") } func TestUnknownClearanceScoresZero(t *testing.T) { c := perfectCandidate() c.Clearance = Clearance{} // zero value: Unknown level, no poly, unknown status res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil) eq(t, res.Clearance, "0") } func TestNoClearanceRequiredAlwaysFull(t *testing.T) { r := perfectRequirement() r.RequiredLevel = ClearanceNone c := perfectCandidate() c.Clearance = Clearance{} // even an unknown clearance passes when none is required res := ScoreCandidate(context.Background(), c, r, nil) eq(t, res.Clearance, "100") } func TestPolyGate(t *testing.T) { r := perfectRequirement() r.RequiredPoly = PolyCI // Candidate without poly fails the gate. c := perfectCandidate() res := ScoreCandidate(context.Background(), c, r, nil) eq(t, res.Clearance, "0") // Full-scope poly satisfies a CI-poly requirement. c.Clearance.Poly = PolyFullScope res = ScoreCandidate(context.Background(), c, r, nil) eq(t, res.Clearance, "100") } // ---------- skills ---------- func TestSkillsPartialFractionAndMandatoryGap(t *testing.T) { c := perfectCandidate() c.Skills = []CandidateSkill{{SkillID: "sk-aws", Name: "AWS"}} // missing mandatory Go res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil) eq(t, res.Skills, "50") // 1 of 2 assertGapContains(t, res, "Go") } func TestSkillsExactMatchByNameWhenNoID(t *testing.T) { r := perfectRequirement() r.RequiredSkills = []RequiredSkill{{Name: "kubernetes", Mandatory: true}} // no SkillID set c := perfectCandidate() c.Skills = []CandidateSkill{{Name: "Kubernetes"}} // case-insensitive name match res := ScoreCandidate(context.Background(), c, r, nil) eq(t, res.Skills, "100") if !res.MatchedSkills[0].Exact { t.Fatalf("expected exact name match") } } // fakeMatcher is a deterministic stand-in for the semantic embedding matcher. type fakeMatcher struct { hits map[string]struct { // keyed by required SkillID cand string sim string } } func (f fakeMatcher) Match(_ context.Context, required RequiredSkill, cands []CandidateSkill, threshold decimal.Decimal) (SkillMatch, bool) { h, ok := f.hits[required.SkillID] if !ok { return SkillMatch{}, false } sim := dec(h.sim) if sim.LessThan(threshold) { return SkillMatch{}, false } for _, cs := range cands { if cs.Name == h.cand { return SkillMatch{CandidateSkillID: cs.SkillID, CandidateName: cs.Name, Similarity: sim}, true } } return SkillMatch{}, false } func TestSemanticSkillMatchViaInjectedMatcher(t *testing.T) { r := perfectRequirement() r.RequiredSkills = []RequiredSkill{{SkillID: "sk-eks", Name: "Amazon EKS", Mandatory: true}} c := perfectCandidate() c.Skills = []CandidateSkill{{SkillID: "sk-k8s", Name: "Kubernetes"}} // not exact, but semantically adjacent fm := fakeMatcher{hits: map[string]struct { cand string sim string }{ "sk-eks": {cand: "Kubernetes", sim: "0.86"}, }} res := ScoreCandidate(context.Background(), c, r, fm) eq(t, res.Skills, "100") m := res.MatchedSkills[0] if m.Exact || !m.Matched { t.Fatalf("expected a non-exact matched skill, got %+v", m) } eq(t, m.Similarity, "0.86") if m.RequiredSkillName != "Amazon EKS" || m.CandidateName != "Kubernetes" { t.Fatalf("engine must own required-side identity: %+v", m) } } func TestSemanticBelowThresholdDoesNotMatch(t *testing.T) { r := perfectRequirement() r.RequiredSkills = []RequiredSkill{{SkillID: "sk-eks", Name: "Amazon EKS", Mandatory: true}} c := perfectCandidate() c.Skills = []CandidateSkill{{SkillID: "sk-k8s", Name: "Kubernetes"}} fm := fakeMatcher{hits: map[string]struct { cand string sim string }{ "sk-eks": {cand: "Kubernetes", sim: "0.79"}, // just below DefaultSemanticThreshold 0.80 }} res := ScoreCandidate(context.Background(), c, r, fm) eq(t, res.Skills, "0") assertGapContains(t, res, "Amazon EKS") } // ---------- certs / customer / experience / location ---------- func TestCertsFractionAndGap(t *testing.T) { r := perfectRequirement() r.RequiredCerts = []string{"Security+", "CISSP"} c := perfectCandidate() // holds only Security+ res := ScoreCandidate(context.Background(), c, r, nil) eq(t, res.Certifications, "50") assertGapContains(t, res, "CISSP") } func TestCustomerExperienceBinary(t *testing.T) { c := perfectCandidate() c.CustomerExperience = []string{"Army"} // not DISA res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil) eq(t, res.CustomerExperience, "0") c.CustomerExperience = []string{"disa"} // case-insensitive hit res = ScoreCandidate(context.Background(), c, perfectRequirement(), nil) eq(t, res.CustomerExperience, "100") } func TestExperiencePartial(t *testing.T) { c := perfectCandidate() c.YearsExperience = 4 // requirement is 5 res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil) eq(t, res.Experience, "80") } func TestLocationAvailabilityCombinations(t *testing.T) { r := perfectRequirement() // available + exact location = 100 res := ScoreCandidate(context.Background(), perfectCandidate(), r, nil) eq(t, res.Location, "100") // available + relocatable (wrong location) = (50 + 100)/2 = 75 c := perfectCandidate() c.Location = "Denver, CO" c.Relocatable = true res = ScoreCandidate(context.Background(), c, r, nil) eq(t, res.Location, "75") // unavailable + exact location = (100 + 0)/2 = 50 c = perfectCandidate() c.Available = false res = ScoreCandidate(context.Background(), c, r, nil) eq(t, res.Location, "50") // remote allowed + remote candidate = 100 location, available = 100 r.RemoteAllowed = true c = perfectCandidate() c.Location = "Anywhere" c.Remote = true res = ScoreCandidate(context.Background(), c, r, nil) eq(t, res.Location, "100") } // ---------- bands (boundary precision) ---------- func TestBandBoundaries(t *testing.T) { cases := []struct { total string want Band }{ {"0", BandRed}, {"74.99", BandRed}, {"75", BandYellow}, {"75.00", BandYellow}, {"89.99", BandYellow}, {"90", BandGreen}, {"90.00", BandGreen}, {"100", BandGreen}, } for _, tc := range cases { if got := bandFor(dec(tc.total)); got != tc.want { t.Errorf("bandFor(%s) = %v, want %v", tc.total, got, tc.want) } } } // ---------- determinism & weights ---------- func TestWeightsSumToOne(t *testing.T) { sum := WeightClearance.Add(WeightSkills).Add(WeightCerts). Add(WeightCustomer).Add(WeightExperience).Add(WeightLocation) if !sum.Equal(dec("1")) { t.Fatalf("weights sum to %s, want 1.00", sum.String()) } } func TestTraceWeightedSumEqualsTotal(t *testing.T) { res := ScoreCandidate(context.Background(), perfectCandidate(), perfectRequirement(), nil) sum := zero for _, c := range res.Trace { if !c.Weighted.Equal(c.Raw.Mul(c.Weight)) { t.Fatalf("trace row %s weighted %s != raw*weight", c.Component, c.Weighted) } sum = sum.Add(c.Weighted) } if !sum.Round(2).Equal(res.Total) { t.Fatalf("trace sum %s != total %s", sum.Round(2), res.Total) } } func TestDeterministicAndOrderIndependent(t *testing.T) { c := perfectCandidate() r := perfectRequirement() a := ScoreCandidate(context.Background(), c, r, nil) b := ScoreCandidate(context.Background(), c, r, nil) if !a.Total.Equal(b.Total) { t.Fatalf("non-deterministic total: %s vs %s", a.Total, b.Total) } // Shuffle input order; the normalized engine must produce the identical total and matched order. c2 := perfectCandidate() c2.Skills = []CandidateSkill{{SkillID: "sk-go", Name: "Go"}, {SkillID: "sk-aws", Name: "AWS"}} r2 := perfectRequirement() r2.RequiredSkills = []RequiredSkill{ {SkillID: "sk-go", Name: "Go", Mandatory: true}, {SkillID: "sk-aws", Name: "AWS", Mandatory: true}, } d := ScoreCandidate(context.Background(), c2, r2, nil) if !a.Total.Equal(d.Total) { t.Fatalf("order-dependent total: %s vs %s", a.Total, d.Total) } if len(a.MatchedSkills) != len(d.MatchedSkills) { t.Fatalf("matched length differs") } for i := range a.MatchedSkills { if a.MatchedSkills[i].RequiredSkillID != d.MatchedSkills[i].RequiredSkillID { t.Fatalf("matched skill order not normalized at %d: %s vs %s", i, a.MatchedSkills[i].RequiredSkillID, d.MatchedSkills[i].RequiredSkillID) } } } // ---------- fairness guard (AEDT): no demographic input may exist ---------- func TestCandidateHasNoDemographicFields(t *testing.T) { // Banned word-prefixes, matched against each camelCase token of a field name (whole-token, not // naive substring, "YearsExperience" must not trip on the "sex" hiding inside "years-experience"). banned := []string{ "race", "gender", "sex", "ethnic", "religio", "disab", "national", "origin", "marital", "dob", "birth", "pregnan", "veteran", "orientation", "color", "creed", "age", } tp := reflect.TypeOf(Candidate{}) for i := 0; i < tp.NumField(); i++ { for _, tok := range splitCamel(tp.Field(i).Name) { for _, b := range banned { if strings.HasPrefix(tok, b) { t.Fatalf("Candidate.%s token %q looks like a protected attribute (%q), AEDT fairness violation", tp.Field(i).Name, tok, b) } } } } } // splitCamel breaks "CustomerExperience" into ["customer","experience"] for token-level checks. func splitCamel(s string) []string { var tokens []string var cur strings.Builder for _, r := range s { if r >= 'A' && r <= 'Z' && cur.Len() > 0 { tokens = append(tokens, strings.ToLower(cur.String())) cur.Reset() } cur.WriteRune(r) } if cur.Len() > 0 { tokens = append(tokens, strings.ToLower(cur.String())) } return tokens } // Two candidates identical on every job-relevant field must score identically, the engine has no // hidden side channel. (There is no demographic field to vary, so this also documents that intent.) func TestIdenticalJobRelevantInputsScoreIdentically(t *testing.T) { c1 := perfectCandidate() c1.ID = "alice" c2 := perfectCandidate() c2.ID = "bob" r := perfectRequirement() if !ScoreCandidate(context.Background(), c1, r, nil).Total.Equal(ScoreCandidate(context.Background(), c2, r, nil).Total) { t.Fatalf("identical job-relevant inputs produced different scores") } } func assertGapContains(t *testing.T, res MatchResult, substr string) { t.Helper() for _, g := range res.GapSummary { if strings.Contains(g, substr) { return } } t.Fatalf("expected a gap containing %q, got %v", substr, res.GapSummary) }