удалён вход Telegram, владелец записи стал обязателен в схеме
- убраны клиент бота, транспорт обновлений, отправитель сообщений, сборка входа при старте, секция настроек и зависимость go-telegram-bot-api; из конвейера ушла доставка ответа отправителю — исход виден опросом готовности. Колонки адресата и значение источника остались в схеме: применённые шаги не переписываются - шаг 202608140003 запрещает пустого владельца у аудиозаписи и у файла; существующие строки он не проверяет, и это принято сознательно — искать их надо запросом до выкладки - ревью нашло два пред-существующих дефекта, оба закрыты: пустой второй ответ распознавателя стирал сохранённую расшифровку, а пустая расшифровка перестала быть заметной вместе с убранной доставкой. Попутно поднят golang.org/x/image до v0.45.0 — красный шаг vulns, воспроизводился и на чистом master
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
@@ -101,7 +103,7 @@ func TestStructureIsBuiltFromStoredPayload(t *testing.T) {
|
||||
rec := &countingRecognizer{}
|
||||
env := newPipelineEnvWith(t, &okMetaViewer{}, &okConverter{}, rec)
|
||||
|
||||
record := newTelegramRecord(t, env)
|
||||
record := newRecord(t, env)
|
||||
drain(t, env, record.Id)
|
||||
|
||||
after := readRecord(t, env, record.Id)
|
||||
@@ -141,7 +143,7 @@ func TestPaidWorkIsNotRepeated(t *testing.T) {
|
||||
rec := &countingRecognizer{}
|
||||
env := newPipelineEnvWith(t, &okMetaViewer{}, &okConverter{}, rec)
|
||||
|
||||
record := newTelegramRecord(t, env)
|
||||
record := newRecord(t, env)
|
||||
|
||||
// Приведение.
|
||||
require.NoError(t, env.service.RunStep(t.Context()))
|
||||
@@ -171,7 +173,7 @@ func TestPollingPostponesWithoutSpendingAttempts(t *testing.T) {
|
||||
rec.inProgress.Store(3)
|
||||
env := newPipelineEnvWith(t, &okMetaViewer{}, &okConverter{}, rec)
|
||||
|
||||
record := newTelegramRecord(t, env)
|
||||
record := newRecord(t, env)
|
||||
|
||||
require.NoError(t, env.service.RunStep(t.Context())) // приведение
|
||||
require.NoError(t, env.service.RunStep(t.Context())) // отправка
|
||||
@@ -195,3 +197,88 @@ func TestPollingPostponesWithoutSpendingAttempts(t *testing.T) {
|
||||
|
||||
assert.Len(t, delays, 3, "все три прогона отложили работу")
|
||||
}
|
||||
|
||||
// emptyRecognizer отдаёт готовую операцию с пустым результатом: так выглядит
|
||||
// оплаченное распознавание, из которого ничего не вышло.
|
||||
type emptyRecognizer struct {
|
||||
countingRecognizer
|
||||
}
|
||||
|
||||
func (r *emptyRecognizer) Fetch(context.Context, string) (*entity.RecognitionOutcome, error) {
|
||||
r.fetches.Add(1)
|
||||
return &entity.RecognitionOutcome{}, nil
|
||||
}
|
||||
|
||||
// Пустой результат виден владельцу сервиса записью журнала «может стать
|
||||
// проблемой». Прежде этот случай был виден ответом отправителю — «на записи нет
|
||||
// текста», — и вместе с убранной доставкой он исчез бы вовсе: запись доходит до
|
||||
// конца молча и от успешной не отличается. Текста в записи нет по инварианту
|
||||
// приватности: пустой ему взяться неоткуда, а проверка судит уровень и
|
||||
// идентификатор.
|
||||
func TestEmptyRecognitionIsNamedInJournal(t *testing.T) {
|
||||
journal := &bytes.Buffer{}
|
||||
|
||||
env := newPipelineEnvWithLogger(t, &okMetaViewer{}, &okConverter{}, &emptyRecognizer{},
|
||||
slog.New(slog.NewTextHandler(journal, &slog.HandlerOptions{Level: slog.LevelDebug})))
|
||||
|
||||
record := newRecord(t, env)
|
||||
drain(t, env, record.Id)
|
||||
|
||||
written := journal.String()
|
||||
assert.Contains(t, written, "Recognition returned empty text", "пустой результат назван")
|
||||
assert.Contains(t, written, "level=WARN", "уровень — «может стать проблемой»")
|
||||
assert.Contains(t, written, record.Id, "запись названа идентификатором")
|
||||
}
|
||||
|
||||
// exhaustibleRecognizer отдаёт полный результат один раз, а на всяком следующем
|
||||
// обращении — пустой: так выглядит провайдер, чей поток закрылся на первом же
|
||||
// ответе. Отказом это не считается ни у него, ни у нас.
|
||||
type exhaustibleRecognizer struct {
|
||||
countingRecognizer
|
||||
served atomic.Bool
|
||||
}
|
||||
|
||||
func (r *exhaustibleRecognizer) Fetch(context.Context, string) (*entity.RecognitionOutcome, error) {
|
||||
r.fetches.Add(1)
|
||||
if r.served.Swap(true) {
|
||||
return &entity.RecognitionOutcome{}, nil
|
||||
}
|
||||
return r.Parse(countingPayload(t0Replicas))
|
||||
}
|
||||
|
||||
// Повторный опрос той же операции — обычное дело: держатель захвата умер,
|
||||
// сохранение рубежа отказало, человек снял остановку в панели. Пустой ответ
|
||||
// провайдера при этом не должен стирать уже сохранённую расшифровку: сервис
|
||||
// объявлен архивом, а восстановления у стёртого текста нет.
|
||||
func TestEmptySecondAnswerKeepsArchivedText(t *testing.T) {
|
||||
rec := &exhaustibleRecognizer{}
|
||||
env := newPipelineEnvWith(t, &okMetaViewer{}, &okConverter{}, rec)
|
||||
|
||||
record := newRecord(t, env)
|
||||
drain(t, env, record.Id)
|
||||
|
||||
stored := readRecord(t, env, record.Id)
|
||||
require.NotNil(t, stored.TranscriptTextID, "расшифровка сохранена первым ответом")
|
||||
|
||||
before, err := env.repos.Texts.GetByID(*stored.TranscriptTextID)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, before.Contents)
|
||||
|
||||
// Второй опрос той же записи: рубеж возвращается на отправленный, и шаг
|
||||
// забирает результат заново — теперь пустой.
|
||||
back := readRecord(t, env, record.Id)
|
||||
back.MoveToState(entity.StateSubmitted)
|
||||
require.NoError(t, env.recordRepo.Save(back, ""))
|
||||
clearDelay(t, env, record.Id)
|
||||
require.NoError(t, env.service.RunStep(t.Context()))
|
||||
|
||||
// Проверка обязана дойти до второго ответа: иначе она зеленела бы, ничего не
|
||||
// проверив, — а класс «проверка, не способная упасть» в этом проекте ловили
|
||||
// уже трижды.
|
||||
require.EqualValues(t, 2, rec.fetches.Load(), "результат забирали дважды")
|
||||
|
||||
after, err := env.repos.Texts.GetByID(*stored.TranscriptTextID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, before.Contents, after.Contents,
|
||||
"пустой ответ провайдера не стирает сохранённую расшифровку")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user