191 lines
6.3 KiB
Go
191 lines
6.3 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sigs.k8s.io/yaml"
|
|
)
|
|
|
|
// readYAML parses a paper-global.yml into a nested map for assertions.
|
|
func readYAML(t *testing.T, path string) map[string]any {
|
|
t.Helper()
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read %s: %v", path, err)
|
|
}
|
|
var m map[string]any
|
|
if err := yaml.Unmarshal(b, &m); err != nil {
|
|
t.Fatalf("parse %s: %v", path, err)
|
|
}
|
|
return m
|
|
}
|
|
|
|
// velocityBlock digs out proxies.velocity from a parsed paper-global.yml.
|
|
func velocityBlock(t *testing.T, root map[string]any) map[string]any {
|
|
t.Helper()
|
|
proxies, ok := root["proxies"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("no proxies map: %v", root)
|
|
}
|
|
vel, ok := proxies["velocity"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("no proxies.velocity map: %v", proxies)
|
|
}
|
|
return vel
|
|
}
|
|
|
|
// An empty secret must touch nothing: a proxy that is not in modern mode
|
|
// provisions no Secret, and the init must not wedge the pod over it.
|
|
func TestWritePaperForwardingEmptySecretIsNoop(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := writePaperForwarding(dir, ""); err != nil {
|
|
t.Fatalf("writePaperForwarding: %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "config", "paper-global.yml")); !os.IsNotExist(err) {
|
|
t.Errorf("paper-global.yml should not exist, stat err = %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "server.properties")); !os.IsNotExist(err) {
|
|
t.Errorf("server.properties should not exist, stat err = %v", err)
|
|
}
|
|
}
|
|
|
|
// A fresh data dir gets a complete velocity block and an offline server.properties.
|
|
func TestWritePaperForwardingFreshDir(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := writePaperForwarding(dir, "s3cr3t"); err != nil {
|
|
t.Fatalf("writePaperForwarding: %v", err)
|
|
}
|
|
|
|
vel := velocityBlock(t, readYAML(t, filepath.Join(dir, "config", "paper-global.yml")))
|
|
if vel["enabled"] != true {
|
|
t.Errorf("velocity.enabled = %v, want true", vel["enabled"])
|
|
}
|
|
if vel["online-mode"] != true {
|
|
t.Errorf("velocity.online-mode = %v, want true", vel["online-mode"])
|
|
}
|
|
if vel["secret"] != "s3cr3t" {
|
|
t.Errorf("velocity.secret = %v, want s3cr3t", vel["secret"])
|
|
}
|
|
|
|
props, err := os.ReadFile(filepath.Join(dir, "server.properties"))
|
|
if err != nil {
|
|
t.Fatalf("read server.properties: %v", err)
|
|
}
|
|
if !strings.Contains(string(props), "online-mode=false") {
|
|
t.Errorf("server.properties missing online-mode=false:\n%s", props)
|
|
}
|
|
}
|
|
|
|
// A pre-existing paper-global.yml (as Paper expands it on first boot) must keep
|
|
// all of its other keys — clobbering them would silently reset the user's tuning
|
|
// on every restart. This is the whole reason the writer merges rather than
|
|
// overwrites.
|
|
func TestWritePaperGlobalPreservesExistingKeys(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfg := filepath.Join(dir, "config")
|
|
if err := os.MkdirAll(cfg, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
existing := []byte("proxies:\n velocity:\n enabled: false\n secret: OLD\nchunk-loading:\n autoconfig-send-distance: true\nmisc:\n max-joins-per-tick: 5\n")
|
|
if err := os.WriteFile(filepath.Join(cfg, "paper-global.yml"), existing, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := writePaperGlobal(dir, "NEW"); err != nil {
|
|
t.Fatalf("writePaperGlobal: %v", err)
|
|
}
|
|
|
|
root := readYAML(t, filepath.Join(cfg, "paper-global.yml"))
|
|
vel := velocityBlock(t, root)
|
|
if vel["enabled"] != true || vel["secret"] != "NEW" {
|
|
t.Errorf("velocity not updated: %v", vel)
|
|
}
|
|
// Unrelated trees survive.
|
|
if _, ok := root["chunk-loading"].(map[string]any); !ok {
|
|
t.Errorf("chunk-loading tree lost: %v", root)
|
|
}
|
|
misc, ok := root["misc"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("misc tree lost: %v", root)
|
|
}
|
|
// sigs.k8s.io/yaml decodes numbers via JSON, so 5 arrives as float64(5).
|
|
if misc["max-joins-per-tick"] != float64(5) {
|
|
t.Errorf("misc.max-joins-per-tick = %v, want 5", misc["max-joins-per-tick"])
|
|
}
|
|
}
|
|
|
|
// online-mode=false must be forced while every other property line — comments
|
|
// included — is left untouched.
|
|
func TestForceServerPropertyOfflinePreservesOthers(t *testing.T) {
|
|
dir := t.TempDir()
|
|
existing := "#Minecraft server properties\nmotd=Hello World\nonline-mode=true\ndifficulty=hard\n"
|
|
if err := os.WriteFile(filepath.Join(dir, "server.properties"), []byte(existing), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := forceServerPropertyOffline(dir); err != nil {
|
|
t.Fatalf("forceServerPropertyOffline: %v", err)
|
|
}
|
|
got, err := os.ReadFile(filepath.Join(dir, "server.properties"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := string(got)
|
|
if strings.Contains(s, "online-mode=true") {
|
|
t.Errorf("online-mode=true not replaced:\n%s", s)
|
|
}
|
|
if !strings.Contains(s, "online-mode=false") {
|
|
t.Errorf("online-mode=false not set:\n%s", s)
|
|
}
|
|
for _, keep := range []string{"#Minecraft server properties", "motd=Hello World", "difficulty=hard"} {
|
|
if !strings.Contains(s, keep) {
|
|
t.Errorf("lost line %q:\n%s", keep, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
// upsertProperty appends when the key is absent and does not grow blank lines.
|
|
func TestUpsertPropertyAppends(t *testing.T) {
|
|
got := string(upsertProperty([]byte("motd=hi"), "online-mode", "false"))
|
|
if got != "motd=hi\nonline-mode=false\n" {
|
|
t.Errorf("append form wrong: %q", got)
|
|
}
|
|
empty := string(upsertProperty(nil, "online-mode", "false"))
|
|
if empty != "online-mode=false\n" {
|
|
t.Errorf("empty form wrong: %q", empty)
|
|
}
|
|
// A commented key must not count as present.
|
|
commented := string(upsertProperty([]byte("#online-mode=true\n"), "online-mode", "false"))
|
|
if !strings.Contains(commented, "#online-mode=true") || !strings.Contains(commented, "\nonline-mode=false\n") {
|
|
t.Errorf("comment mishandled: %q", commented)
|
|
}
|
|
}
|
|
|
|
// The written files land at fwdFileMode: owner-writable for the game uid the init
|
|
// shares with the server container, and no longer world-writable. chmod semantics
|
|
// are POSIX-only, so this asserts on non-Windows.
|
|
func TestWriteForwardingFileModes(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("POSIX file modes not represented on Windows")
|
|
}
|
|
dir := t.TempDir()
|
|
if err := writePaperForwarding(dir, "x"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, p := range []string{
|
|
filepath.Join(dir, "config", "paper-global.yml"),
|
|
filepath.Join(dir, "server.properties"),
|
|
} {
|
|
fi, err := os.Stat(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if fi.Mode().Perm() != fwdFileMode {
|
|
t.Errorf("%s mode = %o, want %o", p, fi.Mode().Perm(), fwdFileMode)
|
|
}
|
|
}
|
|
}
|