aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend/game
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-16 20:05:39 +0900
committernsfisis <nsfisis@gmail.com>2026-02-16 20:13:47 +0900
commit071e7cc78d3f13fa782dbc6ca5fcec3a37263a4d (patch)
treed0174928c0d320bb76e0cdb899beee0476643d55 /backend/game
parent5ed369a6c70707543fd5ec9a13c79851fdfc5d6c (diff)
downloadphperkaigi-2026-albatross-071e7cc78d3f13fa782dbc6ca5fcec3a37263a4d.tar.gz
phperkaigi-2026-albatross-071e7cc78d3f13fa782dbc6ca5fcec3a37263a4d.tar.zst
phperkaigi-2026-albatross-071e7cc78d3f13fa782dbc6ca5fcec3a37263a4d.zip
test(backend): add unit tests for auth, config, ratelimit, game, and api
Cover previously untested logic: session ID generation/hashing, password authentication, IP rate limiting, game state helpers, handler endpoints, task enqueue/result processing, and config loading. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'backend/game')
-rw-r--r--backend/game/hub_test.go249
1 files changed, 248 insertions, 1 deletions
diff --git a/backend/game/hub_test.go b/backend/game/hub_test.go
index a8fad58..59ce996 100644
--- a/backend/game/hub_test.go
+++ b/backend/game/hub_test.go
@@ -1,6 +1,253 @@
package game
-import "testing"
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "albatross-2026-backend/db"
+ "albatross-2026-backend/taskqueue"
+)
+
+// mockTaskQueue implements TaskQueueInterface for testing.
+type mockTaskQueue struct {
+ enqueued []taskqueue.TaskPayloadRunTestcase
+ err error
+}
+
+func (m *mockTaskQueue) EnqueueTaskRunTestcase(gameID, userID, submissionID, testcaseID int, language, code, stdin, stdout string) error {
+ if m.err != nil {
+ return m.err
+ }
+ m.enqueued = append(m.enqueued, taskqueue.TaskPayloadRunTestcase{
+ GameID: gameID,
+ UserID: userID,
+ SubmissionID: submissionID,
+ TestcaseID: testcaseID,
+ Language: language,
+ Code: code,
+ Stdin: stdin,
+ Stdout: stdout,
+ })
+ return nil
+}
+
+// mockQuerier implements db.Querier for testing.
+type mockQuerier struct {
+ db.Querier
+ listTestcasesByGameIDFunc func(ctx context.Context, gameID int32) ([]db.Testcase, error)
+ createTestcaseResultFunc func(ctx context.Context, arg db.CreateTestcaseResultParams) error
+ createTestcaseResultCalls []db.CreateTestcaseResultParams
+}
+
+func (m *mockQuerier) ListTestcasesByGameID(ctx context.Context, gameID int32) ([]db.Testcase, error) {
+ if m.listTestcasesByGameIDFunc != nil {
+ return m.listTestcasesByGameIDFunc(ctx, gameID)
+ }
+ return nil, nil
+}
+
+func (m *mockQuerier) CreateTestcaseResult(_ context.Context, arg db.CreateTestcaseResultParams) error {
+ m.createTestcaseResultCalls = append(m.createTestcaseResultCalls, arg)
+ if m.createTestcaseResultFunc != nil {
+ return m.createTestcaseResultFunc(context.Background(), arg)
+ }
+ return nil
+}
+
+func TestEnqueueTestTasks(t *testing.T) {
+ testcases := []db.Testcase{
+ {TestcaseID: 1, ProblemID: 10, Stdin: "input1", Stdout: "output1"},
+ {TestcaseID: 2, ProblemID: 10, Stdin: "input2", Stdout: "output2"},
+ }
+
+ tq := &mockTaskQueue{}
+ mq := &mockQuerier{
+ listTestcasesByGameIDFunc: func(_ context.Context, _ int32) ([]db.Testcase, error) {
+ return testcases, nil
+ },
+ }
+
+ hub := &Hub{q: mq, taskQueue: tq, ctx: context.Background()}
+
+ err := hub.EnqueueTestTasks(context.Background(), 100, 1, 42, "php", "<?php echo 1;")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ if len(tq.enqueued) != 2 {
+ t.Fatalf("expected 2 enqueued tasks, got %d", len(tq.enqueued))
+ }
+ if tq.enqueued[0].TestcaseID != 1 || tq.enqueued[1].TestcaseID != 2 {
+ t.Errorf("unexpected testcase IDs: %d, %d", tq.enqueued[0].TestcaseID, tq.enqueued[1].TestcaseID)
+ }
+ if tq.enqueued[0].SubmissionID != 100 {
+ t.Errorf("expected submission ID 100, got %d", tq.enqueued[0].SubmissionID)
+ }
+}
+
+func TestEnqueueTestTasks_QueueError(t *testing.T) {
+ queueErr := errors.New("queue full")
+ tq := &mockTaskQueue{err: queueErr}
+ mq := &mockQuerier{
+ listTestcasesByGameIDFunc: func(_ context.Context, _ int32) ([]db.Testcase, error) {
+ return []db.Testcase{{TestcaseID: 1, Stdin: "in", Stdout: "out"}}, nil
+ },
+ }
+
+ hub := &Hub{q: mq, taskQueue: tq, ctx: context.Background()}
+
+ err := hub.EnqueueTestTasks(context.Background(), 100, 1, 42, "php", "code")
+ if !errors.Is(err, queueErr) {
+ t.Errorf("expected queue error, got: %v", err)
+ }
+}
+
+func TestEnqueueTestTasks_DBError(t *testing.T) {
+ dbErr := errors.New("db error")
+ tq := &mockTaskQueue{}
+ mq := &mockQuerier{
+ listTestcasesByGameIDFunc: func(_ context.Context, _ int32) ([]db.Testcase, error) {
+ return nil, dbErr
+ },
+ }
+
+ hub := &Hub{q: mq, taskQueue: tq, ctx: context.Background()}
+
+ err := hub.EnqueueTestTasks(context.Background(), 100, 1, 42, "php", "code")
+ if !errors.Is(err, dbErr) {
+ t.Errorf("expected db error, got: %v", err)
+ }
+}
+
+func TestProcessTaskResultRunTestcase_Success_Correct(t *testing.T) {
+ mq := &mockQuerier{}
+ hub := &Hub{q: mq, ctx: context.Background()}
+
+ result := &taskqueue.TaskResultRunTestcase{
+ TaskPayload: &taskqueue.TaskPayloadRunTestcase{
+ SubmissionID: 1,
+ TestcaseID: 2,
+ Stdout: "expected output",
+ },
+ Status: "success",
+ Stdout: "expected output",
+ Stderr: "",
+ }
+
+ err := hub.processTaskResultRunTestcase(result)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ if len(mq.createTestcaseResultCalls) != 1 {
+ t.Fatalf("expected 1 call, got %d", len(mq.createTestcaseResultCalls))
+ }
+ if mq.createTestcaseResultCalls[0].Status != "success" {
+ t.Errorf("expected status 'success', got %q", mq.createTestcaseResultCalls[0].Status)
+ }
+}
+
+func TestProcessTaskResultRunTestcase_Success_WrongAnswer(t *testing.T) {
+ mq := &mockQuerier{}
+ hub := &Hub{q: mq, ctx: context.Background()}
+
+ result := &taskqueue.TaskResultRunTestcase{
+ TaskPayload: &taskqueue.TaskPayloadRunTestcase{
+ SubmissionID: 1,
+ TestcaseID: 2,
+ Stdout: "expected",
+ },
+ Status: "success",
+ Stdout: "wrong",
+ Stderr: "",
+ }
+
+ err := hub.processTaskResultRunTestcase(result)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ if len(mq.createTestcaseResultCalls) != 1 {
+ t.Fatalf("expected 1 call, got %d", len(mq.createTestcaseResultCalls))
+ }
+ if mq.createTestcaseResultCalls[0].Status != "wrong_answer" {
+ t.Errorf("expected status 'wrong_answer', got %q", mq.createTestcaseResultCalls[0].Status)
+ }
+}
+
+func TestProcessTaskResultRunTestcase_NonSuccess(t *testing.T) {
+ mq := &mockQuerier{}
+ hub := &Hub{q: mq, ctx: context.Background()}
+
+ result := &taskqueue.TaskResultRunTestcase{
+ TaskPayload: &taskqueue.TaskPayloadRunTestcase{
+ SubmissionID: 1,
+ TestcaseID: 2,
+ Stdout: "expected",
+ },
+ Status: "timeout",
+ Stdout: "",
+ Stderr: "execution timed out",
+ }
+
+ err := hub.processTaskResultRunTestcase(result)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ if len(mq.createTestcaseResultCalls) != 1 {
+ t.Fatalf("expected 1 call, got %d", len(mq.createTestcaseResultCalls))
+ }
+ if mq.createTestcaseResultCalls[0].Status != "timeout" {
+ t.Errorf("expected status 'timeout', got %q", mq.createTestcaseResultCalls[0].Status)
+ }
+}
+
+func TestProcessTaskResultRunTestcase_TaskError(t *testing.T) {
+ mq := &mockQuerier{}
+ hub := &Hub{q: mq, ctx: context.Background()}
+
+ taskErr := errors.New("worker crashed")
+ result := &taskqueue.TaskResultRunTestcase{
+ TaskPayload: &taskqueue.TaskPayloadRunTestcase{
+ SubmissionID: 1,
+ TestcaseID: 2,
+ },
+ Err: taskErr,
+ }
+
+ err := hub.processTaskResultRunTestcase(result)
+ if !errors.Is(err, taskErr) {
+ t.Errorf("expected task error, got: %v", err)
+ }
+ if len(mq.createTestcaseResultCalls) != 0 {
+ t.Error("expected no DB calls when task has error")
+ }
+}
+
+func TestNormalizeTestcaseResultOutput(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ want string
+ }{
+ {"empty", "", ""},
+ {"no changes needed", "hello", "hello"},
+ {"trim spaces", " hello ", "hello"},
+ {"CRLF to LF", "line1\r\nline2", "line1\nline2"},
+ {"CR to LF", "line1\rline2", "line1\nline2"},
+ {"mixed", " line1\r\nline2\r ", "line1\nline2"},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := normalizeTestcaseResultOutput(tt.input)
+ if got != tt.want {
+ t.Errorf("normalizeTestcaseResultOutput(%q) = %q, want %q", tt.input, got, tt.want)
+ }
+ })
+ }
+}
func TestCalcCodeSize_PHP(t *testing.T) {
hub := &Hub{}