http: тесты приёма переписаны на подставные адаптеры
- проверки больше не зовут ffprobe и не меняют рабочий каталог процесса; добавлены случаи на отказ чтения метаданных и на отсутствие поля audio - заведена спека intake на приём по HTTP, ADR о подставных адаптерах, запись в журнал ревью о проверке, которая не могла упасть - go test снят из объявленных долгов CLAUDE.md, послабление errcheck для _test.go в .golangci.yml убрано
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
"mime/multipart"
|
||||
@@ -16,10 +17,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ffmpegconv "git.vakhrushev.me/av/transcriber/internal/adapter/converter/ffmpeg"
|
||||
ffmpegmv "git.vakhrushev.me/av/transcriber/internal/adapter/metaviewer/ffmpeg"
|
||||
"git.vakhrushev.me/av/transcriber/internal/adapter/recognizer"
|
||||
"git.vakhrushev.me/av/transcriber/internal/adapter/repo/sqlite"
|
||||
"git.vakhrushev.me/av/transcriber/internal/contract"
|
||||
"git.vakhrushev.me/av/transcriber/internal/entity"
|
||||
"git.vakhrushev.me/av/transcriber/internal/service"
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
@@ -31,10 +31,60 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Подставные адаптеры вместо ffprobe и ffmpeg. Проверки судят приём — что
|
||||
// запись сохранена, задача заведена и ответ такой, какой обещан, — а не
|
||||
// способность внешней программы разобрать звук. Внешних программ здесь нет
|
||||
// ни одной, и видно это по списку импортов.
|
||||
|
||||
// stubMetaViewer отдаёт заданную длительность либо заданную ошибку.
|
||||
type stubMetaViewer struct {
|
||||
seconds int
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *stubMetaViewer) GetInfo(string) (*contract.AudioInfo, error) {
|
||||
if m.err != nil {
|
||||
return nil, m.err
|
||||
}
|
||||
return &contract.AudioInfo{Seconds: m.seconds}, nil
|
||||
}
|
||||
|
||||
// stubConverter молчалив: приём конвертацию не делает, и ни одна проверка
|
||||
// этого файла её не зовёт.
|
||||
type stubConverter struct{}
|
||||
|
||||
func (c *stubConverter) Convert(string, string) error { return nil }
|
||||
|
||||
// TestTgSender: приём по HTTP в Telegram не отвечает, но сервису отправитель нужен.
|
||||
type TestTgSender struct{}
|
||||
|
||||
func (s *TestTgSender) Send(msg string, chatId int64, replyMsgId *int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// readableMetaViewer — источник метаданных, который читает любую запись.
|
||||
func readableMetaViewer() *stubMetaViewer {
|
||||
return &stubMetaViewer{seconds: 42}
|
||||
}
|
||||
|
||||
// testEnv — собранное окружение одной проверки. Каталог хранения свой у
|
||||
// каждой: рабочий каталог процесса проверки не трогают.
|
||||
type testEnv struct {
|
||||
router *gin.Engine
|
||||
handler *TranscribeHandler
|
||||
db *sql.DB
|
||||
storageDir string
|
||||
}
|
||||
|
||||
func setupTestDB(t *testing.T) (*sql.DB, *goqu.Database) {
|
||||
// Создаем временную базу данных в памяти
|
||||
db, err := sql.Open("sqlite3", ":memory:")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { db.Close() })
|
||||
|
||||
// Каждому новому соединению с `:memory:` драйвер выдаёт свою базу, и
|
||||
// второй потребитель пула не увидел бы накатанных миграций. Одно
|
||||
// соединение снимает класс целиком.
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
gq := goqu.New("sqlite3", db)
|
||||
|
||||
@@ -46,37 +96,39 @@ func setupTestDB(t *testing.T) (*sql.DB, *goqu.Database) {
|
||||
migpath, err := filepath.Abs(path.Join(b, "../../../../migrations"))
|
||||
require.NoError(t, err)
|
||||
|
||||
goose.SetLogger(goose.NopLogger())
|
||||
|
||||
err = goose.Up(db, migpath)
|
||||
require.NoError(t, err)
|
||||
|
||||
return db, gq
|
||||
}
|
||||
|
||||
func setupTestRouter(t *testing.T) (*gin.Engine, *TranscribeHandler) {
|
||||
func setupTestEnv(t *testing.T, metaviewer contract.AudioMetaViewer) *testEnv {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
db, gq := setupTestDB(t)
|
||||
|
||||
storageDir := filepath.Join(t.TempDir(), "files")
|
||||
require.NoError(t, os.MkdirAll(storageDir, 0o755))
|
||||
|
||||
fileRepo := sqlite.NewFileRepository(db, gq)
|
||||
jobRepo := sqlite.NewTranscriptJobRepository(db, gq)
|
||||
|
||||
metaviewer := ffmpegmv.NewFfmpegMetaViewer()
|
||||
converter := ffmpegconv.NewFfmpegConverter()
|
||||
recognizer := &recognizer.MemoryAudioRecognizer{}
|
||||
|
||||
// Создаем тестовый логгер
|
||||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||
Level: slog.LevelError, // Только ошибки в тестах
|
||||
}))
|
||||
// Журнал проверкам не нужен: судят они по ответу и по базе. А ветка отказа
|
||||
// метаданных теперь проходится нарочно, и её ERROR-строки на зелёном
|
||||
// прогоне размывали бы признак, по которому отличают новый красный шаг
|
||||
// гейта от объявленного долга.
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
|
||||
trsService := service.NewTranscribeService(
|
||||
jobRepo,
|
||||
fileRepo,
|
||||
metaviewer,
|
||||
converter,
|
||||
recognizer,
|
||||
&stubConverter{},
|
||||
&recognizer.MemoryAudioRecognizer{},
|
||||
&TestTgSender{},
|
||||
"data/files",
|
||||
storageDir,
|
||||
logger,
|
||||
)
|
||||
|
||||
@@ -91,163 +143,168 @@ func setupTestRouter(t *testing.T) (*gin.Engine, *TranscribeHandler) {
|
||||
api.GET("/status/:id", handler.GetTranscribeJobStatus)
|
||||
}
|
||||
|
||||
return router, handler
|
||||
return &testEnv{router: router, handler: handler, db: db, storageDir: storageDir}
|
||||
}
|
||||
|
||||
func createMultipartRequest(t *testing.T, audioFilePath string) (*http.Request, string) {
|
||||
// Открываем тестовый аудио файл
|
||||
file, err := os.Open(audioFilePath)
|
||||
require.NoError(t, err)
|
||||
defer file.Close()
|
||||
// createMultipartRequest собирает запрос из имени и содержимого. Файла на диске
|
||||
// для этого не нужно: имя проверяет выбор расширения, содержимое — сохранение.
|
||||
func createMultipartRequest(t *testing.T, fileName string, content []byte) *http.Request {
|
||||
return createMultipartRequestWithField(t, "audio", fileName, content)
|
||||
}
|
||||
|
||||
// Создаем буфер для multipart формы
|
||||
// createMultipartRequestWithField кладёт запись в поле с заданным именем —
|
||||
// нужно, чтобы построить форму без поля `audio`.
|
||||
func createMultipartRequestWithField(t *testing.T, field, fileName string, content []byte) *http.Request {
|
||||
var buf bytes.Buffer
|
||||
writer := multipart.NewWriter(&buf)
|
||||
|
||||
// Создаем поле для файла
|
||||
part, err := writer.CreateFormFile("audio", filepath.Base(audioFilePath))
|
||||
part, err := writer.CreateFormFile(field, fileName)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Копируем содержимое файла
|
||||
_, err = io.Copy(part, file)
|
||||
_, err = part.Write(content)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Закрываем writer
|
||||
err = writer.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Создаем HTTP запрос
|
||||
req, err := http.NewRequest("POST", "/api/audio", &buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
return req, writer.FormDataContentType()
|
||||
return req
|
||||
}
|
||||
|
||||
// storedFiles отдаёт содержимое каталога хранения.
|
||||
func storedFiles(t *testing.T, env *testEnv) []string {
|
||||
files, err := filepath.Glob(filepath.Join(env.storageDir, "*"))
|
||||
require.NoError(t, err)
|
||||
return files
|
||||
}
|
||||
|
||||
// countJobs считает заведённые задачи расшифровки.
|
||||
func countJobs(t *testing.T, env *testEnv) int {
|
||||
var count int
|
||||
err := env.db.QueryRow("SELECT COUNT(*) FROM transcribe_jobs").Scan(&count)
|
||||
require.NoError(t, err)
|
||||
return count
|
||||
}
|
||||
|
||||
// storedFileName отдаёт имя файла, записанное в учёте под данным идентификатором.
|
||||
func storedFileName(t *testing.T, env *testEnv, fileID string) string {
|
||||
var name string
|
||||
err := env.db.QueryRow("SELECT file_name FROM files WHERE id = ?", fileID).Scan(&name)
|
||||
require.NoError(t, err)
|
||||
return name
|
||||
}
|
||||
|
||||
func TestCreateTranscribeJob_Success(t *testing.T) {
|
||||
// Создаем временную директорию для файлов
|
||||
tempDir := t.TempDir()
|
||||
env := setupTestEnv(t, readableMetaViewer())
|
||||
|
||||
// Создаем структуру директорий для тестов
|
||||
testDataDir := filepath.Join(tempDir, "data", "files")
|
||||
err := os.MkdirAll(testDataDir, 0755)
|
||||
require.NoError(t, err)
|
||||
content := []byte("содержимое записи, которое обязано доехать до диска целиком")
|
||||
req := createMultipartRequest(t, "sample.m4a", content)
|
||||
|
||||
// Временно меняем рабочую директорию для сохранения файлов
|
||||
originalWd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
defer os.Chdir(originalWd)
|
||||
|
||||
err = os.Chdir(tempDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
router, _ := setupTestRouter(t)
|
||||
|
||||
// Копируем тестовый файл во временную директорию
|
||||
srcFile := filepath.Join(originalWd, "testdata", "sample.m4a")
|
||||
dstFile := "sample.m4a"
|
||||
|
||||
src, err := os.Open(srcFile)
|
||||
require.NoError(t, err)
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(dstFile)
|
||||
require.NoError(t, err)
|
||||
defer dst.Close()
|
||||
|
||||
_, err = io.Copy(dst, src)
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(dstFile)
|
||||
|
||||
// Создаем запрос с тестовым аудио файлом
|
||||
req, _ := createMultipartRequest(t, dstFile)
|
||||
|
||||
// Выполняем запрос
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
env.router.ServeHTTP(w, req)
|
||||
|
||||
// Проверяем результат
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
require.Equal(t, http.StatusCreated, w.Code)
|
||||
|
||||
// Имена полей ответа нормативны: контракт HTTP API объявлен необратимым.
|
||||
// Судим по сырому JSON — разбор в CreateTranscribeJobResponse переименовал
|
||||
// бы тег вместе с ожиданием, и проверка не смогла бы упасть.
|
||||
var raw map[string]json.RawMessage
|
||||
err := json.Unmarshal(w.Body.Bytes(), &raw)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, raw, "job_id")
|
||||
assert.Contains(t, raw, "status")
|
||||
|
||||
var response CreateTranscribeJobResponse
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Проверяем, что возвращается корректный ответ
|
||||
assert.NotEmpty(t, response.JobID)
|
||||
assert.Equal(t, entity.StateCreated, response.State)
|
||||
|
||||
// Проверяем, что файл был сохранен
|
||||
files, err := filepath.Glob(filepath.Join("data", "files", "*"))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, files, 1)
|
||||
// Задача действительно заведена, а не только названа в ответе: иначе
|
||||
// отправитель получит идентификатор записи, которой не будет никогда.
|
||||
require.Equal(t, 1, countJobs(t, env))
|
||||
|
||||
// Проверяем размер сохраненного файла
|
||||
fileInfo, err := os.Stat(files[0])
|
||||
job, err := env.handler.jobRepo.GetByID(response.JobID)
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, fileInfo.Size(), int64(0))
|
||||
assert.Equal(t, entity.StateCreated, job.State)
|
||||
require.NotNil(t, job.FileID)
|
||||
assert.NotEmpty(t, *job.FileID)
|
||||
|
||||
// Содержимое лежит в каталоге хранения одним файлом и целиком.
|
||||
files := storedFiles(t, env)
|
||||
require.Len(t, files, 1)
|
||||
|
||||
stored, err := os.ReadFile(files[0])
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, content, stored)
|
||||
|
||||
// Учёт указывает на этот самый файл, а не на какой-то другой: дальше по
|
||||
// конвейеру путь берётся только из учёта, и разъезд убил бы задачу молча.
|
||||
assert.Equal(t, filepath.Base(files[0]), storedFileName(t, env, *job.FileID))
|
||||
}
|
||||
|
||||
func TestCreateTranscribeJob_NoFile(t *testing.T) {
|
||||
router, _ := setupTestRouter(t)
|
||||
// Две ветки одного сценария: тела нет вовсе и форма есть, а поля в ней нет.
|
||||
// Вторая — та, что описана требованием; первая ходит тем же путём.
|
||||
testCases := []struct {
|
||||
name string
|
||||
req func(t *testing.T) *http.Request
|
||||
}{
|
||||
{
|
||||
name: "no body at all",
|
||||
req: func(t *testing.T) *http.Request {
|
||||
req, err := http.NewRequest("POST", "/api/audio", nil)
|
||||
require.NoError(t, err)
|
||||
return req
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "form without audio field",
|
||||
req: func(t *testing.T) *http.Request {
|
||||
return createMultipartRequestWithField(t, "attachment", "sample.m4a", []byte("запись"))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Создаем запрос без файла
|
||||
req, err := http.NewRequest("POST", "/api/audio", nil)
|
||||
require.NoError(t, err)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
env := setupTestEnv(t, readableMetaViewer())
|
||||
|
||||
// Выполняем запрос
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
w := httptest.NewRecorder()
|
||||
env.router.ServeHTTP(w, tc.req(t))
|
||||
|
||||
// Проверяем результат
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
|
||||
var response map[string]string
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
var response map[string]string
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "No audio file provided", response["error"])
|
||||
assert.Equal(t, "No audio file provided", response["error"])
|
||||
assert.Empty(t, storedFiles(t, env))
|
||||
assert.Equal(t, 0, countJobs(t, env))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTranscribeJob_EmptyFile(t *testing.T) {
|
||||
// Создаем временную директорию для файлов
|
||||
tempDir := t.TempDir()
|
||||
env := setupTestEnv(t, readableMetaViewer())
|
||||
|
||||
// Создаем структуру директорий для тестов
|
||||
testDataDir := filepath.Join(tempDir, "data", "files")
|
||||
err := os.MkdirAll(testDataDir, 0755)
|
||||
require.NoError(t, err)
|
||||
// Собственного порога по размеру у приёма нет: годность записи судит
|
||||
// источник метаданных, а не приём.
|
||||
req := createMultipartRequest(t, "empty.m4a", nil)
|
||||
|
||||
// Временно меняем рабочую директорию для сохранения файлов
|
||||
originalWd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
defer os.Chdir(originalWd)
|
||||
|
||||
err = os.Chdir(tempDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
router, _ := setupTestRouter(t)
|
||||
|
||||
// Создаем пустой временный файл в текущей директории теста
|
||||
emptyFile := "empty.m4a"
|
||||
f, err := os.Create(emptyFile)
|
||||
require.NoError(t, err)
|
||||
f.Close()
|
||||
defer os.Remove(emptyFile)
|
||||
|
||||
// Создаем запрос с пустым файлом
|
||||
req, _ := createMultipartRequest(t, emptyFile)
|
||||
|
||||
// Выполняем запрос
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
env.router.ServeHTTP(w, req)
|
||||
|
||||
// Проверяем результат - даже пустой файл должен быть принят
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
require.Equal(t, http.StatusCreated, w.Code)
|
||||
|
||||
var response CreateTranscribeJobResponse
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEmpty(t, response.JobID)
|
||||
@@ -257,102 +314,96 @@ func TestCreateTranscribeJob_EmptyFile(t *testing.T) {
|
||||
func TestCreateTranscribeJob_DifferentFileExtensions(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
filename string
|
||||
fileName string
|
||||
expectExt string
|
||||
}{
|
||||
{
|
||||
name: "m4a file",
|
||||
filename: "test.m4a",
|
||||
fileName: "test.m4a",
|
||||
expectExt: ".m4a",
|
||||
},
|
||||
{
|
||||
name: "mp3 file",
|
||||
filename: "test.mp3",
|
||||
fileName: "test.mp3",
|
||||
expectExt: ".mp3",
|
||||
},
|
||||
{
|
||||
name: "wav file",
|
||||
filename: "test.wav",
|
||||
fileName: "test.wav",
|
||||
expectExt: ".wav",
|
||||
},
|
||||
{
|
||||
name: "file without extension",
|
||||
filename: "test",
|
||||
fileName: "test",
|
||||
expectExt: ".audio",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Создаем временную директорию для файлов
|
||||
tempDir := t.TempDir()
|
||||
env := setupTestEnv(t, readableMetaViewer())
|
||||
|
||||
// Создаем структуру директорий для тестов
|
||||
testDataDir := filepath.Join(tempDir, "data", "files")
|
||||
err := os.MkdirAll(testDataDir, 0755)
|
||||
require.NoError(t, err)
|
||||
req := createMultipartRequest(t, tc.fileName, []byte("запись"))
|
||||
|
||||
// Временно меняем рабочую директорию для сохранения файлов
|
||||
originalWd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
defer os.Chdir(originalWd)
|
||||
|
||||
err = os.Chdir(tempDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
router, _ := setupTestRouter(t)
|
||||
|
||||
// Создаем временный файл с нужным именем в текущей директории теста
|
||||
testFile := tc.filename
|
||||
f, err := os.Create(testFile)
|
||||
require.NoError(t, err)
|
||||
f.WriteString("test audio content")
|
||||
f.Close()
|
||||
defer os.Remove(testFile)
|
||||
|
||||
// Создаем запрос
|
||||
req, _ := createMultipartRequest(t, testFile)
|
||||
|
||||
// Выполняем запрос
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
env.router.ServeHTTP(w, req)
|
||||
|
||||
// Проверяем результат
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
require.Equal(t, http.StatusCreated, w.Code)
|
||||
|
||||
// Проверяем, что файл сохранен с правильным расширением
|
||||
files, err := filepath.Glob(filepath.Join("data", "files", "*"+tc.expectExt))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, files, 1)
|
||||
files := storedFiles(t, env)
|
||||
require.Len(t, files, 1)
|
||||
|
||||
// Имя отправителя в хранилище не попадает: имя файла — свой
|
||||
// идентификатор, от отправителя взято только расширение.
|
||||
assert.Equal(t, tc.expectExt, filepath.Ext(files[0]))
|
||||
assert.NotContains(t, filepath.Base(files[0]), tc.fileName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTranscribeJob_MetaViewerFailure(t *testing.T) {
|
||||
env := setupTestEnv(t, &stubMetaViewer{err: errors.New("не удалось прочитать запись")})
|
||||
|
||||
req := createMultipartRequest(t, "broken.m4a", []byte("не запись вовсе"))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
env.router.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
|
||||
var response map[string]string
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Причина отказа принадлежит журналу, а не отправителю.
|
||||
assert.Equal(t, "Failed to create transcibe job", response["error"])
|
||||
assert.NotContains(t, w.Body.String(), "не удалось прочитать запись")
|
||||
|
||||
assert.Equal(t, 0, countJobs(t, env))
|
||||
}
|
||||
|
||||
func TestGetTranscribeJobStatus_Success(t *testing.T) {
|
||||
router, handler := setupTestRouter(t)
|
||||
env := setupTestEnv(t, readableMetaViewer())
|
||||
|
||||
// Создаем тестовую запись в базе данных
|
||||
job := &entity.TranscribeJob{
|
||||
Id: "test-job-id",
|
||||
State: entity.StateCreated,
|
||||
Source: entity.SourceApi,
|
||||
FileID: nil,
|
||||
IsError: false,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := handler.jobRepo.Create(job)
|
||||
err := env.handler.jobRepo.Create(job)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Создаем запрос
|
||||
req, err := http.NewRequest("GET", "/api/status/test-job-id", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Выполняем запрос
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
env.router.ServeHTTP(w, req)
|
||||
|
||||
// Проверяем результат
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response GetTranscribeJobResponse
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
@@ -363,19 +414,50 @@ func TestGetTranscribeJobStatus_Success(t *testing.T) {
|
||||
assert.NotZero(t, response.CreatedAt)
|
||||
}
|
||||
|
||||
func TestGetTranscribeJobStatus_NoTranscriptionText(t *testing.T) {
|
||||
env := setupTestEnv(t, readableMetaViewer())
|
||||
|
||||
job := &entity.TranscribeJob{
|
||||
Id: "job-without-text",
|
||||
State: entity.StateCreated,
|
||||
Source: entity.SourceApi,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := env.handler.jobRepo.Create(job)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest("GET", "/api/status/job-without-text", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
env.router.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
// Судим по сырому JSON: пустая строка на месте отсутствующего текста
|
||||
// читается клиентом как «расшифровка пуста», и разобранная структура
|
||||
// эти два случая не различает.
|
||||
var raw map[string]json.RawMessage
|
||||
err = json.Unmarshal(w.Body.Bytes(), &raw)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, raw, "job_id")
|
||||
assert.Contains(t, raw, "status")
|
||||
assert.Contains(t, raw, "created_at")
|
||||
assert.NotContains(t, raw, "transcription_text")
|
||||
}
|
||||
|
||||
func TestGetTranscribeJobStatus_NotFound(t *testing.T) {
|
||||
router, _ := setupTestRouter(t)
|
||||
env := setupTestEnv(t, readableMetaViewer())
|
||||
|
||||
// Создаем запрос с несуществующим ID
|
||||
req, err := http.NewRequest("GET", "/api/status/non-existent-id", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Выполняем запрос
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
env.router.ServeHTTP(w, req)
|
||||
|
||||
// Проверяем результат
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
require.Equal(t, http.StatusNotFound, w.Code)
|
||||
|
||||
var response map[string]string
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
@@ -383,9 +465,3 @@ func TestGetTranscribeJobStatus_NotFound(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "Job not found", response["error"])
|
||||
}
|
||||
|
||||
type TestTgSender struct{}
|
||||
|
||||
func (s *TestTgSender) Send(msg string, chatId int64, replyMsgId *int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user