//go:build integration package integration import ( "context" "testing" "github.com/google/uuid" "github.com/missionworkforce/missionmatch/internal/platform/database/dbgen" "github.com/missionworkforce/missionmatch/internal/platform/extraction" "github.com/missionworkforce/missionmatch/internal/platform/sourcing" ) func candidateOrgCount(t *testing.T, org uuid.UUID) int { t.Helper() var n int if err := migrator.QueryRow(context.Background(), "SELECT count(*) FROM candidates WHERE organization_id=$1 AND deleted_at IS NULL", org).Scan(&n); err != nil { t.Fatalf("candidate count: %v", err) } return n } func skillNamesFor(t *testing.T, candID uuid.UUID) map[string]bool { t.Helper() rows, err := migrator.Query(context.Background(), "SELECT s.name FROM candidate_skills cs JOIN skills s ON s.id=cs.skill_id WHERE cs.candidate_id=$1", candID) if err != nil { t.Fatalf("skill names: %v", err) } defer rows.Close() out := map[string]bool{} for rows.Next() { var n string _ = rows.Scan(&n) out[n] = true } return out } // TestSourcedIngestStructuredSkills proves a sourced lead becomes a real scoring input: structured skill // rows from the provider feed and an agency extracted from the profile text, on a freshly created person. func TestSourcedIngestStructuredSkills(t *testing.T) { ctx := context.Background() org := seedOrg(t) q := dbgen.New(migrator) ext := extraction.NewDeterministic() sp := sourcing.SourcedPerson{ ExternalID: "pdl-" + uuid.NewString(), FullName: "Ada Sourced", JobTitle: "Cyber Engineer supporting the Air Force", Location: "Honolulu, HI", Skills: []string{"Python", "AWS", "Splunk"}, Years: 9, } id, err := sourcing.IngestPerson(ctx, q, org, sp, ext) if err != nil { t.Fatalf("IngestPerson: %v", err) } if id == uuid.Nil || candidateOrgCount(t, org) != 1 { t.Fatalf("expected exactly one new candidate") } skills := skillNamesFor(t, id) for _, want := range []string{"Python", "AWS", "Splunk"} { if !skills[want] { t.Errorf("structured skill %q not written; got %v", want, skills) } } var agencyN int if err := migrator.QueryRow(ctx, "SELECT count(*) FROM candidate_customer_experience WHERE candidate_id=$1 AND agency='Air Force'", id).Scan(&agencyN); err != nil { t.Fatalf("agency count: %v", err) } if agencyN != 1 { t.Errorf("expected the Air Force agency extracted from the profile text, got %d", agencyN) } } // TestSourcedIngestDedupByEmail proves identity resolution merges a sourced lead into an existing person // with the same email instead of creating a duplicate, enriching them rather than clobbering. func TestSourcedIngestDedupByEmail(t *testing.T) { ctx := context.Background() org := seedOrg(t) q := dbgen.New(migrator) ext := extraction.NewDeterministic() email := "merge@example.com" bench, err := q.CreateCandidate(ctx, dbgen.CreateCandidateParams{ OrganizationID: org, FullName: "Bench Person", Email: &email, ClearanceLevel: "secret", ClearancePoly: "none", ClearanceStatus: "active", Available: true, }) if err != nil { t.Fatalf("seed bench candidate: %v", err) } before := candidateOrgCount(t, org) sp := sourcing.SourcedPerson{ ExternalID: "pdl-merge-1", FullName: "Bench Person (PDL)", Emails: []string{email}, Skills: []string{"Go"}, Years: 5, } id, err := sourcing.IngestPerson(ctx, q, org, sp, ext) if err != nil { t.Fatalf("IngestPerson: %v", err) } if id != bench.ID { t.Fatalf("expected merge into the existing person %s, got a different id %s", bench.ID, id) } if after := candidateOrgCount(t, org); after != before { t.Fatalf("dedup failed: candidate count went %d to %d (a duplicate was created)", before, after) } // Enriched: the external id is linked and the new skill is added, without clobbering the name. var extID, name string if err := migrator.QueryRow(ctx, "SELECT COALESCE(external_id,''), full_name FROM candidates WHERE id=$1", bench.ID).Scan(&extID, &name); err != nil { t.Fatalf("read merged: %v", err) } if extID != "pdl-merge-1" { t.Errorf("external id not linked on merge: %q", extID) } if name != "Bench Person" { t.Errorf("merge must not clobber the curated name, got %q", name) } if !skillNamesFor(t, bench.ID)["Go"] { t.Errorf("sourced skill not added on merge") } }