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("мусор принят за архив") } } // В пакете сборки рядом с конфигом сервера лежит и пресет песочницы для // одиночной игры. Первым должен идти набор с конфигом: панель предлагает его по // умолчанию, а из пресета песочницы моды и карты не возьмутся. func TestFromZipServerSetGoesFirst(t *testing.T) { data := buildZip(t, map[string]string{ // Имя пресета нарочно сортируется раньше по алфавиту. "Sandbox Presets/Alpha-Sandbox.cfg": sandboxCFG, "Server/zulu-server.ini": serverINI, "Server/zulu-server_SandboxVars.lua": sandboxLua, }) sets, err := FromZip(data) if err != nil { t.Fatal(err) } if len(sets) != 2 { t.Fatalf("наборов: %d", len(sets)) } if sets[0].INI == nil { t.Fatalf("первым идёт набор без конфига сервера: %q", sets[0].Name) } }