//go:build integration package integration import ( "context" "errors" "testing" "time" "github.com/missionworkforce/missionmatch/internal/modules/matching" "github.com/missionworkforce/missionmatch/internal/platform/audit" ) // TestSetClearanceProvenance proves the provenance write round trips under RLS, audits, clears // verified_at when set back to self reported, and rejects an invalid code. func TestSetClearanceProvenance(t *testing.T) { ctx := context.Background() org := seedOrg(t) cand := seedCandidate(t, org, "ts_sci", "active", true) svc := matching.NewService(appDB, nil, audit.New(appDB)) vd := time.Date(2026, 1, 15, 0, 0, 0, 0, time.UTC) if err := svc.SetClearanceProvenance(ctxOrg(org), cand.ID, "sponsor_verified", &vd); err != nil { t.Fatalf("set sponsor_verified: %v", err) } var prov string var va *time.Time read := func() { if err := migrator.QueryRow(ctx, "SELECT clearance_provenance, clearance_verified_at FROM candidates WHERE id=$1", cand.ID).Scan(&prov, &va); err != nil { t.Fatalf("read back: %v", err) } } read() if prov != "sponsor_verified" || va == nil { t.Fatalf("provenance not persisted: %s %v", prov, va) } var n int if err := migrator.QueryRow(ctx, "SELECT count(*) FROM audit_logs WHERE organization_id=$1 AND action='candidate.clearance_provenance.set' AND entity_id=$2", org, cand.ID.String()).Scan(&n); err != nil { t.Fatalf("audit count: %v", err) } if n != 1 { t.Fatalf("expected 1 provenance audit row, got %d", n) } // Setting back to self reported clears the verification date (a self report is not a verification). if err := svc.SetClearanceProvenance(ctxOrg(org), cand.ID, "self_reported", nil); err != nil { t.Fatalf("set self_reported: %v", err) } read() if prov != "self_reported" || va != nil { t.Fatalf("self_reported must clear verified_at: %s %v", prov, va) } // An invalid provenance code is rejected. if err := svc.SetClearanceProvenance(ctxOrg(org), cand.ID, "bogus", nil); !errors.Is(err, matching.ErrBadRequest) { t.Fatalf("invalid code: want ErrBadRequest, got %v", err) } }