aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend/api/handler_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'backend/api/handler_test.go')
-rw-r--r--backend/api/handler_test.go245
1 files changed, 244 insertions, 1 deletions
diff --git a/backend/api/handler_test.go b/backend/api/handler_test.go
index 47ad92c..a54f995 100644
--- a/backend/api/handler_test.go
+++ b/backend/api/handler_test.go
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"testing"
+ "time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
@@ -15,7 +16,8 @@ import (
// mockQuerier implements db.Querier for testing.
type mockQuerier struct {
db.Querier
- getGameByIDFunc func(ctx context.Context, gameID int32) (db.GetGameByIDRow, error)
+ getGameByIDFunc func(ctx context.Context, gameID int32) (db.GetGameByIDRow, error)
+ listMainPlayersFunc func(ctx context.Context, gameIDs []int32) ([]db.ListMainPlayersRow, error)
}
func (m *mockQuerier) GetGameByID(ctx context.Context, gameID int32) (db.GetGameByIDRow, error) {
@@ -25,6 +27,13 @@ func (m *mockQuerier) GetGameByID(ctx context.Context, gameID int32) (db.GetGame
return db.GetGameByIDRow{}, pgx.ErrNoRows
}
+func (m *mockQuerier) ListMainPlayers(ctx context.Context, gameIDs []int32) ([]db.ListMainPlayersRow, error) {
+ if m.listMainPlayersFunc != nil {
+ return m.listMainPlayersFunc(ctx, gameIDs)
+ }
+ return nil, nil
+}
+
// mockTxManager implements db.TxManager for testing.
type mockTxManager struct{}
@@ -110,6 +119,240 @@ func TestPostGamePlaySubmit_GameNotRunning(t *testing.T) {
}
}
+func TestIsGameRunning(t *testing.T) {
+ now := time.Now()
+ tests := []struct {
+ name string
+ game db.GetGameByIDRow
+ want bool
+ }{
+ {
+ name: "not started",
+ game: db.GetGameByIDRow{
+ StartedAt: pgtype.Timestamp{Valid: false},
+ DurationSeconds: 300,
+ },
+ want: false,
+ },
+ {
+ name: "running",
+ game: db.GetGameByIDRow{
+ StartedAt: pgtype.Timestamp{Time: now.Add(-1 * time.Minute), Valid: true},
+ DurationSeconds: 300,
+ },
+ want: true,
+ },
+ {
+ name: "finished",
+ game: db.GetGameByIDRow{
+ StartedAt: pgtype.Timestamp{Time: now.Add(-10 * time.Minute), Valid: true},
+ DurationSeconds: 300,
+ },
+ want: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := isGameRunning(tt.game)
+ if got != tt.want {
+ t.Errorf("isGameRunning() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestIsGameFinished(t *testing.T) {
+ now := time.Now()
+ tests := []struct {
+ name string
+ game db.GetGameByIDRow
+ want bool
+ }{
+ {
+ name: "not started",
+ game: db.GetGameByIDRow{
+ StartedAt: pgtype.Timestamp{Valid: false},
+ DurationSeconds: 300,
+ },
+ want: false,
+ },
+ {
+ name: "still running",
+ game: db.GetGameByIDRow{
+ StartedAt: pgtype.Timestamp{Time: now.Add(-1 * time.Minute), Valid: true},
+ DurationSeconds: 300,
+ },
+ want: false,
+ },
+ {
+ name: "finished",
+ game: db.GetGameByIDRow{
+ StartedAt: pgtype.Timestamp{Time: now.Add(-10 * time.Minute), Valid: true},
+ DurationSeconds: 300,
+ },
+ want: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := isGameFinished(tt.game)
+ if got != tt.want {
+ t.Errorf("isGameFinished() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestToNullable(t *testing.T) {
+ t.Run("nil value", func(t *testing.T) {
+ result := toNullable[string](nil)
+ if !result.IsNull() {
+ t.Error("expected null for nil input")
+ }
+ })
+ t.Run("non-nil value", func(t *testing.T) {
+ s := "hello"
+ result := toNullable(&s)
+ 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)
+ }
+ })
+}
+
+func TestGetMe(t *testing.T) {
+ h := Handler{
+ q: &mockQuerier{},
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{},
+ }
+ user := &db.User{
+ UserID: 1,
+ Username: "testuser",
+ DisplayName: "Test User",
+ IsAdmin: false,
+ }
+ resp, err := h.GetMe(context.Background(), GetMeRequestObject{}, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ okResp, ok := resp.(GetMe200JSONResponse)
+ if !ok {
+ t.Fatalf("expected 200 response, got %T", resp)
+ }
+ if okResp.User.UserID != 1 {
+ t.Errorf("expected user ID 1, got %d", okResp.User.UserID)
+ }
+ if okResp.User.Username != "testuser" {
+ t.Errorf("expected username 'testuser', got %q", okResp.User.Username)
+ }
+ if okResp.User.IsAdmin {
+ t.Error("expected non-admin user")
+ }
+}
+
+func TestGetGame_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.GetGame(context.Background(), GetGameRequestObject{GameID: 999}, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if _, ok := resp.(GetGame404JSONResponse); !ok {
+ t.Errorf("expected 404 response, got %T", resp)
+ }
+}
+
+func TestGetGame_NonPublicAsNonAdmin(t *testing.T) {
+ h := Handler{
+ q: &mockQuerier{
+ getGameByIDFunc: func(_ context.Context, _ int32) (db.GetGameByIDRow, error) {
+ return db.GetGameByIDRow{
+ GameID: 1,
+ IsPublic: false,
+ Language: "php",
+ }, nil
+ },
+ },
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{},
+ }
+ user := &db.User{UserID: 1, IsAdmin: false}
+ resp, err := h.GetGame(context.Background(), GetGameRequestObject{GameID: 1}, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if _, ok := resp.(GetGame404JSONResponse); !ok {
+ t.Errorf("expected 404 for non-public game as non-admin, got %T", resp)
+ }
+}
+
+func TestGetGame_PublicGameSuccess(t *testing.T) {
+ now := time.Now()
+ h := Handler{
+ q: &mockQuerier{
+ getGameByIDFunc: func(_ context.Context, _ int32) (db.GetGameByIDRow, error) {
+ return db.GetGameByIDRow{
+ GameID: 1,
+ IsPublic: true,
+ Language: "php",
+ DisplayName: "Test Game",
+ DurationSeconds: 300,
+ StartedAt: pgtype.Timestamp{Time: now, Valid: true},
+ GameType: "golf",
+ ProblemID: 10,
+ Title: "Test Problem",
+ Description: "desc",
+ SampleCode: "<?php",
+ }, nil
+ },
+ listMainPlayersFunc: func(_ context.Context, _ []int32) ([]db.ListMainPlayersRow, error) {
+ return []db.ListMainPlayersRow{
+ {UserID: 1, Username: "player1", DisplayName: "Player 1", IsAdmin: false},
+ }, nil
+ },
+ },
+ txm: &mockTxManager{},
+ hub: &mockGameHub{},
+ auth: &mockAuthenticator{},
+ conf: &config.Config{},
+ }
+ user := &db.User{UserID: 1, IsAdmin: false}
+ resp, err := h.GetGame(context.Background(), GetGameRequestObject{GameID: 1}, user)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ okResp, ok := resp.(GetGame200JSONResponse)
+ if !ok {
+ t.Fatalf("expected 200 response, got %T", resp)
+ }
+ if okResp.Game.GameID != 1 {
+ t.Errorf("expected game ID 1, got %d", okResp.Game.GameID)
+ }
+ if len(okResp.Game.MainPlayers) != 1 {
+ t.Fatalf("expected 1 main player, got %d", len(okResp.Game.MainPlayers))
+ }
+ if okResp.Game.MainPlayers[0].Username != "player1" {
+ t.Errorf("expected username 'player1', got %q", okResp.Game.MainPlayers[0].Username)
+ }
+}
+
func TestPostLogin_AuthFailure(t *testing.T) {
h := Handler{
q: &mockQuerier{},