From 5531b471d166c44198c77f772f65d1b1842a4fcb Mon Sep 17 00:00:00 2001 From: Molecule AI Core-DevOps Date: Thu, 14 May 2026 09:03:55 +0000 Subject: [PATCH] handlers: restore db.DB after each test to fix CI/Platform (Go) race failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mc#975 root cause: TestListDelegationsFromLedger_* and TestListDelegationsFromActivityLogs_* assign db.DB = mockDB then defer mockDB.Close(), but never save/restore the previous db.DB value. With go test -race (parallel execution), any test running after one of these 13 tests sees db.DB pointing at a closed sqlmock and fails. Fix: save prevDB := db.DB before assignment, then t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) — the same pattern already used by setupTestDB for the SSRF/restore path. Also fix setupTestDB in handlers_test.go: it called t.Cleanup(func() { mockDB.Close() }) but left db.DB pointing at the closed mock; now it also restores prevDB. Co-Authored-By: Claude Opus 4.7 --- .../internal/handlers/delegation_list_test.go | 39 ++++++++++++------- .../internal/handlers/handlers_test.go | 11 +++++- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/workspace-server/internal/handlers/delegation_list_test.go b/workspace-server/internal/handlers/delegation_list_test.go index 2d57b818..a1b236cf 100644 --- a/workspace-server/internal/handlers/delegation_list_test.go +++ b/workspace-server/internal/handlers/delegation_list_test.go @@ -23,8 +23,9 @@ func TestListDelegationsFromLedger_EmptyResult(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) rows := sqlmock.NewRows([]string{}) mock.ExpectQuery("SELECT .+ FROM delegations"). @@ -49,8 +50,9 @@ func TestListDelegationsFromLedger_SingleRow(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) now := time.Now() rows := sqlmock.NewRows([]string{}).AddRow( @@ -102,8 +104,9 @@ func TestListDelegationsFromLedger_MultipleRows(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) now := time.Now() rows := sqlmock.NewRows([]string{}). @@ -137,8 +140,9 @@ func TestListDelegationsFromLedger_NullsOmitted(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) now := time.Now() rows := sqlmock.NewRows([]string{}). @@ -179,8 +183,9 @@ func TestListDelegationsFromLedger_QueryError(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) mock.ExpectQuery("SELECT .+ FROM delegations"). WithArgs("ws-1"). @@ -205,8 +210,9 @@ func TestListDelegationsFromLedger_RowsErr(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) now := time.Now() rows := sqlmock.NewRows([]string{}). @@ -237,8 +243,9 @@ func TestListDelegationsFromLedger_ScanError(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) now := time.Now() // Wrong column count → scan error @@ -281,8 +288,9 @@ func TestListDelegationsFromActivityLogs_EmptyResult(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) rows := sqlmock.NewRows([]string{}) mock.ExpectQuery("SELECT .+ FROM activity_logs"). @@ -307,8 +315,9 @@ func TestListDelegationsFromActivityLogs_SingleDelegateRow(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) now := time.Now() rows := sqlmock.NewRows([]string{}).AddRow( @@ -360,8 +369,9 @@ func TestListDelegationsFromActivityLogs_DelegateResultWithError(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) now := time.Now() rows := sqlmock.NewRows([]string{}).AddRow( @@ -409,8 +419,9 @@ func TestListDelegationsFromActivityLogs_QueryError(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) mock.ExpectQuery("SELECT .+ FROM activity_logs"). WithArgs("ws-1"). @@ -435,8 +446,9 @@ func TestListDelegationsFromActivityLogs_RowsErr(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) now := time.Now() rows := sqlmock.NewRows([]string{}). @@ -464,8 +476,9 @@ func TestListDelegationsFromActivityLogs_ScanErrorSkipped(t *testing.T) { if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } - defer mockDB.Close() + prevDB := db.DB db.DB = mockDB + t.Cleanup(func() { mockDB.Close(); db.DB = prevDB }) now := time.Now() // Wrong column count → scan error on first row diff --git a/workspace-server/internal/handlers/handlers_test.go b/workspace-server/internal/handlers/handlers_test.go index d57e5811..1d44f19a 100644 --- a/workspace-server/internal/handlers/handlers_test.go +++ b/workspace-server/internal/handlers/handlers_test.go @@ -29,14 +29,23 @@ func init() { // setupTestDB creates a sqlmock DB and assigns it to the global db.DB. // It also disables the SSRF URL check so that httptest.NewServer loopback // URLs and fake hostnames (*.example) used in tests don't trigger rejections. +// +// IMPORTANT: db.DB is saved before assignment and restored via t.Cleanup so +// that tests running after this one are not polluted by a closed mock. +// This is the single root cause of the systemic CI/Platform (Go) failures on +// main HEAD 8026f020 (mc#975). func setupTestDB(t *testing.T) sqlmock.Sqlmock { t.Helper() mockDB, mock, err := sqlmock.New() if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } + prevDB := db.DB // save in case a prior test left a stale value db.DB = mockDB - t.Cleanup(func() { mockDB.Close() }) + t.Cleanup(func() { + mockDB.Close() + db.DB = prevDB // restore so subsequent tests are not polluted + }) // Disable SSRF checks for the duration of this test only. Restore // the previous state via t.Cleanup so that TestIsSafeURL_* tests