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