From 0798ae8d74435b45a1c04060f1a83d0c7d38fbb8 Mon Sep 17 00:00:00 2001 From: Trey t Date: Fri, 1 May 2026 11:00:03 -0700 Subject: [PATCH] fix(testutil): use shared-cache SQLite so concurrent reads see same DB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SetupTestDB used `sqlite.Open(":memory:")`, which creates a *separate* in-memory database for every connection in GORM's pool. Sequential tests never noticed because the pool keeps reusing one connection — but the moment any code path issued concurrent reads (e.g. errgroup-driven parallel COUNT queries), a goroutine could pull a fresh connection, see no migrated tables, and explode with "no such table". Switched to `file:testdb_?mode=memory&cache=shared&_journal=memory` with a per-test atomic counter so every connection in the pool sees the same in-memory DB and tests stay isolated from each other through the unique cache namespace. As a bonus, this also resolves the pre-existing TestTaskHandler_QuickComplete flake — same root cause, just intermittent because the pool occasionally handed out a second connection. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/testutil/testutil.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go index fdd4757..fa87d92 100644 --- a/internal/testutil/testutil.go +++ b/internal/testutil/testutil.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httptest" "sync" + "sync/atomic" "testing" "github.com/labstack/echo/v4" @@ -21,7 +22,10 @@ import ( "github.com/treytartt/honeydue-api/internal/validator" ) -var i18nOnce sync.Once +var ( + i18nOnce sync.Once + testDBCounter uint64 +) // SetupTestDB creates an in-memory SQLite database for testing func SetupTestDB(t *testing.T) *gorm.DB { @@ -30,7 +34,16 @@ func SetupTestDB(t *testing.T) *gorm.DB { i18n.Init() }) - db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ + // SQLite in-memory + GORM connection pool needs `cache=shared` so all + // pool connections see the same DB. With plain ":memory:", each new + // connection gets its own empty database, which makes errgroup-driven + // parallel reads explode with "no such table" once a goroutine pulls a + // fresh connection. The DSN is uniqued per test invocation (atomic + // counter) so tests don't bleed state into each other through the shared + // cache namespace. + id := atomic.AddUint64(&testDBCounter, 1) + dsn := fmt.Sprintf("file:testdb_%d?mode=memory&cache=shared&_journal=memory", id) + db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) require.NoError(t, err)