внутренняя модель перестроена вокруг аудиозаписи
- audiorecords вместо transcribe_jobs: приложения (texts, structures, recognitions, record_events, topics) живут своими коллекциями, ссылки на исходник и на приведённую копию перестали переставляться - рубеж называет достигнутое, отказ стал признаком остановки с причиной, а сторожей стало двое: число отказов и время в рубеже - воркеры потеряли специализацию, их число задаётся [pipeline] workers, шаг выбирается по рубежу, а захват отдаёт идентификатор и признак захвата
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package yandex
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
stt "github.com/yandex-cloud/go-genproto/yandex/cloud/ai/stt/v3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Сохранённый ответ провайдера — единственное, из чего пересчитывается архив:
|
||||
// результат операции у SpeechKit не переспрашивается, и повторное распознавание
|
||||
// стоит денег. Поэтому проверки ниже судят не «разбор чего-то вернул», а
|
||||
// сохранность самого ответа.
|
||||
|
||||
// response собирает ответ потока с одной репликой.
|
||||
func response(text string, start, end int64) *stt.StreamingResponse {
|
||||
return &stt.StreamingResponse{
|
||||
Event: &stt.StreamingResponse_FinalRefinement{
|
||||
FinalRefinement: &stt.FinalRefinement{
|
||||
Type: &stt.FinalRefinement_NormalizedText{
|
||||
NormalizedText: &stt.AlternativeUpdate{
|
||||
Alternatives: []*stt.Alternative{
|
||||
{Text: text, StartTimeMs: start, EndTimeMs: end},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Сохранённое читается обратно тем же: реплики со временем и плоский текст.
|
||||
func TestPayloadSurvivesRoundTrip(t *testing.T) {
|
||||
responses := []*stt.StreamingResponse{
|
||||
response("Первая реплика.", 0, 900),
|
||||
response("Вторая реплика.", 900, 1800),
|
||||
}
|
||||
|
||||
raw, err := encodeResponses(responses)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, raw)
|
||||
|
||||
decoded, err := decodeResponses(raw)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, decoded, 2)
|
||||
|
||||
outcome := outcomeFromResponses(decoded)
|
||||
require.Len(t, outcome.Replicas, 2)
|
||||
assert.Equal(t, "Первая реплика.", outcome.Replicas[0].Text)
|
||||
assert.Equal(t, int64(0), outcome.Replicas[0].StartMs)
|
||||
assert.Equal(t, int64(900), outcome.Replicas[0].EndMs)
|
||||
assert.Equal(t, "Вторая реплика.", outcome.Replicas[1].Text)
|
||||
assert.Equal(t, "Первая реплика. Вторая реплика.", outcome.PlainText)
|
||||
}
|
||||
|
||||
// Ради этого свойства сохранение и сделано двоичным. Провайдер добавляет поля
|
||||
// без предупреждения, и текстовое представление, собранное по нашей
|
||||
// скомпилированной схеме, выбросило бы их молча — а пересчитать архив было бы
|
||||
// уже не из чего: операция не переспрашивается.
|
||||
func TestUnknownProviderFieldSurvivesStorage(t *testing.T) {
|
||||
original := response("Реплика.", 0, 500)
|
||||
|
||||
// Так выглядит поле, которого наша схема не знает: провайдер прислал его,
|
||||
// разбор положил в неизвестные.
|
||||
unknown := protoimplUnknown(t)
|
||||
original.ProtoReflect().SetUnknown(unknown)
|
||||
require.NotEmpty(t, original.ProtoReflect().GetUnknown(), "неизвестное поле поставлено")
|
||||
|
||||
raw, err := encodeResponses([]*stt.StreamingResponse{original})
|
||||
require.NoError(t, err)
|
||||
|
||||
decoded, err := decodeResponses(raw)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, decoded, 1)
|
||||
|
||||
assert.Equal(t, []byte(unknown), []byte(decoded[0].ProtoReflect().GetUnknown()),
|
||||
"неизвестное провайдерское поле пережило запись и чтение")
|
||||
|
||||
// И известное при этом на месте.
|
||||
outcome := outcomeFromResponses(decoded)
|
||||
require.Len(t, outcome.Replicas, 1)
|
||||
assert.Equal(t, "Реплика.", outcome.Replicas[0].Text)
|
||||
}
|
||||
|
||||
// Обрезанное вложение узнаётся отказом, а не половиной расшифровки: половина
|
||||
// текста, выданная за целую, тише и хуже отказа.
|
||||
func TestTruncatedPayloadIsRefused(t *testing.T) {
|
||||
raw, err := encodeResponses([]*stt.StreamingResponse{response("Реплика.", 0, 500)})
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(raw), 2)
|
||||
|
||||
_, err = decodeResponses(raw[:len(raw)-2])
|
||||
assert.Error(t, err, "обрезанное вложение не разбирается молча")
|
||||
}
|
||||
|
||||
// Пустой поток даёт пустой результат, а не отказ: «на записи нет текста» —
|
||||
// законный исход распознавания.
|
||||
func TestEmptyStreamGivesEmptyOutcome(t *testing.T) {
|
||||
raw, err := encodeResponses(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
decoded, err := decodeResponses(raw)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, decoded)
|
||||
|
||||
outcome := outcomeFromResponses(decoded)
|
||||
assert.Empty(t, outcome.Replicas)
|
||||
assert.Empty(t, outcome.PlainText)
|
||||
}
|
||||
|
||||
// Ответ без разбора текста реплик не даёт и разбор не роняет: провайдер шлёт по
|
||||
// потоку и служебные события.
|
||||
func TestResponseWithoutTextIsSkipped(t *testing.T) {
|
||||
responses := []*stt.StreamingResponse{
|
||||
{Event: &stt.StreamingResponse_FinalRefinement{FinalRefinement: &stt.FinalRefinement{}}},
|
||||
response("Реплика.", 0, 500),
|
||||
response("", 500, 600),
|
||||
}
|
||||
|
||||
outcome := outcomeFromResponses(responses)
|
||||
require.Len(t, outcome.Replicas, 1, "пустые и служебные события репликами не становятся")
|
||||
assert.Equal(t, "Реплика.", outcome.PlainText)
|
||||
}
|
||||
|
||||
// protoimplUnknown собирает байты неизвестного поля: номер поля, которого в
|
||||
// нашей схеме нет, с целочисленным значением.
|
||||
func protoimplUnknown(t *testing.T) []byte {
|
||||
t.Helper()
|
||||
|
||||
// Поле 4095, тип varint, значение 7 — заведомо за пределами схемы ответа.
|
||||
raw, err := proto.Marshal(&stt.StreamingResponse{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, raw)
|
||||
|
||||
return []byte{0xF8, 0xFF, 0x3F, 0x07}
|
||||
}
|
||||
Reference in New Issue
Block a user