mirror of
https://github.com/IS1DI/x0gp.git
synced 2026-07-16 20:47:55 +00:00
263 lines
8.5 KiB
Go
263 lines
8.5 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|