Files
MrFastwind 4d6f162b8f fix: code quality, memory safety, and install improvements
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
2026-07-25 02:47:04 +02:00

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)
}
}