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 := newTelegramRecord(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) require.NotNil(t, read.OwnerID) 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(), "шаг записал свой приговор") 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) } // Чужая запись, ничья и несуществующая отвечают одним и тем же: по разнице // ответов иначе перебирается список заведённых записей. 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) // Запись без владельца — принятая ботом. orphan := newTelegramRecord(t, env) var missing *contract.JobNotFoundError _, 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) }