add results service
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package domain
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrResultNotFound = errors.New("result not found")
|
||||
ErrInvalidResultData = errors.New("invalid result data")
|
||||
ErrDuplicateResult = errors.New("duplicate result")
|
||||
ErrCompetitionNotFound = errors.New("competition not found")
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
)
|
||||
@@ -0,0 +1,45 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ResultStatus string
|
||||
|
||||
const (
|
||||
ResultStatusUnspecified ResultStatus = "UNSPECIFIED"
|
||||
ResultStatusPending ResultStatus = "PENDING"
|
||||
ResultStatusProcessing ResultStatus = "PROCESSING"
|
||||
ResultStatusCompleted ResultStatus = "COMPLETED"
|
||||
ResultStatusFailed ResultStatus = "FAILED"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
ID uuid.UUID
|
||||
CompetitionID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
Score float64
|
||||
Rank *int
|
||||
Status ResultStatus
|
||||
SubmissionID *uuid.UUID
|
||||
Metadata map[string]interface{}
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func NewResult(id uuid.UUID, competitionID uuid.UUID, userID uuid.UUID, score float64, rank *int, status ResultStatus, submissionID *uuid.UUID, metadata map[string]interface{}, createdAt time.Time, updatedAt time.Time) *Result {
|
||||
return &Result{
|
||||
ID: id,
|
||||
CompetitionID: competitionID,
|
||||
UserID: userID,
|
||||
Score: score,
|
||||
Rank: rank,
|
||||
Status: status,
|
||||
SubmissionID: submissionID,
|
||||
Metadata: metadata,
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func TestNewResult(t *testing.T) {
|
||||
id := uuid.New()
|
||||
competitionID := uuid.New()
|
||||
userID := uuid.New()
|
||||
submissionID := uuid.New()
|
||||
now := time.Now()
|
||||
rank := 1
|
||||
|
||||
result := NewResult(
|
||||
id,
|
||||
competitionID,
|
||||
userID,
|
||||
95.5,
|
||||
&rank,
|
||||
ResultStatusCompleted,
|
||||
&submissionID,
|
||||
map[string]interface{}{"notes": "test"},
|
||||
now,
|
||||
now,
|
||||
)
|
||||
|
||||
if result.ID != id {
|
||||
t.Errorf("Expected ID %v, got %v", id, result.ID)
|
||||
}
|
||||
if result.CompetitionID != competitionID {
|
||||
t.Errorf("Expected CompetitionID %v, got %v", competitionID, result.CompetitionID)
|
||||
}
|
||||
if result.UserID != userID {
|
||||
t.Errorf("Expected UserID %v, got %v", userID, result.UserID)
|
||||
}
|
||||
if result.Score != 95.5 {
|
||||
t.Errorf("Expected Score 95.5, got %v", result.Score)
|
||||
}
|
||||
if result.Rank == nil || *result.Rank != 1 {
|
||||
t.Errorf("Expected Rank 1, got %v", result.Rank)
|
||||
}
|
||||
if result.Status != ResultStatusCompleted {
|
||||
t.Errorf("Expected Status %v, got %v", ResultStatusCompleted, result.Status)
|
||||
}
|
||||
if result.SubmissionID == nil || *result.SubmissionID != submissionID {
|
||||
t.Errorf("Expected SubmissionID %v, got %v", submissionID, result.SubmissionID)
|
||||
}
|
||||
if len(result.Metadata) != 1 {
|
||||
t.Errorf("Expected Metadata with 1 item, got %d", len(result.Metadata))
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultStatusConstants(t *testing.T) {
|
||||
tests := []struct {
|
||||
status ResultStatus
|
||||
expected string
|
||||
}{
|
||||
{ResultStatusUnspecified, "UNSPECIFIED"},
|
||||
{ResultStatusPending, "PENDING"},
|
||||
{ResultStatusProcessing, "PROCESSING"},
|
||||
{ResultStatusCompleted, "COMPLETED"},
|
||||
{ResultStatusFailed, "FAILED"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.status), func(t *testing.T) {
|
||||
if string(tt.status) != tt.expected {
|
||||
t.Errorf("Expected %s, got %s", tt.expected, tt.status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultWithNilValues(t *testing.T) {
|
||||
id := uuid.New()
|
||||
competitionID := uuid.New()
|
||||
userID := uuid.New()
|
||||
now := time.Now()
|
||||
|
||||
result := NewResult(
|
||||
id,
|
||||
competitionID,
|
||||
userID,
|
||||
0.0,
|
||||
nil,
|
||||
ResultStatusPending,
|
||||
nil,
|
||||
nil,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
|
||||
if result.Rank != nil {
|
||||
t.Errorf("Expected Rank to be nil, got %v", result.Rank)
|
||||
}
|
||||
if result.SubmissionID != nil {
|
||||
t.Errorf("Expected SubmissionID to be nil, got %v", result.SubmissionID)
|
||||
}
|
||||
if result.Metadata != nil {
|
||||
t.Errorf("Expected Metadata to be nil, got %v", result.Metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultMetadata(t *testing.T) {
|
||||
id := uuid.New()
|
||||
competitionID := uuid.New()
|
||||
userID := uuid.New()
|
||||
now := time.Now()
|
||||
|
||||
metadata := map[string]interface{}{
|
||||
"attempt": 3,
|
||||
"time_spent": 3600,
|
||||
"notes": "First submission",
|
||||
"tags": []string{"urgent", "review"},
|
||||
}
|
||||
|
||||
result := NewResult(
|
||||
id,
|
||||
competitionID,
|
||||
userID,
|
||||
95.5,
|
||||
nil,
|
||||
ResultStatusCompleted,
|
||||
nil,
|
||||
metadata,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
|
||||
if len(result.Metadata) != 4 {
|
||||
t.Errorf("Expected 4 metadata items, got %d", len(result.Metadata))
|
||||
}
|
||||
|
||||
if result.Metadata["attempt"] != 3 {
|
||||
t.Errorf("Expected attempt=3, got %v", result.Metadata["attempt"])
|
||||
}
|
||||
|
||||
if result.Metadata["notes"] != "First submission" {
|
||||
t.Errorf("Expected notes='First submission', got %v", result.Metadata["notes"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultScoreRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
score float64
|
||||
valid bool
|
||||
}{
|
||||
{"Zero score", 0.0, true},
|
||||
{"Positive score", 95.5, true},
|
||||
{"Max score", 100.0, true},
|
||||
{"High score", 1000.0, true},
|
||||
{"Negative score", -10.0, true}, // Negative might be valid in some contexts
|
||||
{"Decimal precision", 95.123456, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := &Result{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: tt.score,
|
||||
Status: ResultStatusCompleted,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if result.Score != tt.score {
|
||||
t.Errorf("Expected score %v, got %v", tt.score, result.Score)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultRankValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
rank *int
|
||||
}{
|
||||
{"First place", intPtr(1)},
|
||||
{"Middle rank", intPtr(50)},
|
||||
{"Last place", intPtr(1000)},
|
||||
{"No rank", nil},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := &Result{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: 95.5,
|
||||
Rank: tt.rank,
|
||||
Status: ResultStatusCompleted,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if tt.rank != nil && result.Rank != nil && *result.Rank != *tt.rank {
|
||||
t.Errorf("Expected rank %d, got %d", *tt.rank, *result.Rank)
|
||||
}
|
||||
|
||||
if tt.rank == nil && result.Rank != nil {
|
||||
t.Errorf("Expected rank to be nil, got %d", *result.Rank)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultStatusTransitions(t *testing.T) {
|
||||
result := &Result{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: 0.0,
|
||||
Status: ResultStatusPending,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
result.Status = ResultStatusProcessing
|
||||
if result.Status != ResultStatusProcessing {
|
||||
t.Errorf("Expected status %v, got %v", ResultStatusProcessing, result.Status)
|
||||
}
|
||||
|
||||
result.Status = ResultStatusCompleted
|
||||
result.Score = 95.5
|
||||
if result.Status != ResultStatusCompleted {
|
||||
t.Errorf("Expected status %v, got %v", ResultStatusCompleted, result.Status)
|
||||
}
|
||||
|
||||
result.Status = ResultStatusFailed
|
||||
if result.Status != ResultStatusFailed {
|
||||
t.Errorf("Expected status %v, got %v", ResultStatusFailed, result.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultTimestamps(t *testing.T) {
|
||||
now := time.Now()
|
||||
result := &Result{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: 95.5,
|
||||
Status: ResultStatusCompleted,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if !result.CreatedAt.Equal(now) {
|
||||
t.Errorf("Expected CreatedAt %v, got %v", now, result.CreatedAt)
|
||||
}
|
||||
|
||||
if !result.UpdatedAt.Equal(now) {
|
||||
t.Errorf("Expected UpdatedAt %v, got %v", now, result.UpdatedAt)
|
||||
}
|
||||
|
||||
if result.UpdatedAt.Before(result.CreatedAt) {
|
||||
t.Errorf("UpdatedAt should not be before CreatedAt")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultWithSubmission(t *testing.T) {
|
||||
submissionID := uuid.New()
|
||||
result := &Result{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
Score: 95.5,
|
||||
Status: ResultStatusCompleted,
|
||||
SubmissionID: &submissionID,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if result.SubmissionID == nil {
|
||||
t.Error("Expected SubmissionID to be set")
|
||||
}
|
||||
|
||||
if *result.SubmissionID != submissionID {
|
||||
t.Errorf("Expected SubmissionID %v, got %v", submissionID, *result.SubmissionID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResultComparison(t *testing.T) {
|
||||
competitionID := uuid.New()
|
||||
now := time.Now()
|
||||
|
||||
result1 := &Result{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: competitionID,
|
||||
UserID: uuid.New(),
|
||||
Score: 100.0,
|
||||
Rank: intPtr(1),
|
||||
Status: ResultStatusCompleted,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
result2 := &Result{
|
||||
ID: uuid.New(),
|
||||
CompetitionID: competitionID,
|
||||
UserID: uuid.New(),
|
||||
Score: 95.0,
|
||||
Rank: intPtr(2),
|
||||
Status: ResultStatusCompleted,
|
||||
CreatedAt: now.Add(1 * time.Second),
|
||||
UpdatedAt: now.Add(1 * time.Second),
|
||||
}
|
||||
|
||||
if result1.Score <= result2.Score {
|
||||
t.Errorf("result1 should have higher score than result2")
|
||||
}
|
||||
|
||||
if *result1.Rank >= *result2.Rank {
|
||||
t.Errorf("result1 should have lower (better) rank than result2")
|
||||
}
|
||||
|
||||
if result1.CreatedAt.After(result2.CreatedAt) {
|
||||
t.Errorf("result1 should be created before result2")
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function
|
||||
func intPtr(i int) *int {
|
||||
return &i
|
||||
}
|
||||
Reference in New Issue
Block a user