внутренняя модель перестроена вокруг аудиозаписи
- audiorecords вместо transcribe_jobs: приложения (texts, structures, recognitions, record_events, topics) живут своими коллекциями, ссылки на исходник и на приведённую копию перестали переставляться - рубеж называет достигнутое, отказ стал признаком остановки с причиной, а сторожей стало двое: число отказов и время в рубеже - воркеры потеряли специализацию, их число задаётся [pipeline] workers, шаг выбирается по рубежу, а захват отдаёт идентификатор и признак захвата
This commit is contained in:
@@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -16,7 +15,7 @@ import (
|
||||
// показывать, а не кому её считать. Сужение остановило бы расшифровку записей
|
||||
// бота вовсе, а записи остальных поставило бы в зависимость от того, кто первым
|
||||
// завёл учётную запись.
|
||||
func TestWorkerTakesJobsOfEveryOwner(t *testing.T) {
|
||||
func TestWorkerTakesRecordsOfEveryOwner(t *testing.T) {
|
||||
env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})
|
||||
|
||||
first, err := env.service.CreateJobFromApi(t.Context(),
|
||||
@@ -28,42 +27,48 @@ func TestWorkerTakesJobsOfEveryOwner(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Третья пришла ботом, и владельца у неё нет вовсе.
|
||||
third := newTelegramJob(t, env)
|
||||
third := newTelegramRecord(t, env)
|
||||
|
||||
// Срок протухания в прошлом: захваченная задача остаётся за держателем, и
|
||||
// следующий вызов берёт следующую, а не ту же самую.
|
||||
// Захваченная запись остаётся за держателем, и следующий вызов берёт
|
||||
// следующую, а не ту же самую.
|
||||
taken := map[string]bool{}
|
||||
for _, holder := range []string{"one", "two", "three"} {
|
||||
job, err := env.jobRepo.FindAndAcquire(entity.StateCreated, holder, time.Now().Add(-time.Hour))
|
||||
require.NoError(t, err, "воркер берёт задачи подряд, владельцем не сужаясь")
|
||||
taken[job.Id] = true
|
||||
for range 3 {
|
||||
acquired, err := env.recordRepo.FindAndAcquire(entity.WorkingStages())
|
||||
require.NoError(t, err, "воркер берёт записи подряд, владельцем не сужаясь")
|
||||
taken[acquired.ID] = true
|
||||
}
|
||||
|
||||
assert.True(t, taken[first.Id], "задача первого владельца досталась воркеру")
|
||||
assert.True(t, taken[first.Id], "запись первого владельца досталась воркеру")
|
||||
assert.True(t, taken[second.Id], "и второго")
|
||||
assert.True(t, taken[third.Id], "и задача без владельца")
|
||||
assert.True(t, taken[third.Id], "и запись без владельца")
|
||||
|
||||
_, err = env.jobRepo.FindAndAcquire(entity.StateCreated, "next", time.Now().Add(-time.Hour))
|
||||
_, err = env.recordRepo.FindAndAcquire(entity.WorkingStages())
|
||||
var missing *contract.JobNotFoundError
|
||||
assert.ErrorAs(t, err, &missing, "больше в этом состоянии никого")
|
||||
assert.ErrorAs(t, err, &missing, "больше пригодных к работе записей нет")
|
||||
}
|
||||
|
||||
// Захват читает владельца: снимок задачи, в котором он всегда пуст, был бы
|
||||
// ловушкой для первого же шага, начавшего сохранять задачу целиком, — и сегодня
|
||||
// уже ломал бы файл, который шаг заводит.
|
||||
func TestAcquireCarriesOwner(t *testing.T) {
|
||||
// Захват отдаёт идентификатор и признак своего захвата — не перечень колонок.
|
||||
// Колонки шаг читает сам: иначе всякая новая колонка записи попадала бы под
|
||||
// инвариант проекта о колонках очереди.
|
||||
func TestAcquireReturnsIdentifierAndHolder(t *testing.T) {
|
||||
env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})
|
||||
|
||||
owner := newOwner(t, env.app)
|
||||
job, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", owner)
|
||||
record, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", owner)
|
||||
require.NoError(t, err)
|
||||
|
||||
acquired, err := env.jobRepo.FindAndAcquire(entity.StateCreated, "holder", time.Now().Add(-time.Hour))
|
||||
acquired, err := env.recordRepo.FindAndAcquire(entity.WorkingStages())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, job.Id, acquired.Id)
|
||||
|
||||
require.NotNil(t, acquired.OwnerID, "владелец приехал из захвата")
|
||||
assert.Equal(t, owner, *acquired.OwnerID)
|
||||
assert.Equal(t, record.Id, acquired.ID, "захват назвал запись")
|
||||
assert.NotEmpty(t, acquired.Holder, "и признак своего захвата")
|
||||
|
||||
// Колонки читаются отдельным чтением, и владелец среди них.
|
||||
read := readRecord(t, env, record.Id)
|
||||
require.NotNil(t, read.OwnerID)
|
||||
assert.Equal(t, owner, *read.OwnerID)
|
||||
assert.Equal(t, acquired.Holder, *read.AcquisitionID, "признак захвата записан в саму запись")
|
||||
require.NotNil(t, read.AcquireExpiresAt, "срок протухания приехал с рубежом")
|
||||
}
|
||||
|
||||
// Шаг конвейера владельца не затирает: сохранение кладёт только то, чем
|
||||
@@ -72,34 +77,54 @@ func TestPipelineStepKeepsOwner(t *testing.T) {
|
||||
env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})
|
||||
|
||||
owner := newOwner(t, env.app)
|
||||
job, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", owner)
|
||||
record, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", owner)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Отказ конвертации — приговор записи: шаг переводит задачу в `failed` и
|
||||
// сохраняет её. Это сохранение владельца тронуть не должно.
|
||||
require.NoError(t, env.service.FindAndRunConversionJob(t.Context()))
|
||||
// Отказ приведения — приговор записи: шаг останавливает её и сохраняет. Это
|
||||
// сохранение владельца тронуть не должно.
|
||||
require.NoError(t, env.service.RunStep(t.Context()))
|
||||
|
||||
after, err := readJob(env.app, job.Id)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, entity.StateFailed, after.State, "шаг записал свой приговор")
|
||||
after := readRecord(t, env, record.Id)
|
||||
require.True(t, after.IsHalted(), "шаг записал свой приговор")
|
||||
require.NotNil(t, after.OwnerID, "владелец пережил шаг")
|
||||
assert.Equal(t, owner, *after.OwnerID)
|
||||
}
|
||||
|
||||
// Приём из веба без владельца задачи не заводит. Обязательность держит здесь
|
||||
// Приём из веба без владельца записи не заводит. Обязательность держит здесь
|
||||
// код, а не схема: колонка допускает пустое значение ради записей бота.
|
||||
func TestCreateJobFromApiRequiresOwner(t *testing.T) {
|
||||
env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})
|
||||
|
||||
_, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", "")
|
||||
|
||||
require.ErrorIs(t, err, contract.ErrOwnerRequired)
|
||||
}
|
||||
|
||||
records, err := env.app.FindAllRecords("transcribe_jobs")
|
||||
// Чужая запись, ничья и несуществующая отвечают одним и тем же: по разнице
|
||||
// ответов иначе перебирается список заведённых записей.
|
||||
func TestGetByIDHidesForeignRecords(t *testing.T) {
|
||||
env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})
|
||||
|
||||
owner := newOwner(t, env.app)
|
||||
stranger := newOwner(t, env.app)
|
||||
|
||||
record, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", owner)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, records, "задачи не заведено")
|
||||
|
||||
// Запись без владельца — принятая ботом.
|
||||
orphan := newTelegramRecord(t, env)
|
||||
|
||||
var missing *contract.JobNotFoundError
|
||||
|
||||
files, err := env.app.FindAllRecords("files")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, files, "и файла тоже: отказ наступает раньше укладки")
|
||||
_, err = env.recordRepo.GetByID(record.Id, stranger)
|
||||
require.ErrorAs(t, err, &missing, "чужая запись неотличима от несуществующей")
|
||||
|
||||
_, err = env.recordRepo.GetByID(orphan.Id, stranger)
|
||||
require.ErrorAs(t, err, &missing, "ничья запись не достаётся никому")
|
||||
|
||||
_, err = env.recordRepo.GetByID(record.Id, "")
|
||||
require.ErrorAs(t, err, &missing, "пустой владелец не совпадает ни с чем")
|
||||
|
||||
own, err := env.recordRepo.GetByID(record.Id, owner)
|
||||
require.NoError(t, err, "своя запись отдаётся")
|
||||
assert.Equal(t, record.Id, own.Id)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user