Files
pzmanager/internal/preset/preset_test.go
T
mrleo1nidandClaude Opus 5 aec932f68f
Release / release (push) Successful in 30s
CI / test (push) Successful in 23s
Импорт пресетов сборок из ZIP-пакета
Крупные сборки раздают готовый комплект настроек архивом. Панель разбирает
его сама: находит серверный .ini, песочницу и точки появления, показывает,
что нашла (сколько модов, карт, параметров), и только по подтверждению
раскладывает файлы в новый или существующий профиль.

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

Посторонние .ini из архива (настройки модов) за конфиг сервера не
принимаются: он опознаётся по знакомым параметрам.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 17:18:44 +03:00

195 lines
6.3 KiB
Go

package preset
import (
"archive/zip"
"bytes"
"errors"
"strings"
"testing"
)
// serverINI — сокращённый конфиг сервера: маркеров хватает, чтобы отличить его
// от постороннего .ini.
const serverINI = `PublicName=Sophie Server
PublicDescription=Vanilla++
DefaultPort=16261
MaxPlayers=32
PVP=true
Map=Brandenburg, KY;Muldraugh, KY
Mods=SophieCore;SophieExtras
WorkshopItems=3773856464;2392709985
`
const sandboxLua = `SandboxVars = {
VERSION = 5,
Zombies = 3,
ZombieLore = {
Speed = 2,
},
}
`
const spawnRegions = `function SpawnRegions()
return { { name = "Muldraugh,KY", file = "media/maps/Muldraugh, KY/spawnpoints.lua" } }
end
`
func buildZip(t *testing.T, files map[string]string) []byte {
t.Helper()
var buf bytes.Buffer
w := zip.NewWriter(&buf)
for name, content := range files {
f, err := w.Create(name)
if err != nil {
t.Fatal(err)
}
if _, err := f.Write([]byte(content)); err != nil {
t.Fatal(err)
}
}
if err := w.Close(); err != nil {
t.Fatal(err)
}
return buf.Bytes()
}
// Пакет сборки — это архив пользовательской папки Zomboid: серверные файлы в
// нём лежат вперемешку с тем, что нужно только одиночной игре.
func TestFromZipPack(t *testing.T) {
data := buildZip(t, map[string]string{
"readme.txt": "put these folders...",
"changelog.txt": "v2",
"THIS IS WHERE TO PUT THESE FOLDERS.png": "\x89PNG fake",
"Lua/ModOptions.ini": "[SomeMod]\nenabled=true\n",
"Lua/ModFolders.cfg": "folders",
"Sandbox Presets/Sophie-42.cfg": "Version=6\nZombies=4\n",
"Server/sophie-42.ini": serverINI,
"Server/sophie-42_SandboxVars.lua": sandboxLua,
"Server/sophie-42_spawnregions.lua": spawnRegions,
})
sets, err := FromZip(data)
if err != nil {
t.Fatal(err)
}
if len(sets) != 1 {
names := []string{}
for _, s := range sets {
names = append(names, s.Name)
}
t.Fatalf("наборов: %d (%s), ожидался один", len(sets), strings.Join(names, ", "))
}
set := sets[0]
if set.Name != "sophie-42" {
t.Errorf("имя набора = %q", set.Name)
}
if set.INI == nil || set.Sandbox == nil || set.SpawnRegions == nil {
t.Fatalf("не все файлы найдены: ini=%v sandbox=%v spawn=%v",
set.INI != nil, set.Sandbox != nil, set.SpawnRegions != nil)
}
got := set.Describe()
if got.ServerName != "Sophie Server" {
t.Errorf("имя сервера = %q", got.ServerName)
}
if got.Mods != 2 || got.WorkshopItems != 2 {
t.Errorf("модов %d, пакетов мастерской %d", got.Mods, got.WorkshopItems)
}
if len(got.Maps) != 2 || got.Maps[0] != "Brandenburg, KY" {
t.Errorf("карты = %v", got.Maps)
}
if got.SandboxSettings == 0 {
t.Error("параметры песочницы не посчитаны")
}
if !got.HasSpawnRegions {
t.Error("точки появления не найдены")
}
}
// Настройки мода лежат в таком же .ini, и принимать их за конфиг сервера
// нельзя: импорт затёр бы настоящий конфиг мусором.
func TestFromZipIgnoresForeignINI(t *testing.T) {
data := buildZip(t, map[string]string{
"Lua/ModOptions.ini": "[SomeMod]\nenabled=true\nPVP=true\n",
"notes.txt": "hi",
})
if _, err := FromZip(data); !errors.Is(err, ErrNoServerFiles) {
t.Fatalf("ожидалась ErrNoServerFiles, получено %v", err)
}
}
// Файлы могут лежать в корне архива или в папке с другим именем — важен
// только их суффикс.
func TestFromZipFlatLayout(t *testing.T) {
data := buildZip(t, map[string]string{
"my-server.ini": serverINI,
"configs/my-server_SandboxVars.lua": sandboxLua,
})
sets, err := FromZip(data)
if err != nil {
t.Fatal(err)
}
if len(sets) != 1 || sets[0].Name != "my-server" {
t.Fatalf("наборы = %+v", sets)
}
if sets[0].Sandbox == nil {
t.Error("песочница из другой папки не подхвачена")
}
}
// Несколько вариантов конфигов в одном архиве — человек выбирает сам, поэтому
// набора должно быть два, а не один слипшийся.
func TestFromZipSeveralSets(t *testing.T) {
data := buildZip(t, map[string]string{
"Server/pvp.ini": serverINI,
"Server/pvp_SandboxVars.lua": sandboxLua,
"Server/pve.ini": serverINI,
"Server/pve_SandboxVars.lua": sandboxLua,
"Server/pve_spawnregions.lua": spawnRegions,
})
sets, err := FromZip(data)
if err != nil {
t.Fatal(err)
}
if len(sets) != 2 {
t.Fatalf("наборов: %d", len(sets))
}
// Порядок задан именами: панель показывает их списком.
if sets[0].Name != "pve" || sets[1].Name != "pvp" {
t.Fatalf("порядок наборов: %q, %q", sets[0].Name, sets[1].Name)
}
}
// Одни точки появления импортировать некуда.
func TestFromZipSpawnRegionsAlone(t *testing.T) {
data := buildZip(t, map[string]string{"Server/x_spawnregions.lua": spawnRegions})
if _, err := FromZip(data); !errors.Is(err, ErrNoServerFiles) {
t.Fatalf("ожидалась ErrNoServerFiles, получено %v", err)
}
}
// Имя файла из архива в путь не превращается: берём только последний сегмент,
// поэтому «../» из имени записи никуда не ведёт.
func TestFromZipIgnoresPaths(t *testing.T) {
data := buildZip(t, map[string]string{
"../../../etc/evil.ini": serverINI,
"..\\..\\windows\\evil_SandboxVars.lua": sandboxLua,
})
sets, err := FromZip(data)
if err != nil {
t.Fatal(err)
}
for _, set := range sets {
if strings.ContainsAny(set.Name, `/\.`) {
t.Errorf("в имени набора остался путь: %q", set.Name)
}
}
}
func TestFromZipRejectsGarbage(t *testing.T) {
if _, err := FromZip([]byte("это не архив")); err == nil {
t.Fatal("мусор принят за архив")
}
}