Крупные сборки раздают готовый комплект настроек архивом. Панель разбирает его сама: находит серверный .ini, песочницу и точки появления, показывает, что нашла (сколько модов, карт, параметров), и только по подтверждению раскладывает файлы в новый или существующий профиль. Порты, пароли и название сервера при этом можно оставить свои — они относятся к машине, а не к сборке; у нового профиля они берутся из активного. Прежние конфиги остаются рядом как .bak, мир не трогается, а импорт в работающий профиль отклоняется: PZ перезаписал бы файлы своими при остановке. Посторонние .ini из архива (настройки модов) за конфиг сервера не принимаются: он опознаётся по знакомым параметрам. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
195 lines
6.3 KiB
Go
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("мусор принят за архив")
|
|
}
|
|
}
|