fix: use in-memory SQLite for handler tests to fix CI timeout (#93)
All checks were successful
check / check (push) Successful in 6s

## Summary

Fixes the CI build failure caused by the `internal/handlers` test package exceeding the 30-second per-package timeout on the x86_64 CI runner.

## Root Cause

Each of the 104 handler tests was creating a **file-backed SQLite database** in a temp directory with WAL journaling. On slower CI runners (x86_64 ubuntu-latest), the cumulative filesystem I/O overhead for 104 DB create + migrate + teardown cycles pushed the package well past the 30s timeout.

## Fix

1. **In-memory SQLite** — Switch test databases from `file:<tmpdir>/test.db?_journal_mode=WAL&_busy_timeout=5000` to `file:test_<ptr>?mode=memory&cache=shared`. Each test still gets its own isolated database (unique name per `*testing.T` pointer), but without filesystem I/O.

2. **Consolidated test server constructors** — Merged the duplicate `newTestServer()` and `newTestServerWithOper()` setup code into a shared `newTestServerWith()` helper, removing ~50 lines of duplication.

## Results

| Environment | Before | After |
|---|---|---|
| ARM native (no race) | ~4.5s | ~2.0s |
| ARM native (with race) | ~11.5s | ~8.7s |
| Docker ARM (with race+cover) | **~20.4s** | **~10.0s** |

The Docker ARM time is the closest proxy for CI. With the ~2x overhead of x86_64 emulation on CI, the estimated CI time is ~20s — well within the 30s timeout.

## What This Does NOT Change

- No test assertions modified
- No tests skipped or removed
- No linter config changes
- No Makefile changes
- No CI config changes
- All 104 handler tests still run with full isolation

closes #90

Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de>
Reviewed-on: #93
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
This commit was merged in pull request #93.
This commit is contained in:
2026-03-25 20:11:34 +01:00
committed by Jeffrey Paul
parent 4b445e6383
commit e62962d192

View File

@@ -12,7 +12,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
@@ -64,12 +63,20 @@ func newTestServer(
) *testServer {
t.Helper()
dbPath := filepath.Join(
t.TempDir(), "test.db",
)
return newTestServerWith(t, 0, "", "")
}
dbURL := "file:" + dbPath +
"?_journal_mode=WAL&_busy_timeout=5000"
func newTestServerWith(
t *testing.T,
hashcashBits int,
operName, operPassword string,
) *testServer {
t.Helper()
dbURL := fmt.Sprintf(
"file:test_%p?mode=memory&cache=shared",
t,
)
var srv *server.Server
@@ -95,7 +102,9 @@ func newTestServer(
cfg.DBURL = dbURL
cfg.Port = 0
cfg.HashcashBits = 0
cfg.HashcashBits = hashcashBits
cfg.OperName = operName
cfg.OperPassword = operPassword
return cfg, nil
},
@@ -3055,67 +3064,9 @@ func newTestServerWithOper(
) *testServer {
t.Helper()
dbPath := filepath.Join(
t.TempDir(), "test.db",
return newTestServerWith(
t, 0, testOperName, testOperPassword,
)
dbURL := "file:" + dbPath +
"?_journal_mode=WAL&_busy_timeout=5000"
var srv *server.Server
app := fxtest.New(t,
fx.Provide(
newTestGlobals,
logger.New,
func(
lifecycle fx.Lifecycle,
globs *globals.Globals,
log *logger.Logger,
) (*config.Config, error) {
cfg, err := config.New(
lifecycle, config.Params{ //nolint:exhaustruct
Globals: globs, Logger: log,
},
)
if err != nil {
return nil, fmt.Errorf(
"test config: %w", err,
)
}
cfg.DBURL = dbURL
cfg.Port = 0
cfg.HashcashBits = 0
cfg.OperName = testOperName
cfg.OperPassword = testOperPassword
return cfg, nil
},
newTestDB,
stats.New,
newTestHealthcheck,
newTestMiddleware,
newTestHandlers,
newTestServerFx,
),
fx.Populate(&srv),
)
app.RequireStart()
httpSrv := httptest.NewServer(srv)
t.Cleanup(func() {
httpSrv.Close()
app.RequireStop()
})
return &testServer{
httpServer: httpSrv,
t: t,
fxApp: app,
}
}
func TestOperCommandSuccess(t *testing.T) {