Watches any number of ARK:SE servers (RCON for players and chat, the query port for map, version and player cap), records joins, leaves, chat and an audit trail, and keeps a player database across servers. Control: save, broadcast, chat, whisper, kick, time of day, MOTD, wild dino wipe, a raw console with history and macros, restarts with timed warnings and a world save, optional start command and auto-start. Bans are kept by Megalodon: timed, cluster-wide and re-enforced when the player returns. Automation: schedules (every N or at times of day), Discord/ntfy/webhook notifications, welcome message, chat word filter. Access: viewer, moderator and admin roles, API tokens, Prometheus metrics. cmd/fakeark simulates a server for trying it out. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
92 lines
3.1 KiB
Go
92 lines
3.1 KiB
Go
package config
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestPasswordRoundTrip(t *testing.T) {
|
|
h, err := HashPassword("correct horse")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !CheckPassword(h, "correct horse") || CheckPassword(h, "wrong") || CheckPassword("garbage", "x") {
|
|
t.Fatal("password check is wrong")
|
|
}
|
|
}
|
|
|
|
func TestRoles(t *testing.T) {
|
|
if !Admin.Allows(Moderator) || !Moderator.Allows(Viewer) || Viewer.Allows(Moderator) || Role("").Allows(Viewer) || Role("root").Allows(Viewer) {
|
|
t.Fatal("role ordering is wrong")
|
|
}
|
|
}
|
|
|
|
func TestValidate(t *testing.T) {
|
|
good := Config{Servers: []Server{{ID: "island", Host: "10.0.0.2", RCONPort: 27020}}}
|
|
good.Defaults()
|
|
if err := good.Validate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cases := map[string]func(c *Config){
|
|
"bad id": func(c *Config) { c.Servers[0].ID = "The Island" },
|
|
"duplicate id": func(c *Config) { c.Servers = append(c.Servers, c.Servers[0]) },
|
|
"no host": func(c *Config) { c.Servers[0].Host = "" },
|
|
"bad port": func(c *Config) { c.Servers[0].RCONPort = 70000 },
|
|
"bad user role": func(c *Config) { c.Users = []User{{Name: "a", Role: "root"}} },
|
|
"job no timing": func(c *Config) { c.Jobs = []Job{{ID: "j", Name: "j", Action: "save"}} },
|
|
"job both": func(c *Config) {
|
|
c.Jobs = []Job{{ID: "j", Name: "j", Action: "save", Every: "1h", At: []string{"04:00"}}}
|
|
},
|
|
"job too often": func(c *Config) { c.Jobs = []Job{{ID: "j", Name: "j", Action: "save", Every: "5s"}} },
|
|
"job bad time": func(c *Config) { c.Jobs = []Job{{ID: "j", Name: "j", Action: "save", At: []string{"4am"}}} },
|
|
"job bad server": func(c *Config) {
|
|
c.Jobs = []Job{{ID: "j", Name: "j", Action: "save", Every: "1h", Servers: []string{"nope"}}}
|
|
},
|
|
"job no message": func(c *Config) { c.Jobs = []Job{{ID: "j", Name: "j", Action: "broadcast", Every: "1h"}} },
|
|
"notify bad url": func(c *Config) { c.Notify = []Notify{{Name: "n", Kind: "discord", URL: "javascript:alert(1)"}} },
|
|
"filter action": func(c *Config) { c.ChatFilter.Action = "nuke" },
|
|
}
|
|
for name, mutate := range cases {
|
|
c := clone(good)
|
|
mutate(&c)
|
|
if err := c.Validate(); err == nil {
|
|
t.Errorf("%s: expected an error", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdatePersistsAndRejects(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "megalodon.json")
|
|
h, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.Exists() {
|
|
t.Fatal("file should not exist before the first update")
|
|
}
|
|
err = h.Update(func(c *Config) error {
|
|
c.Servers = append(c.Servers, Server{ID: "island", Host: "ark.lan", Password: "secret"})
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := h.Update(func(c *Config) error { c.Servers[0].ID = "BAD ID"; return nil }); err == nil || !strings.Contains(err.Error(), "server id") {
|
|
t.Fatalf("invalid update accepted: %v", err)
|
|
}
|
|
h2, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c := h2.Get()
|
|
if len(c.Servers) != 1 || c.Servers[0].ID != "island" || c.Servers[0].Password != "secret" || c.Servers[0].RCONPort != 27020 {
|
|
t.Fatalf("reloaded config is wrong: %+v", c.Servers)
|
|
}
|
|
// Get returns a copy.
|
|
c.Servers[0].Host = "changed"
|
|
if h2.Get().Servers[0].Host != "ark.lan" {
|
|
t.Fatal("Get leaked internal state")
|
|
}
|
|
}
|