внутренняя модель перестроена вокруг аудиозаписи
- audiorecords вместо transcribe_jobs: приложения (texts, structures, recognitions, record_events, topics) живут своими коллекциями, ссылки на исходник и на приведённую копию перестали переставляться - рубеж называет достигнутое, отказ стал признаком остановки с причиной, а сторожей стало двое: число отказов и время в рубеже - воркеры потеряли специализацию, их число задаётся [pipeline] workers, шаг выбирается по рубежу, а захват отдаёт идентификатор и признак захвата
This commit is contained in:
@@ -5,67 +5,71 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.vakhrushev.me/av/transcriber/internal/contract"
|
||||
"git.vakhrushev.me/av/transcriber/internal/entity"
|
||||
)
|
||||
|
||||
// Путь признака «работы нет» состоит из двух звеньев: репозиторий рождает
|
||||
// «подходящей задачи не нашлось», сервис переводит это в «работы нет», и уже
|
||||
// его читает воркер. Проверки воркера подменяют работу целиком и второе звено
|
||||
// не видят — без этого файла правку в сервисе принимал бы только линтер, а он
|
||||
// судит форму записи, а не то, узнаётся ли признак на самом деле.
|
||||
// «пригодной записи не нашлось», сервис переводит это в «работы нет», и уже его
|
||||
// читает воркер. Проверки воркера подменяют работу целиком и второе звено не
|
||||
// видят — без этого файла правку в сервисе принимал бы только линтер, а он судит
|
||||
// форму записи, а не то, узнаётся ли признак на самом деле.
|
||||
|
||||
// stubJobRepo отдаёт заданную ошибку на запрос задачи. Прочих методов запроса
|
||||
// задачи проверки этого файла не зовут.
|
||||
type stubJobRepo struct {
|
||||
// stubRecordRepo отдаёт заданную ошибку на запрос записи. Прочих методов
|
||||
// проверки этого файла не зовут.
|
||||
type stubRecordRepo struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *stubJobRepo) Create(*entity.TranscribeJob) error { return nil }
|
||||
func (r *stubJobRepo) Save(*entity.TranscribeJob, string) error { return nil }
|
||||
func (r *stubRecordRepo) Create(*entity.AudioRecord) error { return nil }
|
||||
func (r *stubRecordRepo) Save(*entity.AudioRecord, string) error { return nil }
|
||||
|
||||
func (r *stubJobRepo) GetByID(string, string) (*entity.TranscribeJob, error) {
|
||||
func (r *stubRecordRepo) GetByID(string, string) (*entity.AudioRecord, error) {
|
||||
return nil, errors.New("не зовётся этими проверками")
|
||||
}
|
||||
|
||||
func (r *stubJobRepo) FindAndAcquire(string, string, time.Time) (*entity.TranscribeJob, error) {
|
||||
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.TranscriptJobRepository) *TranscribeService {
|
||||
logger := slog.New(slog.DiscardHandler)
|
||||
return NewTranscribeService(repo, nil, nil, nil, nil, nil, logger)
|
||||
func serviceWithRepo(repo contract.AudioRecordRepository) *TranscribeService {
|
||||
return NewTranscribeService(
|
||||
Repositories{Records: repo},
|
||||
nil, nil, nil, nil,
|
||||
entity.StuckLimits{},
|
||||
slog.New(slog.DiscardHandler),
|
||||
)
|
||||
}
|
||||
|
||||
// Репозиторий вправе добавить своему отказу пояснение — соседние ветки того же
|
||||
// метода уже оборачивают ошибки `%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"}),
|
||||
// бы писать в журнал раз в секунду на каждом воркере пула.
|
||||
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.findJob("created", time.Minute)
|
||||
_, _, err := svc.acquire()
|
||||
|
||||
var noop *contract.NoopJobError
|
||||
if !errors.As(err, &noop) {
|
||||
t.Fatalf("обёрнутое «задачи нет» не переведено в пустой прогон: получено %v", err)
|
||||
}
|
||||
if noop.State != "created" {
|
||||
t.Errorf("состояние потеряно при переводе: %q", noop.State)
|
||||
t.Fatalf("обёрнутое «работы нет» не переведено в пустой прогон: получено %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Оборотная сторона: настоящий отказ хранилища пустым прогоном считаться не
|
||||
// должен, иначе задача молча крутилась бы в цикле без единой записи.
|
||||
func TestFindJobKeepsRealFailure(t *testing.T) {
|
||||
svc := serviceWithRepo(&stubJobRepo{err: errors.New("database is gone")})
|
||||
// должен, иначе запись молча крутилась бы в цикле без единой записи в журнале.
|
||||
func TestAcquireKeepsRealFailure(t *testing.T) {
|
||||
svc := serviceWithRepo(&stubRecordRepo{err: errors.New("database is gone")})
|
||||
|
||||
_, _, err := svc.findJob("created", time.Minute)
|
||||
_, _, err := svc.acquire()
|
||||
|
||||
var noop *contract.NoopJobError
|
||||
if errors.As(err, &noop) {
|
||||
|
||||
Reference in New Issue
Block a user