aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend/api/handler_test.go
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-16 22:02:58 +0900
committernsfisis <nsfisis@gmail.com>2026-02-16 22:02:58 +0900
commitdb87f85aa7055e597800481b8cc6d006c70bcc88 (patch)
tree57630fde35a39e445c177a278cacf243b7fb0d52 /backend/api/handler_test.go
parent08c121c21a7e429e43e2d51fa4a3d8bd945c5d01 (diff)
downloadphperkaigi-2026-albatross-db87f85aa7055e597800481b8cc6d006c70bcc88.tar.gz
phperkaigi-2026-albatross-db87f85aa7055e597800481b8cc6d006c70bcc88.tar.zst
phperkaigi-2026-albatross-db87f85aa7055e597800481b8cc6d006c70bcc88.zip
test(backend): add unit tests for auth_middleware, fortee, processor, account, and more handlers
Cover previously untested code: SessionCookieMiddleware, context helpers, downloadFile, addAcceptHeader, doProcessTaskRunTestcase, updateSubmissionAndGameState, PostLogout, GetGames, PostGamePlayCode, GetGameWatchRanking, GetGameWatchLatestStates. 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.go354
1 files changed, 354 insertions, 0 deletions
diff --git a/backend/api/handler_test.go b/backend/api/handler_test.go
index a68dfa0..2340d33 100644
--- a/backend/api/handler_test.go
+++ b/backend/api/handler_test.go
@@ -18,6 +18,12 @@ type mockQuerier struct {
db.Querier
getGameByIDFunc func(ctx context.Context, gameID int32) (db.GetGameByIDRow, error)
listMainPlayersFunc func(ctx context.Context, gameIDs []int32) ([]db.ListMainPlayersRow, error)
+ listPublicGamesFunc func(ctx context.Context) ([]db.ListPublicGamesRow, error)
+ deleteSessionFunc func(ctx context.Context, sessionID string) error
+ getLatestStateFunc func(ctx context.Context, arg db.GetLatestStateParams) (db.GetLatestStateRow, error)
+ updateCodeFunc func(ctx context.Context, arg db.UpdateCodeParams) error
+ getRankingFunc func(ctx context.Context, gameID int32) ([]db.GetRankingRow, error)
+ getLatestStatesFunc func(ctx context.Context, gameID int32) ([]db.GetLatestStatesOfMainPlayersRow, error)
}
func (m *mockQuerier) GetGameByID(ctx context.Context, gameID int32) (db.GetGameByIDRow, error) {
@@ -34,6 +40,48 @@ func (m *mockQuerier) ListMainPlayers(ctx context.Context, gameIDs []int32) ([]d
return nil, nil
}
+func (m *mockQuerier) ListPublicGames(ctx context.Context) ([]db.ListPublicGamesRow, error) {
+ if m.listPublicGamesFunc != nil {
+ return m.listPublicGamesFunc(ctx)
+ }
+ return nil, nil
+}
+
+func (m *mockQuerier) DeleteSession(ctx context.Context, sessionID string) error {
+ if m.deleteSessionFunc != nil {
+ return m.deleteSessionFunc(ctx, sessionID)
+ }
+ return nil
+}
+
+func (m *mockQuerier) GetLatestState(ctx context.Context, arg db.GetLatestStateParams) (db.GetLatestStateRow, error) {
+ if m.getLatestStateFunc != nil {
+ return m.getLatestStateFunc(ctx, arg)
+ }
+ return db.GetLatestStateRow{}, pgx.ErrNoRows
+}
+
+func (m *mockQuerier) UpdateCode(ctx context.Context, arg db.UpdateCodeParams) error {
+ if m.updateCodeFunc != nil {
+ return m.updateCodeFunc(ctx, arg)
+ }
+ return nil
+}
+
+func (m *mockQuerier) GetRanking(ctx context.Context, gameID int32) ([]db.GetRankingRow, error) {
+ if m.getRankingFunc != nil {
+ return m.getRankingFunc(ctx, gameID)
+ }
+ return nil, nil
+}
+
+func (m *mockQuerier) GetLatestStatesOfMainPlayers(ctx context.Context, gameID int32) ([]db.GetLatestStatesOfMainPlayersRow, error) {
+ if m.getLatestStatesFunc != nil {
+ return m.getLatestStatesFunc(ctx, gameID)
+ }
+ return nil, nil
+}
+
// mockTxManager implements db.TxManager for testing.
type mockTxManager struct{}
@@ -371,3 +419,309 @@ func TestPostLogin_AuthFailure(t *testing.T) {
t.Errorf("expected 401 response, got %T", resp)
}
}
+
+func TestPostLogout(t *testing.T) {
+ h := Handler{
+ q: &mockQuerier{},
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{BasePath: "/"},
+ }
+ user := &db.User{UserID: 1}
+ // Set session ID in context
+ ctx := context.WithValue(context.Background(), sessionIDContextKey{}, "hashed-session")
+ resp, err := h.PostLogout(ctx, PostLogoutRequestObject{}, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if _, ok := resp.(postLogoutCookieResponse); !ok {
+ t.Errorf("expected postLogoutCookieResponse, got %T", resp)
+ }
+}
+
+func TestGetGames_Empty(t *testing.T) {
+ h := Handler{
+ q: &mockQuerier{},
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{},
+ }
+ user := &db.User{UserID: 1}
+ resp, err := h.GetGames(context.Background(), GetGamesRequestObject{}, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ okResp, ok := resp.(GetGames200JSONResponse)
+ if !ok {
+ t.Fatalf("expected 200 response, got %T", resp)
+ }
+ if len(okResp.Games) != 0 {
+ t.Errorf("expected 0 games, got %d", len(okResp.Games))
+ }
+}
+
+func TestGetGames_WithGames(t *testing.T) {
+ now := time.Now()
+ h := Handler{
+ q: &mockQuerier{
+ listPublicGamesFunc: func(_ context.Context) ([]db.ListPublicGamesRow, error) {
+ return []db.ListPublicGamesRow{
+ {
+ GameID: 1,
+ GameType: "golf",
+ IsPublic: true,
+ DisplayName: "Game 1",
+ DurationSeconds: 300,
+ StartedAt: pgtype.Timestamp{Time: now, Valid: true},
+ ProblemID: 10,
+ Title: "Problem 1",
+ Description: "desc",
+ Language: "php",
+ SampleCode: "<?php",
+ },
+ }, nil
+ },
+ listMainPlayersFunc: func(_ context.Context, _ []int32) ([]db.ListMainPlayersRow, error) {
+ return nil, nil
+ },
+ },
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{},
+ }
+ user := &db.User{UserID: 1}
+ resp, err := h.GetGames(context.Background(), GetGamesRequestObject{}, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ okResp, ok := resp.(GetGames200JSONResponse)
+ if !ok {
+ t.Fatalf("expected 200 response, got %T", resp)
+ }
+ if len(okResp.Games) != 1 {
+ t.Fatalf("expected 1 game, got %d", len(okResp.Games))
+ }
+ if okResp.Games[0].DisplayName != "Game 1" {
+ t.Errorf("expected display name 'Game 1', got %q", okResp.Games[0].DisplayName)
+ }
+ if okResp.Games[0].StartedAt == nil {
+ t.Error("expected non-nil StartedAt")
+ }
+}
+
+func TestGetGamePlayLatestState_NoState(t *testing.T) {
+ h := Handler{
+ q: &mockQuerier{},
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{},
+ }
+ user := &db.User{UserID: 1}
+ resp, err := h.GetGamePlayLatestState(context.Background(), GetGamePlayLatestStateRequestObject{GameID: 1}, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ okResp, ok := resp.(GetGamePlayLatestState200JSONResponse)
+ if !ok {
+ t.Fatalf("expected 200 response, got %T", resp)
+ }
+ if okResp.State.Code != "" {
+ t.Errorf("expected empty code, got %q", okResp.State.Code)
+ }
+ if okResp.State.Status != None {
+ t.Errorf("expected status 'none', got %q", okResp.State.Status)
+ }
+}
+
+func TestPostGamePlayCode_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.PostGamePlayCode(context.Background(), PostGamePlayCodeRequestObject{
+ GameID: 999,
+ Body: &PostGamePlayCodeJSONRequestBody{Code: "test"},
+ }, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if _, ok := resp.(PostGamePlayCode404JSONResponse); !ok {
+ t.Errorf("expected 404 response, got %T", resp)
+ }
+}
+
+func TestPostGamePlayCode_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{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{},
+ }
+ user := &db.User{UserID: 1}
+ resp, err := h.PostGamePlayCode(context.Background(), PostGamePlayCodeRequestObject{
+ GameID: 1,
+ Body: &PostGamePlayCodeJSONRequestBody{Code: "<?php echo 1;"},
+ }, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if _, ok := resp.(PostGamePlayCode403JSONResponse); !ok {
+ t.Errorf("expected 403 response, got %T", resp)
+ }
+}
+
+func TestPostGamePlayCode_Success(t *testing.T) {
+ now := time.Now()
+ var updatedCode string
+ h := Handler{
+ q: &mockQuerier{
+ getGameByIDFunc: func(_ context.Context, _ int32) (db.GetGameByIDRow, error) {
+ return db.GetGameByIDRow{
+ GameID: 1,
+ Language: "php",
+ StartedAt: pgtype.Timestamp{Time: now, Valid: true},
+ DurationSeconds: 600,
+ }, nil
+ },
+ updateCodeFunc: func(_ context.Context, arg db.UpdateCodeParams) error {
+ updatedCode = arg.Code
+ return nil
+ },
+ },
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{},
+ }
+ user := &db.User{UserID: 1}
+ resp, err := h.PostGamePlayCode(context.Background(), PostGamePlayCodeRequestObject{
+ GameID: 1,
+ Body: &PostGamePlayCodeJSONRequestBody{Code: "<?php echo 42;"},
+ }, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if _, ok := resp.(PostGamePlayCode200Response); !ok {
+ t.Errorf("expected 200 response, got %T", resp)
+ }
+ if updatedCode != "<?php echo 42;" {
+ t.Errorf("expected code '<?php echo 42;', got %q", updatedCode)
+ }
+}
+
+func TestGetGameWatchRanking_NotFound(t *testing.T) {
+ h := Handler{
+ q: &mockQuerier{},
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{},
+ }
+ user := &db.User{UserID: 1}
+ resp, err := h.GetGameWatchRanking(context.Background(), GetGameWatchRankingRequestObject{GameID: 999}, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if _, ok := resp.(GetGameWatchRanking404JSONResponse); !ok {
+ t.Errorf("expected 404 response, got %T", resp)
+ }
+}
+
+func TestGetGameWatchRanking_EmptyRanking(t *testing.T) {
+ now := time.Now()
+ h := Handler{
+ q: &mockQuerier{
+ getGameByIDFunc: func(_ context.Context, _ int32) (db.GetGameByIDRow, error) {
+ return db.GetGameByIDRow{
+ GameID: 1,
+ Language: "php",
+ StartedAt: pgtype.Timestamp{Time: now.Add(-10 * time.Minute), Valid: true},
+ DurationSeconds: 300,
+ }, nil
+ },
+ getRankingFunc: func(_ context.Context, _ int32) ([]db.GetRankingRow, error) {
+ return nil, nil
+ },
+ },
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{},
+ }
+ user := &db.User{UserID: 1}
+ resp, err := h.GetGameWatchRanking(context.Background(), GetGameWatchRankingRequestObject{GameID: 1}, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ okResp, ok := resp.(GetGameWatchRanking200JSONResponse)
+ if !ok {
+ t.Fatalf("expected 200 response, got %T", resp)
+ }
+ if len(okResp.Ranking) != 0 {
+ t.Errorf("expected empty ranking, got %d entries", len(okResp.Ranking))
+ }
+}
+
+func TestGetGameWatchLatestStates_Empty(t *testing.T) {
+ h := Handler{
+ q: &mockQuerier{},
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{},
+ }
+ user := &db.User{UserID: 1}
+ resp, err := h.GetGameWatchLatestStates(context.Background(), GetGameWatchLatestStatesRequestObject{GameID: 1}, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ okResp, ok := resp.(GetGameWatchLatestStates200JSONResponse)
+ if !ok {
+ t.Fatalf("expected 200 response, got %T", resp)
+ }
+ if len(okResp.States) != 0 {
+ t.Errorf("expected 0 states, got %d", len(okResp.States))
+ }
+}
+
+func TestToNullableWith(t *testing.T) {
+ t.Run("nil value", func(t *testing.T) {
+ result := toNullableWith[int, string](nil, func(_ int) string { return "x" })
+ if !result.IsNull() {
+ t.Error("expected null for nil input")
+ }
+ })
+ t.Run("non-nil value", func(t *testing.T) {
+ x := 42
+ result := toNullableWith(&x, func(_ int) string { return "hello" })
+ if result.IsNull() {
+ t.Error("expected non-null for non-nil input")
+ }
+ v, err := result.Get()
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if v != "hello" {
+ t.Errorf("expected 'hello', got %q", v)
+ }
+ })
+}