package extraction import ( "context" "slices" "testing" ) func TestExtractResumeClearance(t *testing.T) { ex := NewDeterministic() cases := []struct { name string text string wantLevel string wantPoly string wantActive bool }{ {"ts sci slash active", "Active TS/SCI with full scope poly", "ts_sci", "full_scope", true}, {"ts sci spaced", "Holds TS SCI clearance", "ts_sci", "", true}, {"top secret only", "Top Secret clearance, CI poly", "top_secret", "ci", true}, {"secret", "Secret clearance, 10 years", "secret", "", true}, {"confidential", "Confidential clearance", "confidential", "", true}, {"expired vetoes active", "TS/SCI clearance expired in 2019", "ts_sci", "", false}, {"none", "Software engineer with no clearance listed here", "", "", false}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { rx, err := ex.ExtractResume(context.Background(), tc.text) if err != nil { t.Fatalf("ExtractResume: %v", err) } if rx.ClearanceLevel != tc.wantLevel { t.Errorf("level: got %q want %q", rx.ClearanceLevel, tc.wantLevel) } if rx.ClearancePoly != tc.wantPoly { t.Errorf("poly: got %q want %q", rx.ClearancePoly, tc.wantPoly) } if rx.ClearanceActive != tc.wantActive { t.Errorf("active: got %v want %v", rx.ClearanceActive, tc.wantActive) } }) } } func TestTopSecretIsNotDowngradedToSecret(t *testing.T) { rx, _ := NewDeterministic().ExtractResume(context.Background(), "Top Secret clearance held since 2015") if rx.ClearanceLevel != "top_secret" { t.Fatalf("expected top_secret, got %q (secret is a substring of top secret, order must guard it)", rx.ClearanceLevel) } } func TestExtractResumeSkillsCertsAgenciesYears(t *testing.T) { text := `Jane Q Operator Honolulu, HI Senior Cyber Engineer with 12 years of experience supporting the Air Force and DISA. Skills: Python, AWS, Kubernetes, Splunk, Incident Response, RMF. Certifications: CISSP, CompTIA Security+.` rx, err := NewDeterministic().ExtractResume(context.Background(), text) if err != nil { t.Fatalf("ExtractResume: %v", err) } if rx.YearsExperience != 12 { t.Errorf("years: got %d want 12", rx.YearsExperience) } if rx.Location != "Honolulu, HI" { t.Errorf("location: got %q want %q", rx.Location, "Honolulu, HI") } if rx.FullName != "Jane Q Operator" { t.Errorf("name: got %q want %q", rx.FullName, "Jane Q Operator") } for _, want := range []string{"Python", "AWS", "Kubernetes", "Splunk", "Incident Response", "RMF"} { if !slices.Contains(rx.Skills, want) { t.Errorf("skills missing %q; got %v", want, rx.Skills) } } for _, want := range []string{"CISSP", "CompTIA Security+"} { if !slices.Contains(rx.Certifications, want) { t.Errorf("certs missing %q; got %v", want, rx.Certifications) } } for _, want := range []string{"Air Force", "DISA"} { if !slices.Contains(rx.Agencies, want) { t.Errorf("agencies missing %q; got %v", want, rx.Agencies) } } if !rx.HasSignal() { t.Error("expected HasSignal true for a rich resume") } } func TestExtractRequirementFromJD(t *testing.T) { jd := `Cyber Engineer, requires an active Secret clearance and 8 years of experience. Required skills: Python, AWS, Splunk. Supporting the Space Force.` rx, err := NewDeterministic().ExtractRequirement(context.Background(), jd) if err != nil { t.Fatalf("ExtractRequirement: %v", err) } if rx.ClearanceLevel != "secret" { t.Errorf("required level: got %q want secret", rx.ClearanceLevel) } if rx.YearsExperience != 8 { t.Errorf("required years: got %d want 8", rx.YearsExperience) } if !slices.Contains(rx.Agencies, "Space Force") { t.Errorf("agencies missing Space Force; got %v", rx.Agencies) } for _, want := range []string{"Python", "AWS", "Splunk"} { if !slices.Contains(rx.Skills, want) { t.Errorf("required skills missing %q; got %v", want, rx.Skills) } } } // TestSkillBoundaryMatching covers the word boundary fix: Java is detected in the dominant comma, // period, and newline separated forms, is NOT confused with JavaScript, and common English words no // longer false positive the ATO acronym. func TestSkillBoundaryMatching(t *testing.T) { ex := NewDeterministic() javaForms := []string{ "Skills: Python, Java, AWS", "Languages:\nJava\nPython", "Proficient in Java.", "Java/Spring developer", } for _, text := range javaForms { rx, _ := ex.ExtractResume(context.Background(), text) if !slices.Contains(rx.Skills, "Java") { t.Errorf("Java not detected in %q; got %v", text, rx.Skills) } } // JavaScript must not yield the Java skill. rx, _ := ex.ExtractResume(context.Background(), "Senior JavaScript engineer building JavaScript apps") if slices.Contains(rx.Skills, "Java") { t.Errorf("JavaScript text wrongly yielded the Java skill: %v", rx.Skills) } if !slices.Contains(rx.Skills, "JavaScript") { t.Errorf("JavaScript not detected; got %v", rx.Skills) } // Common English words must not false positive the ATO acronym. rx, _ = ex.ExtractResume(context.Background(), "I love potato salad and staccato music, 5 years as a chef") if slices.Contains(rx.Skills, "ATO") { t.Errorf("potato/staccato wrongly yielded the ATO skill: %v", rx.Skills) } // The real ATO acronym is still detected. rx, _ = ex.ExtractResume(context.Background(), "Led the ATO package and the system authority to operate") if !slices.Contains(rx.Skills, "ATO") { t.Errorf("ATO acronym not detected; got %v", rx.Skills) } } // TestLocationRejectsRoleFragments covers the USPS state validation: a real location is kept and a // "Word, XX" role or org fragment is rejected. func TestLocationRejectsRoleFragments(t *testing.T) { ex := NewDeterministic() if rx, _ := ex.ExtractResume(context.Background(), "Manager, HR and Director, VP at a firm in Honolulu, HI"); rx.Location != "Honolulu, HI" { t.Errorf("location: got %q want Honolulu, HI (role fragments must be rejected)", rx.Location) } if rx, _ := ex.ExtractResume(context.Background(), "Senior Engineer, IT with no location listed"); rx.Location != "" { t.Errorf("location: got %q want empty (IT is not a state)", rx.Location) } } func TestEmptyTextHasNoSignal(t *testing.T) { rx, _ := NewDeterministic().ExtractResume(context.Background(), " \n hello world \n") if rx.HasSignal() { t.Fatalf("expected no signal from junk text, got %+v", rx) } }