aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend/admin/handler_test.go
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-20 21:30:49 +0900
committernsfisis <nsfisis@gmail.com>2026-02-20 21:30:49 +0900
commitfa788237eb5649e08b2a38ec21689b481b10c073 (patch)
tree13ad37ff587b81810f59e8e7c5943aafd25f61dd /backend/admin/handler_test.go
parent9f9efc2bc07810d2e06b37bad94e5922681eadef (diff)
downloadphperkaigi-2026-albatross-fa788237eb5649e08b2a38ec21689b481b10c073.tar.gz
phperkaigi-2026-albatross-fa788237eb5649e08b2a38ec21689b481b10c073.tar.zst
phperkaigi-2026-albatross-fa788237eb5649e08b2a38ec21689b481b10c073.zip
feat(admin): add rejudge functionality for submissions
Allow administrators to re-execute test cases for a specific submission from the submission detail page. This is useful after testcase fixes or worker issues. 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.go205
1 files changed, 172 insertions, 33 deletions
diff --git a/backend/admin/handler_test.go b/backend/admin/handler_test.go
index 0e13c60..3b7a2ba 100644
--- a/backend/admin/handler_test.go
+++ b/backend/admin/handler_test.go
@@ -22,38 +22,40 @@ import (
// mockQuerier implements db.Querier for admin handler testing.
type mockQuerier struct {
db.Querier
- getUserByIDFunc func(ctx context.Context, userID int32) (db.User, error)
- listUsersFunc func(ctx context.Context) ([]db.User, error)
- updateUserFunc func(ctx context.Context, arg db.UpdateUserParams) error
- listAllGamesFunc func(ctx context.Context) ([]db.Game, error)
- getGameByIDFunc func(ctx context.Context, gameID int32) (db.GetGameByIDRow, error)
- listProblemsFunc func(ctx context.Context) ([]db.Problem, error)
- getProblemByIDFunc func(ctx context.Context, problemID int32) (db.Problem, error)
- createGameFunc func(ctx context.Context, arg db.CreateGameParams) (int32, error)
- createProblemFunc func(ctx context.Context, arg db.CreateProblemParams) (int32, error)
- updateProblemFunc func(ctx context.Context, arg db.UpdateProblemParams) error
- listTestcasesByProblemIDFunc func(ctx context.Context, problemID int32) ([]db.Testcase, error)
- getTestcaseByIDFunc func(ctx context.Context, testcaseID int32) (db.Testcase, error)
- createTestcaseFunc func(ctx context.Context, arg db.CreateTestcaseParams) (int32, error)
- updateTestcaseFunc func(ctx context.Context, arg db.UpdateTestcaseParams) error
- deleteTestcaseFunc func(ctx context.Context, testcaseID int32) error
- listMainPlayersFunc func(ctx context.Context, gameIDs []int32) ([]db.ListMainPlayersRow, error)
- listSubmissionIDsFunc func(ctx context.Context) ([]int32, error)
- getSubmissionsByGameIDFunc func(ctx context.Context, gameID int32) ([]db.Submission, error)
- getSubmissionByIDFunc func(ctx context.Context, submissionID int32) (db.Submission, error)
- 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
+ getUserByIDFunc func(ctx context.Context, userID int32) (db.User, error)
+ listUsersFunc func(ctx context.Context) ([]db.User, error)
+ updateUserFunc func(ctx context.Context, arg db.UpdateUserParams) error
+ listAllGamesFunc func(ctx context.Context) ([]db.Game, error)
+ getGameByIDFunc func(ctx context.Context, gameID int32) (db.GetGameByIDRow, error)
+ listProblemsFunc func(ctx context.Context) ([]db.Problem, error)
+ getProblemByIDFunc func(ctx context.Context, problemID int32) (db.Problem, error)
+ createGameFunc func(ctx context.Context, arg db.CreateGameParams) (int32, error)
+ createProblemFunc func(ctx context.Context, arg db.CreateProblemParams) (int32, error)
+ updateProblemFunc func(ctx context.Context, arg db.UpdateProblemParams) error
+ listTestcasesByProblemIDFunc func(ctx context.Context, problemID int32) ([]db.Testcase, error)
+ getTestcaseByIDFunc func(ctx context.Context, testcaseID int32) (db.Testcase, error)
+ createTestcaseFunc func(ctx context.Context, arg db.CreateTestcaseParams) (int32, error)
+ updateTestcaseFunc func(ctx context.Context, arg db.UpdateTestcaseParams) error
+ deleteTestcaseFunc func(ctx context.Context, testcaseID int32) error
+ deleteTestcaseResultsBySubmissionIDFunc func(ctx context.Context, submissionID int32) error
+ listMainPlayersFunc func(ctx context.Context, gameIDs []int32) ([]db.ListMainPlayersRow, error)
+ listSubmissionIDsFunc func(ctx context.Context) ([]int32, error)
+ getSubmissionsByGameIDFunc func(ctx context.Context, gameID int32) ([]db.Submission, error)
+ getSubmissionByIDFunc func(ctx context.Context, submissionID int32) (db.Submission, error)
+ getTestcaseResultsBySubmIDFunc func(ctx context.Context, submissionID int32) ([]db.TestcaseResult, error)
+ updateSubmissionStatusFunc func(ctx context.Context, arg db.UpdateSubmissionStatusParams) 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) {
@@ -277,6 +279,20 @@ func (m *mockQuerier) CreateTournamentMatch(ctx context.Context, arg db.CreateTo
return nil
}
+func (m *mockQuerier) DeleteTestcaseResultsBySubmissionID(ctx context.Context, submissionID int32) error {
+ if m.deleteTestcaseResultsBySubmissionIDFunc != nil {
+ return m.deleteTestcaseResultsBySubmissionIDFunc(ctx, submissionID)
+ }
+ return nil
+}
+
+func (m *mockQuerier) UpdateSubmissionStatus(ctx context.Context, arg db.UpdateSubmissionStatusParams) error {
+ if m.updateSubmissionStatusFunc != nil {
+ return m.updateSubmissionStatusFunc(ctx, arg)
+ }
+ return nil
+}
+
func (m *mockQuerier) UpdateTournamentMatchGame(ctx context.Context, arg db.UpdateTournamentMatchGameParams) error {
if m.updateTournamentMatchGameFunc != nil {
return m.updateTournamentMatchGameFunc(ctx, arg)
@@ -284,6 +300,18 @@ func (m *mockQuerier) UpdateTournamentMatchGame(ctx context.Context, arg db.Upda
return nil
}
+// mockGameHub implements GameHub for testing.
+type mockGameHub struct {
+ enqueueTestTasksFunc func(ctx context.Context, submissionID, gameID, userID int, language, code string) error
+}
+
+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)
+ }
+ return nil
+}
+
// mockTxManager implements db.TxManager for testing.
type mockTxManager struct {
runInTxFunc func(ctx context.Context, fn func(q db.Querier) error) error
@@ -312,6 +340,7 @@ func newTestHandler(q *mockQuerier) *Handler {
return &Handler{
q: q,
txm: &mockTxManager{},
+ hub: &mockGameHub{},
conf: &config.Config{BasePath: "/test/"},
}
}
@@ -1230,7 +1259,8 @@ func TestPostTournamentNew_Success(t *testing.T) {
var createdParams db.CreateTournamentParams
var matchCount int
h := &Handler{
- q: &mockQuerier{},
+ q: &mockQuerier{},
+ hub: &mockGameHub{},
txm: &mockTxManager{
runInTxFunc: func(_ context.Context, fn func(q db.Querier) error) error {
return fn(&mockQuerier{
@@ -1360,3 +1390,112 @@ func TestPostTournamentEdit_NotFound(t *testing.T) {
t.Errorf("status = %d, want %d", httpErr.Code, http.StatusNotFound)
}
}
+
+// --- Rejudge tests ---
+
+func TestPostSubmissionRejudge_Success(t *testing.T) {
+ var deletedSubmissionID int32
+ var updatedStatus db.UpdateSubmissionStatusParams
+ var enqueuedSubmissionID, enqueuedGameID, enqueuedUserID int
+ var enqueuedLanguage, enqueuedCode string
+
+ q := &mockQuerier{
+ getSubmissionByIDFunc: func(_ context.Context, submissionID int32) (db.Submission, error) {
+ return db.Submission{
+ SubmissionID: submissionID,
+ GameID: 1,
+ UserID: 10,
+ Code: "<?php echo 1;",
+ CodeSize: 14,
+ Status: "wrong_answer",
+ CreatedAt: pgtype.Timestamp{Valid: true},
+ }, nil
+ },
+ getGameByIDFunc: func(_ context.Context, gameID int32) (db.GetGameByIDRow, error) {
+ return db.GetGameByIDRow{GameID: gameID, ProblemID: 1, Language: "php"}, nil
+ },
+ }
+
+ hub := &mockGameHub{
+ enqueueTestTasksFunc: func(_ context.Context, submissionID, gameID, userID int, language, code string) error {
+ enqueuedSubmissionID = submissionID
+ enqueuedGameID = gameID
+ enqueuedUserID = userID
+ enqueuedLanguage = language
+ enqueuedCode = code
+ return nil
+ },
+ }
+
+ 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/"}}
+
+ c, rec := newEchoContextWithForm("/admin/games/1/submissions/5/rejudge", map[string]string{
+ "gameID": "1",
+ "submissionID": "5",
+ }, url.Values{})
+
+ err := h.postSubmissionRejudge(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 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)
+ }
+ if enqueuedGameID != 1 {
+ t.Errorf("enqueued game ID = %d, want 1", enqueuedGameID)
+ }
+ if enqueuedUserID != 10 {
+ t.Errorf("enqueued user ID = %d, want 10", enqueuedUserID)
+ }
+ if enqueuedLanguage != "php" {
+ t.Errorf("enqueued language = %q, want %q", enqueuedLanguage, "php")
+ }
+ if enqueuedCode != "<?php echo 1;" {
+ t.Errorf("enqueued code = %q, want %q", enqueuedCode, "<?php echo 1;")
+ }
+}
+
+func TestPostSubmissionRejudge_SubmissionNotFound(t *testing.T) {
+ h := newTestHandler(&mockQuerier{})
+
+ c, _ := newEchoContextWithForm("/admin/games/1/submissions/999/rejudge", map[string]string{
+ "gameID": "1",
+ "submissionID": "999",
+ }, url.Values{})
+
+ err := h.postSubmissionRejudge(c)
+ if err == nil {
+ t.Fatal("expected error for non-existent submission")
+ }
+ 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)
+ }
+}