хранилище переехало с PocketBase на SQLite со своим каталогом файлов
- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose под файловым замком, одна миграция начальной схемы вместо семи прежних - транспорт переписан на net/http: свои слои, свой ограничитель частоты, отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли - по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета читается справа налево, узнавание известного идёт читающим пулом
This commit is contained in:
@@ -8,12 +8,9 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
pbrepo "git.vakhrushev.me/av/transcriber/internal/adapter/repo/pocketbase"
|
||||
"git.vakhrushev.me/av/transcriber/internal/entity"
|
||||
)
|
||||
|
||||
@@ -47,7 +44,7 @@ func TestRecordCard_CarriesNoTextButListsViews(t *testing.T) {
|
||||
|
||||
record := jobWithFile(t, env)
|
||||
|
||||
texts := pbrepo.NewTextRepository(env.app)
|
||||
texts := env.handler.textRepo
|
||||
transcript, err := texts.Put(record.Id, entity.TextKindTranscript, "сырая расшифровка")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -120,7 +117,7 @@ func TestRecordText_TranscriptView(t *testing.T) {
|
||||
|
||||
record := jobWithFile(t, env)
|
||||
|
||||
texts := pbrepo.NewTextRepository(env.app)
|
||||
texts := env.handler.textRepo
|
||||
transcript, err := texts.Put(record.Id, entity.TextKindTranscript, "сырая расшифровка")
|
||||
require.NoError(t, err)
|
||||
literary, err := texts.Put(record.Id, entity.TextKindLiterary, "вычитанный текст")
|
||||
@@ -149,7 +146,7 @@ func TestRecordText_ReplicasView(t *testing.T) {
|
||||
|
||||
record := jobWithFile(t, env)
|
||||
|
||||
structures := pbrepo.NewStructureRepository(env.app)
|
||||
structures := env.handler.structureRepo
|
||||
structure, err := structures.Put(record.Id, 1, []entity.Replica{
|
||||
{StartMs: 0, EndMs: 1500, Text: "первая реплика"},
|
||||
{StartMs: 1500, EndMs: 3000, Text: "вторая реплика"},
|
||||
@@ -179,7 +176,7 @@ func TestRecordText_NotReadyDiffersFromNotFound(t *testing.T) {
|
||||
|
||||
record := jobWithFile(t, env)
|
||||
|
||||
texts := pbrepo.NewTextRepository(env.app)
|
||||
texts := env.handler.textRepo
|
||||
transcript, err := texts.Put(record.Id, entity.TextKindTranscript, "сырая расшифровка")
|
||||
require.NoError(t, err)
|
||||
record.TranscriptTextID = &transcript.Id
|
||||
@@ -204,6 +201,64 @@ func TestRecordText_NotReadyDiffersFromNotFound(t *testing.T) {
|
||||
"«ещё не готово» и «записи нет» ведут к разным действиям человека")
|
||||
}
|
||||
|
||||
// Каждая ветвь «ещё не готово» отвечает одним кодом и одним телом.
|
||||
//
|
||||
// Ветвей четыре, и достижимы все: текста нет вовсе, текст заведён пустым,
|
||||
// структуры реплик нет, структура заведена без единой реплики. Шаг завершения
|
||||
// пишет результат несколькими операциями, поэтому запись законно застаётся между
|
||||
// ними. Ветвь, отвечающая иначе, отправила бы приложение за текстом, которого
|
||||
// нет, либо показала бы «записи нет» на своей записи.
|
||||
func TestRecordText_EveryNotReadyBranchIsOneAnswer(t *testing.T) {
|
||||
env := setupTestEnv(t, readableMetaViewer())
|
||||
|
||||
notReady := func(t *testing.T, record *entity.AudioRecord, view string) {
|
||||
t.Helper()
|
||||
|
||||
w := textOf(t, env, record.Id, view)
|
||||
require.Equal(t, http.StatusConflict, w.Code, view)
|
||||
|
||||
var body ErrorBody
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body))
|
||||
assert.Equal(t, CodeNotReady, body.Code, view)
|
||||
assert.NotEmpty(t, body.Message, view)
|
||||
}
|
||||
|
||||
t.Run("текста нет вовсе", func(t *testing.T) {
|
||||
record := jobWithFile(t, env)
|
||||
notReady(t, record, entity.TextViewTranscript)
|
||||
notReady(t, record, entity.TextViewLiterary)
|
||||
notReady(t, record, entity.TextViewReplicas)
|
||||
})
|
||||
|
||||
t.Run("текст заведён пустым", func(t *testing.T) {
|
||||
record := jobWithFile(t, env)
|
||||
|
||||
transcript, err := env.handler.textRepo.Put(record.Id, entity.TextKindTranscript, "")
|
||||
require.NoError(t, err)
|
||||
record.TranscriptTextID = &transcript.Id
|
||||
|
||||
literary, err := env.handler.textRepo.Put(record.Id, entity.TextKindLiterary, "")
|
||||
require.NoError(t, err)
|
||||
record.LiteraryTextID = &literary.Id
|
||||
|
||||
require.NoError(t, env.handler.recordRepo.Save(record, ""))
|
||||
|
||||
notReady(t, record, entity.TextViewTranscript)
|
||||
notReady(t, record, entity.TextViewLiterary)
|
||||
})
|
||||
|
||||
t.Run("структура заведена без единой реплики", func(t *testing.T) {
|
||||
record := jobWithFile(t, env)
|
||||
|
||||
structure, err := env.handler.structureRepo.Put(record.Id, 1, []entity.Replica{})
|
||||
require.NoError(t, err)
|
||||
record.StructureID = &structure.Id
|
||||
require.NoError(t, env.handler.recordRepo.Save(record, ""))
|
||||
|
||||
notReady(t, record, entity.TextViewReplicas)
|
||||
})
|
||||
}
|
||||
|
||||
// Вид, которого сервис не знает, и незаданный вид дают отказ по негодному вводу:
|
||||
// умолчание сделало бы ответ функцией того, что успел записать конвейер.
|
||||
func TestRecordText_UnknownViewIsBadRequest(t *testing.T) {
|
||||
@@ -241,7 +296,7 @@ func TestRecordText_ReadFailureIsNotANotFound(t *testing.T) {
|
||||
|
||||
record := jobWithFile(t, env)
|
||||
|
||||
texts := pbrepo.NewTextRepository(env.app)
|
||||
texts := env.handler.textRepo
|
||||
transcript, err := texts.Put(record.Id, entity.TextKindTranscript, "сырая расшифровка")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -251,20 +306,21 @@ func TestRecordText_ReadFailureIsNotANotFound(t *testing.T) {
|
||||
// Обработчик пересобирается с отказывающим хранилищем текстов: остальная
|
||||
// цепочка та же, что и в проде.
|
||||
journal := &journalBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(journal, nil))
|
||||
handler := NewAppHandler(
|
||||
env.handler.recordRepo,
|
||||
&failingTextRepo{},
|
||||
pbrepo.NewStructureRepository(env.app),
|
||||
env.handler.structureRepo,
|
||||
env.handler.fileRepo,
|
||||
env.handler.trsService,
|
||||
slog.New(slog.NewTextHandler(journal, nil)),
|
||||
logger,
|
||||
)
|
||||
|
||||
r, err := apis.NewRouter(env.app)
|
||||
require.NoError(t, err)
|
||||
r.Bind(TrustedHeaderIdentity(env.app, ServiceMounts(handler, http.NotFoundHandler()), testTrustedNetworks(t), nil))
|
||||
handler.Register(r)
|
||||
mux, err := r.BuildMux()
|
||||
require.NoError(t, err)
|
||||
mounts := ServiceMounts(
|
||||
AppChain(handler.Routes(), env.users, testTrustedNetworks(t), logger),
|
||||
http.NotFoundHandler(),
|
||||
)
|
||||
mux := BuildHandler(mounts, NewWebappHandler(builtDist(), true, logger), logger)
|
||||
|
||||
req := httptest.NewRequest("GET", "/app/audiorecords/"+record.Id+"/text?view="+entity.TextViewTranscript, http.NoBody)
|
||||
asUser(req, env.login)
|
||||
@@ -280,16 +336,25 @@ func TestRecordText_ReadFailureIsNotANotFound(t *testing.T) {
|
||||
|
||||
// Прежние адреса приложения убраны целиком: контракт объявлен сломанным, и
|
||||
// адрес, отвечающий по-старому, означал бы два дома у одного вопроса.
|
||||
//
|
||||
// Отвечают они теперь тем же, чем отвечает всякий путь вне корней сервиса, —
|
||||
// разметкой приложения: корень `/api` перестал существовать вместе со
|
||||
// встроенным хранилищем, и резервировать имя за отказом сервис не берётся.
|
||||
func TestFormerAddressesAreGone(t *testing.T) {
|
||||
env := setupTestEnv(t, readableMetaViewer())
|
||||
|
||||
record := jobWithFile(t, env)
|
||||
before := countJobs(t, env)
|
||||
|
||||
status := httptest.NewRecorder()
|
||||
env.serve(status, httptest.NewRequest("GET", "/api/status/"+record.Id, http.NoBody))
|
||||
assert.Equal(t, http.StatusNotFound, status.Code)
|
||||
assert.Equal(t, http.StatusOK, status.Code)
|
||||
assert.Contains(t, status.Body.String(), "приложение", "прежний адрес отдал не разметку")
|
||||
assert.NotContains(t, status.Body.String(), entity.StateUploaded,
|
||||
"прежний адрес отдал данные записи")
|
||||
|
||||
intake := httptest.NewRecorder()
|
||||
env.serve(intake, createMultipartRequestAt(t, "/api/audio", "запись.mp3", []byte("данные")))
|
||||
assert.Equal(t, http.StatusNotFound, intake.Code)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, intake.Code)
|
||||
assert.Equal(t, before, countJobs(t, env), "прежний адрес приёма завёл запись")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user