aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend/api/handler_test.go
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-15 22:13:50 +0900
committernsfisis <nsfisis@gmail.com>2026-02-15 22:16:22 +0900
commit5ed369a6c70707543fd5ec9a13c79851fdfc5d6c (patch)
treee5678d6d88fab3ac0ae8c05b85236f3e7d5eddfd /backend/api/handler_test.go
parent87e9f5ed48af3a8dca5f6373ae900336f285eef5 (diff)
downloadphperkaigi-2026-albatross-5ed369a6c70707543fd5ec9a13c79851fdfc5d6c.tar.gz
phperkaigi-2026-albatross-5ed369a6c70707543fd5ec9a13c79851fdfc5d6c.tar.zst
phperkaigi-2026-albatross-5ed369a6c70707543fd5ec9a13c79851fdfc5d6c.zip
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 <noreply@anthropic.com>
Diffstat (limited to 'backend/api/handler_test.go')
-rw-r--r--backend/api/handler_test.go130
1 files changed, 130 insertions, 0 deletions
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: "<?php echo 1;"},
+ }, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if r, ok := resp.(PostGamePlaySubmit403JSONResponse); !ok {
+ t.Errorf("expected 403 response, got %T", resp)
+ } else if r.Message != "Game is not running" {
+ t.Errorf("unexpected message: %s", r.Message)
+ }
+}
+
+func TestPostLogin_AuthFailure(t *testing.T) {
+ h := Handler{
+ q: &mockQuerier{},
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{loginErr: errors.New("invalid credentials")},
+ conf: &config.Config{},
+ }
+ resp, err := h.PostLogin(context.Background(), PostLoginRequestObject{
+ Body: &PostLoginJSONRequestBody{Username: "user", Password: "wrong"},
+ })
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if _, ok := resp.(PostLogin401JSONResponse); !ok {
+ t.Errorf("expected 401 response, got %T", resp)
+ }
+}