//go:build integration // Backfill integration proof: real slate decisions become outcome_events, with the per dimension // scores carried into metadata, and a second run is idempotent (dedup, append only). package integration import ( "context" "testing" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/missionworkforce/missionmatch/internal/aioe" ) func TestBackfillConvertsSlateDecisionsAndDedups(t *testing.T) { org := seedOrg(t) ctx := ctxOrg(org) // Seed one requirement, one candidate, one slate, and one APPROVED slate member with real // dimension scores. Everything through the app role under RLS. var reqID, candID, slateID uuid.UUID err := appDB.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error { if err := tx.QueryRow(ctx, ` INSERT INTO requirements (organization_id, title, required_level) VALUES ($1, 'Backfill Req', 'secret') RETURNING id`, org).Scan(&reqID); err != nil { return err } if err := tx.QueryRow(ctx, ` INSERT INTO candidates (organization_id, full_name, clearance_level, clearance_status, available) VALUES ($1, 'Backfill Cand', 'secret', 'active', true) RETURNING id`, org).Scan(&candID); err != nil { return err } if err := tx.QueryRow(ctx, ` INSERT INTO slates (organization_id, requirement_id, name, status) VALUES ($1, $2, 'Backfill Slate', 'draft') RETURNING id`, org, reqID).Scan(&slateID); err != nil { return err } _, err := tx.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, 88.0, 'green', 100, 95, 80, 70, 90, 60, 'ok', '[]'::jsonb, 'approved', now())`, org, slateID, candID) return err }) if err != nil { t.Fatalf("seed: %v", err) } // First backfill run: one slate decision becomes one outcome event. var rep aioe.BackfillReport if err := appDB.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error { var e error rep, e = aioe.BackfillOrg(ctx, tx, "test") return e }); err != nil { t.Fatalf("backfill: %v", err) } if rep.SlateDecisions != 1 { t.Fatalf("expected 1 slate decision backfilled, got %+v", rep) } // The event exists, is +1 (approved), carries the dimension scores, and dates to the decision. if err := appDB.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error { var signal, skills string var eventType string if err := tx.QueryRow(ctx, ` SELECT signal_value::text, event_type, metadata->'dimension_scores'->>'skills' FROM outcome_events WHERE subject_id = $1 AND metadata->>'backfill' = 'v1'`, candID).Scan(&signal, &eventType, &skills); err != nil { return err } if signal != "1.0000" { t.Fatalf("approved decision must be +1, got %s", signal) } if eventType != "slate_result" { t.Fatalf("event_type = %s", eventType) } if skills != "95.0000" { t.Fatalf("dimension scores must be carried into metadata, skills = %q", skills) } return nil }); err != nil { t.Fatal(err) } // Second run is idempotent: nothing new, one skipped. var rep2 aioe.BackfillReport if err := appDB.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error { var e error rep2, e = aioe.BackfillOrg(ctx, tx, "test") return e }); err != nil { t.Fatalf("backfill 2: %v", err) } if rep2.SlateDecisions != 0 || rep2.SkippedExisting != 1 { t.Fatalf("re-run must be idempotent, got %+v", rep2) } // And exactly one backfilled event exists for the pair (append only, no duplicate). if err := appDB.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error { var n int if err := tx.QueryRow(ctx, ` SELECT count(*) FROM outcome_events WHERE subject_id = $1 AND metadata->>'backfill' = 'v1'`, candID).Scan(&n); err != nil { return err } if n != 1 { t.Fatalf("expected exactly 1 backfilled event, got %d", n) } return nil }); err != nil { t.Fatal(err) } }