Files
Datarush/internal/results/handler/grpc/results_test.go
T
2025-12-17 10:25:18 +03:00

654 lines
18 KiB
Go

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)
}
}