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("отказ хранилища потерян") } }