Files
transcriber/internal/service/shutdown_test.go
T
av 8f7c3a057a удалён вход Telegram, владелец записи стал обязателен в схеме
- убраны клиент бота, транспорт обновлений, отправитель сообщений, сборка
  входа при старте, секция настроек и зависимость go-telegram-bot-api; из
  конвейера ушла доставка ответа отправителю — исход виден опросом готовности.
  Колонки адресата и значение источника остались в схеме: применённые шаги не
  переписываются
- шаг 202608140003 запрещает пустого владельца у аудиозаписи и у файла;
  существующие строки он не проверяет, и это принято сознательно — искать их
  надо запросом до выкладки
- ревью нашло два пред-существующих дефекта, оба закрыты: пустой второй ответ
  распознавателя стирал сохранённую расшифровку, а пустая расшифровка перестала
  быть заметной вместе с убранной доставкой. Попутно поднят golang.org/x/image
  до v0.45.0 — красный шаг vulns, воспроизводился и на чистом master
2026-08-15 07:24:35 +03:00

78 lines
3.4 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 service
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"git.vakhrushev.me/av/transcriber/internal/contract"
"git.vakhrushev.me/av/transcriber/internal/entity"
)
// killedConverter ведёт себя как настоящий `ffmpeg`, убитый по контексту:
// дожидается отмены и отдаёт отказ, который **не** несёт `context.Canceled`.
// Это не упрощение, а суть проверки: `exec` отдаёт `*exec.ExitError` с текстом
// «signal: killed», и отличить остановку от негодной записи по самой ошибке
// нельзя — только по контексту шага.
type killedConverter struct {
cancel func()
}
func (c *killedConverter) Convert(ctx context.Context, _, _ string) error {
c.cancel()
<-ctx.Done()
return errors.New("ffmpeg conversion failed: signal: killed")
}
// Критерий приёмки 9. Остановка сервиса посреди приведения не выносит записи
// приговора и **не тратит отказа**: запись не виновата в том, что нас
// перезапустили, и несколько выкладок подряд иначе останавливают здоровую
// многочасовую запись с приговором «попытки исчерпаны».
func TestShutdownDuringConversionKeepsRecordRetryable(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
converter := &killedConverter{cancel: cancel}
env := newPipelineEnv(t, &okMetaViewer{}, converter)
record := newRecord(t, env)
err := env.service.RunStep(ctx)
require.Error(t, err, "шаг обязан сообщить об обрыве наверх")
require.ErrorIs(t, err, context.Canceled, "обрыв узнаётся по смыслу, а не по тексту")
after := readRecord(t, env, record.Id)
assert.Equal(t, entity.StateUploaded, after.State, "запись осталась на повтор")
assert.False(t, after.IsHalted(), "приговора не выносили")
assert.Nil(t, after.AcquisitionID, "захват снят: запись возьмёт следующий прогон")
assert.Equal(t, 0, after.Attempts, "остановка отказа не тратит")
assert.Nil(t, after.ErrorText)
}
// Запись, которую шаг не успел взять, потому что нас уже остановили, остаётся
// нетронутой: захват не случился, отказ не потрачен.
func TestShutdownBeforeStepLeavesRecordUntouched(t *testing.T) {
env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})
record := newRecord(t, env)
ctx, cancel := context.WithCancel(t.Context())
cancel()
err := env.service.RunStep(ctx)
// Исход «шаг не сделал ничего» — это `NoopJobError`: воркер не пишет о нём
// владельцу и не считает его в метрику.
require.Error(t, err)
var noop *contract.NoopJobError
require.ErrorAs(t, err, &noop)
after := readRecord(t, env, record.Id)
assert.Equal(t, entity.StateUploaded, after.State)
assert.Equal(t, 0, after.Attempts, "захвата не было — отказу взяться неоткуда")
assert.Nil(t, after.AcquisitionID)
}