- признаки «работы нет» и «задача не найдена» узнаются по смыслу, а не приведением типа: обёртка `%w` на пути больше не превращает пустой прогон воркера в отказ раз в секунду - отказ закрытия соединения с распознавателем доходит до вызывающего (`errors.Join`) либо до журнала; у `errcheck` включён `check-blank`, иначе критерий принимал реализацию, выбрасывающую отказ в пустоту - заведены первые тесты пакета worker и capability `pipeline`; долг из четырёх замечаний линтера закрыт, гейт зелёный целиком
79 lines
3.5 KiB
Go
79 lines
3.5 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"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) 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.NewTextHandler(io.Discard, nil))
|
|
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("отказ хранилища потерян")
|
|
}
|
|
}
|