Critical fixes: - Fix Dockerfile: reorder stages so frontend assets embed into Go binary - Fix Go version 1.25 (nonexistent) to 1.24 across Dockerfile, go.mod, CI - Add graceful game server shutdown on SIGTERM/SIGINT - Order startup tasks: updates complete before auto-start - Fix TOCTOU race in UpdateSettings with atomic Update() method Security: - Add optional AUTH_TOKEN bearer auth middleware on API/WS routes - Fix path traversal in DeleteMod using filepath.Rel instead of HasPrefix - Add input validation for IPPort, ServerParameters, ScheduledUpdate Memory safety: - Cap RPT buffer allocation to 64KB to prevent OOM on large logs - Cap GetLog file read to 10MB - Fix context cancel leak in SteamCmdManager.run() - Remove data-raced cancel field in steamcmd.go - Atomic file writes (write-temp-then-rename) across all managers Reliability: - Log save errors in ProcessManager.Stop() - Atomic file writes prevent corruption on crash Tests: - Add mod_manager_test.go (12 tests: ListWorkshopMods, ListLocalMods, BuildUsageMap, RemoveMod, dirSize) - Add scheduler_test.go (6 tests: Start/Stop, Refresh with empty, invalid, valid, and replaced cron expressions) - Add TestRestart to server_process_test.go CI/Docs: - Add -race flag to go test in CI and Makefile - Add npm lint step to CI - Add Go/npm module caching to CI - Update README: prerequisites, AUTH_TOKEN/GIN_MODE/SERVERS_DIR docs, fix manual quickstart to use make build
137 lines
3.1 KiB
Go
137 lines
3.1 KiB
Go
package services
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"arma3-web-server/internal/models"
|
|
)
|
|
|
|
func TestNewScheduler(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
settings := NewSettingsManager(dataDir)
|
|
modlists := NewModlistManager(dataDir)
|
|
streamer := NewLogStreamer()
|
|
steamcmd := NewSteamCmdManager(t.TempDir(), streamer)
|
|
|
|
s := NewScheduler(settings, modlists, steamcmd)
|
|
if s == nil {
|
|
t.Fatal("expected non-nil scheduler")
|
|
}
|
|
if s.cron == nil {
|
|
t.Fatal("expected non-nil cron")
|
|
}
|
|
}
|
|
|
|
func TestScheduler_StartStop(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
settings := NewSettingsManager(dataDir)
|
|
modlists := NewModlistManager(dataDir)
|
|
streamer := NewLogStreamer()
|
|
steamcmd := NewSteamCmdManager(t.TempDir(), streamer)
|
|
|
|
s := NewScheduler(settings, modlists, steamcmd)
|
|
s.Start()
|
|
time.Sleep(50 * time.Millisecond)
|
|
s.Stop()
|
|
}
|
|
|
|
func TestScheduler_Refresh_Empty(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
settings := NewSettingsManager(dataDir)
|
|
modlists := NewModlistManager(dataDir)
|
|
streamer := NewLogStreamer()
|
|
steamcmd := NewSteamCmdManager(t.TempDir(), streamer)
|
|
|
|
s := NewScheduler(settings, modlists, steamcmd)
|
|
s.Start()
|
|
defer s.Stop()
|
|
|
|
s.Refresh()
|
|
if s.entryID != 0 {
|
|
t.Errorf("expected entryID 0 with empty settings, got %d", s.entryID)
|
|
}
|
|
}
|
|
|
|
func TestScheduler_Refresh_InvalidCron(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
settings := NewSettingsManager(dataDir)
|
|
modlists := NewModlistManager(dataDir)
|
|
streamer := NewLogStreamer()
|
|
steamcmd := NewSteamCmdManager(t.TempDir(), streamer)
|
|
|
|
_, err := settings.Update(func(s *models.ServerSettings) {
|
|
s.ScheduledUpdate = "not-a-cron"
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sched := NewScheduler(settings, modlists, steamcmd)
|
|
sched.Start()
|
|
defer sched.Stop()
|
|
|
|
sched.Refresh()
|
|
if sched.entryID != 0 {
|
|
t.Errorf("expected entryID 0 with invalid cron, got %d", sched.entryID)
|
|
}
|
|
}
|
|
|
|
func TestScheduler_Refresh_ValidCron(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
settings := NewSettingsManager(dataDir)
|
|
modlists := NewModlistManager(dataDir)
|
|
streamer := NewLogStreamer()
|
|
steamcmd := NewSteamCmdManager(t.TempDir(), streamer)
|
|
|
|
_, err := settings.Update(func(s *models.ServerSettings) {
|
|
s.ScheduledUpdate = "0 4 * * *"
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sched := NewScheduler(settings, modlists, steamcmd)
|
|
sched.Start()
|
|
defer sched.Stop()
|
|
|
|
sched.Refresh()
|
|
if sched.entryID == 0 {
|
|
t.Errorf("expected non-zero entryID with valid cron")
|
|
}
|
|
}
|
|
|
|
func TestScheduler_Refresh_Replace(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
settings := NewSettingsManager(dataDir)
|
|
modlists := NewModlistManager(dataDir)
|
|
streamer := NewLogStreamer()
|
|
steamcmd := NewSteamCmdManager(t.TempDir(), streamer)
|
|
|
|
sched := NewScheduler(settings, modlists, steamcmd)
|
|
sched.Start()
|
|
defer sched.Stop()
|
|
|
|
_, err := settings.Update(func(s *models.ServerSettings) {
|
|
s.ScheduledUpdate = "0 4 * * *"
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sched.Refresh()
|
|
id1 := sched.entryID
|
|
|
|
_, err = settings.Update(func(s *models.ServerSettings) {
|
|
s.ScheduledUpdate = "0 5 * * *"
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sched.Refresh()
|
|
id2 := sched.entryID
|
|
|
|
if id1 == id2 {
|
|
t.Errorf("expected different entryIDs after refresh, both got %d", id1)
|
|
}
|
|
}
|