package grpc import ( "context" "errors" "testing" "datarush/internal/results/domain" pb "datarush/pkg/api/results" "github.com/google/uuid" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type MockResultService struct { CreateResultFunc func(ctx context.Context, result *domain.Result) (*domain.Result, error) GetResultFunc func(ctx context.Context, id uuid.UUID) (*domain.Result, error) UpdateResultFunc func(ctx context.Context, result *domain.Result) (*domain.Result, error) DeleteResultFunc func(ctx context.Context, id uuid.UUID) error ListResultsFunc func(ctx context.Context, pageSize int32, pageToken int32, competitionID *uuid.UUID, userID *uuid.UUID, status *domain.ResultStatus, minScore *float64, maxScore *float64) (results []domain.Result, totalCount int32, nextPageToken int32, err error) GetByCompetitionAndUserFunc func(ctx context.Context, competitionID uuid.UUID, userID uuid.UUID) (*domain.Result, error) GetLeaderboardFunc func(ctx context.Context, competitionID uuid.UUID, limit int32, offset int32) ([]domain.Result, int32, error) UpdateStatusFunc func(ctx context.Context, id uuid.UUID, status domain.ResultStatus) (*domain.Result, error) RecalculateRanksFunc func(ctx context.Context, competitionID uuid.UUID) error } func (m *MockResultService) CreateResult(ctx context.Context, result *domain.Result) (*domain.Result, error) { if m.CreateResultFunc != nil { return m.CreateResultFunc(ctx, result) } return nil, errors.New("not implemented") } func (m *MockResultService) GetResult(ctx context.Context, id uuid.UUID) (*domain.Result, error) { if m.GetResultFunc != nil { return m.GetResultFunc(ctx, id) } return nil, errors.New("not implemented") } func (m *MockResultService) UpdateResult(ctx context.Context, result *domain.Result) (*domain.Result, error) { if m.UpdateResultFunc != nil { return m.UpdateResultFunc(ctx, result) } return nil, errors.New("not implemented") } func (m *MockResultService) DeleteResult(ctx context.Context, id uuid.UUID) error { if m.DeleteResultFunc != nil { return m.DeleteResultFunc(ctx, id) } return errors.New("not implemented") } func (m *MockResultService) ListResults(ctx context.Context, pageSize int32, pageToken int32, competitionID *uuid.UUID, userID *uuid.UUID, status *domain.ResultStatus, minScore *float64, maxScore *float64) (results []domain.Result, totalCount int32, nextPageToken int32, err error) { if m.ListResultsFunc != nil { return m.ListResultsFunc(ctx, pageSize, pageToken, competitionID, userID, status, minScore, maxScore) } return nil, 0, 0, errors.New("not implemented") } func (m *MockResultService) GetByCompetitionAndUser(ctx context.Context, competitionID uuid.UUID, userID uuid.UUID) (*domain.Result, error) { if m.GetByCompetitionAndUserFunc != nil { return m.GetByCompetitionAndUserFunc(ctx, competitionID, userID) } return nil, errors.New("not implemented") } func (m *MockResultService) GetLeaderboard(ctx context.Context, competitionID uuid.UUID, limit int32, offset int32) ([]domain.Result, int32, error) { if m.GetLeaderboardFunc != nil { return m.GetLeaderboardFunc(ctx, competitionID, limit, offset) } return nil, 0, errors.New("not implemented") } func (m *MockResultService) UpdateStatus(ctx context.Context, id uuid.UUID, status domain.ResultStatus) (*domain.Result, error) { if m.UpdateStatusFunc != nil { return m.UpdateStatusFunc(ctx, id, status) } return nil, errors.New("not implemented") } func (m *MockResultService) RecalculateRanks(ctx context.Context, competitionID uuid.UUID) error { if m.RecalculateRanksFunc != nil { return m.RecalculateRanksFunc(ctx, competitionID) } return errors.New("not implemented") } func TestNewResultsHandler(t *testing.T) { mockService := &MockResultService{} handler := NewResultsHandler(mockService, "localhost:50051", "localhost:50055", "localhost:50056") if handler == nil { t.Fatal("Expected handler to be created, got nil") } if handler.service == nil { t.Error("Expected service to be set") } if handler.userServiceAddr != "localhost:50051" { t.Errorf("Expected userServiceAddr to be localhost:50051, got %s", handler.userServiceAddr) } if handler.submissionServiceAddr != "localhost:50055" { t.Errorf("Expected submissionServiceAddr to be localhost:50055, got %s", handler.submissionServiceAddr) } if handler.taskServiceAddr != "localhost:50056" { t.Errorf("Expected taskServiceAddr to be localhost:50056, got %s", handler.taskServiceAddr) } } func TestGetCompetitionResults_InvalidCompetitionID(t *testing.T) { mockService := &MockResultService{} handler := NewResultsHandler(mockService, "", "", "") req := &pb.GetCompetitionResultsRequest{ CompetitionId: "invalid-uuid", PageSize: 10, PageToken: 0, } resp, err := handler.GetCompetitionResults(context.Background(), req) if err == nil { t.Fatal("Expected error for invalid competition ID, got nil") } if resp != nil { t.Error("Expected nil response for invalid competition ID") } st, ok := status.FromError(err) if !ok { t.Fatal("Expected gRPC status error") } if st.Code() != codes.InvalidArgument { t.Errorf("Expected code InvalidArgument, got %v", st.Code()) } } func TestGetCompetitionResults_ServiceError(t *testing.T) { mockService := &MockResultService{ GetLeaderboardFunc: func(ctx context.Context, competitionID uuid.UUID, limit int32, offset int32) ([]domain.Result, int32, error) { return nil, 0, errors.New("database error") }, } handler := NewResultsHandler(mockService, "", "", "") competitionID := uuid.New() req := &pb.GetCompetitionResultsRequest{ CompetitionId: competitionID.String(), PageSize: 10, PageToken: 0, } resp, err := handler.GetCompetitionResults(context.Background(), req) if err == nil { t.Fatal("Expected error from service, got nil") } if resp != nil { t.Error("Expected nil response on service error") } st, ok := status.FromError(err) if !ok { t.Fatal("Expected gRPC status error") } if st.Code() != codes.Internal { t.Errorf("Expected code Internal, got %v", st.Code()) } } func TestGetCompetitionResults_EmptyLeaderboard(t *testing.T) { mockService := &MockResultService{ GetLeaderboardFunc: func(ctx context.Context, competitionID uuid.UUID, limit int32, offset int32) ([]domain.Result, int32, error) { return []domain.Result{}, 0, nil }, } handler := NewResultsHandler(mockService, "", "", "") competitionID := uuid.New() req := &pb.GetCompetitionResultsRequest{ CompetitionId: competitionID.String(), PageSize: 10, PageToken: 0, } resp, err := handler.GetCompetitionResults(context.Background(), req) if err != nil { t.Fatalf("Expected no error, got %v", err) } if resp == nil { t.Fatal("Expected response, got nil") } if len(resp.Results) != 0 { t.Errorf("Expected 0 results, got %d", len(resp.Results)) } if resp.TotalCount != 0 { t.Errorf("Expected total count 0, got %d", resp.TotalCount) } if resp.NextPageToken != 0 { t.Errorf("Expected next page token 0, got %d", resp.NextPageToken) } } func TestGetUserCompetitionResults_InvalidCompetitionID(t *testing.T) { mockService := &MockResultService{} handler := NewResultsHandler(mockService, "", "", "") req := &pb.GetUserCompetitionResultsRequest{ CompetitionId: "invalid-uuid", UserId: uuid.New().String(), } resp, err := handler.GetUserCompetitionResults(context.Background(), req) if err == nil { t.Fatal("Expected error for invalid competition ID, got nil") } if resp != nil { t.Error("Expected nil response for invalid competition ID") } st, ok := status.FromError(err) if !ok { t.Fatal("Expected gRPC status error") } if st.Code() != codes.InvalidArgument { t.Errorf("Expected code InvalidArgument, got %v", st.Code()) } } func TestGetUserCompetitionResults_InvalidUserID(t *testing.T) { mockService := &MockResultService{} handler := NewResultsHandler(mockService, "", "", "") req := &pb.GetUserCompetitionResultsRequest{ CompetitionId: uuid.New().String(), UserId: "invalid-uuid", } resp, err := handler.GetUserCompetitionResults(context.Background(), req) if err == nil { t.Fatal("Expected error for invalid user ID, got nil") } if resp != nil { t.Error("Expected nil response for invalid user ID") } st, ok := status.FromError(err) if !ok { t.Fatal("Expected gRPC status error") } if st.Code() != codes.InvalidArgument { t.Errorf("Expected code InvalidArgument, got %v", st.Code()) } } func TestGetUserCompetitionResults_NotFound(t *testing.T) { mockService := &MockResultService{ GetByCompetitionAndUserFunc: func(ctx context.Context, competitionID uuid.UUID, userID uuid.UUID) (*domain.Result, error) { return nil, domain.ErrResultNotFound }, } handler := NewResultsHandler(mockService, "", "", "") req := &pb.GetUserCompetitionResultsRequest{ CompetitionId: uuid.New().String(), UserId: uuid.New().String(), } resp, err := handler.GetUserCompetitionResults(context.Background(), req) if err == nil { t.Fatal("Expected error for not found result, got nil") } if resp != nil { t.Error("Expected nil response for not found result") } st, ok := status.FromError(err) if !ok { t.Fatal("Expected gRPC status error") } if st.Code() != codes.NotFound { t.Errorf("Expected code NotFound, got %v", st.Code()) } } func TestGetUserCompetitionResults_Success(t *testing.T) { rank := 1 competitionID := uuid.New() userID := uuid.New() mockService := &MockResultService{ GetByCompetitionAndUserFunc: func(ctx context.Context, compID uuid.UUID, uID uuid.UUID) (*domain.Result, error) { return &domain.Result{ ID: uuid.New(), CompetitionID: compID, UserID: uID, Score: 95.5, Rank: &rank, Status: domain.ResultStatusCompleted, }, nil }, } handler := NewResultsHandler(mockService, "", "", "") req := &pb.GetUserCompetitionResultsRequest{ CompetitionId: competitionID.String(), UserId: userID.String(), } resp, err := handler.GetUserCompetitionResults(context.Background(), req) if err != nil { t.Fatalf("Expected no error, got %v", err) } if resp == nil { t.Fatal("Expected response, got nil") } if resp.Result == nil { t.Fatal("Expected result, got nil") } if resp.Result.UserId != userID.String() { t.Errorf("Expected user ID %s, got %s", userID.String(), resp.Result.UserId) } if resp.Result.TotalScore != 95 { t.Errorf("Expected total score 95, got %d", resp.Result.TotalScore) } if resp.Result.OverallPosition != 1 { t.Errorf("Expected overall position 1, got %d", resp.Result.OverallPosition) } } func TestRecalculateResults_InvalidCompetitionID(t *testing.T) { mockService := &MockResultService{} handler := NewResultsHandler(mockService, "", "", "") req := &pb.RecalculateResultsRequest{ CompetitionId: "invalid-uuid", } resp, err := handler.RecalculateResults(context.Background(), req) if err == nil { t.Fatal("Expected error for invalid competition ID, got nil") } if resp != nil { t.Error("Expected nil response for invalid competition ID") } st, ok := status.FromError(err) if !ok { t.Fatal("Expected gRPC status error") } if st.Code() != codes.InvalidArgument { t.Errorf("Expected code InvalidArgument, got %v", st.Code()) } } func TestRecalculateResults_ServiceError(t *testing.T) { mockService := &MockResultService{ RecalculateRanksFunc: func(ctx context.Context, competitionID uuid.UUID) error { return errors.New("recalculation failed") }, } handler := NewResultsHandler(mockService, "", "", "") req := &pb.RecalculateResultsRequest{ CompetitionId: uuid.New().String(), } resp, err := handler.RecalculateResults(context.Background(), req) if err == nil { t.Fatal("Expected error from service, got nil") } if resp != nil { t.Error("Expected nil response on service error") } st, ok := status.FromError(err) if !ok { t.Fatal("Expected gRPC status error") } if st.Code() != codes.Internal { t.Errorf("Expected code Internal, got %v", st.Code()) } } func TestRecalculateResults_Success(t *testing.T) { mockService := &MockResultService{ RecalculateRanksFunc: func(ctx context.Context, competitionID uuid.UUID) error { return nil }, } handler := NewResultsHandler(mockService, "", "", "") req := &pb.RecalculateResultsRequest{ CompetitionId: uuid.New().String(), } resp, err := handler.RecalculateResults(context.Background(), req) if err != nil { t.Fatalf("Expected no error, got %v", err) } if resp == nil { t.Fatal("Expected response, got nil") } } func TestFetchUsername(t *testing.T) { handler := &ResultsHandler{ userServiceAddr: "invalid-address", } // Test with invalid address - should return empty string username := handler.fetchUsername(context.Background(), uuid.New().String()) if username != "" { t.Errorf("Expected empty username on error, got %s", username) } } func TestFetchTaskStatuses(t *testing.T) { handler := &ResultsHandler{ submissionServiceAddr: "invalid-address", } // Test with invalid address - should return empty slice statuses := handler.fetchTaskStatuses(context.Background(), uuid.New().String(), uuid.New().String()) if statuses == nil { t.Fatal("Expected empty slice, got nil") } if len(statuses) != 0 { t.Errorf("Expected 0 statuses on error, got %d", len(statuses)) } } func TestEnrichUserResult(t *testing.T) { rank := 1 result := &domain.Result{ ID: uuid.New(), CompetitionID: uuid.New(), UserID: uuid.New(), Score: 95.5, Rank: &rank, Status: domain.ResultStatusCompleted, } handler := &ResultsHandler{ userServiceAddr: "invalid-address", submissionServiceAddr: "invalid-address", taskServiceAddr: "invalid-address", } competitionID := uuid.New().String() pbResult := handler.enrichUserResult(context.Background(), result, competitionID) if pbResult == nil { t.Fatal("Expected result, got nil") } if pbResult.UserId != result.UserID.String() { t.Errorf("Expected user ID %s, got %s", result.UserID.String(), pbResult.UserId) } if pbResult.TotalScore != int32(result.Score) { t.Errorf("Expected total score %d, got %d", int32(result.Score), pbResult.TotalScore) } if pbResult.OverallPosition != int32(*result.Rank) { t.Errorf("Expected position %d, got %d", *result.Rank, pbResult.OverallPosition) } if pbResult.Username != "" { t.Errorf("Expected empty username, got %s", pbResult.Username) } if len(pbResult.TaskStatuses) != 0 { t.Errorf("Expected 0 task statuses, got %d", len(pbResult.TaskStatuses)) } } func TestEnrichUserResult_WithoutRank(t *testing.T) { result := &domain.Result{ ID: uuid.New(), CompetitionID: uuid.New(), UserID: uuid.New(), Score: 95.5, Rank: nil, Status: domain.ResultStatusCompleted, } handler := &ResultsHandler{ userServiceAddr: "invalid-address", submissionServiceAddr: "invalid-address", taskServiceAddr: "invalid-address", } competitionID := uuid.New().String() pbResult := handler.enrichUserResult(context.Background(), result, competitionID) if pbResult == nil { t.Fatal("Expected result, got nil") } if pbResult.OverallPosition != 0 { t.Errorf("Expected position 0 for nil rank, got %d", pbResult.OverallPosition) } } func TestGetCompetitionResults_Pagination(t *testing.T) { rank1, rank2, rank3 := 1, 2, 3 results := []domain.Result{ { ID: uuid.New(), CompetitionID: uuid.New(), UserID: uuid.New(), Score: 100.0, Rank: &rank1, Status: domain.ResultStatusCompleted, }, { ID: uuid.New(), CompetitionID: uuid.New(), UserID: uuid.New(), Score: 95.0, Rank: &rank2, Status: domain.ResultStatusCompleted, }, { ID: uuid.New(), CompetitionID: uuid.New(), UserID: uuid.New(), Score: 90.0, Rank: &rank3, Status: domain.ResultStatusCompleted, }, } mockService := &MockResultService{ GetLeaderboardFunc: func(ctx context.Context, competitionID uuid.UUID, limit int32, offset int32) ([]domain.Result, int32, error) { return results, 25, nil }, } handler := NewResultsHandler(mockService, "", "", "") competitionID := uuid.New() req := &pb.GetCompetitionResultsRequest{ CompetitionId: competitionID.String(), PageSize: 10, PageToken: 0, } resp, err := handler.GetCompetitionResults(context.Background(), req) if err != nil { t.Fatalf("Expected no error, got %v", err) } if resp == nil { t.Fatal("Expected response, got nil") } if len(resp.Results) != 3 { t.Errorf("Expected 3 results, got %d", len(resp.Results)) } if resp.TotalCount != 25 { t.Errorf("Expected total count 25, got %d", resp.TotalCount) } if resp.NextPageToken != 1 { t.Errorf("Expected next page token 1, got %d", resp.NextPageToken) } } func TestGetCompetitionResults_LastPage(t *testing.T) { rank1 := 1 results := []domain.Result{ { ID: uuid.New(), CompetitionID: uuid.New(), UserID: uuid.New(), Score: 100.0, Rank: &rank1, Status: domain.ResultStatusCompleted, }, } mockService := &MockResultService{ GetLeaderboardFunc: func(ctx context.Context, competitionID uuid.UUID, limit int32, offset int32) ([]domain.Result, int32, error) { return results, 1, nil }, } handler := NewResultsHandler(mockService, "", "", "") competitionID := uuid.New() req := &pb.GetCompetitionResultsRequest{ CompetitionId: competitionID.String(), PageSize: 10, PageToken: 0, } resp, err := handler.GetCompetitionResults(context.Background(), req) if err != nil { t.Fatalf("Expected no error, got %v", err) } if resp == nil { t.Fatal("Expected response, got nil") } if resp.NextPageToken != 0 { t.Errorf("Expected next page token 0 on last page, got %d", resp.NextPageToken) } }