diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-16 20:05:39 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-16 20:13:47 +0900 |
| commit | 071e7cc78d3f13fa782dbc6ca5fcec3a37263a4d (patch) | |
| tree | d0174928c0d320bb76e0cdb899beee0476643d55 /backend/game | |
| parent | 5ed369a6c70707543fd5ec9a13c79851fdfc5d6c (diff) | |
| download | phperkaigi-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.go | 249 |
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{} |
