Files
transcriber/internal/service/find_job_test.go
T
av 1d243ad2f6 Время читается единой точкой, а конец потока узнаётся не по тексту
- заведён internal/clock: Now даёт метку в UTC, Start — начало измерения
  длительности с монотонными часами; девять мест рабочего кода и запрос захвата
  переведены на него, долг «время time.Now() по месту» закрыт
- клиент SpeechKit узнавал конец потока сравнением err.Error() == "EOF": отказ
  с тем же текстом вернул бы усечённую расшифровку как готовую, теперь
  errors.Is(err, io.EOF)
- починены находки новых линтеров: две опечатки, два slog.DiscardHandler,
  четыре неэкранированные подстановки в docker/entrypoint.sh
2026-08-13 09:01:36 +03:00

78 lines
3.5 KiB
Go

package service
import (
"errors"
"fmt"
"log/slog"
"testing"
"time"
"git.vakhrushev.me/av/transcriber/internal/contract"
"git.vakhrushev.me/av/transcriber/internal/entity"
)
// Путь признака «работы нет» состоит из двух звеньев: репозиторий рождает
// «подходящей задачи не нашлось», сервис переводит это в «работы нет», и уже
// его читает воркер. Проверки воркера подменяют работу целиком и второе звено
// не видят — без этого файла правку в сервисе принимал бы только линтер, а он
// судит форму записи, а не то, узнаётся ли признак на самом деле.
// stubJobRepo отдаёт заданную ошибку на запрос задачи. Прочих методов запроса
// задачи проверки этого файла не зовут.
type stubJobRepo struct {
err error
}
func (r *stubJobRepo) Create(*entity.TranscribeJob) error { return nil }
func (r *stubJobRepo) Save(*entity.TranscribeJob, string) error { return nil }
func (r *stubJobRepo) GetByID(string) (*entity.TranscribeJob, error) {
return nil, errors.New("не зовётся этими проверками")
}
func (r *stubJobRepo) FindAndAcquire(string, string, time.Time) (*entity.TranscribeJob, error) {
return nil, r.err
}
func serviceWithRepo(repo contract.TranscriptJobRepository) *TranscribeService {
logger := slog.New(slog.DiscardHandler)
return NewTranscribeService(repo, nil, nil, nil, nil, nil, logger)
}
// Репозиторий вправе добавить своему отказу пояснение — соседние ветки того же
// метода уже оборачивают ошибки `%w` подряд. Пока признак узнавался приведением
// типа, первая такая обёртка превратила бы пустой прогон в отказ: воркер начал
// бы писать в журнал раз в секунду на каждом из трёх воркеров.
func TestFindJobTranslatesWrappedNotFoundToNoop(t *testing.T) {
svc := serviceWithRepo(&stubJobRepo{
err: fmt.Errorf("find and acquire job: %w",
&contract.JobNotFoundError{State: "created", Message: "appropriate job not found"}),
})
_, _, err := svc.findJob("created", time.Minute)
var noop *contract.NoopJobError
if !errors.As(err, &noop) {
t.Fatalf("обёрнутое «задачи нет» не переведено в пустой прогон: получено %v", err)
}
if noop.State != "created" {
t.Errorf("состояние потеряно при переводе: %q", noop.State)
}
}
// Оборотная сторона: настоящий отказ хранилища пустым прогоном считаться не
// должен, иначе задача молча крутилась бы в цикле без единой записи.
func TestFindJobKeepsRealFailure(t *testing.T) {
svc := serviceWithRepo(&stubJobRepo{err: errors.New("database is gone")})
_, _, err := svc.findJob("created", time.Minute)
var noop *contract.NoopJobError
if errors.As(err, &noop) {
t.Fatalf("отказ хранилища зачтён пустым прогоном: %v", err)
}
if err == nil {
t.Fatal("отказ хранилища потерян")
}
}