diff options
Diffstat (limited to 'backend/admin/handler_test.go')
| -rw-r--r-- | backend/admin/handler_test.go | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/backend/admin/handler_test.go b/backend/admin/handler_test.go index 20c45ef..0e13c60 100644 --- a/backend/admin/handler_test.go +++ b/backend/admin/handler_test.go @@ -44,6 +44,16 @@ type mockQuerier struct { getTestcaseResultsBySubmIDFunc func(ctx context.Context, submissionID int32) ([]db.TestcaseResult, error) listTestcasesByGameIDFunc func(ctx context.Context, gameID int32) ([]db.Testcase, error) updateGameStartedAtFunc func(ctx context.Context, arg db.UpdateGameStartedAtParams) error + listTournamentsFunc func(ctx context.Context) ([]db.Tournament, error) + getTournamentByIDFunc func(ctx context.Context, tournamentID int32) (db.Tournament, error) + createTournamentFunc func(ctx context.Context, arg db.CreateTournamentParams) (int32, error) + updateTournamentFunc func(ctx context.Context, arg db.UpdateTournamentParams) error + listTournamentEntriesFunc func(ctx context.Context, tournamentID int32) ([]db.ListTournamentEntriesRow, error) + deleteTournamentEntriesFunc func(ctx context.Context, tournamentID int32) error + createTournamentEntryFunc func(ctx context.Context, arg db.CreateTournamentEntryParams) error + listTournamentMatchesFunc func(ctx context.Context, tournamentID int32) ([]db.TournamentMatch, error) + createTournamentMatchFunc func(ctx context.Context, arg db.CreateTournamentMatchParams) error + updateTournamentMatchGameFunc func(ctx context.Context, arg db.UpdateTournamentMatchGameParams) error } func (m *mockQuerier) GetUserByID(ctx context.Context, userID int32) (db.User, error) { @@ -204,6 +214,76 @@ func (m *mockQuerier) ListTestcasesByProblemIDForUpdate(ctx context.Context, pro return m.ListTestcasesByProblemID(ctx, problemID) } +func (m *mockQuerier) ListTournaments(ctx context.Context) ([]db.Tournament, error) { + if m.listTournamentsFunc != nil { + return m.listTournamentsFunc(ctx) + } + return nil, nil +} + +func (m *mockQuerier) GetTournamentByID(ctx context.Context, tournamentID int32) (db.Tournament, error) { + if m.getTournamentByIDFunc != nil { + return m.getTournamentByIDFunc(ctx, tournamentID) + } + return db.Tournament{}, pgx.ErrNoRows +} + +func (m *mockQuerier) CreateTournament(ctx context.Context, arg db.CreateTournamentParams) (int32, error) { + if m.createTournamentFunc != nil { + return m.createTournamentFunc(ctx, arg) + } + return 1, nil +} + +func (m *mockQuerier) UpdateTournament(ctx context.Context, arg db.UpdateTournamentParams) error { + if m.updateTournamentFunc != nil { + return m.updateTournamentFunc(ctx, arg) + } + return nil +} + +func (m *mockQuerier) ListTournamentEntries(ctx context.Context, tournamentID int32) ([]db.ListTournamentEntriesRow, error) { + if m.listTournamentEntriesFunc != nil { + return m.listTournamentEntriesFunc(ctx, tournamentID) + } + return nil, nil +} + +func (m *mockQuerier) DeleteTournamentEntries(ctx context.Context, tournamentID int32) error { + if m.deleteTournamentEntriesFunc != nil { + return m.deleteTournamentEntriesFunc(ctx, tournamentID) + } + return nil +} + +func (m *mockQuerier) CreateTournamentEntry(ctx context.Context, arg db.CreateTournamentEntryParams) error { + if m.createTournamentEntryFunc != nil { + return m.createTournamentEntryFunc(ctx, arg) + } + return nil +} + +func (m *mockQuerier) ListTournamentMatches(ctx context.Context, tournamentID int32) ([]db.TournamentMatch, error) { + if m.listTournamentMatchesFunc != nil { + return m.listTournamentMatchesFunc(ctx, tournamentID) + } + return nil, nil +} + +func (m *mockQuerier) CreateTournamentMatch(ctx context.Context, arg db.CreateTournamentMatchParams) error { + if m.createTournamentMatchFunc != nil { + return m.createTournamentMatchFunc(ctx, arg) + } + return nil +} + +func (m *mockQuerier) UpdateTournamentMatchGame(ctx context.Context, arg db.UpdateTournamentMatchGameParams) error { + if m.updateTournamentMatchGameFunc != nil { + return m.updateTournamentMatchGameFunc(ctx, arg) + } + return nil +} + // mockTxManager implements db.TxManager for testing. type mockTxManager struct { runInTxFunc func(ctx context.Context, fn func(q db.Querier) error) error @@ -1062,3 +1142,221 @@ func TestGetSubmissionDetail_NotFound(t *testing.T) { t.Errorf("status = %d, want %d", httpErr.Code, http.StatusNotFound) } } + +// --- Tournament admin tests --- + +func TestNextPowerOf2(t *testing.T) { + tests := []struct { + input int + expected int + }{ + {2, 2}, + {3, 4}, + {4, 4}, + {5, 8}, + {6, 8}, + {7, 8}, + {8, 8}, + {9, 16}, + } + for _, tt := range tests { + got := nextPowerOf2(tt.input) + if got != tt.expected { + t.Errorf("nextPowerOf2(%d) = %d, want %d", tt.input, got, tt.expected) + } + } +} + +func TestLog2Int(t *testing.T) { + tests := []struct { + input int + expected int + }{ + {1, 0}, + {2, 1}, + {4, 2}, + {8, 3}, + {16, 4}, + } + for _, tt := range tests { + got := log2Int(tt.input) + if got != tt.expected { + t.Errorf("log2Int(%d) = %d, want %d", tt.input, got, tt.expected) + } + } +} + +func TestGetTournaments_Empty(t *testing.T) { + h := newTestHandler(&mockQuerier{}) + c, rec := newEchoContext(http.MethodGet, "/admin/tournaments", nil) + err := h.getTournaments(c) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want %d", rec.Code, http.StatusOK) + } +} + +func TestGetTournaments_WithData(t *testing.T) { + h := newTestHandler(&mockQuerier{ + listTournamentsFunc: func(_ context.Context) ([]db.Tournament, error) { + return []db.Tournament{ + {TournamentID: 1, DisplayName: "Tournament A", BracketSize: 4}, + {TournamentID: 2, DisplayName: "Tournament B", BracketSize: 8}, + }, nil + }, + }) + c, _ := newEchoContext(http.MethodGet, "/admin/tournaments", nil) + err := h.getTournaments(c) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestGetTournamentNew(t *testing.T) { + h := newTestHandler(&mockQuerier{}) + c, rec := newEchoContext(http.MethodGet, "/admin/tournaments/new", nil) + err := h.getTournamentNew(c) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want %d", rec.Code, http.StatusOK) + } +} + +func TestPostTournamentNew_Success(t *testing.T) { + var createdParams db.CreateTournamentParams + var matchCount int + h := &Handler{ + q: &mockQuerier{}, + txm: &mockTxManager{ + runInTxFunc: func(_ context.Context, fn func(q db.Querier) error) error { + return fn(&mockQuerier{ + createTournamentFunc: func(_ context.Context, arg db.CreateTournamentParams) (int32, error) { + createdParams = arg + return 1, nil + }, + createTournamentMatchFunc: func(_ context.Context, _ db.CreateTournamentMatchParams) error { + matchCount++ + return nil + }, + }) + }, + }, + conf: &config.Config{BasePath: "/test/"}, + } + form := url.Values{ + "display_name": {"Test Tournament"}, + "num_participants": {"3"}, + } + c, rec := newEchoContextWithForm("/admin/tournaments/new", nil, form) + err := h.postTournamentNew(c) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if rec.Code != http.StatusSeeOther { + t.Errorf("status = %d, want %d", rec.Code, http.StatusSeeOther) + } + if createdParams.DisplayName != "Test Tournament" { + t.Errorf("display_name = %q, want %q", createdParams.DisplayName, "Test Tournament") + } + if createdParams.BracketSize != 4 { + t.Errorf("bracket_size = %d, want 4", createdParams.BracketSize) + } + if createdParams.NumRounds != 2 { + t.Errorf("num_rounds = %d, want 2", createdParams.NumRounds) + } + // bracket_size=4, num_rounds=2 → round 0: 2 matches + round 1: 1 match = 3 matches + if matchCount != 3 { + t.Errorf("match count = %d, want 3", matchCount) + } +} + +func TestPostTournamentNew_InvalidParticipants(t *testing.T) { + h := newTestHandler(&mockQuerier{}) + form := url.Values{ + "display_name": {"Test"}, + "num_participants": {"abc"}, + } + c, _ := newEchoContextWithForm("/admin/tournaments/new", nil, form) + err := h.postTournamentNew(c) + if err == nil { + t.Fatal("expected error for invalid num_participants") + } + httpErr, ok := err.(*echo.HTTPError) + if !ok { + t.Fatalf("expected echo.HTTPError, got %T", err) + } + if httpErr.Code != http.StatusBadRequest { + t.Errorf("status = %d, want %d", httpErr.Code, http.StatusBadRequest) + } +} + +func TestGetTournamentEdit_NotFound(t *testing.T) { + h := newTestHandler(&mockQuerier{}) + c, _ := newEchoContext(http.MethodGet, "/admin/tournaments/999", map[string]string{ + "tournamentID": "999", + }) + err := h.getTournamentEdit(c) + if err == nil { + t.Fatal("expected error for non-existent tournament") + } + httpErr, ok := err.(*echo.HTTPError) + if !ok { + t.Fatalf("expected echo.HTTPError, got %T", err) + } + if httpErr.Code != http.StatusNotFound { + t.Errorf("status = %d, want %d", httpErr.Code, http.StatusNotFound) + } +} + +func TestGetTournamentEdit_Success(t *testing.T) { + h := newTestHandler(&mockQuerier{ + getTournamentByIDFunc: func(_ context.Context, _ int32) (db.Tournament, error) { + return db.Tournament{ + TournamentID: 1, + DisplayName: "Test", + BracketSize: 4, + NumRounds: 2, + }, nil + }, + listTournamentMatchesFunc: func(_ context.Context, _ int32) ([]db.TournamentMatch, error) { + return []db.TournamentMatch{ + {TournamentMatchID: 1, Round: 0, Position: 0}, + }, nil + }, + }) + c, rec := newEchoContext(http.MethodGet, "/admin/tournaments/1", map[string]string{ + "tournamentID": "1", + }) + err := h.getTournamentEdit(c) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if rec.Code != http.StatusOK { + t.Errorf("status = %d, want %d", rec.Code, http.StatusOK) + } +} + +func TestPostTournamentEdit_NotFound(t *testing.T) { + h := newTestHandler(&mockQuerier{}) + form := url.Values{ + "display_name": {"Updated"}, + } + c, _ := newEchoContextWithForm("/admin/tournaments/999", map[string]string{ + "tournamentID": "999", + }, form) + err := h.postTournamentEdit(c) + if err == nil { + t.Fatal("expected error for non-existent tournament") + } + httpErr, ok := err.(*echo.HTTPError) + if !ok { + t.Fatalf("expected echo.HTTPError, got %T", err) + } + if httpErr.Code != http.StatusNotFound { + t.Errorf("status = %d, want %d", httpErr.Code, http.StatusNotFound) + } +} |
