package main import "testing" // TestWayfarerQuestDefsStructuralIntegrity проверяет каждый из // реальных (не тестовых) квестов на внутреннюю согласованность: // все NextStep указывают на существующие шаги, все CargoIdx либо -1 // (не используется), либо валидный индекс в WayfarerCommodities, и // у каждого квеста есть хотя бы один способ дойти до Terminal-выбора // от стартового шага. func TestWayfarerQuestDefsStructuralIntegrity(t *testing.T) { if len(WayfarerQuestDefs) != 8 { t.Fatalf("ожидалось 8 квестов, получено %d", len(WayfarerQuestDefs)) } seenKeys := map[string]bool{} for _, q := range WayfarerQuestDefs { if q.Key == "" || q.TitleKey == "" || q.BriefKey == "" { t.Errorf("квест с пустыми обязательными ключами: %+v", q) } if seenKeys[q.Key] { t.Errorf("дублирующийся ключ квеста: %s", q.Key) } seenKeys[q.Key] = true if len(q.Steps) == 0 { t.Errorf("квест %s: должен иметь хотя бы один шаг", q.Key) } reachesTerminal := false for stepIdx, step := range q.Steps { if step.TextKey == "" { t.Errorf("квест %s, шаг %d: пустой TextKey", q.Key, stepIdx) } if len(step.Choices) == 0 { t.Errorf("квест %s, шаг %d: нет ни одного варианта выбора", q.Key, stepIdx) } for choiceIdx, c := range step.Choices { if c.LabelKey == "" { t.Errorf("квест %s, шаг %d, выбор %d: пустой LabelKey", q.Key, stepIdx, choiceIdx) } if c.CargoIdx != -1 && (c.CargoIdx < 0 || c.CargoIdx >= len(WayfarerCommodities)) { t.Errorf("квест %s, шаг %d, выбор %d: недопустимый CargoIdx %d", q.Key, stepIdx, choiceIdx, c.CargoIdx) } if c.Terminal { if c.ResultKey == "" { t.Errorf("квест %s, шаг %d, выбор %d: terminal-выбор без ResultKey", q.Key, stepIdx, choiceIdx) } reachesTerminal = true } else { if c.NextStep < 0 || c.NextStep >= len(q.Steps) { t.Errorf("квест %s, шаг %d, выбор %d: NextStep %d вне диапазона шагов (0..%d)", q.Key, stepIdx, choiceIdx, c.NextStep, len(q.Steps)-1) } } } } if !reachesTerminal { t.Errorf("квест %s: ни один выбор во всём квесте не завершает его (нет ни одного Terminal)", q.Key) } } } // TestWayfarerQuestDefsAllTranslated проверяет, что для каждого // ключа перевода, используемого в квестах, есть запись в таблице // translations на обоих языках (это ловит опечатки в ключах и // забытые переводы). func TestWayfarerQuestDefsAllTranslated(t *testing.T) { checkKey := func(key string) { t.Helper() if key == "" { return } entry, ok := translations[key] if !ok { t.Errorf("отсутствует перевод для ключа %q", key) return } if entry.ru == "" || entry.en == "" { t.Errorf("перевод для ключа %q неполный: ru=%q en=%q", key, entry.ru, entry.en) } } for _, q := range WayfarerQuestDefs { checkKey(q.TitleKey) checkKey(q.BriefKey) for _, step := range q.Steps { checkKey(step.TextKey) for _, c := range step.Choices { checkKey(c.LabelKey) checkKey(c.ResultKey) } } } } // TestWayfarerQuestDefsPlayableToCompletion проверяет, что каждый // квест реально проходим от начала до какого-либо Terminal-выбора, // просто выбирая первый не-terminal вариант на каждом шаге (а если // таких нет — берёт первый попавшийся) — ловит зависания/бесконечные // циклы в ручной разметке шагов. func TestWayfarerQuestDefsPlayableToCompletion(t *testing.T) { for _, q := range WayfarerQuestDefs { s := NewWayfarerShip(WayfarerSystemID{}) if err := s.StartQuest(q.Key); err != nil { t.Fatalf("квест %s: не удалось начать: %v", q.Key, err) } const maxSteps = 20 for i := 0; i < maxSteps && s.ActiveQuest != nil; i++ { step, _ := s.CurrentQuestStep() choiceIdx := 0 for idx, c := range step.Choices { if !c.Terminal { choiceIdx = idx break } } if _, err := s.ChooseQuestOption(choiceIdx); err != nil { t.Fatalf("квест %s: неожиданная ошибка выбора на шаге: %v", q.Key, err) } } if s.ActiveQuest != nil { t.Errorf("квест %s: не завершился за %d шагов (возможен бесконечный цикл)", q.Key, maxSteps) } } }