Recovered source transcript
1package engine
2
3import (
4 "context"
5 "reflect"
6 "strings"
7 "testing"
8
9 "github.com/shopspring/decimal"
10)
11
12// ---------- test fixtures ----------
13
14// perfectCandidate / perfectRequirement form a 100/100 Green match. Individual tests degrade one
15// dimension at a time so each sub-score is isolated.
16func perfectRequirement() Requirement {
17 return Requirement{
18 ID: "req-1",
19 RequiredLevel: ClearanceSecret,
20 RequiredPoly: PolyNone,
21 RequiredSkills: []RequiredSkill{
22 {SkillID: "sk-aws", Name: "AWS", Mandatory: true},
23 {SkillID: "sk-go", Name: "Go", Mandatory: true},
24 },
25 RequiredCerts: []string{"Security+"},
26 CustomerAgency: "DISA",
27 RequiredYears: 5,
28 WorkLocation: "Fort Meade, MD",
29 RemoteAllowed: false,
30 }
31}
32
33func perfectCandidate() Candidate {
34 return Candidate{
35 ID: "cand-1",
36 Clearance: Clearance{Level: ClearanceSecret, Poly: PolyNone, Status: StatusActive},
37 Skills: []CandidateSkill{
38 {SkillID: "sk-aws", Name: "AWS"},
39 {SkillID: "sk-go", Name: "Go"},
40 },
41 Certifications: []string{"Security+"},
42 CustomerExperience: []string{"DISA"},
43 YearsExperience: 7,
44 Location: "Fort Meade, MD",
45 Available: true,
46 }
47}
48
49func dec(s string) decimal.Decimal { return decimal.RequireFromString(s) }
50
51func eq(t *testing.T, got decimal.Decimal, want string) {
52 t.Helper()
53 if !got.Equal(dec(want)) {
54 t.Fatalf("got %s, want %s", got.String(), want)
55 }
56}
57
58// ---------- happy path ----------
59
60func TestPerfectMatchScores100Green(t *testing.T) {
61 res := ScoreCandidate(context.Background(), perfectCandidate(), perfectRequirement(), nil)
62 eq(t, res.Total, "100")
63 if res.Band != BandGreen {
64 t.Fatalf("band = %v, want Green", res.Band)
65 }
66 if len(res.GapSummary) != 0 {
67 t.Fatalf("expected no gaps, got %v", res.GapSummary)
68 }
69 if len(res.Trace) != 6 {
70 t.Fatalf("expected 6 trace rows, got %d", len(res.Trace))
71 }
72}
73
74// ---------- clearance hierarchy ----------
75
76func TestClearanceHigherSatisfiesLower(t *testing.T) {
77 c := perfectCandidate()
78 c.Clearance.Level = ClearanceTSSCI // TS/SCI must satisfy a Secret requirement
79 res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil)
80 eq(t, res.Clearance, "100")
81}
82
83func TestClearanceLowerDoesNotSatisfyHigher(t *testing.T) {
84 r := perfectRequirement()
85 r.RequiredLevel = ClearanceTSSCI // require TS/SCI
86 c := perfectCandidate() // holds only Secret
87 res := ScoreCandidate(context.Background(), c, r, nil)
88 eq(t, res.Clearance, "0")
89 assertGapContains(t, res, "TS/SCI")
90}
91
92func TestExpiredClearanceScoresZero(t *testing.T) {
93 c := perfectCandidate()
94 c.Clearance.Status = StatusExpired
95 res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil)
96 eq(t, res.Clearance, "0")
97 assertGapContains(t, res, "expired")
98}
99
100func TestUnknownClearanceScoresZero(t *testing.T) {
101 c := perfectCandidate()
102 c.Clearance = Clearance{} // zero value: Unknown level, no poly, unknown status
103 res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil)
104 eq(t, res.Clearance, "0")
105}
106
107func TestNoClearanceRequiredAlwaysFull(t *testing.T) {
108 r := perfectRequirement()
109 r.RequiredLevel = ClearanceNone
110 c := perfectCandidate()
111 c.Clearance = Clearance{} // even an unknown clearance passes when none is required
112 res := ScoreCandidate(context.Background(), c, r, nil)
113 eq(t, res.Clearance, "100")
114}
115
116func TestPolyGate(t *testing.T) {
117 r := perfectRequirement()
118 r.RequiredPoly = PolyCI
119
120 // Candidate without poly fails the gate.
121 c := perfectCandidate()
122 res := ScoreCandidate(context.Background(), c, r, nil)
123 eq(t, res.Clearance, "0")
124
125 // Full-scope poly satisfies a CI-poly requirement.
126 c.Clearance.Poly = PolyFullScope
127 res = ScoreCandidate(context.Background(), c, r, nil)
128 eq(t, res.Clearance, "100")
129}
130
131// ---------- skills ----------
132
133func TestSkillsPartialFractionAndMandatoryGap(t *testing.T) {
134 c := perfectCandidate()
135 c.Skills = []CandidateSkill{{SkillID: "sk-aws", Name: "AWS"}} // missing mandatory Go
136 res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil)
137 eq(t, res.Skills, "50") // 1 of 2
138 assertGapContains(t, res, "Go")
139}
140
141func TestSkillsExactMatchByNameWhenNoID(t *testing.T) {
142 r := perfectRequirement()
143 r.RequiredSkills = []RequiredSkill{{Name: "kubernetes", Mandatory: true}} // no SkillID set
144 c := perfectCandidate()
145 c.Skills = []CandidateSkill{{Name: "Kubernetes"}} // case-insensitive name match
146 res := ScoreCandidate(context.Background(), c, r, nil)
147 eq(t, res.Skills, "100")
148 if !res.MatchedSkills[0].Exact {
149 t.Fatalf("expected exact name match")
150 }
151}
152
153// fakeMatcher is a deterministic stand-in for the semantic embedding matcher.
154type fakeMatcher struct {
155 hits map[string]struct { // keyed by required SkillID
156 cand string
157 sim string
158 }
159}
160
161func (f fakeMatcher) Match(_ context.Context, required RequiredSkill, cands []CandidateSkill, threshold decimal.Decimal) (SkillMatch, bool) {
162 h, ok := f.hits[required.SkillID]
163 if !ok {
164 return SkillMatch{}, false
165 }
166 sim := dec(h.sim)
167 if sim.LessThan(threshold) {
168 return SkillMatch{}, false
169 }
170 for _, cs := range cands {
171 if cs.Name == h.cand {
172 return SkillMatch{CandidateSkillID: cs.SkillID, CandidateName: cs.Name, Similarity: sim}, true
173 }
174 }
175 return SkillMatch{}, false
176}
177
178func TestSemanticSkillMatchViaInjectedMatcher(t *testing.T) {
179 r := perfectRequirement()
180 r.RequiredSkills = []RequiredSkill{{SkillID: "sk-eks", Name: "Amazon EKS", Mandatory: true}}
181 c := perfectCandidate()
182 c.Skills = []CandidateSkill{{SkillID: "sk-k8s", Name: "Kubernetes"}} // not exact, but semantically adjacent
183
184 fm := fakeMatcher{hits: map[string]struct {
185 cand string
186 sim string
187 }{
188 "sk-eks": {cand: "Kubernetes", sim: "0.86"},
189 }}
190
191 res := ScoreCandidate(context.Background(), c, r, fm)
192 eq(t, res.Skills, "100")
193 m := res.MatchedSkills[0]
194 if m.Exact || !m.Matched {
195 t.Fatalf("expected a non-exact matched skill, got %+v", m)
196 }
197 eq(t, m.Similarity, "0.86")
198 if m.RequiredSkillName != "Amazon EKS" || m.CandidateName != "Kubernetes" {
199 t.Fatalf("engine must own required-side identity: %+v", m)
200 }
201}
202
203func TestSemanticBelowThresholdDoesNotMatch(t *testing.T) {
204 r := perfectRequirement()
205 r.RequiredSkills = []RequiredSkill{{SkillID: "sk-eks", Name: "Amazon EKS", Mandatory: true}}
206 c := perfectCandidate()
207 c.Skills = []CandidateSkill{{SkillID: "sk-k8s", Name: "Kubernetes"}}
208
209 fm := fakeMatcher{hits: map[string]struct {
210 cand string
211 sim string
212 }{
213 "sk-eks": {cand: "Kubernetes", sim: "0.55"}, // just below DefaultSemanticThreshold 0.62 (in the unrelated band)
214 }}
215
216 res := ScoreCandidate(context.Background(), c, r, fm)
217 eq(t, res.Skills, "0")
218 assertGapContains(t, res, "Amazon EKS")
219}
220
221// ---------- certs / customer / experience / location ----------
222
223func TestCertsFractionAndGap(t *testing.T) {
224 r := perfectRequirement()
225 r.RequiredCerts = []string{"Security+", "CISSP"}
226 c := perfectCandidate() // holds only Security+
227 res := ScoreCandidate(context.Background(), c, r, nil)
228 eq(t, res.Certifications, "50")
229 assertGapContains(t, res, "CISSP")
230}
231
232func TestCustomerExperienceBinary(t *testing.T) {
233 c := perfectCandidate()
234 c.CustomerExperience = []string{"Army"} // not DISA
235 res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil)
236 eq(t, res.CustomerExperience, "0")
237
238 c.CustomerExperience = []string{"disa"} // case-insensitive hit
239 res = ScoreCandidate(context.Background(), c, perfectRequirement(), nil)
240 eq(t, res.CustomerExperience, "100")
241}
242
243func TestExperiencePartial(t *testing.T) {
244 c := perfectCandidate()
245 c.YearsExperience = 4 // requirement is 5
246 res := ScoreCandidate(context.Background(), c, perfectRequirement(), nil)
247 eq(t, res.Experience, "80")
248}
249
250func TestLocationAvailabilityCombinations(t *testing.T) {
251 r := perfectRequirement()
252
253 // available + exact location = 100
254 res := ScoreCandidate(context.Background(), perfectCandidate(), r, nil)
255 eq(t, res.Location, "100")
256
257 // available + relocatable (wrong location) = (50 + 100)/2 = 75
258 c := perfectCandidate()
259 c.Location = "Denver, CO"
260 c.Relocatable = true
261 res = ScoreCandidate(context.Background(), c, r, nil)
262 eq(t, res.Location, "75")
263
264 // unavailable + exact location = (100 + 0)/2 = 50
265 c = perfectCandidate()
266 c.Available = false
267 res = ScoreCandidate(context.Background(), c, r, nil)
268 eq(t, res.Location, "50")
269
270 // remote allowed + remote candidate = 100 location, available = 100
271 r.RemoteAllowed = true
272 c = perfectCandidate()
273 c.Location = "Anywhere"
274 c.Remote = true
275 res = ScoreCandidate(context.Background(), c, r, nil)
276 eq(t, res.Location, "100")
277}
278
279// ---------- bands (boundary precision) ----------
280
281func TestBandBoundaries(t *testing.T) {
282 cases := []struct {
283 total string
284 want Band
285 }{
286 {"0", BandRed},
287 {"74.99", BandRed},
288 {"75", BandYellow},
289 {"75.00", BandYellow},
290 {"89.99", BandYellow},
291 {"90", BandGreen},
292 {"90.00", BandGreen},
293 {"100", BandGreen},
294 }
295 for _, tc := range cases {
296 if got := bandFor(dec(tc.total)); got != tc.want {
297 t.Errorf("bandFor(%s) = %v, want %v", tc.total, got, tc.want)
298 }
299 }
300}
301
302// ---------- determinism & weights ----------
303
304func TestWeightsSumToOne(t *testing.T) {
305 sum := WeightClearance.Add(WeightSkills).Add(WeightCerts).
306 Add(WeightCustomer).Add(WeightExperience).Add(WeightLocation)
307 if !sum.Equal(dec("1")) {
308 t.Fatalf("weights sum to %s, want 1.00", sum.String())
309 }
310}
311
312func TestTraceWeightedSumEqualsTotal(t *testing.T) {
313 res := ScoreCandidate(context.Background(), perfectCandidate(), perfectRequirement(), nil)
314 sum := zero
315 for _, c := range res.Trace {
316 if !c.Weighted.Equal(c.Raw.Mul(c.Weight)) {
317 t.Fatalf("trace row %s weighted %s != raw*weight", c.Component, c.Weighted)
318 }
319 sum = sum.Add(c.Weighted)
320 }
321 if !sum.Round(2).Equal(res.Total) {
322 t.Fatalf("trace sum %s != total %s", sum.Round(2), res.Total)
323 }
324}
325
326func TestDeterministicAndOrderIndependent(t *testing.T) {
327 c := perfectCandidate()
328 r := perfectRequirement()
329
330 a := ScoreCandidate(context.Background(), c, r, nil)
331 b := ScoreCandidate(context.Background(), c, r, nil)
332 if !a.Total.Equal(b.Total) {
333 t.Fatalf("non-deterministic total: %s vs %s", a.Total, b.Total)
334 }
335
336 // Shuffle input order; the normalized engine must produce the identical total and matched order.
337 c2 := perfectCandidate()
338 c2.Skills = []CandidateSkill{{SkillID: "sk-go", Name: "Go"}, {SkillID: "sk-aws", Name: "AWS"}}
339 r2 := perfectRequirement()
340 r2.RequiredSkills = []RequiredSkill{
341 {SkillID: "sk-go", Name: "Go", Mandatory: true},
342 {SkillID: "sk-aws", Name: "AWS", Mandatory: true},
343 }
344 d := ScoreCandidate(context.Background(), c2, r2, nil)
345 if !a.Total.Equal(d.Total) {
346 t.Fatalf("order-dependent total: %s vs %s", a.Total, d.Total)
347 }
348 if len(a.MatchedSkills) != len(d.MatchedSkills) {
349 t.Fatalf("matched length differs")
350 }
351 for i := range a.MatchedSkills {
352 if a.MatchedSkills[i].RequiredSkillID != d.MatchedSkills[i].RequiredSkillID {
353 t.Fatalf("matched skill order not normalized at %d: %s vs %s",
354 i, a.MatchedSkills[i].RequiredSkillID, d.MatchedSkills[i].RequiredSkillID)
355 }
356 }
357}
358
359// ---------- fairness guard (AEDT): no demographic input may exist ----------
360
361func TestCandidateHasNoDemographicFields(t *testing.T) {
362 // Banned word-prefixes, matched against each camelCase token of a field name (whole-token, not
363 // naive substring, "YearsExperience" must not trip on the "sex" hiding inside "years-experience").
364 banned := []string{
365 "race", "gender", "sex", "ethnic", "religio", "disab", "national",
366 "origin", "marital", "dob", "birth", "pregnan", "veteran",
367 "orientation", "color", "creed", "age",
368 }
369 tp := reflect.TypeOf(Candidate{})
370 for i := 0; i < tp.NumField(); i++ {
371 for _, tok := range splitCamel(tp.Field(i).Name) {
372 for _, b := range banned {
373 if strings.HasPrefix(tok, b) {
374 t.Fatalf("Candidate.%s token %q looks like a protected attribute (%q), AEDT fairness violation",
375 tp.Field(i).Name, tok, b)
376 }
377 }
378 }
379 }
380}
381
382// splitCamel breaks "CustomerExperience" into ["customer","experience"] for token-level checks.
383func splitCamel(s string) []string {
384 var tokens []string
385 var cur strings.Builder
386 for _, r := range s {
387 if r >= 'A' && r <= 'Z' && cur.Len() > 0 {
388 tokens = append(tokens, strings.ToLower(cur.String()))
389 cur.Reset()
390 }
391 cur.WriteRune(r)
392 }
393 if cur.Len() > 0 {
394 tokens = append(tokens, strings.ToLower(cur.String()))
395 }
396 return tokens
397}
398
399// Two candidates identical on every job-relevant field must score identically, the engine has no
400// hidden side channel. (There is no demographic field to vary, so this also documents that intent.)
401func TestIdenticalJobRelevantInputsScoreIdentically(t *testing.T) {
402 c1 := perfectCandidate()
403 c1.ID = "alice"
404 c2 := perfectCandidate()
405 c2.ID = "bob"
406 r := perfectRequirement()
407 if !ScoreCandidate(context.Background(), c1, r, nil).Total.Equal(ScoreCandidate(context.Background(), c2, r, nil).Total) {
408 t.Fatalf("identical job-relevant inputs produced different scores")
409 }
410}
411
412func assertGapContains(t *testing.T, res MatchResult, substr string) {
413 t.Helper()
414 for _, g := range res.GapSummary {
415 if strings.Contains(g, substr) {
416 return
417 }
418 }
419 t.Fatalf("expected a gap containing %q, got %v", substr, res.GapSummary)
420}
421