fix(integration-tests): UUID IDs + valid enum status for real-PG tests
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 1s
sop-checklist / all-items-acked (pull_request_target) Has been cancelled
sop-checklist / review-refire (pull_request_target) Has been cancelled
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 3s
CI / Detect changes (pull_request) Successful in 9s
E2E API Smoke Test / detect-changes (pull_request) Successful in 10s
E2E Chat / detect-changes (pull_request) Successful in 11s
Harness Replays / detect-changes (pull_request) Successful in 5s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 9s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 8s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 8s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 7s
qa-review / approved (pull_request_target) Failing after 3s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 4s
gate-check-v3 / gate-check (pull_request_target) Failing after 3s
security-review / approved (pull_request_target) Failing after 3s
sop-tier-check / tier-check (pull_request_target) Successful in 3s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 56s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 3s
CI / Canvas (Next.js) (pull_request) Successful in 1m11s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m19s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 6s
Harness Replays / Harness Replays (pull_request) Successful in 5s
E2E Chat / E2E Chat (pull_request) Successful in 39s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Failing after 1m16s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / Platform (Go) (pull_request) Successful in 9m13s
CI / all-required (pull_request) Successful in 3s

Follow-up to f410b8e1 which only addressed the LIKE-against-UUID-column
shape error. The Handlers PG Integration re-run on f410b8e1 revealed
two further issues that the surface fix masked:

  1. `workspaces.id` is UUID-typed (001_workspaces.sql:2), but the
     test fixtures used non-UUID strings like "integ-ash-ws-ok" as the
     workspace id, producing
       pq: invalid input syntax for type uuid: "integ-ash-ws-ok"
     on every seed INSERT.

  2. `workspaces.status` is a `workspace_status` ENUM (migration 043)
     with values provisioning/online/offline/degraded/failed/removed/
     paused/hibernated/awaiting_agent/hibernating. The tests wrote
     'running' everywhere, which the enum rejects:
       pq: invalid input value for enum workspace_status: "running"

Both are intrinsic to PR #2171 (test files don't exist or are 1 line
on main, per the CEO DECIDING TEST), so scope stays IN of #8400.

Fix:
- Add integration_test_helpers_test.go: a single integUUID(s) helper
  that maps a human-readable name to a deterministic UUID via
  SHA-1(uuid.NameSpaceURL, s). Same input always yields the same UUID,
  so the readable name in `wsX := integUUID("integ-ash-ws-ok")` is
  recoverable from any failure log line.
- Wrap every test fixture ID (wsA, wsB, wsCap, wsRemoved, wsGhost,
  wsOK, wsStale, "ws-a-over" case) with integUUID.
- Replace the hard-coded 'running' status with 'online' in the four
  seedWorkspace_* helpers (the only valid enum value that exercises
  the "workspace exists and is reachable" path).
- Re-do the cleanup LIKE filter: instead of `WHERE id LIKE 'integ-%'`
  (workspaces.id is UUID, so still needs a cast there), filter on the
  TEXT `name` column which already carries the integ- prefix. Child
  tables (workspace_schedules, activity_logs, workspace_auth_tokens)
  join through the workspace_id FK against a `name LIKE` subquery on
  workspaces. The TEXT-vs-UUID mismatch that f410b8e1 was working
  around goes away entirely.

