Implement leaderboard service and POC HTTP handlers with Swagger documentation

This commit is contained in:
2026-07-15 11:29:19 +04:00
parent c25e0bc581
commit 4a894a4399
10 changed files with 1300 additions and 3 deletions
+262
View File
@@ -0,0 +1,262 @@
package races
import (
"context"
"os"
"testing"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
func TestLeaderboard(t *testing.T) {
dsn := os.Getenv("DATABASE_URL")
if dsn == "" {
t.Skip("DATABASE_URL is not set, skipping database integration tests")
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, dsn)
if err != nil {
t.Fatalf("failed to connect to test database: %v", err)
}
defer pool.Close()
store := NewPgStore(pool)
// Clean up any left-overs with our test prefix
cleanup := func() {
_, _ = pool.Exec(ctx, "DELETE FROM race_drivers WHERE race_id LIKE 'test-race-%'")
_, _ = pool.Exec(ctx, "DELETE FROM races WHERE id LIKE 'test-race-%'")
_, _ = pool.Exec(ctx, "DELETE FROM drivers WHERE id LIKE 'test-driver-%'")
_, _ = pool.Exec(ctx, "DELETE FROM clans WHERE id LIKE 'test-clan-%'")
}
cleanup()
defer cleanup()
// 1. Create a test clan and test drivers
nowMs := time.Now().UnixMilli()
_, err = pool.Exec(ctx, `
INSERT INTO clans (id, tag, name, avatar_url, created_ms, updated_ms)
VALUES ('test-clan-1', 'TST', 'Test Clan', 'http://avatar.clan', $1, $1)`, nowMs)
if err != nil {
t.Fatalf("failed to insert test clan: %v", err)
}
driversData := []struct {
id string
nick string
name string
}{
{"test-driver-1", "DRV", "Driver One"},
{"test-driver-2", "TWO", "Driver Two"},
{"test-driver-3", "THR", "Driver Three"},
{"test-driver-4", "FOR", "Driver Four"},
}
for _, d := range driversData {
_, err = pool.Exec(ctx, `
INSERT INTO drivers (id, nickname, name, avatar_url, clan_id, created_ms, updated_ms)
VALUES ($1, $2, $3, 'http://avatar', 'test-clan-1', $4, $4)`,
d.id, d.nick, d.name, nowMs)
if err != nil {
t.Fatalf("failed to insert test driver %s: %v", d.id, err)
}
}
// 2. Create test races on different tracks for current year
currentYear := time.Now().Year()
testYearMs := time.Date(currentYear, time.June, 1, 12, 0, 0, 0, time.UTC).UnixMilli()
racesData := []struct {
id string
trackID string
}{
{"test-race-1", "test-track-A"},
{"test-race-2", "test-track-A"},
{"test-race-3", "test-track-B"},
}
for _, r := range racesData {
_, err = pool.Exec(ctx, `
INSERT INTO races (id, name, track_id, max_cars, laps, time_limit_s, status, created_ms, started_ms, finished_ms, updated_ms)
VALUES ($1, 'Test Race', $2, 4, 10, 60, 'finished', $3, $3, $3, $3)`,
r.id, r.trackID, testYearMs)
if err != nil {
t.Fatalf("failed to insert test race %s: %v", r.id, err)
}
}
// 3. Insert race drivers results
// Race 1 (Track A):
// - test-driver-1: best_lap 100 (1st fastest -> 25 points)
// - test-driver-2: best_lap 400 (currently 3rd fastest -> 25 points)
// - test-driver-3: best_lap 300 (currently 2nd fastest -> 25 points)
// - test-driver-4: best_lap 0 (DNF, completed 0 laps -> 1 point)
resultsRace1 := []struct {
driverID string
bestLapMs int64
pos any
}{
{"test-driver-1", 100, 1},
{"test-driver-2", 400, 2},
{"test-driver-3", 300, 3},
{"test-driver-4", 0, nil},
}
for _, res := range resultsRace1 {
_, err = pool.Exec(ctx, `
INSERT INTO race_drivers (race_id, driver_id, slot, joined_ms, total_time_ms, best_lap_ms, position)
VALUES ('test-race-1', $1, 0, $2, 1000, $3, $4)`,
res.driverID, testYearMs, res.bestLapMs, res.pos)
if err != nil {
t.Fatalf("failed to insert race driver result: %v", err)
}
}
// Race 2 (Track A - testing best results):
// - test-driver-2: best_lap 150 (2nd fastest -> 25 points, overrides 400ms lap time)
// This makes the times:
// - test-driver-1: 100ms (rank 1 -> 25 points)
// - test-driver-2: 150ms (rank 2 -> 25 points)
// - test-driver-3: 300ms (rank 3 -> 25 points)
// - test-driver-4: NULL (rank 4 -> 1 point)
// Exactly 3 drivers have 25 points.
_, err = pool.Exec(ctx, `
INSERT INTO race_drivers (race_id, driver_id, slot, joined_ms, total_time_ms, best_lap_ms, position)
VALUES ('test-race-2', 'test-driver-2', 0, $1, 1000, 150, 1)`, testYearMs)
if err != nil {
t.Fatalf("failed to insert race 2 result: %v", err)
}
// Race 3 (Track B):
// - test-driver-1: best_lap 500 (rank 2 on Track B -> 25 points)
// - test-driver-3: best_lap 200 (rank 1 on Track B -> 25 points)
resultsRace3 := []struct {
driverID string
bestLapMs int64
pos any
}{
{"test-driver-1", 500, 2},
{"test-driver-3", 200, 1},
}
for _, res := range resultsRace3 {
_, err = pool.Exec(ctx, `
INSERT INTO race_drivers (race_id, driver_id, slot, joined_ms, total_time_ms, best_lap_ms, position)
VALUES ('test-race-3', $1, 0, $2, 1000, $3, $4)`,
res.driverID, testYearMs, res.bestLapMs, res.pos)
if err != nil {
t.Fatalf("failed to insert race 3 driver result: %v", err)
}
}
// Test Track A Leaderboard
t.Run("Track A Leaderboard", func(t *testing.T) {
entries, total, err := store.GetTrackLeaderboard(ctx, "test-track-A", currentYear, 10, 0)
if err != nil {
t.Fatalf("failed to get track leaderboard: %v", err)
}
if total != 4 {
t.Errorf("expected total 4, got %d", total)
}
if len(entries) != 4 {
t.Fatalf("expected 4 entries, got %d", len(entries))
}
// Rank 1: test-driver-1 (25 pts, best_time 100ms)
// Rank 2: test-driver-2 (25 pts, best_time 150ms)
// Rank 3: test-driver-3 (25 pts, best_time 300ms)
// Rank 4: test-driver-4 (1 pt, best_time NULL)
drv1 := entries[0]
if drv1.DriverID != "test-driver-1" || drv1.Points != 25 || *drv1.BestTimeMs != 100 || drv1.Rank != 1 {
t.Errorf("invalid rank 1: %+v", drv1)
}
drv2 := entries[1]
if drv2.DriverID != "test-driver-2" || drv2.Points != 25 || *drv2.BestTimeMs != 150 || drv2.Rank != 2 {
t.Errorf("invalid rank 2: %+v", drv2)
}
// Check entries by looping to find specific drivers
for _, e := range entries {
if e.DriverID == "test-driver-3" {
if e.Points != 25 || *e.BestTimeMs != 300 || e.Rank != 3 {
t.Errorf("invalid test-driver-3: %+v", e)
}
}
if e.DriverID == "test-driver-4" {
if e.Points != 1 || e.BestTimeMs != nil || e.Rank != 4 {
t.Errorf("invalid test-driver-4: %+v", e)
}
}
}
})
// Test Track A Driver Rank
t.Run("Track A Driver Rank", func(t *testing.T) {
entry, err := store.GetTrackLeaderboardDriver(ctx, "test-track-A", currentYear, "test-driver-2")
if err != nil {
t.Fatalf("failed to get driver rank: %v", err)
}
if entry == nil || entry.Rank != 2 || entry.Points != 25 || *entry.BestTimeMs != 150 {
t.Errorf("expected rank 2, points 25, best_time 150, got %+v", entry)
}
})
// Test Overall Leaderboard
t.Run("Overall Leaderboard", func(t *testing.T) {
// Overall points calculation:
// - test-driver-1: Track A (25) + Track B (25) = 50 points
// - test-driver-3: Track A (25) + Track B (25) = 50 points
// - test-driver-2: Track A (25) + Track B (0) = 25 points
// - test-driver-4: Track A (1) + Track B (0) = 1 point
entries, _, err := store.GetOverallLeaderboard(ctx, currentYear, 200, 0)
if err != nil {
t.Fatalf("failed to get overall leaderboard: %v", err)
}
// Filter to only our test drivers
var testEntries []LeaderboardEntry
for _, entry := range entries {
if len(entry.DriverID) >= 11 && entry.DriverID[:12] == "test-driver-" {
testEntries = append(testEntries, entry)
}
}
if len(testEntries) != 4 {
t.Fatalf("expected 4 test entries, got %d", len(testEntries))
}
// test-driver-1 and test-driver-3 should both have 50 points
if testEntries[0].Points != 50 || testEntries[1].Points != 50 {
t.Errorf("expected top 2 test drivers to have 50 points, got: %+v and %+v", testEntries[0], testEntries[1])
}
if testEntries[2].DriverID != "test-driver-2" || testEntries[2].Points != 25 {
t.Errorf("invalid overall rank 3: %+v", testEntries[2])
}
if testEntries[3].DriverID != "test-driver-4" || testEntries[3].Points != 1 {
t.Errorf("invalid overall rank 4: %+v", testEntries[3])
}
})
// Test Overall Driver Rank
t.Run("Overall Driver Rank", func(t *testing.T) {
entry, err := store.GetOverallLeaderboardDriver(ctx, currentYear, "test-driver-3")
if err != nil {
t.Fatalf("failed to get overall driver rank: %v", err)
}
if entry == nil || entry.Points != 50 || entry.Rank <= 0 {
t.Errorf("expected rank > 0, points 50, got %+v", entry)
}
})
// Test Fallback Profile
t.Run("Fallback Profile", func(t *testing.T) {
entry, err := store.GetDriverLeaderboardProfile(ctx, "test-driver-1")
if err != nil {
t.Fatalf("failed to get profile: %v", err)
}
if entry == nil || entry.Name != "Driver One" || entry.Nickname != "DRV" || entry.ClanTag != "TST" {
t.Errorf("expected Driver One, DRV, TST, got %+v", entry)
}
})
}