291 lines
11 KiB
Go
291 lines
11 KiB
Go
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)
|
||
}
|
||
}
|