Files:
- integration_test_helpers_test.go (new, +36)
- admin_schedules_health_integration_test.go (4 sites)
- budget_integration_test.go (3 sites + 1 SQL literal)
- schedules_integration_test.go (6 sites + 1 SQL literal)
- tokens_integration_test.go (4 sites + 1 SQL literal)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Molecule AI Dev Engineer B (MiniMax)
2026-06-03 23:52:09 +00:00
parent 1524f36f9e
commit f1ef6c405e
5 changed files with 77 additions and 36 deletions
@@ -57,18 +57,18 @@ func integrationDB_AdminSchedulesHealth(t *testing.T) *sql.DB {
t.Fatalf("ping: %v", err)
}
if _, err := conn.ExecContext(context.Background(),
`DELETE FROM workspace_schedules WHERE workspace_id LIKE 'integ-ash-%'`); err != nil {
`DELETE FROM workspace_schedules WHERE workspace_id IN (SELECT id FROM workspaces WHERE name LIKE 'integ-ash-%')`); err != nil {
t.Fatalf("cleanup schedules: %v", err)
}
if _, err := conn.ExecContext(context.Background(),
`DELETE FROM workspaces WHERE id LIKE 'integ-ash-%'`); err != nil {
`DELETE FROM workspaces WHERE name LIKE 'integ-ash-%'`); err != nil {
t.Fatalf("cleanup workspaces: %v", err)
}
prev := db.DB
db.DB = conn
t.Cleanup(func() {
conn.ExecContext(context.Background(), `DELETE FROM workspace_schedules WHERE workspace_id LIKE 'integ-ash-%'`)
conn.ExecContext(context.Background(), `DELETE FROM workspaces WHERE id LIKE 'integ-ash-%'`)
conn.ExecContext(context.Background(), `DELETE FROM workspace_schedules WHERE workspace_id IN (SELECT id FROM workspaces WHERE name LIKE 'integ-ash-%')`)
conn.ExecContext(context.Background(), `DELETE FROM workspaces WHERE name LIKE 'integ-ash-%'`)
db.DB = prev
conn.Close()
})
@@ -109,11 +109,14 @@ func TestIntegration_AdminSchedulesHealth_ClassifiesRows(t *testing.T) {
handler := NewAdminSchedulesHealthHandler()
// Two visible workspaces + one removed (must NOT appear in results).
wsOK := "integ-ash-ws-ok"
wsStale := "integ-ash-ws-stale"
wsRemoved := "integ-ash-ws-removed"
seedWorkspace_AdminSchedulesHealth(t, conn, wsOK, "running")
seedWorkspace_AdminSchedulesHealth(t, conn, wsStale, "running")
// IDs are derived from the human-readable name via integUUID so the
// schema (UUID-typed id column) is satisfied while failure logs still
// print a recognizable name.
wsOK := integUUID("integ-ash-ws-ok")
wsStale := integUUID("integ-ash-ws-stale")
wsRemoved := integUUID("integ-ash-ws-removed")
seedWorkspace_AdminSchedulesHealth(t, conn, wsOK, "online")
seedWorkspace_AdminSchedulesHealth(t, conn, wsStale, "online")
seedWorkspace_AdminSchedulesHealth(t, conn, wsRemoved, "removed")
// --- never_run: last_run_at IS NULL ---
@@ -63,13 +63,13 @@ func integrationDB_Budget(t *testing.T) *sql.DB {
t.Fatalf("ping: %v", err)
}
if _, err := conn.ExecContext(context.Background(),
`DELETE FROM workspaces WHERE id LIKE 'integ-bud-%'`); err != nil {
`DELETE FROM workspaces WHERE name LIKE 'integ-bud-%'`); err != nil {
t.Fatalf("cleanup: %v", err)
}
prev := db.DB
db.DB = conn
t.Cleanup(func() {
conn.ExecContext(context.Background(), `DELETE FROM workspaces WHERE id LIKE 'integ-bud-%'`)
conn.ExecContext(context.Background(), `DELETE FROM workspaces WHERE name LIKE 'integ-bud-%'`)
db.DB = prev
conn.Close()
})
@@ -77,8 +77,9 @@ func integrationDB_Budget(t *testing.T) *sql.DB {
}
// seedWorkspace_Budget inserts a workspaces row with optional budget_limit
// (nil = NULL) and a fixed monthly_spend. The status is always 'running' —
// the removed-status case uses a separate helper.
// (nil = NULL) and a fixed monthly_spend. The status is hardcoded to
// 'online' (a valid workspace_status enum value — see migration 043).
// The removed-status case uses a separate helper.
func seedWorkspace_Budget(t *testing.T, conn *sql.DB, id string, budgetLimit *int64, monthlySpend int64) {
t.Helper()
var lim interface{} = nil
@@ -87,7 +88,7 @@ func seedWorkspace_Budget(t *testing.T, conn *sql.DB, id string, budgetLimit *in
}
if _, err := conn.ExecContext(context.Background(),
`INSERT INTO workspaces (id, name, status, budget_limit, monthly_spend)
VALUES ($1, $2, 'running', $3, $4)`,
VALUES ($1, $2, 'online', $3, $4)`,
id, "integ-bud-"+id, lim, monthlySpend); err != nil {
t.Fatalf("seed: %v", err)
}
@@ -125,10 +126,10 @@ func TestIntegration_Budget_GetPatchPersistsAndValidates(t *testing.T) {
conn := integrationDB_Budget(t)
handler := NewBudgetHandler()
wsA := "integ-bud-ws-a"
wsB := "integ-bud-ws-b"
wsRemoved := "integ-bud-ws-removed"
wsGhost := "integ-bud-ws-ghost"
wsA := integUUID("integ-bud-ws-a")
wsB := integUUID("integ-bud-ws-b")
wsRemoved := integUUID("integ-bud-ws-removed")
wsGhost := integUUID("integ-bud-ws-ghost")
// Case A: no budget set (budget_limit NULL)
// Case B: under budget (limit 10000, spend 2500 → remaining 7500)
@@ -137,7 +138,7 @@ func TestIntegration_Budget_GetPatchPersistsAndValidates(t *testing.T) {
seedWorkspace_Budget(t, conn, wsA, nil, 0)
seedWorkspace_Budget(t, conn, wsB, int64Ptr(10000), 2500)
overLim := int64(1000)
seedWorkspace_Budget(t, conn, wsA+"over", &overLim, 1500)
seedWorkspace_Budget(t, conn, integUUID("integ-bud-ws-a-over"), &overLim, 1500)
// removed-workspace case
if _, err := conn.ExecContext(context.Background(),
`INSERT INTO workspaces (id, name, status, budget_limit, monthly_spend)
@@ -0,0 +1,37 @@
//go:build integration
// +build integration
// integration_test_helpers_test.go — shared helpers for the
// `//go:build integration` test files.
//
// The handlers package uses github.com/google/uuid in production code
// (workspaces.id, workspace_schedules.workspace_id, activity_logs.workspace_id,
// and workspace_auth_tokens.workspace_id are all UUID columns — see
// migrations 001_workspaces.sql, 015_workspace_schedules.sql,
// 009_activity_logs.sql, 020_workspace_auth_tokens.up.sql). Real
// Postgres rejects non-UUID-shaped strings on insert.
//
// The integration tests in this package want human-readable fixture
// names so failures print obviously ("integ-sch-ws-a", not a random
// UUID). integUUID is a tiny helper that maps any string to a
// stable UUID via SHA-1 in the URL namespace — same input → same
// UUID, different inputs → different UUIDs. The test can keep its
// readable names but every place that needs a UUID-shaped value
// passes through this helper.
//
// Cleanup is driven off `workspaces.name` (a TEXT column we set to
// the test marker) rather than `workspaces.id` (a UUID column) so
// we don't have to keep a running list of generated UUIDs in sync
// between the test body and the cleanup helper.
package handlers
import "github.com/google/uuid"
// integUUID returns a deterministic UUID derived from s. The URL
// namespace keeps the input space disjoint from production UUIDs
// (which use the random v4 generator) and from the OID namespace
// (which uuid.NewSHA1 would default to).
func integUUID(s string) string {
return uuid.NewSHA1(uuid.NameSpaceURL, []byte(s)).String()
}
@@ -62,9 +62,9 @@ func integrationDB_Schedules(t *testing.T) *sql.DB {
// Wipe in FK order: activity_logs first (references workspaces), then
// workspace_schedules (references workspaces), then workspaces.
for _, stmt := range []string{
`DELETE FROM activity_logs WHERE workspace_id LIKE 'integ-sch-%'`,
`DELETE FROM workspace_schedules WHERE workspace_id LIKE 'integ-sch-%'`,
`DELETE FROM workspaces WHERE id LIKE 'integ-sch-%'`,
`DELETE FROM activity_logs WHERE workspace_id IN (SELECT id FROM workspaces WHERE name LIKE 'integ-sch-%')`,
`DELETE FROM workspace_schedules WHERE workspace_id IN (SELECT id FROM workspaces WHERE name LIKE 'integ-sch-%')`,
`DELETE FROM workspaces WHERE name LIKE 'integ-sch-%'`,
} {
if _, err := conn.ExecContext(context.Background(), stmt); err != nil {
t.Fatalf("cleanup %q: %v", stmt, err)
@@ -73,9 +73,9 @@ func integrationDB_Schedules(t *testing.T) *sql.DB {
prev := db.DB
db.DB = conn
t.Cleanup(func() {
conn.ExecContext(context.Background(), `DELETE FROM activity_logs WHERE workspace_id LIKE 'integ-sch-%'`)
conn.ExecContext(context.Background(), `DELETE FROM workspace_schedules WHERE workspace_id LIKE 'integ-sch-%'`)
conn.ExecContext(context.Background(), `DELETE FROM workspaces WHERE id LIKE 'integ-sch-%'`)
conn.ExecContext(context.Background(), `DELETE FROM activity_logs WHERE workspace_id IN (SELECT id FROM workspaces WHERE name LIKE 'integ-sch-%')`)
conn.ExecContext(context.Background(), `DELETE FROM workspace_schedules WHERE workspace_id IN (SELECT id FROM workspaces WHERE name LIKE 'integ-sch-%')`)
conn.ExecContext(context.Background(), `DELETE FROM workspaces WHERE name LIKE 'integ-sch-%'`)
db.DB = prev
conn.Close()
})
@@ -85,7 +85,7 @@ func integrationDB_Schedules(t *testing.T) *sql.DB {
func seedWorkspace_Schedules(t *testing.T, conn *sql.DB, id string) {
t.Helper()
if _, err := conn.ExecContext(context.Background(),
`INSERT INTO workspaces (id, name, status) VALUES ($1, $2, 'running')`,
`INSERT INTO workspaces (id, name, status) VALUES ($1, $2, 'online')`,
id, "integ-sch-"+id); err != nil {
t.Fatalf("seed workspace: %v", err)
}
@@ -132,8 +132,8 @@ func TestIntegration_Schedules_CRUDRunHistoryHealth_RoundTrip(t *testing.T) {
conn := integrationDB_Schedules(t)
handler := NewScheduleHandler()
wsA := "integ-sch-ws-a"
wsB := "integ-sch-ws-b"
wsA := integUUID("integ-sch-ws-a")
wsB := integUUID("integ-sch-ws-b")
seedWorkspace_Schedules(t, conn, wsA)
seedWorkspace_Schedules(t, conn, wsB)
@@ -60,18 +60,18 @@ func integrationDB_Tokens(t *testing.T) *sql.DB {
t.Fatalf("ping: %v", err)
}
if _, err := conn.ExecContext(context.Background(),
`DELETE FROM workspace_auth_tokens WHERE workspace_id LIKE 'integ-tok-%'`); err != nil {
`DELETE FROM workspace_auth_tokens WHERE workspace_id IN (SELECT id FROM workspaces WHERE name LIKE 'integ-tok-%')`); err != nil {
t.Fatalf("cleanup tokens: %v", err)
}
if _, err := conn.ExecContext(context.Background(),
`DELETE FROM workspaces WHERE id LIKE 'integ-tok-%'`); err != nil {
`DELETE FROM workspaces WHERE name LIKE 'integ-tok-%'`); err != nil {
t.Fatalf("cleanup workspaces: %v", err)
}
prev := db.DB
db.DB = conn
t.Cleanup(func() {
conn.ExecContext(context.Background(), `DELETE FROM workspace_auth_tokens WHERE workspace_id LIKE 'integ-tok-%'`)
conn.ExecContext(context.Background(), `DELETE FROM workspaces WHERE id LIKE 'integ-tok-%'`)
conn.ExecContext(context.Background(), `DELETE FROM workspace_auth_tokens WHERE workspace_id IN (SELECT id FROM workspaces WHERE name LIKE 'integ-tok-%')`)
conn.ExecContext(context.Background(), `DELETE FROM workspaces WHERE name LIKE 'integ-tok-%'`)
db.DB = prev
conn.Close()
})
@@ -81,7 +81,7 @@ func integrationDB_Tokens(t *testing.T) *sql.DB {
func seedWorkspace_Tokens(t *testing.T, conn *sql.DB, id string) {
t.Helper()
if _, err := conn.ExecContext(context.Background(),
`INSERT INTO workspaces (id, name, status) VALUES ($1, $2, 'running')`,
`INSERT INTO workspaces (id, name, status) VALUES ($1, $2, 'online')`,
id, "integ-tok-"+id); err != nil {
t.Fatalf("seed: %v", err)
}
@@ -105,8 +105,8 @@ func TestIntegration_Tokens_CreateListRevoke_RoundTrip(t *testing.T) {
conn := integrationDB_Tokens(t)
handler := NewTokenHandler()
wsA := "integ-tok-ws-a"
wsB := "integ-tok-ws-b"
wsA := integUUID("integ-tok-ws-a")
wsB := integUUID("integ-tok-ws-b")
seedWorkspace_Tokens(t, conn, wsA)
seedWorkspace_Tokens(t, conn, wsB)
@@ -234,7 +234,7 @@ func TestIntegration_Tokens_CreateListRevoke_RoundTrip(t *testing.T) {
}
// --- Case 5: max-active-cap (50) — seed 50, then 51st → 429 ---
wsCap := "integ-tok-ws-cap"
wsCap := integUUID("integ-tok-ws-cap")
seedWorkspace_Tokens(t, conn, wsCap)
// Insert 50 active tokens directly to avoid hammering IssueToken 50 times.
for i := 0; i < maxTokensPerWorkspace; i++ {