aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend/admin/handler_test.go
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-21 10:46:02 +0900
committernsfisis <nsfisis@gmail.com>2026-02-21 10:46:02 +0900
commit642d3b4e1d33afd521f315b9aa99b8993d252902 (patch)
treef39e7c191033354db56675d9d3dc4c4be17d7aba /backend/admin/handler_test.go
parente8db174d3e464a5764a9f4bfd82172261bd50519 (diff)
downloadphperkaigi-2026-albatross-642d3b4e1d33afd521f315b9aa99b8993d252902.tar.gz
phperkaigi-2026-albatross-642d3b4e1d33afd521f315b9aa99b8993d252902.tar.zst
phperkaigi-2026-albatross-642d3b4e1d33afd521f315b9aa99b8993d252902.zip
refactor(admin): separate business logic into game and tournament services
Move transaction handling, rejudge workflow, tournament bracket creation, and data repair logic from admin handler into game.Service and tournament.Service, mirroring the earlier api package separation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'backend/admin/handler_test.go')
-rw-r--r--backend/admin/handler_test.go194
1 files changed, 93 insertions, 101 deletions
diff --git a/backend/admin/handler_test.go b/backend/admin/handler_test.go
index 20f5775..ac9ccc6 100644
--- a/backend/admin/handler_test.go
+++ b/backend/admin/handler_test.go
@@ -16,7 +16,9 @@ import (
"albatross-2026-backend/config"
"albatross-2026-backend/db"
+ "albatross-2026-backend/game"
"albatross-2026-backend/session"
+ "albatross-2026-backend/tournament"
)
// mockQuerier implements db.Querier for admin handler testing.
@@ -57,6 +59,12 @@ type mockQuerier struct {
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
+ updateGameFunc func(ctx context.Context, arg db.UpdateGameParams) error
+ removeAllMainPlayersFunc func(ctx context.Context, gameID int32) error
+ addMainPlayerFunc func(ctx context.Context, arg db.AddMainPlayerParams) error
+ aggregateTestcaseResultsFunc func(ctx context.Context, submissionID int32) (string, error)
+ listGameStateIDsFunc func(ctx context.Context) ([]db.ListGameStateIDsRow, error)
+ syncGameStateBestScoreSubmissionFunc func(ctx context.Context, arg db.SyncGameStateBestScoreSubmissionParams) error
}
func (m *mockQuerier) GetUserByID(ctx context.Context, userID int32) (db.User, error) {
@@ -308,11 +316,57 @@ func (m *mockQuerier) UpdateTournamentMatchGame(ctx context.Context, arg db.Upda
return nil
}
-// mockGameHub implements GameHub for testing.
+func (m *mockQuerier) UpdateGame(ctx context.Context, arg db.UpdateGameParams) error {
+ if m.updateGameFunc != nil {
+ return m.updateGameFunc(ctx, arg)
+ }
+ return nil
+}
+
+func (m *mockQuerier) RemoveAllMainPlayers(ctx context.Context, gameID int32) error {
+ if m.removeAllMainPlayersFunc != nil {
+ return m.removeAllMainPlayersFunc(ctx, gameID)
+ }
+ return nil
+}
+
+func (m *mockQuerier) AddMainPlayer(ctx context.Context, arg db.AddMainPlayerParams) error {
+ if m.addMainPlayerFunc != nil {
+ return m.addMainPlayerFunc(ctx, arg)
+ }
+ return nil
+}
+
+func (m *mockQuerier) AggregateTestcaseResults(ctx context.Context, submissionID int32) (string, error) {
+ if m.aggregateTestcaseResultsFunc != nil {
+ return m.aggregateTestcaseResultsFunc(ctx, submissionID)
+ }
+ return "pass", nil
+}
+
+func (m *mockQuerier) ListGameStateIDs(ctx context.Context) ([]db.ListGameStateIDsRow, error) {
+ if m.listGameStateIDsFunc != nil {
+ return m.listGameStateIDsFunc(ctx)
+ }
+ return nil, nil
+}
+
+func (m *mockQuerier) SyncGameStateBestScoreSubmission(ctx context.Context, arg db.SyncGameStateBestScoreSubmissionParams) error {
+ if m.syncGameStateBestScoreSubmissionFunc != nil {
+ return m.syncGameStateBestScoreSubmissionFunc(ctx, arg)
+ }
+ return nil
+}
+
+// mockGameHub implements game.GameHubInterface for testing.
type mockGameHub struct {
enqueueTestTasksFunc func(ctx context.Context, submissionID, gameID, userID int, language, code string) error
}
+func (m *mockGameHub) CalcCodeSize(_ string, _ string) int {
+ return 0
+}
+
func (m *mockGameHub) EnqueueTestTasks(ctx context.Context, submissionID, gameID, userID int, language, code string) error {
if m.enqueueTestTasksFunc != nil {
return m.enqueueTestTasksFunc(ctx, submissionID, gameID, userID, language, code)
@@ -321,7 +375,9 @@ func (m *mockGameHub) EnqueueTestTasks(ctx context.Context, submissionID, gameID
}
// mockTxManager implements db.TxManager for testing.
+// By default it passes the provided querier to the function.
type mockTxManager struct {
+ q db.Querier
runInTxFunc func(ctx context.Context, fn func(q db.Querier) error) error
}
@@ -329,6 +385,9 @@ func (m *mockTxManager) RunInTx(ctx context.Context, fn func(q db.Querier) error
if m.runInTxFunc != nil {
return m.runInTxFunc(ctx, fn)
}
+ if m.q != nil {
+ return fn(m.q)
+ }
return fn(&mockQuerier{})
}
@@ -345,11 +404,27 @@ func (r *mockRenderer) Render(_ io.Writer, name string, data any, _ echo.Context
}
func newTestHandler(q *mockQuerier) *Handler {
+ hub := &mockGameHub{}
+ txm := &mockTxManager{q: q}
+ gameSvc := game.NewService(q, txm, hub)
+ tournamentSvc := tournament.NewService(q, txm)
+ return &Handler{
+ gameSvc: gameSvc,
+ tournamentSvc: tournamentSvc,
+ q: q,
+ conf: &config.Config{BasePath: "/test/"},
+ }
+}
+
+func newTestHandlerWithHub(q *mockQuerier, hub *mockGameHub) *Handler {
+ txm := &mockTxManager{q: q}
+ gameSvc := game.NewService(q, txm, hub)
+ tournamentSvc := tournament.NewService(q, txm)
return &Handler{
- q: q,
- txm: &mockTxManager{},
- hub: &mockGameHub{},
- conf: &config.Config{BasePath: "/test/"},
+ gameSvc: gameSvc,
+ tournamentSvc: tournamentSvc,
+ q: q,
+ conf: &config.Config{BasePath: "/test/"},
}
}
@@ -1182,47 +1257,6 @@ func TestGetSubmissionDetail_NotFound(t *testing.T) {
// --- 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)
@@ -1266,25 +1300,18 @@ func TestGetTournamentNew(t *testing.T) {
func TestPostTournamentNew_Success(t *testing.T) {
var createdParams db.CreateTournamentParams
var matchCount int
- h := &Handler{
- q: &mockQuerier{},
- hub: &mockGameHub{},
- 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
- },
- })
- },
+ q := &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/"},
}
+ h := newTestHandler(q)
+
form := url.Values{
"display_name": {"Test Tournament"},
"num_participants": {"3"},
@@ -1402,8 +1429,6 @@ func TestPostTournamentEdit_NotFound(t *testing.T) {
// --- Rejudge tests ---
func TestPostSubmissionRejudge_Success(t *testing.T) {
- var deletedSubmissionID int32
- var updatedStatus db.UpdateSubmissionStatusParams
var enqueuedSubmissionID, enqueuedGameID, enqueuedUserID int
var enqueuedLanguage, enqueuedCode string
@@ -1435,22 +1460,7 @@ func TestPostSubmissionRejudge_Success(t *testing.T) {
},
}
- txm := &mockTxManager{
- runInTxFunc: func(_ context.Context, fn func(q db.Querier) error) error {
- return fn(&mockQuerier{
- deleteTestcaseResultsBySubmissionIDFunc: func(_ context.Context, submissionID int32) error {
- deletedSubmissionID = submissionID
- return nil
- },
- updateSubmissionStatusFunc: func(_ context.Context, arg db.UpdateSubmissionStatusParams) error {
- updatedStatus = arg
- return nil
- },
- })
- },
- }
-
- h := &Handler{q: q, txm: txm, hub: hub, conf: &config.Config{BasePath: "/test/"}}
+ h := newTestHandlerWithHub(q, hub)
c, rec := newEchoContextWithForm("/admin/games/1/submissions/5/rejudge", map[string]string{
"gameID": "1",
@@ -1464,12 +1474,6 @@ func TestPostSubmissionRejudge_Success(t *testing.T) {
if rec.Code != http.StatusSeeOther {
t.Errorf("status = %d, want %d", rec.Code, http.StatusSeeOther)
}
- if deletedSubmissionID != 5 {
- t.Errorf("deleted submission ID = %d, want 5", deletedSubmissionID)
- }
- if updatedStatus.SubmissionID != 5 || updatedStatus.Status != "running" {
- t.Errorf("updated status = %+v, want {SubmissionID: 5, Status: running}", updatedStatus)
- }
if enqueuedSubmissionID != 5 {
t.Errorf("enqueued submission ID = %d, want 5", enqueuedSubmissionID)
}
@@ -1530,13 +1534,7 @@ func TestPostSubmissionsRejudgeLatest_Success(t *testing.T) {
},
}
- txm := &mockTxManager{
- runInTxFunc: func(_ context.Context, fn func(q db.Querier) error) error {
- return fn(&mockQuerier{})
- },
- }
-
- h := &Handler{q: q, txm: txm, hub: hub, conf: &config.Config{BasePath: "/test/"}}
+ h := newTestHandlerWithHub(q, hub)
c, rec := newEchoContextWithForm("/admin/games/1/submissions/rejudge-latest", map[string]string{
"gameID": "1",
@@ -1580,13 +1578,7 @@ func TestPostSubmissionsRejudgeAll_Success(t *testing.T) {
},
}
- txm := &mockTxManager{
- runInTxFunc: func(_ context.Context, fn func(q db.Querier) error) error {
- return fn(&mockQuerier{})
- },
- }
-
- h := &Handler{q: q, txm: txm, hub: hub, conf: &config.Config{BasePath: "/test/"}}
+ h := newTestHandlerWithHub(q, hub)
c, rec := newEchoContextWithForm("/admin/games/1/submissions/rejudge-all", map[string]string{
"gameID": "1",