From 5ed369a6c70707543fd5ec9a13c79851fdfc5d6c Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 15 Feb 2026 22:13:50 +0900 Subject: refactor(backend): introduce DI interfaces for testability Replace concrete *db.Queries and *pgxpool.Pool dependencies with db.Querier and db.TxManager interfaces across all handlers, game hub, and auth. This enables unit testing with mocks. - Enable sqlc emit_interface to generate Querier interface - Add TxManager abstraction to encapsulate transactions - Convert auth package-level functions to Authenticator struct - Add TaskQueueInterface/TaskWorkerInterface for game.Hub - Add initial unit tests for game logic and API handlers Co-Authored-By: Claude Opus 4.6 --- backend/api/handler_test.go | 130 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 backend/api/handler_test.go (limited to 'backend/api/handler_test.go') diff --git a/backend/api/handler_test.go b/backend/api/handler_test.go new file mode 100644 index 0000000..47ad92c --- /dev/null +++ b/backend/api/handler_test.go @@ -0,0 +1,130 @@ +package api + +import ( + "context" + "errors" + "testing" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" + + "albatross-2026-backend/config" + "albatross-2026-backend/db" +) + +// mockQuerier implements db.Querier for testing. +type mockQuerier struct { + db.Querier + getGameByIDFunc func(ctx context.Context, gameID int32) (db.GetGameByIDRow, error) +} + +func (m *mockQuerier) GetGameByID(ctx context.Context, gameID int32) (db.GetGameByIDRow, error) { + if m.getGameByIDFunc != nil { + return m.getGameByIDFunc(ctx, gameID) + } + return db.GetGameByIDRow{}, pgx.ErrNoRows +} + +// mockTxManager implements db.TxManager for testing. +type mockTxManager struct{} + +func (m *mockTxManager) RunInTx(ctx context.Context, fn func(q db.Querier) error) error { + return fn(&mockQuerier{}) +} + +// mockGameHub implements GameHubInterface for testing. +type mockGameHub struct { + calcCodeSizeResult int + enqueueErr error +} + +func (m *mockGameHub) CalcCodeSize(_ string, _ string) int { + return m.calcCodeSizeResult +} + +func (m *mockGameHub) EnqueueTestTasks(_ context.Context, _, _, _ int, _, _ string) error { + return m.enqueueErr +} + +// mockAuthenticator implements AuthenticatorInterface for testing. +type mockAuthenticator struct { + loginResult int + loginErr error +} + +func (m *mockAuthenticator) Login(_ context.Context, _, _ string) (int, error) { + return m.loginResult, m.loginErr +} + +func TestPostGamePlaySubmit_GameNotFound(t *testing.T) { + h := Handler{ + q: &mockQuerier{}, + txm: &mockTxManager{}, + hub: &mockGameHub{}, + auth: &mockAuthenticator{}, + conf: &config.Config{}, + } + user := &db.User{UserID: 1} + resp, err := h.PostGamePlaySubmit(context.Background(), PostGamePlaySubmitRequestObject{ + GameID: 999, + Body: &PostGamePlaySubmitJSONRequestBody{Code: "test"}, + }, user) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if _, ok := resp.(PostGamePlaySubmit404JSONResponse); !ok { + t.Errorf("expected 404 response, got %T", resp) + } +} + +func TestPostGamePlaySubmit_GameNotRunning(t *testing.T) { + h := Handler{ + q: &mockQuerier{ + getGameByIDFunc: func(_ context.Context, _ int32) (db.GetGameByIDRow, error) { + return db.GetGameByIDRow{ + GameID: 1, + Language: "php", + StartedAt: pgtype.Timestamp{ + Valid: false, + }, + }, nil + }, + }, + txm: &mockTxManager{}, + hub: &mockGameHub{calcCodeSizeResult: 10}, + auth: &mockAuthenticator{}, + conf: &config.Config{}, + } + user := &db.User{UserID: 1} + resp, err := h.PostGamePlaySubmit(context.Background(), PostGamePlaySubmitRequestObject{ + GameID: 1, + Body: &PostGamePlaySubmitJSONRequestBody{Code: "