package api import ( "context" "errors" "testing" "time" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" "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 testing. type mockQuerier struct { db.Querier getGameByIDFunc func(ctx context.Context, gameID int32) (db.GetGameByIDRow, error) listMainPlayersFunc func(ctx context.Context, gameIDs []int32) ([]db.ListMainPlayersRow, error) listPublicGamesFunc func(ctx context.Context) ([]db.ListPublicGamesRow, error) deleteSessionFunc func(ctx context.Context, sessionID string) error getLatestStateFunc func(ctx context.Context, arg db.GetLatestStateParams) (db.GetLatestStateRow, error) updateCodeFunc func(ctx context.Context, arg db.UpdateCodeParams) error getRankingFunc func(ctx context.Context, gameID int32) ([]db.GetRankingRow, error) getLatestStatesFunc func(ctx context.Context, gameID int32) ([]db.GetLatestStatesOfMainPlayersRow, error) getTournamentByIDFunc func(ctx context.Context, tournamentID int32) (db.Tournament, error) listTournamentEntriesFunc func(ctx context.Context, tournamentID int32) ([]db.ListTournamentEntriesRow, error) listTournamentMatchesFunc func(ctx context.Context, tournamentID int32) ([]db.TournamentMatch, error) getSubmissionsByGameIDAndUserIDFunc func(ctx context.Context, arg db.GetSubmissionsByGameIDAndUserIDParams) ([]db.Submission, error) getUserByIDFunc func(ctx context.Context, userID int32) (db.User, error) } func (m *mockQuerier) GetGameByID(ctx context.Context, gameID int32) (db.GetGameByIDRow, error) { if m.getGameByIDFunc != nil { return m.getGameByIDFunc(ctx, gameID) } return db.GetGameByIDRow{}, pgx.ErrNoRows } func (m *mockQuerier) ListMainPlayers(ctx context.Context, gameIDs []int32) ([]db.ListMainPlayersRow, error) { if m.listMainPlayersFunc != nil { return m.listMainPlayersFunc(ctx, gameIDs) } return nil, nil } func (m *mockQuerier) ListPublicGames(ctx context.Context) ([]db.ListPublicGamesRow, error) { if m.listPublicGamesFunc != nil { return m.listPublicGamesFunc(ctx) } return nil, nil } func (m *mockQuerier) DeleteSession(ctx context.Context, sessionID string) error { if m.deleteSessionFunc != nil { return m.deleteSessionFunc(ctx, sessionID) } return nil } func (m *mockQuerier) GetLatestState(ctx context.Context, arg db.GetLatestStateParams) (db.GetLatestStateRow, error) { if m.getLatestStateFunc != nil { return m.getLatestStateFunc(ctx, arg) } return db.GetLatestStateRow{}, pgx.ErrNoRows } func (m *mockQuerier) UpdateCode(ctx context.Context, arg db.UpdateCodeParams) error { if m.updateCodeFunc != nil { return m.updateCodeFunc(ctx, arg) } return nil } func (m *mockQuerier) GetRanking(ctx context.Context, gameID int32) ([]db.GetRankingRow, error) { if m.getRankingFunc != nil { return m.getRankingFunc(ctx, gameID) } return nil, nil } func (m *mockQuerier) GetLatestStatesOfMainPlayers(ctx context.Context, gameID int32) ([]db.GetLatestStatesOfMainPlayersRow, error) { if m.getLatestStatesFunc != nil { return m.getLatestStatesFunc(ctx, gameID) } return nil, nil } func (m *mockQuerier) GetSubmissionsByGameIDAndUserID(ctx context.Context, arg db.GetSubmissionsByGameIDAndUserIDParams) ([]db.Submission, error) { if m.getSubmissionsByGameIDAndUserIDFunc != nil { return m.getSubmissionsByGameIDAndUserIDFunc(ctx, arg) } 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) ListTournamentEntries(ctx context.Context, tournamentID int32) ([]db.ListTournamentEntriesRow, error) { if m.listTournamentEntriesFunc != nil { return m.listTournamentEntriesFunc(ctx, tournamentID) } return nil, 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) GetUserByID(ctx context.Context, userID int32) (db.User, error) { if m.getUserByIDFunc != nil { return m.getUserByIDFunc(ctx, userID) } return db.User{}, pgx.ErrNoRows } // mockTxManager implements db.TxManager for testing. type mockTxManager struct{} func (m *mockTxManager) RunInTx(_ context.Context, fn func(q db.Querier) error) error { return fn(&mockQuerier{}) } // mockGameHub implements game.GameHubInterface for testing. type mockGameHub struct { calcCodeSizeResult int enqueueErr error } func (m *mockGameHub) CalcCodeSize(_ string, _ string) int { return m.calcCodeSizeResult } func (m *mockGameHub) EnqueueTestTasks(_ context.Context, _, _, _ int, _, _ string) error { return m.enqueueErr } // mockAuthenticator implements AuthenticatorInterface for testing. type mockAuthenticator struct { loginResult int loginErr error } func (m *mockAuthenticator) Login(_ context.Context, _, _ string) (int, error) { return m.loginResult, m.loginErr } func newTestHandler(q *mockQuerier) Handler { hub := &mockGameHub{} return Handler{ gameSvc: game.NewService(q, &mockTxManager{}, hub), tournamentSvc: tournament.NewService(q, &mockTxManager{}), auth: &mockAuthenticator{}, conf: &config.Config{}, q: q, } } func newTestHandlerWithHub(q *mockQuerier, hub *mockGameHub) Handler { return Handler{ gameSvc: game.NewService(q, &mockTxManager{}, hub), tournamentSvc: tournament.NewService(q, &mockTxManager{}), auth: &mockAuthenticator{}, conf: &config.Config{}, q: q, } } func TestGetGamePlaySubmissions_GameNotFound(t *testing.T) { h := newTestHandler(&mockQuerier{}) user := &db.User{UserID: 1} resp, err := h.GetGamePlaySubmissions(context.Background(), GetGamePlaySubmissionsRequestObject{ GameID: 999, }, user) if err != nil { t.Fatalf("unexpected error: %v", err) } if _, ok := resp.(GetGamePlaySubmissions404JSONResponse); !ok { t.Errorf("expected 404 response, got %T", resp) } } func TestGetGamePlaySubmissions_Empty(t *testing.T) { h := newTestHandler(&mockQuerier{ getGameByIDFunc: func(_ context.Context, _ int32) (db.GetGameByIDRow, error) { return db.GetGameByIDRow{ GameID: 1, Language: "php", }, nil }, }) user := &db.User{UserID: 1} resp, err := h.GetGamePlaySubmissions(context.Background(), GetGamePlaySubmissionsRequestObject{ GameID: 1, }, user) if err != nil { t.Fatalf("unexpected error: %v", err) } okResp, ok := resp.(GetGamePlaySubmissions200JSONResponse) if !ok { t.Fatalf("expected 200 response, got %T", resp) } if len(okResp.Submissions) != 0 { t.Errorf("expected 0 submissions, got %d", len(okResp.Submissions)) } } func TestGetGamePlaySubmissions_WithSubmissions(t *testing.T) { now := time.Now() h := newTestHandler(&mockQuerier{ getGameByIDFunc: func(_ context.Context, _ int32) (db.GetGameByIDRow, error) { return db.GetGameByIDRow{ GameID: 1, Language: "php", }, nil }, getSubmissionsByGameIDAndUserIDFunc: func(_ context.Context, arg db.GetSubmissionsByGameIDAndUserIDParams) ([]db.Submission, error) { if arg.GameID != 1 || arg.UserID != 42 { t.Errorf("unexpected query params: game_id=%d, user_id=%d", arg.GameID, arg.UserID) } return []db.Submission{ { SubmissionID: 10, GameID: 1, UserID: 42, Code: "