Files
pzmanager/internal/pzconfig/pzconfig_test.go
T
mrleo1nidandClaude Opus 5 5d50b609f6
CI / test (push) Successful in 21s
Release / release (push) Successful in 26s
Настройки сервера и песочницы разложены по разделам
Полторы сотни параметров сплошным списком читать невозможно. Теперь они
разбиты на разделы — «Основное», «PVP и урон», «Убежища», «Античит» и так
далее, — свёрнутые по умолчанию, кроме первого. Поиск раскрывает разделы с
совпадениями и прячет остальные, а раскрытые разделы не схлопываются при
возврате на вкладку.

Категории составлены по настоящим конфигам Build 41 и 42: все 144 ключа
.ini и 270 параметров песочницы раскладываются без остатка. Нумерованные
и однотипные ключи (AntiCheatProtectionType1..24, *Loot, Animal*, Firearm*)
подхватываются по префиксу, а незнакомые попадают в «Прочее» — в новых
версиях игры ключи добавляют, и терять их со страницы нельзя.

Описания параметров панель больше не выдумывает, а берёт из самого
конфига: игра пишет их комментариями на языке сервера. Полный текст — во
всплывающей подсказке, начало — под полем; поиск ищет и по описанию, так
что параметр можно найти по-русски.

Заодно исправлено сохранение файлов с виндовыми переводами строк: панель
переписывала их в LF, и правка одного параметра меняла каждую строку
файла. Проверено на реальном SandboxVars.lua: правка одного значения
меняет ровно одну строку, размер файла не меняется.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 03:07:59 +03:00

188 lines
6.1 KiB
Go

