This commit is contained in:
2026-10-28 23:58:05 +07:00
parent 1676ccdffb
commit 7874dae0f4
3 changed files with 55 additions and 10 deletions
@@ -9,14 +9,14 @@ import (
) )
func TestSafeFailure(t *testing.T) { func TestSafeFailure(t *testing.T) {
validation := &game_package_service.ValidationError{Reason: "некорректная команда"} validation := &game_package_service.ValidationError{Reason: "шаг экранов №2 — текст экрана не должен быть пустым"}
for _, tc := range []struct { for _, tc := range []struct {
name string name string
err error err error
want string want string
}{ }{
{"validation", validation, "Не удалось подготовить автономный пакет: некорректная команда"}, {"validation", validation, "Не удалось подготовить автономный пакет: шаг экранов №2 — текст экрана не должен быть пустым"},
{"wrapped validation", fmt.Errorf("private path: %w", validation), "Не удалось подготовить автономный пакет: некорректная команда"}, {"wrapped validation", fmt.Errorf("private path: %w", validation), "Не удалось подготовить автономный пакет: шаг экранов №2 — текст экрана не должен быть пустым"},
{"invalid package", errors.Join(game_package_service.ErrInvalidPackage, errors.New("private")), "Данные игры не подходят для автономного пакета."}, {"invalid package", errors.Join(game_package_service.ErrInvalidPackage, errors.New("private")), "Данные игры не подходят для автономного пакета."},
{"technical", errors.New("private password"), "Не удалось подготовить пакет. Запустите экспорт повторно."}, {"technical", errors.New("private password"), "Не удалось подготовить пакет. Запустите экспорт повторно."},
{"media", game_package_service.ErrMediaUnavailable, "Не удалось получить медиафайлы игры. Проверьте их доступность и повторите экспорт."}, {"media", game_package_service.ErrMediaUnavailable, "Не удалось получить медиафайлы игры. Проверьте их доступность и повторите экспорт."},
@@ -882,3 +882,33 @@ func TestPackageValidatesScreenSteps(t *testing.T) {
}) })
} }
} }
func TestScreenStepFailureExplainsReason(t *testing.T) {
for _, tc := range []struct {
name string
step game_screen_plans_repo.Step
reason string
}{
{"mode", game_screen_plans_repo.Step{Mode: 11}, "неподдерживаемый режим экрана"},
{"timer type", game_screen_plans_repo.Step{Mode: 7, TimerType: 3}, "неизвестный тип таймера"},
{"duration", game_screen_plans_repo.Step{Mode: 7}, "длительность таймера должна быть от 1 до 86400 секунд"},
{"timer label", game_screen_plans_repo.Step{Mode: 7, TimerDurationSeconds: 1, TimerLabel: strings.Repeat("a", 201)}, "подпись таймера превышает 200 байт"},
{"empty text", game_screen_plans_repo.Step{Mode: 8, Text: " "}, "текст экрана не должен быть пустым"},
{"long text", game_screen_plans_repo.Step{Mode: 8, Text: strings.Repeat("a", 10001)}, "текст экрана превышает 10000 байт"},
{"QR URL", game_screen_plans_repo.Step{Mode: 9, QRURL: "javascript:private"}, "ссылка QR-кода должна использовать HTTP/HTTPS, содержать адрес сайта, не содержать логин, пароль и крайние пробелы и занимать не более 2048 байт"},
{"QR caption", game_screen_plans_repo.Step{Mode: 9, QRURL: "https://example.org", Text: strings.Repeat("a", 10001)}, "подпись QR-кода превышает 10000 байт"},
} {
t.Run(tc.name, func(t *testing.T) {
snapshot, _ := fixture()
snapshot.Data.ScreenPlan.Steps = []game_screen_plans_repo.Step{{Mode: 1}, tc.step}
err := validateData(&snapshot.Data)
var validation *ValidationError
if !errors.Is(err, ErrInvalidPackage) || !errors.As(err, &validation) {
t.Fatalf("expected validation error, got %v", err)
}
if want := "шаг экранов №2 — " + tc.reason; validation.Reason != want {
t.Fatalf("got %q, want %q", validation.Reason, want)
}
})
}
}
@@ -156,13 +156,28 @@ func validateData(data *Data) error {
(len(plan.Steps) > 0 && plan.CurrentStep >= len(plan.Steps)) { (len(plan.Steps) > 0 && plan.CurrentStep >= len(plan.Steps)) {
return invalid("некорректный план экранов") return invalid("некорректный план экранов")
} }
for _, step := range plan.Steps { for i, step := range plan.Steps {
if !screen_service.ValidMode(step.Mode) || var reason string
!screen_service.ValidTimerType(step.TimerType) || switch {
(step.Mode == 9 && !screen_service.ValidQR(step.QRURL, step.Text)) || case !screen_service.ValidMode(step.Mode):
(step.Mode == 7 && (step.TimerDurationSeconds <= 0 || step.TimerDurationSeconds > 86400 || len(step.TimerLabel) > 200)) || reason = "неподдерживаемый режим экрана"
(step.Mode == 8 && (strings.TrimSpace(step.Text) == "" || len(step.Text) > 10000)) { case !screen_service.ValidTimerType(step.TimerType):
return invalid("некорректный шаг плана экранов") reason = "неизвестный тип таймера"
case step.Mode == 7 && (step.TimerDurationSeconds <= 0 || step.TimerDurationSeconds > 86400):
reason = "длительность таймера должна быть от 1 до 86400 секунд"
case step.Mode == 7 && len(step.TimerLabel) > 200:
reason = "подпись таймера превышает 200 байт"
case step.Mode == 8 && strings.TrimSpace(step.Text) == "":
reason = "текст экрана не должен быть пустым"
case step.Mode == 8 && len(step.Text) > 10000:
reason = "текст экрана превышает 10000 байт"
case step.Mode == 9 && !screen_service.ValidQR(step.QRURL, ""):
reason = "ссылка QR-кода должна использовать HTTP/HTTPS, содержать адрес сайта, не содержать логин, пароль и крайние пробелы и занимать не более 2048 байт"
case step.Mode == 9 && len(step.Text) > 10000:
reason = "подпись QR-кода превышает 10000 байт"
}
if reason != "" {
return invalid(fmt.Sprintf("шаг экранов №%d — %s", i+1, reason))
} }
} }
} }