add results service
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"datarush/internal/results/domain"
|
||||
"datarush/internal/results/repository"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo repository.ResultRepository
|
||||
}
|
||||
|
||||
func NewService(repo repository.ResultRepository) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
func (s *Service) CreateResult(ctx context.Context, result *domain.Result) (*domain.Result, error) {
|
||||
now := time.Now()
|
||||
if result.ID == uuid.Nil {
|
||||
result.ID = uuid.New()
|
||||
}
|
||||
result.CreatedAt = now
|
||||
result.UpdatedAt = now
|
||||
|
||||
if result.Status == "" {
|
||||
result.Status = domain.ResultStatusPending
|
||||
}
|
||||
|
||||
if err := s.repo.Create(ctx, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetResult(ctx context.Context, id uuid.UUID) (*domain.Result, error) {
|
||||
return s.repo.Get(ctx, id)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateResult(ctx context.Context, result *domain.Result) (*domain.Result, error) {
|
||||
result.UpdatedAt = time.Now()
|
||||
if err := s.repo.Update(ctx, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.repo.Get(ctx, result.ID)
|
||||
}
|
||||
|
||||
func (s *Service) DeleteResult(ctx context.Context, id uuid.UUID) error {
|
||||
return s.repo.Delete(ctx, id)
|
||||
}
|
||||
|
||||
func (s *Service) ListResults(ctx context.Context, pageSize int32, pageToken int32, competitionID *uuid.UUID, userID *uuid.UUID, status *domain.ResultStatus, minScore *float64, maxScore *float64) ([]domain.Result, int32, int32, error) {
|
||||
opts := repository.ListResultsOptions{
|
||||
Page: int(pageToken),
|
||||
PageSize: int(pageSize),
|
||||
CompetitionID: competitionID,
|
||||
UserID: userID,
|
||||
Status: status,
|
||||
MinScore: minScore,
|
||||
MaxScore: maxScore,
|
||||
}
|
||||
|
||||
results, total, err := s.repo.List(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
var nextPageToken int32
|
||||
if (opts.Page+1)*opts.PageSize < total {
|
||||
nextPageToken = int32(opts.Page + 1)
|
||||
}
|
||||
|
||||
return results, int32(total), nextPageToken, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetByCompetitionAndUser(ctx context.Context, competitionID uuid.UUID, userID uuid.UUID) (*domain.Result, error) {
|
||||
return s.repo.GetByCompetitionAndUser(ctx, competitionID, userID)
|
||||
}
|
||||
|
||||
func (s *Service) GetLeaderboard(ctx context.Context, competitionID uuid.UUID, limit int32, offset int32) ([]domain.Result, int32, error) {
|
||||
results, total, err := s.repo.GetLeaderboard(ctx, competitionID, int(limit), int(offset))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return results, int32(total), nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateStatus(ctx context.Context, id uuid.UUID, status domain.ResultStatus) (*domain.Result, error) {
|
||||
return s.repo.UpdateStatus(ctx, id, status)
|
||||
}
|
||||
|
||||
func (s *Service) RecalculateRanks(ctx context.Context, competitionID uuid.UUID) error {
|
||||
return s.repo.RecalculateRanks(ctx, competitionID)
|
||||
}
|
||||
@@ -0,0 +1,815 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"datarush/internal/results/domain"
|
||||
"datarush/internal/results/repository"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type MockRepository struct {
|
||||
CreateFunc func(ctx context.Context, result *domain.Result) error
|
||||
GetFunc func(ctx context.Context, id uuid.UUID) (*domain.Result, error)
|
||||
UpdateFunc func(ctx context.Context, result *domain.Result) error
|
||||
DeleteFunc func(ctx context.Context, id uuid.UUID) error
|
||||
ListFunc func(ctx context.Context, opts repository.ListResultsOptions) ([]domain.Result, int, error)
|
||||
GetByCompetitionAndUserFunc func(ctx context.Context, competitionID uuid.UUID, userID uuid.UUID) (*domain.Result, error)
|
||||
GetLeaderboardFunc func(ctx context.Context, competitionID uuid.UUID, limit int, offset int) ([]domain.Result, int, 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 *MockRepository) Create(ctx context.Context, result *domain.Result) error {
|
||||
if m.CreateFunc != nil {
|
||||
return m.CreateFunc(ctx, result)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockRepository) Get(ctx context.Context, id uuid.UUID) (*domain.Result, error) {
|
||||
if m.GetFunc != nil {
|
||||
return m.GetFunc(ctx, id)
|
||||
}
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (m *MockRepository) Update(ctx context.Context, result *domain.Result) error {
|
||||
if m.UpdateFunc != nil {
|
||||
return m.UpdateFunc(ctx, result)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
if m.DeleteFunc != nil {
|
||||
return m.DeleteFunc(ctx, id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockRepository) List(ctx context.Context, opts repository.ListResultsOptions) ([]domain.Result, int, error) {
|
||||
if m.ListFunc != nil {
|
||||
return m.ListFunc(ctx, opts)
|
||||
}
|
||||
return nil, 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (m *MockRepository) 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 *MockRepository) GetLeaderboard(ctx context.Context, competitionID uuid.UUID, limit int, offset int) ([]domain.Result, int, error) {
|
||||
if m.GetLeaderboardFunc != nil {
|
||||
return m.GetLeaderboardFunc(ctx, competitionID, limit, offset)
|
||||
}
|
||||
return nil, 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (m *MockRepository) 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 *MockRepository) RecalculateRanks(ctx context.Context, competitionID uuid.UUID) error {
|
||||
if m.RecalculateRanksFunc != nil {
|
||||
return m.RecalculateRanksFunc(ctx, competitionID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestNewService(t *testing.T) {
|
||||
repo := &MockRepository{}
|
||||
svc := NewService(repo)
|
||||
|
||||
if svc == nil {
|
||||
t.Fatal("Expected service to be created, got nil")
|
||||
}
|
||||
|
||||
if svc.repo == nil {
|
||||
t.Error("Expected repository to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateResult(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input *domain.Result
|
||||
repoError error
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "successful creation with new ID",
|
||||
input: &domain.Result{
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: 95.5,
|
||||
Status: domain.ResultStatusCompleted,
|
||||
},
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "successful creation with existing ID",
|
||||
input: &domain.Result{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: 95.5,
|
||||
Status: domain.ResultStatusCompleted,
|
||||
},
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
input: &domain.Result{
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: 95.5,
|
||||
},
|
||||
repoError: errors.New("database error"),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "empty status defaults to pending",
|
||||
input: &domain.Result{
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: 95.5,
|
||||
},
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := &MockRepository{
|
||||
CreateFunc: func(ctx context.Context, result *domain.Result) error {
|
||||
return tt.repoError
|
||||
},
|
||||
}
|
||||
svc := NewService(repo)
|
||||
|
||||
result, err := svc.CreateResult(context.Background(), tt.input)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if result.ID == uuid.Nil {
|
||||
t.Error("Expected ID to be set")
|
||||
}
|
||||
|
||||
if result.CreatedAt.IsZero() {
|
||||
t.Error("Expected CreatedAt to be set")
|
||||
}
|
||||
|
||||
if result.UpdatedAt.IsZero() {
|
||||
t.Error("Expected UpdatedAt to be set")
|
||||
}
|
||||
|
||||
if tt.input.Status == "" && result.Status != domain.ResultStatusPending {
|
||||
t.Errorf("Expected status to default to PENDING, got %v", result.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetResult(t *testing.T) {
|
||||
resultID := uuid.New()
|
||||
expectedResult := &domain.Result{
|
||||
ID: resultID,
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: 95.5,
|
||||
Status: domain.ResultStatusCompleted,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
resultID uuid.UUID
|
||||
repoResult *domain.Result
|
||||
repoError error
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "successful get",
|
||||
resultID: resultID,
|
||||
repoResult: expectedResult,
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "result not found",
|
||||
resultID: uuid.New(),
|
||||
repoResult: nil,
|
||||
repoError: domain.ErrResultNotFound,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
resultID: resultID,
|
||||
repoResult: nil,
|
||||
repoError: errors.New("database error"),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := &MockRepository{
|
||||
GetFunc: func(ctx context.Context, id uuid.UUID) (*domain.Result, error) {
|
||||
if id != tt.resultID {
|
||||
return nil, domain.ErrResultNotFound
|
||||
}
|
||||
return tt.repoResult, tt.repoError
|
||||
},
|
||||
}
|
||||
svc := NewService(repo)
|
||||
|
||||
result, err := svc.GetResult(context.Background(), tt.resultID)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if result.ID != expectedResult.ID {
|
||||
t.Errorf("Expected ID %v, got %v", expectedResult.ID, result.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateResult(t *testing.T) {
|
||||
resultID := uuid.New()
|
||||
originalTime := time.Now().Add(-1 * time.Hour)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input *domain.Result
|
||||
repoError error
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "successful update",
|
||||
input: &domain.Result{
|
||||
ID: resultID,
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: 100.0,
|
||||
Status: domain.ResultStatusCompleted,
|
||||
CreatedAt: originalTime,
|
||||
UpdatedAt: originalTime,
|
||||
},
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
input: &domain.Result{
|
||||
ID: resultID,
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: 100.0,
|
||||
},
|
||||
repoError: errors.New("update failed"),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := &MockRepository{
|
||||
UpdateFunc: func(ctx context.Context, result *domain.Result) error {
|
||||
return tt.repoError
|
||||
},
|
||||
GetFunc: func(ctx context.Context, id uuid.UUID) (*domain.Result, error) {
|
||||
if tt.repoError != nil {
|
||||
return nil, tt.repoError
|
||||
}
|
||||
return tt.input, nil
|
||||
},
|
||||
}
|
||||
svc := NewService(repo)
|
||||
|
||||
beforeUpdate := time.Now()
|
||||
result, err := svc.UpdateResult(context.Background(), tt.input)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if !result.UpdatedAt.After(originalTime) {
|
||||
t.Error("Expected UpdatedAt to be updated")
|
||||
}
|
||||
|
||||
if result.UpdatedAt.Before(beforeUpdate) {
|
||||
t.Error("Expected UpdatedAt to be recent")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteResult(t *testing.T) {
|
||||
resultID := uuid.New()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
resultID uuid.UUID
|
||||
repoError error
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "successful delete",
|
||||
resultID: resultID,
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "result not found",
|
||||
resultID: uuid.New(),
|
||||
repoError: domain.ErrResultNotFound,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
resultID: resultID,
|
||||
repoError: errors.New("delete failed"),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := &MockRepository{
|
||||
DeleteFunc: func(ctx context.Context, id uuid.UUID) error {
|
||||
return tt.repoError
|
||||
},
|
||||
}
|
||||
svc := NewService(repo)
|
||||
|
||||
err := svc.DeleteResult(context.Background(), tt.resultID)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListResults(t *testing.T) {
|
||||
competitionID := uuid.New()
|
||||
userID := uuid.New()
|
||||
status := domain.ResultStatusCompleted
|
||||
minScore := 50.0
|
||||
maxScore := 100.0
|
||||
|
||||
results := []domain.Result{
|
||||
{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: competitionID,
|
||||
UserID: userID,
|
||||
Score: 95.0,
|
||||
Status: status,
|
||||
},
|
||||
{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: competitionID,
|
||||
UserID: uuid.New(),
|
||||
Score: 85.0,
|
||||
Status: status,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pageSize int32
|
||||
pageToken int32
|
||||
competitionID *uuid.UUID
|
||||
userID *uuid.UUID
|
||||
status *domain.ResultStatus
|
||||
minScore *float64
|
||||
maxScore *float64
|
||||
repoResults []domain.Result
|
||||
repoTotal int
|
||||
repoError error
|
||||
expectError bool
|
||||
expectedTotal int32
|
||||
expectedNext int32
|
||||
}{
|
||||
{
|
||||
name: "successful list with filters",
|
||||
pageSize: 10,
|
||||
pageToken: 0,
|
||||
competitionID: &competitionID,
|
||||
userID: &userID,
|
||||
status: &status,
|
||||
minScore: &minScore,
|
||||
maxScore: &maxScore,
|
||||
repoResults: results,
|
||||
repoTotal: 2,
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
expectedTotal: 2,
|
||||
expectedNext: 0,
|
||||
},
|
||||
{
|
||||
name: "pagination with next page",
|
||||
pageSize: 10,
|
||||
pageToken: 0,
|
||||
competitionID: &competitionID,
|
||||
repoResults: results,
|
||||
repoTotal: 25,
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
expectedTotal: 25,
|
||||
expectedNext: 1,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
pageSize: 10,
|
||||
pageToken: 0,
|
||||
repoResults: nil,
|
||||
repoTotal: 0,
|
||||
repoError: errors.New("database error"),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := &MockRepository{
|
||||
ListFunc: func(ctx context.Context, opts repository.ListResultsOptions) ([]domain.Result, int, error) {
|
||||
return tt.repoResults, tt.repoTotal, tt.repoError
|
||||
},
|
||||
}
|
||||
svc := NewService(repo)
|
||||
|
||||
results, total, nextPage, err := svc.ListResults(
|
||||
context.Background(),
|
||||
tt.pageSize,
|
||||
tt.pageToken,
|
||||
tt.competitionID,
|
||||
tt.userID,
|
||||
tt.status,
|
||||
tt.minScore,
|
||||
tt.maxScore,
|
||||
)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if len(results) != len(tt.repoResults) {
|
||||
t.Errorf("Expected %d results, got %d", len(tt.repoResults), len(results))
|
||||
}
|
||||
|
||||
if total != tt.expectedTotal {
|
||||
t.Errorf("Expected total %d, got %d", tt.expectedTotal, total)
|
||||
}
|
||||
|
||||
if nextPage != tt.expectedNext {
|
||||
t.Errorf("Expected next page token %d, got %d", tt.expectedNext, nextPage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByCompetitionAndUser(t *testing.T) {
|
||||
competitionID := uuid.New()
|
||||
userID := uuid.New()
|
||||
expectedResult := &domain.Result{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: competitionID,
|
||||
UserID: userID,
|
||||
Score: 95.5,
|
||||
Status: domain.ResultStatusCompleted,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
competitionID uuid.UUID
|
||||
userID uuid.UUID
|
||||
repoResult *domain.Result
|
||||
repoError error
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "successful get",
|
||||
competitionID: competitionID,
|
||||
userID: userID,
|
||||
repoResult: expectedResult,
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "result not found",
|
||||
competitionID: uuid.New(),
|
||||
userID: uuid.New(),
|
||||
repoResult: nil,
|
||||
repoError: domain.ErrResultNotFound,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := &MockRepository{
|
||||
GetByCompetitionAndUserFunc: func(ctx context.Context, compID uuid.UUID, uID uuid.UUID) (*domain.Result, error) {
|
||||
return tt.repoResult, tt.repoError
|
||||
},
|
||||
}
|
||||
svc := NewService(repo)
|
||||
|
||||
result, err := svc.GetByCompetitionAndUser(context.Background(), tt.competitionID, tt.userID)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if result.ID != expectedResult.ID {
|
||||
t.Errorf("Expected ID %v, got %v", expectedResult.ID, result.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLeaderboard(t *testing.T) {
|
||||
competitionID := uuid.New()
|
||||
rank1, rank2, rank3 := 1, 2, 3
|
||||
|
||||
leaderboard := []domain.Result{
|
||||
{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: competitionID,
|
||||
UserID: uuid.New(),
|
||||
Score: 100.0,
|
||||
Rank: &rank1,
|
||||
Status: domain.ResultStatusCompleted,
|
||||
},
|
||||
{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: competitionID,
|
||||
UserID: uuid.New(),
|
||||
Score: 95.0,
|
||||
Rank: &rank2,
|
||||
Status: domain.ResultStatusCompleted,
|
||||
},
|
||||
{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: competitionID,
|
||||
UserID: uuid.New(),
|
||||
Score: 90.0,
|
||||
Rank: &rank3,
|
||||
Status: domain.ResultStatusCompleted,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
competitionID uuid.UUID
|
||||
limit int32
|
||||
offset int32
|
||||
repoResults []domain.Result
|
||||
repoTotal int
|
||||
repoError error
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "successful leaderboard",
|
||||
competitionID: competitionID,
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
repoResults: leaderboard,
|
||||
repoTotal: 3,
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "empty leaderboard",
|
||||
competitionID: uuid.New(),
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
repoResults: []domain.Result{},
|
||||
repoTotal: 0,
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
competitionID: competitionID,
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
repoResults: nil,
|
||||
repoTotal: 0,
|
||||
repoError: errors.New("database error"),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := &MockRepository{
|
||||
GetLeaderboardFunc: func(ctx context.Context, compID uuid.UUID, limit int, offset int) ([]domain.Result, int, error) {
|
||||
return tt.repoResults, tt.repoTotal, tt.repoError
|
||||
},
|
||||
}
|
||||
svc := NewService(repo)
|
||||
|
||||
results, total, err := svc.GetLeaderboard(context.Background(), tt.competitionID, tt.limit, tt.offset)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if len(results) != len(tt.repoResults) {
|
||||
t.Errorf("Expected %d results, got %d", len(tt.repoResults), len(results))
|
||||
}
|
||||
|
||||
if int32(total) != int32(tt.repoTotal) {
|
||||
t.Errorf("Expected total %d, got %d", tt.repoTotal, total)
|
||||
}
|
||||
|
||||
for i := 0; i < len(results)-1; i++ {
|
||||
if results[i].Rank != nil && results[i+1].Rank != nil {
|
||||
if *results[i].Rank > *results[i+1].Rank {
|
||||
t.Error("Leaderboard should be sorted by rank ascending")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateStatus(t *testing.T) {
|
||||
resultID := uuid.New()
|
||||
updatedResult := &domain.Result{
|
||||
ID: resultID,
|
||||
Status: domain.ResultStatusCompleted,
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
resultID uuid.UUID
|
||||
status domain.ResultStatus
|
||||
repoResult *domain.Result
|
||||
repoError error
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "successful status update",
|
||||
resultID: resultID,
|
||||
status: domain.ResultStatusCompleted,
|
||||
repoResult: updatedResult,
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "result not found",
|
||||
resultID: uuid.New(),
|
||||
status: domain.ResultStatusCompleted,
|
||||
repoResult: nil,
|
||||
repoError: domain.ErrResultNotFound,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := &MockRepository{
|
||||
UpdateStatusFunc: func(ctx context.Context, id uuid.UUID, status domain.ResultStatus) (*domain.Result, error) {
|
||||
return tt.repoResult, tt.repoError
|
||||
},
|
||||
}
|
||||
svc := NewService(repo)
|
||||
|
||||
result, err := svc.UpdateStatus(context.Background(), tt.resultID, tt.status)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if result.Status != tt.status {
|
||||
t.Errorf("Expected status %v, got %v", tt.status, result.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecalculateRanks(t *testing.T) {
|
||||
competitionID := uuid.New()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
competitionID uuid.UUID
|
||||
repoError error
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "successful recalculation",
|
||||
competitionID: competitionID,
|
||||
repoError: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
competitionID: competitionID,
|
||||
repoError: errors.New("recalculation failed"),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := &MockRepository{
|
||||
RecalculateRanksFunc: func(ctx context.Context, compID uuid.UUID) error {
|
||||
return tt.repoError
|
||||
},
|
||||
}
|
||||
svc := NewService(repo)
|
||||
|
||||
err := svc.RecalculateRanks(context.Background(), tt.competitionID)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user