package pzconfig
import (
"strings"
"testing"
)
const sampleINI = `# Comment line
PublicName=My Server
Public=true
MaxPlayers=32
Mods=modA;modB
WorkshopItems=111;222
`
func TestINIPreservesLayout(t *testing.T) {
ini := ParseINI([]byte(sampleINI))
if got := ini.Get("PublicName"); got != "My Server" {
t.Fatalf("PublicName = %q, ожидалось %q", got, "My Server")
}
// Правка одного ключа не должна трогать комментарии и пустые строки.
ini.Set("MaxPlayers", "64")
got := string(ini.Render())
if !strings.HasPrefix(got, "# Comment line\n") {
t.Errorf("комментарий потерян:\n%s", got)
}
if !strings.Contains(got, "\n\nMaxPlayers=64\n") {
t.Errorf("пустая строка или новое значение потеряны:\n%s", got)
}
if strings.Contains(got, "MaxPlayers=32") {
t.Errorf("старое значение осталось:\n%s", got)
}
}
func TestINIAddsMissingKey(t *testing.T) {
ini := ParseINI([]byte("A=1"))
ini.Set("B", "2")
if got := ini.Get("B"); got != "2" {
t.Fatalf("B = %q", got)
}
if got := string(ini.Render()); got != "A=1\nB=2" {
t.Fatalf("render = %q", got)
}
}
func TestINIRejectsNewlineInValue(t *testing.T) {
ini := ParseINI([]byte("A=1"))
ini.Set("A", "нет\nпереносам")
if strings.Contains(string(ini.Render()), "\nп") {
t.Fatalf("перенос строки попал в значение: %q", ini.Get("A"))
}
}
func TestModsRoundTrip(t *testing.T) {
ini := ParseINI([]byte(sampleINI))
mods, workshop := ini.Mods()
if len(mods) != 2 || mods[0] != "modA" {
t.Fatalf("mods = %v", mods)
}
if len(workshop) != 2 || workshop[1] != "222" {
t.Fatalf("workshop = %v", workshop)
}
// Дубликаты отбрасываются, порядок сохраняется.
ini.SetMods([]string{"modB", "modA", "modB"}, []string{"222"})
if got := ini.Get(KeyMods); got != "modB;modA" {
t.Fatalf("Mods = %q", got)
}
if got := ini.Get(KeyWorkshopItems); got != "222" {
t.Fatalf("WorkshopItems = %q", got)
}
}
func TestSplitListIgnoresBlanks(t *testing.T) {
if got := SplitList(" a ; ; b;"); len(got) != 2 || got[0] != "a" || got[1] != "b" {
t.Fatalf("SplitList = %v", got)
}
if got := SplitList(" "); len(got) != 0 {
t.Fatalf("пустая строка дала %v", got)
}
}
const sampleSandbox = `SandboxVars = {
VERSION = 5,
Zombies = 3,
Speed = 2,
ZombieLore = {
Speed = 2,
Strength = 2,
},
WaterShutModifier = 14,
}
`
func TestSandboxParsesNestedPaths(t *testing.T) {
sb := ParseSandbox([]byte(sampleSandbox))
if v, ok := sb.Get("Zombies"); !ok || v != "3" {
t.Fatalf("Zombies = %q (найдено: %v)", v, ok)
}
// Одноимённые ключи на разных уровнях не должны перетирать друг друга.
if v, ok := sb.Get("ZombieLore.Speed"); !ok || v != "2" {
t.Fatalf("ZombieLore.Speed = %q (найдено: %v)", v, ok)
}
if v, ok := sb.Get("Speed"); !ok || v != "2" {
t.Fatalf("Speed верхнего уровня = %q (найдено: %v)", v, ok)
}
if v, ok := sb.Get("WaterShutModifier"); !ok || v != "14" {
t.Fatalf("WaterShutModifier после закрытия таблицы = %q (найдено: %v)", v, ok)
}
}
func TestSandboxSetKeepsFormatting(t *testing.T) {
sb := ParseSandbox([]byte(sampleSandbox))
if err := sb.Set("ZombieLore.Speed", "1"); err != nil {
t.Fatal(err)
}
got := string(sb.Render())
if !strings.Contains(got, " Speed = 1,") {
t.Errorf("отступ или запятая потеряны:\n%s", got)
}
if !strings.Contains(got, " Speed = 2,") {
t.Errorf("параметр верхнего уровня изменён по ошибке:\n%s", got)
}
if !strings.HasSuffix(got, "}\n") {
t.Errorf("хвост файла изменён:\n%s", got)
}
}
func TestSandboxRejectsUnknownAndBrokenValues(t *testing.T) {
sb := ParseSandbox([]byte(sampleSandbox))
if err := sb.Set("НетТакого", "1"); err == nil {
t.Error("ожидалась ошибка для неизвестного параметра")
}
if err := sb.Set("Zombies", "{ 1, 2 }"); err == nil {
t.Error("ожидалась ошибка для значения с фигурными скобками")
}
}
func TestSandboxEntriesFollowFileOrder(t *testing.T) {
entries := ParseSandbox([]byte(sampleSandbox)).Entries()
want := []string{"VERSION", "Zombies", "Speed", "ZombieLore.Speed", "ZombieLore.Strength", "WaterShutModifier"}
if len(entries) != len(want) {
t.Fatalf("получено %d параметров, ожидалось %d: %v", len(entries), len(want), entries)
}
for i, path := range want {
if entries[i].Path != path {
t.Errorf("entries[%d] = %q, ожидалось %q", i, entries[i].Path, path)
}
}
}
// TestKeepsWindowsLineEndings: конфиги, созданные игрой на Windows, приходят с
// CRLF. Если при сохранении переписать их в LF, изменится каждая строка файла —
// правка одного параметра превратится в полную перезапись.
func TestKeepsWindowsLineEndings(t *testing.T) {
ini := ParseINI([]byte("# Комментарий\r\nMaxPlayers=32\r\nPVP=true\r\n"))
ini.Set("MaxPlayers", "64")
got := string(ini.Render())
if strings.Count(got, "\r\n") != 3 {
t.Errorf("переводы строк не сохранены:\n%q", got)
}
if strings.Contains(strings.ReplaceAll(got, "\r\n", ""), "\n") {
t.Errorf("в файле появились одиночные LF:\n%q", got)
}
// Файл с LF таким и остаётся.
unix := ParseINI([]byte("MaxPlayers=32\nPVP=true"))
unix.Set("PVP", "false")
if strings.Contains(string(unix.Render()), "\r") {
t.Error("в файл с LF добавились CR")
}
sandbox := ParseSandbox([]byte("SandboxVars = {\r\n Zombies = 3,\r\n}\r\n"))
if err := sandbox.Set("Zombies", "1"); err != nil {
t.Fatal(err)
}
if strings.Count(string(sandbox.Render()), "\r\n") != 3 {
t.Errorf("песочница потеряла CRLF:\n%q", string(sandbox.Render()))
}
}