- убраны клиент бота, транспорт обновлений, отправитель сообщений, сборка входа при старте, секция настроек и зависимость go-telegram-bot-api; из конвейера ушла доставка ответа отправителю — исход виден опросом готовности. Колонки адресата и значение источника остались в схеме: применённые шаги не переписываются - шаг 202608140003 запрещает пустого владельца у аудиозаписи и у файла; существующие строки он не проверяет, и это принято сознательно — искать их надо запросом до выкладки - ревью нашло два пред-существующих дефекта, оба закрыты: пустой второй ответ распознавателя стирал сохранённую расшифровку, а пустая расшифровка перестала быть заметной вместе с убранной доставкой. Попутно поднят golang.org/x/image до v0.45.0 — красный шаг vulns, воспроизводился и на чистом master
123 lines
6.0 KiB
Go
123 lines
6.0 KiB
Go
package service
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"git.vakhrushev.me/av/transcriber/internal/contract"
|
|
"git.vakhrushev.me/av/transcriber/internal/entity"
|
|
)
|
|
|
|
// Выборка воркера владельцем не сужается: владелец решает, кому запись
|
|
// показывать, а не кому её считать. Сужение поставило бы записи одних людей в
|
|
// зависимость от того, кто первым завёл учётную запись.
|
|
func TestWorkerTakesRecordsOfEveryOwner(t *testing.T) {
|
|
env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})
|
|
|
|
first, err := env.service.CreateJobFromApi(t.Context(),
|
|
strings.NewReader("первая"), "one.mp3", newOwner(t, env.app))
|
|
require.NoError(t, err)
|
|
|
|
second, err := env.service.CreateJobFromApi(t.Context(),
|
|
strings.NewReader("вторая"), "two.mp3", newOwner(t, env.app))
|
|
require.NoError(t, err)
|
|
|
|
// Третья — ещё одного владельца: воркер не сужается ни одним из них.
|
|
third := newRecord(t, env)
|
|
|
|
// Захваченная запись остаётся за держателем, и следующий вызов берёт
|
|
// следующую, а не ту же самую.
|
|
taken := map[string]bool{}
|
|
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[second.Id], "и второго")
|
|
assert.True(t, taken[third.Id], "и третьего")
|
|
|
|
_, err = env.recordRepo.FindAndAcquire(entity.WorkingStages())
|
|
var missing *contract.JobNotFoundError
|
|
assert.ErrorAs(t, err, &missing, "больше пригодных к работе записей нет")
|
|
}
|
|
|
|
// Захват отдаёт идентификатор и признак своего захвата — не перечень колонок.
|
|
// Колонки шаг читает сам: иначе всякая новая колонка записи попадала бы под
|
|
// инвариант проекта о колонках очереди.
|
|
func TestAcquireReturnsIdentifierAndHolder(t *testing.T) {
|
|
env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})
|
|
|
|
owner := newOwner(t, env.app)
|
|
record, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", owner)
|
|
require.NoError(t, err)
|
|
|
|
acquired, err := env.recordRepo.FindAndAcquire(entity.WorkingStages())
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, record.Id, acquired.ID, "захват назвал запись")
|
|
assert.NotEmpty(t, acquired.Holder, "и признак своего захвата")
|
|
|
|
// Колонки читаются отдельным чтением, и владелец среди них.
|
|
read := readRecord(t, env, record.Id)
|
|
assert.Equal(t, owner, read.OwnerID)
|
|
assert.Equal(t, acquired.Holder, *read.AcquisitionID, "признак захвата записан в саму запись")
|
|
require.NotNil(t, read.AcquireExpiresAt, "срок протухания приехал с рубежом")
|
|
}
|
|
|
|
// Шаг конвейера владельца не затирает: сохранение кладёт только то, чем
|
|
// распоряжается конвейер, и владельца среди этого нет.
|
|
func TestPipelineStepKeepsOwner(t *testing.T) {
|
|
env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})
|
|
|
|
owner := newOwner(t, env.app)
|
|
record, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", owner)
|
|
require.NoError(t, err)
|
|
|
|
// Отказ приведения — приговор записи: шаг останавливает её и сохраняет. Это
|
|
// сохранение владельца тронуть не должно.
|
|
require.NoError(t, env.service.RunStep(t.Context()))
|
|
|
|
after := readRecord(t, env, record.Id)
|
|
require.True(t, after.IsHalted(), "шаг записал свой приговор")
|
|
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)
|
|
}
|
|
|
|
// Чужая запись, ничья и несуществующая отвечают одним и тем же: по разнице
|
|
// ответов иначе перебирается список заведённых записей.
|
|
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)
|
|
|
|
var missing *contract.JobNotFoundError
|
|
|
|
_, err = env.recordRepo.GetByID(record.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)
|
|
}
|