go_games_collection/wayfarer_quest_loader_test.go
2026-07-07 09:12:09 +03:00

291 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"os"
"path/filepath"
"testing"
)
func intPtr(n int) *int { return &n }
func validYAMLQuest() wayfarerQuestYAML {
return wayfarerQuestYAML{
Key: "quest.test_valid",
Title: wfLocalizedText{RU: "Тестовый квест", EN: "Test Quest"},
Brief: wfLocalizedText{RU: "Краткое описание", EN: "Brief description"},
Steps: []wayfarerQuestStepYAML{
{
Text: wfLocalizedText{RU: "Ситуация на шаге 0", EN: "Situation at step 0"},
Choices: []wayfarerQuestChoiceYAML{
{Label: wfLocalizedText{RU: "Идти дальше", EN: "Go on"}, NextStep: intPtr(1)},
{Label: wfLocalizedText{RU: "Отступить", EN: "Retreat"}, Terminal: true, Success: false,
Result: wfLocalizedText{RU: "Провал", EN: "Failure"}},
},
},
{
Text: wfLocalizedText{RU: "Ситуация на шаге 1", EN: "Situation at step 1"},
Choices: []wayfarerQuestChoiceYAML{
{Label: wfLocalizedText{RU: "Завершить", EN: "Finish"}, Terminal: true, Success: true,
Result: wfLocalizedText{RU: "Успех", EN: "Success"},
CreditsDelta: 500, HullDelta: -5},
},
},
},
}
}
func TestConvertYAMLQuestValid(t *testing.T) {
def, texts, err := convertYAMLQuest(validYAMLQuest(), "test.yaml")
if err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
if def.Key != "quest.test_valid" {
t.Errorf("неверный ключ квеста: %q", def.Key)
}
if len(def.Steps) != 2 {
t.Fatalf("ожидалось 2 шага, получено %d", len(def.Steps))
}
if def.Steps[0].Choices[0].NextStep != 1 {
t.Errorf("ожидался NextStep=1 у первого выбора")
}
if def.Steps[0].Choices[1].Terminal != true || def.Steps[0].Choices[1].Success != false {
t.Errorf("второй выбор должен быть терминальным провалом")
}
if def.Steps[1].Choices[0].CreditsDelta != 500 || def.Steps[1].Choices[0].HullDelta != -5 {
t.Errorf("не перенеслись числовые последствия выбора")
}
if def.Steps[0].Choices[0].CargoIdx != -1 {
t.Errorf("ожидался CargoIdx=-1 по умолчанию, получено %d", def.Steps[0].Choices[0].CargoIdx)
}
if len(texts) == 0 {
t.Errorf("ожидались тексты для регистрации переводов")
}
}
func TestConvertYAMLQuestMissingKey(t *testing.T) {
y := validYAMLQuest()
y.Key = ""
if _, _, err := convertYAMLQuest(y, "test.yaml"); err == nil {
t.Errorf("ожидалась ошибка при отсутствующем key")
}
}
func TestConvertYAMLQuestNoSteps(t *testing.T) {
y := validYAMLQuest()
y.Steps = nil
if _, _, err := convertYAMLQuest(y, "test.yaml"); err == nil {
t.Errorf("ожидалась ошибка при отсутствии шагов")
}
}
func TestConvertYAMLQuestNoChoicesInStep(t *testing.T) {
y := validYAMLQuest()
y.Steps[0].Choices = nil
if _, _, err := convertYAMLQuest(y, "test.yaml"); err == nil {
t.Errorf("ожидалась ошибка при отсутствии вариантов выбора на шаге")
}
}
func TestConvertYAMLQuestMissingNextStepOnNonTerminal(t *testing.T) {
y := validYAMLQuest()
y.Steps[0].Choices[0].NextStep = nil
if _, _, err := convertYAMLQuest(y, "test.yaml"); err == nil {
t.Errorf("ожидалась ошибка при отсутствии next_step у нетерминального выбора")
}
}
func TestConvertYAMLQuestNextStepOutOfRange(t *testing.T) {
y := validYAMLQuest()
y.Steps[0].Choices[0].NextStep = intPtr(99)
if _, _, err := convertYAMLQuest(y, "test.yaml"); err == nil {
t.Errorf("ожидалась ошибка при next_step вне диапазона")
}
}
func TestConvertYAMLQuestExplicitCargoIdx(t *testing.T) {
y := validYAMLQuest()
y.Steps[1].Choices[0].CargoIdx = intPtr(2)
y.Steps[1].Choices[0].CargoDelta = -3
def, _, err := convertYAMLQuest(y, "test.yaml")
if err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
if def.Steps[1].Choices[0].CargoIdx != 2 || def.Steps[1].Choices[0].CargoDelta != -3 {
t.Errorf("явно указанный cargo_idx/cargo_delta не сохранился")
}
}
func writeYAMLFile(t *testing.T, dir, name, content string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644); err != nil {
t.Fatalf("не удалось записать тестовый файл: %v", err)
}
}
func TestLoadWayfarerQuestsFromDirMissingDirIsHarmless(t *testing.T) {
loaded, warnings := LoadWayfarerQuestsFromDir(filepath.Join(t.TempDir(), "does-not-exist"), nil)
if loaded != nil || warnings != nil {
t.Errorf("отсутствующая директория должна давать (nil, nil), получено (%v, %v)", loaded, warnings)
}
}
func TestLoadWayfarerQuestsFromDirValidFile(t *testing.T) {
dir := t.TempDir()
writeYAMLFile(t, dir, "myquest.yaml", `
key: quest.loader_test_ok
title: {ru: "Загруженный квест", en: "Loaded Quest"}
brief: {ru: "Описание", en: "Brief"}
steps:
- text: {ru: "Шаг 0", en: "Step 0"}
choices:
- label: {ru: "Дальше", en: "Onward"}
next_step: 1
- text: {ru: "Шаг 1", en: "Step 1"}
choices:
- label: {ru: "Конец", en: "The end"}
terminal: true
success: true
result: {ru: "Победа", en: "Victory"}
`)
loaded, warnings := LoadWayfarerQuestsFromDir(dir, nil)
if len(warnings) != 0 {
t.Errorf("не ожидалось предупреждений, получено: %v", warnings)
}
if len(loaded) != 1 {
t.Fatalf("ожидался ровно 1 загруженный квест, получено %d", len(loaded))
}
if loaded[0].Key != "quest.loader_test_ok" {
t.Errorf("неверный ключ загруженного квеста: %q", loaded[0].Key)
}
if T("quest.loader_test_ok.title") != "Загруженный квест" {
t.Errorf("перевод названия не зарегистрировался в общей карте переводов, T() вернул: %q", T("quest.loader_test_ok.title"))
}
}
func TestLoadWayfarerQuestsFromDirSkipsMalformedYAML(t *testing.T) {
dir := t.TempDir()
writeYAMLFile(t, dir, "broken.yaml", "key: [это не валидный YAML для этой структуры\n - вложенность сломана")
loaded, warnings := LoadWayfarerQuestsFromDir(dir, nil)
if len(loaded) != 0 {
t.Errorf("сломанный файл не должен был загрузиться, получено %d квестов", len(loaded))
}
if len(warnings) != 1 {
t.Fatalf("ожидалось ровно 1 предупреждение, получено %d: %v", len(warnings), warnings)
}
}
func TestLoadWayfarerQuestsFromDirSkipsDuplicateKey(t *testing.T) {
dir := t.TempDir()
writeYAMLFile(t, dir, "dup.yaml", `
key: quest.supernova
title: {ru: "Дубликат", en: "Duplicate"}
brief: {ru: "x", en: "x"}
steps:
- text: {ru: "x", en: "x"}
choices:
- label: {ru: "x", en: "x"}
terminal: true
success: true
result: {ru: "x", en: "x"}
`)
existing := map[string]bool{"quest.supernova": true}
loaded, warnings := LoadWayfarerQuestsFromDir(dir, existing)
if len(loaded) != 0 {
t.Errorf("квест с уже занятым ключом не должен был загрузиться")
}
if len(warnings) != 1 {
t.Fatalf("ожидалось предупреждение о занятом ключе, получено: %v", warnings)
}
}
func TestLoadWayfarerQuestsFromDirSkipsIncompleteTranslation(t *testing.T) {
dir := t.TempDir()
writeYAMLFile(t, dir, "incomplete.yaml", `
key: quest.loader_test_incomplete
title: {ru: "Есть по-русски", en: ""}
brief: {ru: "x", en: "x"}
steps:
- text: {ru: "x", en: "x"}
choices:
- label: {ru: "x", en: "x"}
terminal: true
success: true
result: {ru: "x", en: "x"}
`)
loaded, warnings := LoadWayfarerQuestsFromDir(dir, nil)
if len(loaded) != 0 {
t.Errorf("квест с неполным переводом не должен был загрузиться")
}
if len(warnings) == 0 {
t.Errorf("ожидалось предупреждение о неполном переводе")
}
}
func TestLoadWayfarerQuestsFromDirIgnoresNonYAMLFiles(t *testing.T) {
dir := t.TempDir()
writeYAMLFile(t, dir, "readme.txt", "это не квест, просто заметка")
loaded, warnings := LoadWayfarerQuestsFromDir(dir, nil)
if len(loaded) != 0 || len(warnings) != 0 {
t.Errorf("файлы не .yaml/.yml должны молча игнорироваться, получено loaded=%v warnings=%v", loaded, warnings)
}
}
// TestLoadedQuestIsFullyPlayable проверяет, что загруженный из YAML
// квест не просто структурно корректен, а реально проходим через
// обычный игровой цикл (StartQuest -> ChooseQuestOption -> ...).
func TestLoadedQuestIsFullyPlayable(t *testing.T) {
dir := t.TempDir()
writeYAMLFile(t, dir, "playable.yaml", `
key: quest.loader_test_playable
title: {ru: "Играбельный квест", en: "Playable Quest"}
brief: {ru: "x", en: "x"}
steps:
- text: {ru: "Шаг 0", en: "Step 0"}
choices:
- label: {ru: "Вариант A", en: "Option A"}
next_step: 1
- text: {ru: "Шаг 1", en: "Step 1"}
choices:
- label: {ru: "Завершить успешно", en: "Finish successfully"}
terminal: true
success: true
result: {ru: "Готово", en: "Done"}
credits_delta: 250
`)
loaded, warnings := LoadWayfarerQuestsFromDir(dir, nil)
if len(warnings) != 0 {
t.Fatalf("не ожидалось предупреждений: %v", warnings)
}
if len(loaded) != 1 {
t.Fatalf("ожидался 1 квест, получено %d", len(loaded))
}
oldDefs := WayfarerQuestDefs
WayfarerQuestDefs = append(append([]WayfarerQuestDef{}, oldDefs...), loaded...)
defer func() { WayfarerQuestDefs = oldDefs }()
s := NewWayfarerShip(WayfarerSystemID{})
creditsBefore := s.Credits
if err := s.StartQuest("quest.loader_test_playable"); err != nil {
t.Fatalf("неожиданная ошибка запуска квеста: %v", err)
}
if _, err := s.ChooseQuestOption(0); err != nil {
t.Fatalf("неожиданная ошибка выбора на шаге 0: %v", err)
}
step, ok := s.CurrentQuestStep()
if !ok || T(step.TextKey) != "Шаг 1" {
t.Fatalf("ожидался переход на шаг 1, получено: %+v (ok=%v)", step, ok)
}
if _, err := s.ChooseQuestOption(0); err != nil {
t.Fatalf("неожиданная ошибка завершающего выбора: %v", err)
}
if s.ActiveQuest != nil {
t.Errorf("квест должен был завершиться")
}
if !s.CompletedQuests["quest.loader_test_playable"] {
t.Errorf("квест должен был отметиться как завершённый успешно")
}
if s.Credits != creditsBefore+250 {
t.Errorf("награда за квест не начислилась: было %v, стало %v", creditsBefore, s.Credits)
}
}