//go:build integration // M7 bid outcomes ledger integration. Proves at runtime against a REAL Postgres what the unit tests cannot: // the bid_outcomes RLS boundary (org A never sees org B's outcomes, on the table and through the advisory // base rate read), the recording path under the app role, and that the bid_win_rate_by_segment advisory // read computes the correct win rate per customer agency segment, RLS scoped, with the win rate scanned into // shopspring/decimal (never float64). It seeds two orgs, records outcomes for both, and asserts each org // sees only its own ledger and its own base rate. // // Run: go test -tags=integration ./internal/integration/ -run BidOutcomes -v // Env: MM_APP_DSN, MM_MIGRATOR_DSN (required). package integration import ( "context" "testing" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/shopspring/decimal" "github.com/missionworkforce/missionmatch/internal/modules/ai" "github.com/missionworkforce/missionmatch/internal/modules/bidoutcomes" ) // seedRequirementWithAgency inserts a requirement with a customer agency via the BYPASSRLS migrator, so the // test can wire bid outcomes to a known segment across tenants. func seedRequirementWithAgency(t *testing.T, org uuid.UUID, agency string) uuid.UUID { t.Helper() id := uuid.New() if _, err := migrator.Exec(context.Background(), `INSERT INTO requirements (id, organization_id, title, customer_agency, required_level, required_poly) VALUES ($1,$2,$3,$4,'none','none')`, id, org, "Req for "+agency, agency); err != nil { t.Fatalf("seed requirement: %v", err) } return id } // recordOutcome records one bid outcome under the org's RLS session via the service (the real recording // path), tying it to a requirement so it carries a customer agency segment. func recordOutcome(t *testing.T, svc *bidoutcomes.Service, org, reqID uuid.UUID, result string) bidoutcomes.BidOutcome { t.Helper() out, err := svc.RecordBidOutcome(ctxOrg(org), bidoutcomes.RecordInput{ RequirementID: reqID.String(), Result: result, SubmittedAt: time.Now().UTC().Format(time.RFC3339), }) if err != nil { t.Fatalf("record outcome (%s): %v", result, err) } return out } // TestBidOutcomesRLSIsolation proves org A's bid_outcomes never leak into org B and vice versa, both on a // direct table read and through the advisory base rate read. func TestBidOutcomesRLSIsolation(t *testing.T) { orgA, orgB := seedOrg(t), seedOrg(t) reqA := seedRequirementWithAgency(t, orgA, "DISA") reqB := seedRequirementWithAgency(t, orgB, "USAF") svc := bidoutcomes.NewService(appDB, nil) outA := recordOutcome(t, svc, orgA, reqA, bidoutcomes.ResultWin) outB := recordOutcome(t, svc, orgB, reqB, bidoutcomes.ResultLoss) // Org A lists its own bid_outcomes under RLS and must see only its own row. var ids []uuid.UUID if err := appDB.RunInTenantTx(ctxOrg(orgA), func(ctx context.Context, tx pgx.Tx) error { rows, e := tx.Query(ctx, "SELECT id FROM bid_outcomes") if e != nil { return e } defer rows.Close() for rows.Next() { var id uuid.UUID if e := rows.Scan(&id); e != nil { return e } ids = append(ids, id) } return rows.Err() }); err != nil { t.Fatalf("list org A outcomes: %v", err) } sawA, sawB := false, false for _, id := range ids { sawA = sawA || id.String() == outA.ID sawB = sawB || id.String() == outB.ID } if !sawA { t.Fatal("org A must see its own bid outcome") } if sawB { t.Fatal("ISOLATION BREACH: org A saw org B's bid outcome") } // A direct read of B's outcome under org A's session must return zero rows (RLS hides it). var n int if err := appDB.RunInTenantTx(ctxOrg(orgA), func(ctx context.Context, tx pgx.Tx) error { return tx.QueryRow(ctx, "SELECT count(*) FROM bid_outcomes WHERE id = $1", outB.ID).Scan(&n) }); err != nil { t.Fatalf("cross tenant read: %v", err) } if n != 0 { t.Fatalf("ISOLATION BREACH: org A read org B's outcome by id (count %d)", n) } } // TestBidWinRateBaseRatePerAgency proves the advisory base rate read returns the correct win rate per // customer agency segment for the caller's own ledger, and that a second org sees only its own base rate. // Org A records, for DISA, two wins and one loss (win rate 2/3), and for USAF one win and one loss (win // rate 1/2); a pending bid is excluded from the decided denominator. Org B records a single DISA loss, so // its DISA win rate is 0/1, proving the read is RLS scoped and never blends the two tenants. func TestBidWinRateBaseRatePerAgency(t *testing.T) { orgA, orgB := seedOrg(t), seedOrg(t) disaA := seedRequirementWithAgency(t, orgA, "DISA") usafA := seedRequirementWithAgency(t, orgA, "USAF") disaB := seedRequirementWithAgency(t, orgB, "DISA") svc := bidoutcomes.NewService(appDB, nil) // org A: DISA 2 wins + 1 loss (+ 1 pending, excluded), USAF 1 win + 1 loss. recordOutcome(t, svc, orgA, disaA, bidoutcomes.ResultWin) recordOutcome(t, svc, orgA, disaA, bidoutcomes.ResultWin) recordOutcome(t, svc, orgA, disaA, bidoutcomes.ResultLoss) recordOutcome(t, svc, orgA, disaA, bidoutcomes.ResultPending) recordOutcome(t, svc, orgA, usafA, bidoutcomes.ResultWin) recordOutcome(t, svc, orgA, usafA, bidoutcomes.ResultLoss) // org B: a single DISA loss. recordOutcome(t, svc, orgB, disaB, bidoutcomes.ResultLoss) gotA := readBaseRate(t, orgA) assertSegment(t, gotA, "DISA", 2, 3, decimal.RequireFromString("0.6666666666666667")) assertSegment(t, gotA, "USAF", 1, 2, decimal.RequireFromString("0.5")) gotB := readBaseRate(t, orgB) if _, ok := gotB["USAF"]; ok { t.Fatal("ISOLATION BREACH: org B base rate carries org A's USAF segment") } assertSegment(t, gotB, "DISA", 0, 1, decimal.Zero) } // segmentRow is one row of the advisory base rate read. type segmentRow struct { winCount int64 totalDecided int64 winRate *decimal.Decimal // NULL (nil) when no decided bid } // readBaseRate runs the registered bid_win_rate_by_segment intent SQL under the org's RLS session and scans // each row, proving the win rate scans into shopspring/decimal (never float64). It uses the SQL straight // from the closed AI Registry so the test exercises the exact reviewed read. func readBaseRate(t *testing.T, org uuid.UUID) map[string]segmentRow { t.Helper() intent, ok := ai.IntentByName(ai.IntentBidWinRateBySegment) if !ok { t.Fatal("bid_win_rate_by_segment intent is not registered") } out := map[string]segmentRow{} if err := appDB.RunInTenantTx(ctxOrg(org), func(ctx context.Context, tx pgx.Tx) error { rows, e := tx.Query(ctx, intent.SQL) if e != nil { return e } defer rows.Close() for rows.Next() { var ( agency string sr segmentRow ) if e := rows.Scan(&agency, &sr.winCount, &sr.totalDecided, &sr.winRate); e != nil { return e } out[agency] = sr } return rows.Err() }); err != nil { t.Fatalf("base rate read for org: %v", err) } return out } // assertSegment checks one segment's win count, decided total, and win rate (to a tolerance, since the SQL // numeric division carries many digits). func assertSegment(t *testing.T, got map[string]segmentRow, agency string, wantWin, wantDecided int64, wantRate decimal.Decimal) { t.Helper() sr, ok := got[agency] if !ok { t.Fatalf("segment %q missing from base rate", agency) } if sr.winCount != wantWin || sr.totalDecided != wantDecided { t.Fatalf("segment %q win/decided = %d/%d, want %d/%d", agency, sr.winCount, sr.totalDecided, wantWin, wantDecided) } if sr.winRate == nil { t.Fatalf("segment %q win rate is NULL, want %s", agency, wantRate) } if sr.winRate.Sub(wantRate).Abs().GreaterThan(decimal.RequireFromString("0.0001")) { t.Fatalf("segment %q win rate = %s, want ~%s", agency, sr.winRate, wantRate) } }