//go:build integration package integration import ( "context" "testing" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/missionworkforce/missionmatch/internal/modules/matching" "github.com/missionworkforce/missionmatch/internal/platform/database/dbgen" ) // TestScreenPersistsEvaluationUpsertLatest proves Workstream B's persistence contract: a screenMATCH // qualification writes a candidate_evaluations row that missionMATCH can read instead of recomputing, // and a rescreen UPSERTS the same pair (latest wins) rather than appending. The frozen requirement is // Secret, 10 years, DISA customer, Fort Meade. The candidate starts at 75.00 Yellow (5 of 10 years, no // DISA, wrong fixed location) and, after enrichment to 10 years plus DISA history, rescreens to 95.00 // Green, with exactly one row for the pair throughout. func TestScreenPersistsEvaluationUpsertLatest(t *testing.T) { ctx := context.Background() org := seedOrg(t) agency := "DISA" location := "Fort Meade, MD" req, err := dbgen.New(migrator).CreateRequirement(ctx, dbgen.CreateRequirementParams{ OrganizationID: org, Title: "Persisted Screen Role", RequiredLevel: "secret", RequiredPoly: "none", RequiredYears: 10, CustomerAgency: &agency, WorkLocation: &location, RemoteAllowed: false, }) if err != nil { t.Fatalf("seed requirement: %v", err) } cand := seedCandidate(t, org, "secret", "active", true) if _, err := migrator.Exec(ctx, "UPDATE candidates SET years_experience=5 WHERE id=$1", cand.ID); err != nil { t.Fatalf("set years: %v", err) } svc := matching.NewService(appDB, nil, nil) // First screen: 75.00 Yellow, one persisted row. if _, err := svc.ScreenCandidate(ctxOrg(org), cand.ID, req.ID); err != nil { t.Fatalf("first ScreenCandidate: %v", err) } row := getEvaluation(t, org, cand.ID, req.ID) if row.TotalScore.StringFixed(2) != "75.00" || row.Band != "yellow" { t.Fatalf("first persisted evaluation = %s/%s, want 75.00/yellow", row.TotalScore.StringFixed(2), row.Band) } if got := countEvaluations(t, org, cand.ID, req.ID); got != 1 { t.Fatalf("after first screen want 1 row, got %d", got) } firstEvaluatedAt := row.EvaluatedAt // Enrich the candidate so the score changes, then rescreen: the SAME pair must update to 95.00 Green. if _, err := migrator.Exec(ctx, "UPDATE candidates SET years_experience=10 WHERE id=$1", cand.ID); err != nil { t.Fatalf("bump years: %v", err) } if err := dbgen.New(migrator).AddCandidateCustomerExperience(ctx, dbgen.AddCandidateCustomerExperienceParams{ OrganizationID: org, CandidateID: cand.ID, Agency: "DISA", }); err != nil { t.Fatalf("add customer experience: %v", err) } // Rescreen through the Screen view path (the other persisting entry point) to also prove both paths // write to the one row. if _, err := svc.Screen(ctxOrg(org), cand.ID, req.ID); err != nil { t.Fatalf("rescreen Screen: %v", err) } row2 := getEvaluation(t, org, cand.ID, req.ID) if row2.TotalScore.StringFixed(2) != "95.00" || row2.Band != "green" { t.Fatalf("rescreened evaluation = %s/%s, want 95.00/green", row2.TotalScore.StringFixed(2), row2.Band) } if got := countEvaluations(t, org, cand.ID, req.ID); got != 1 { t.Fatalf("after rescreen want 1 row (upsert), got %d", got) } // evaluated_at is the first screen time, preserved; updated_at advances on rescreen. if !row2.EvaluatedAt.Equal(firstEvaluatedAt) { t.Fatalf("evaluated_at must be preserved across rescreen: first %v, now %v", firstEvaluatedAt, row2.EvaluatedAt) } if !row2.UpdatedAt.After(row2.EvaluatedAt) && !row2.UpdatedAt.Equal(row2.EvaluatedAt) { t.Fatalf("updated_at %v should be at or after evaluated_at %v", row2.UpdatedAt, row2.EvaluatedAt) } } func getEvaluation(t *testing.T, org, candID, reqID uuid.UUID) dbgen.CandidateEvaluation { t.Helper() var row dbgen.CandidateEvaluation if err := appDB.RunInTenantTx(ctxOrg(org), func(ctx context.Context, tx pgx.Tx) error { r, e := dbgen.New(tx).GetCandidateEvaluation(ctx, dbgen.GetCandidateEvaluationParams{CandidateID: candID, RequirementID: reqID}) if e != nil { return e } row = r return nil }); err != nil { t.Fatalf("get evaluation: %v", err) } return row } func countEvaluations(t *testing.T, org, candID, reqID uuid.UUID) int { t.Helper() var n int if err := appDB.RunInTenantTx(ctxOrg(org), func(ctx context.Context, tx pgx.Tx) error { return tx.QueryRow(ctx, "SELECT count(*) FROM candidate_evaluations WHERE candidate_id=$1 AND requirement_id=$2", candID, reqID).Scan(&n) }); err != nil { t.Fatalf("count evaluations: %v", err) } return n }