- адреса приложения переехали в своё пространство `/app/`, опрос готовности убран целиком: рубеж и причину остановки владелец узнаёт карточкой записи, текст — отдельным адресом названного вида - заведена единая точка отображения доменной ошибки и слой, приводящий к той же форме отказы библиотеки: тело несёт машиночитаемый код рядом с сообщением - у записи появились имя файла отправителя, длительность и размер своими колонками, а у ленты владельца — свой индекс: без него страница сканировала весь архив сервиса
90 lines
3.7 KiB
Go
90 lines
3.7 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) List(contract.RecordQuery) (*contract.RecordPage, error) {
|
|
return &contract.RecordPage{}, nil
|
|
}
|
|
|
|
func (r *stubRecordRepo) ResolveTopicNames(string, []string) (map[string]string, error) {
|
|
return map[string]string{}, nil
|
|
}
|
|
|
|
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("отказ хранилища потерян")
|
|
}
|
|
}
|