//go:build integration // Cross opportunity collision deconfliction integration. Proves at runtime against a REAL Postgres what the // unit tests cannot: the alerts watch loop's fourth trigger detects a scarce cleared candidate proposed or // approved on the slates of two or more DISTINCT competing OPEN opportunities, surfaces ONE advisory alert per // collided candidate (urgent when scarce cleared or double committed) naming the competing bids and the // deterministic best fit suggested primary, is idempotent across sweeps, and is RLS isolated per org. It never // auto rejects a slate member. Migration 0040 (the collision alert_type) must be applied. // // Run: go test -tags=integration ./internal/integration/ -run Collision -v // Env: MM_APP_DSN, MM_MIGRATOR_DSN. package integration import ( "context" "strings" "testing" "github.com/google/uuid" "github.com/missionworkforce/missionmatch/internal/modules/alerts" ) // seedOpenOpportunityWithMember seeds one OPEN opportunity, a requirement under it, a slate for that // requirement, and the given candidate as an approved slate member with the given total score, via the migrator. // It returns the opportunity name so the test can assert the collision detail and suggested primary. func seedOpenOpportunityWithMember(t *testing.T, org, cand uuid.UUID, name string, total int) { t.Helper() ctx := context.Background() oppID := uuid.New() if _, err := migrator.Exec(ctx, `INSERT INTO opportunities (id, organization_id, name, status, due_date) VALUES ($1,$2,$3,'open', now() + interval '30 days')`, oppID, org, name); err != nil { t.Fatalf("seed opportunity: %v", err) } reqID := uuid.New() if _, err := migrator.Exec(ctx, `INSERT INTO requirements (id, organization_id, opportunity_id, title, required_level, required_poly) VALUES ($1,$2,$3,$4,'secret','none')`, reqID, org, oppID, "Req for "+name); err != nil { t.Fatalf("seed requirement: %v", err) } slateID := uuid.New() if _, err := migrator.Exec(ctx, `INSERT INTO slates (id, organization_id, requirement_id, name) VALUES ($1,$2,$3,$4)`, slateID, org, reqID, "Slate for "+name); err != nil { t.Fatalf("seed slate: %v", err) } band := "yellow" if total >= 90 { band = "green" } if _, err := migrator.Exec(ctx, `INSERT INTO slate_members (organization_id, slate_id, candidate_id, total_score, band, clearance_score, skills_score, certs_score, customer_score, experience_score, location_score, explanation, trace, decision, decided_at) VALUES ($1,$2,$3,$4,$5,100,80,80,80,80,80,'seed','[]','approved', now())`, org, slateID, cand, total, band); err != nil { t.Fatalf("seed slate member: %v", err) } } func TestCollisionDeconflictionSweepFlagsScarceDoubleCommitment(t *testing.T) { org := seedOrg(t) // A scarce cleared person: TS/SCI active. They are approved on TWO distinct open bids, Alpha (best fit) and // Beta, so this is both a scarce cleared collision and a true double commitment: urgent. cand := seedCandidate(t, org, "ts_sci", "active", true) seedOpenOpportunityWithMember(t, org, cand.ID, "Program Alpha", 92) // higher score => the best fit primary seedOpenOpportunityWithMember(t, org, cand.ID, "Program Beta", 74) svc := alerts.NewService(appDB, nil) sum, err := svc.SweepOrg(ctxOrg(org)) if err != nil { t.Fatalf("sweep: %v", err) } if sum.Collision < 1 { t.Fatalf("the sweep must detect at least one collision, got %d", sum.Collision) } // Find the collision alert for this candidate. list, err := svc.ListAlerts(ctxOrg(org), "") if err != nil { t.Fatalf("list alerts: %v", err) } var found *alerts.Alert for i := range list { if list[i].AlertType == alerts.TypeCollision && list[i].SubjectID == cand.ID.String() { found = &list[i] break } } if found == nil { t.Fatalf("a collision alert for the double committed candidate must exist, got %+v", list) } if found.Severity != alerts.SeverityUrgent { t.Errorf("a scarce cleared double commitment must be urgent, got %q", found.Severity) } if found.SubjectType != alerts.SubjectCandidate { t.Errorf("the collision subject must be the candidate, got %q", found.SubjectType) } if !strings.Contains(found.Title, "Scarce cleared") || !strings.Contains(found.Title, "2 competing") { t.Errorf("the title must name a scarce cleared person on 2 competing bids, got %q", found.Title) } // The detail names both bids, the deterministic best fit suggested primary (Alpha, the higher score), and // the clearance disclosure. It never auto rejects. for _, want := range []string{"Program Alpha", "Program Beta", "Suggested primary (best fit): Program Alpha", "not verified"} { if !strings.Contains(found.Detail, want) { t.Errorf("the collision detail must contain %q, got %q", want, found.Detail) } } // Idempotent: a second sweep upserts the same one row, no duplicate collision alert for this candidate. if _, err := svc.SweepOrg(ctxOrg(org)); err != nil { t.Fatalf("second sweep: %v", err) } list2, err := svc.ListAlerts(ctxOrg(org), "") if err != nil { t.Fatalf("list after second sweep: %v", err) } count := 0 for _, a := range list2 { if a.AlertType == alerts.TypeCollision && a.SubjectID == cand.ID.String() { count++ } } if count != 1 { t.Fatalf("the dedupe upsert must keep exactly one collision alert per candidate, got %d", count) } } func TestCollisionDeconflictionRLSIsolation(t *testing.T) { // Two orgs, each with its OWN scarce cleared candidate double committed across two of its OWN open bids. // Each org's sweep must surface only its own collision; neither org's candidate may leak into the other's // alert list (the RLS boundary the collision join must never cross). orgA, orgB := seedOrg(t), seedOrg(t) candA := seedCandidate(t, orgA, "ts_sci", "active", true) candB := seedCandidate(t, orgB, "ts_sci", "active", true) seedOpenOpportunityWithMember(t, orgA, candA.ID, "A One", 90) seedOpenOpportunityWithMember(t, orgA, candA.ID, "A Two", 70) seedOpenOpportunityWithMember(t, orgB, candB.ID, "B One", 90) seedOpenOpportunityWithMember(t, orgB, candB.ID, "B Two", 70) svc := alerts.NewService(appDB, nil) if _, err := svc.SweepOrg(ctxOrg(orgA)); err != nil { t.Fatalf("sweep org A: %v", err) } if _, err := svc.SweepOrg(ctxOrg(orgB)); err != nil { t.Fatalf("sweep org B: %v", err) } // Org A sees its own candidate's collision, and never org B's candidate. listA, err := svc.ListAlerts(ctxOrg(orgA), "") if err != nil { t.Fatalf("list org A: %v", err) } sawOwnA, sawForeignA := false, false for _, a := range listA { if a.AlertType != alerts.TypeCollision { continue } if a.SubjectID == candA.ID.String() { sawOwnA = true } if a.SubjectID == candB.ID.String() || strings.Contains(a.Detail, "B One") || strings.Contains(a.Detail, "B Two") { sawForeignA = true } } if !sawOwnA { t.Error("org A must see its own candidate's collision") } if sawForeignA { t.Error("ISOLATION BREACH: org A saw org B's collision candidate or bids") } // Org B symmetrically sees only its own. listB, err := svc.ListAlerts(ctxOrg(orgB), "") if err != nil { t.Fatalf("list org B: %v", err) } for _, a := range listB { if a.AlertType == alerts.TypeCollision && (a.SubjectID == candA.ID.String() || strings.Contains(a.Detail, "A One") || strings.Contains(a.Detail, "A Two")) { t.Error("ISOLATION BREACH: org B saw org A's collision candidate or bids") } } } func TestCollisionDeconflictionIgnoresSingleOpportunity(t *testing.T) { // A candidate on only ONE open opportunity is not a collision. org := seedOrg(t) cand := seedCandidate(t, org, "ts_sci", "active", true) seedOpenOpportunityWithMember(t, org, cand.ID, "Solo Program", 88) svc := alerts.NewService(appDB, nil) if _, err := svc.SweepOrg(ctxOrg(org)); err != nil { t.Fatalf("sweep: %v", err) } list, err := svc.ListAlerts(ctxOrg(org), "") if err != nil { t.Fatalf("list alerts: %v", err) } for _, a := range list { if a.AlertType == alerts.TypeCollision && a.SubjectID == cand.ID.String() { t.Fatalf("a candidate on a single opportunity must not be a collision, got %+v", a) } } }