Files
transcriber/internal/service/find_job_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

82 lines
3.5 KiB
Go

package service
import (
"errors"
"fmt"
"log/slog"
"testing"
"git.vakhrushev.me/av/transcriber/internal/contract"
"git.vakhrushev.me/av/transcriber/internal/entity"
)
// Путь признака «работы нет» состоит из двух звеньев: репозиторий рождает
// «пригодной записи не нашлось», сервис переводит это в «работы нет», и уже его
// читает воркер. Проверки воркера подменяют работу целиком и второе звено не
// видят — без этого файла правку в сервисе принимал бы только линтер, а он судит
// форму записи, а не то, узнаётся ли признак на самом деле.
// stubRecordRepo отдаёт заданную ошибку на запрос записи. Прочих методов
// проверки этого файла не зовут.
type stubRecordRepo struct {
err error
}
func (r *stubRecordRepo) Create(*entity.AudioRecord) error { return nil }
func (r *stubRecordRepo) Save(*entity.AudioRecord, string) error { return nil }
func (r *stubRecordRepo) GetByID(string, string) (*entity.AudioRecord, error) {
return nil, errors.New("не зовётся этими проверками")
}
func (r *stubRecordRepo) Get(string) (*entity.AudioRecord, error) {
return nil, errors.New("не зовётся этими проверками")
}
func (r *stubRecordRepo) FindAndAcquire([]entity.Stage) (*contract.AcquiredRecord, error) {
return nil, r.err
}
func serviceWithRepo(repo contract.AudioRecordRepository) *TranscribeService {
return NewTranscribeService(
Repositories{Records: repo},
nil, nil, nil,
entity.StuckLimits{},
slog.New(slog.DiscardHandler),
)
}
// Репозиторий вправе добавить своему отказу пояснение — соседние ветки того же
// метода уже оборачивают ошибки `%w` подряд. Пока признак узнавался приведением
// типа, первая такая обёртка превратила бы пустой прогон в отказ: воркер начал
// бы писать в журнал раз в секунду на каждом воркере пула.
func TestAcquireTranslatesWrappedNotFoundToNoop(t *testing.T) {
svc := serviceWithRepo(&stubRecordRepo{
err: fmt.Errorf("find and acquire record: %w",
&contract.JobNotFoundError{Message: "no record is ready for work"}),
})
_, _, err := svc.acquire()
var noop *contract.NoopJobError
if !errors.As(err, &noop) {
t.Fatalf("обёрнутое «работы нет» не переведено в пустой прогон: получено %v", err)
}
}
// Оборотная сторона: настоящий отказ хранилища пустым прогоном считаться не
// должен, иначе запись молча крутилась бы в цикле без единой записи в журнале.
func TestAcquireKeepsRealFailure(t *testing.T) {
svc := serviceWithRepo(&stubRecordRepo{err: errors.New("database is gone")})
_, _, err := svc.acquire()
var noop *contract.NoopJobError
if errors.As(err, &noop) {
t.Fatalf("отказ хранилища зачтён пустым прогоном: %v", err)
}
if err == nil {
t.Fatal("отказ хранилища потерян")
}
}