Transcribe: add func test
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -52,3 +52,8 @@ Thumbs.db
|
|||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
|
||||||
|
# Sample and test audio files
|
||||||
|
*.m4a
|
||||||
|
*.mp3
|
||||||
|
*.ogg
|
3
go.mod
3
go.mod
@@ -8,6 +8,7 @@ require (
|
|||||||
github.com/google/uuid v1.4.0
|
github.com/google/uuid v1.4.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.17
|
github.com/mattn/go-sqlite3 v1.14.17
|
||||||
github.com/pressly/goose/v3 v3.15.1
|
github.com/pressly/goose/v3 v3.15.1
|
||||||
|
github.com/stretchr/testify v1.10.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -15,6 +16,7 @@ require (
|
|||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
@@ -28,6 +30,7 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
|
3
go.sum
3
go.sum
@@ -81,8 +81,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
374
internal/controller/http/transcribe_test.go
Normal file
374
internal/controller/http/transcribe_test.go
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.vakhrushev.me/av/transcriber/internal/entity"
|
||||||
|
"git.vakhrushev.me/av/transcriber/internal/repo/sqlite"
|
||||||
|
"github.com/doug-martin/goqu/v9"
|
||||||
|
_ "github.com/doug-martin/goqu/v9/dialect/sqlite3"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTestDB(t *testing.T) (*sql.DB, *goqu.Database) {
|
||||||
|
// Создаем временную базу данных в памяти
|
||||||
|
db, err := sql.Open("sqlite3", ":memory:")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
gq := goqu.New("sqlite3", db)
|
||||||
|
|
||||||
|
// Создаем таблицы
|
||||||
|
createFilesTable := `
|
||||||
|
CREATE TABLE files (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
storage TEXT NOT NULL,
|
||||||
|
size INTEGER NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL
|
||||||
|
);`
|
||||||
|
|
||||||
|
createJobsTable := `
|
||||||
|
CREATE TABLE transcribe_jobs (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
state TEXT NOT NULL,
|
||||||
|
file_id TEXT,
|
||||||
|
is_error BOOLEAN NOT NULL DEFAULT 0,
|
||||||
|
error_text TEXT,
|
||||||
|
worker TEXT,
|
||||||
|
acquired_at DATETIME,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
FOREIGN KEY (file_id) REFERENCES files(id)
|
||||||
|
);`
|
||||||
|
|
||||||
|
_, err = db.Exec(createFilesTable)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = db.Exec(createJobsTable)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return db, gq
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestRouter(t *testing.T) (*gin.Engine, *TranscribeHandler) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
|
db, gq := setupTestDB(t)
|
||||||
|
|
||||||
|
fileRepo := sqlite.NewFileRepository(db, gq)
|
||||||
|
jobRepo := sqlite.NewTranscriptJobRepository(db, gq)
|
||||||
|
|
||||||
|
handler := NewTranscribeHandler(jobRepo, fileRepo)
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
router.MaxMultipartMemory = 32 << 20 // 32 MiB
|
||||||
|
|
||||||
|
api := router.Group("/api")
|
||||||
|
{
|
||||||
|
api.POST("/transcribe/audio", handler.CreateTranscribeJob)
|
||||||
|
api.GET("/transcribe/:id", handler.GetTranscribeJobStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return router, handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMultipartRequest(t *testing.T, audioFilePath string) (*http.Request, string) {
|
||||||
|
// Открываем тестовый аудио файл
|
||||||
|
file, err := os.Open(audioFilePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Создаем буфер для multipart формы
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := multipart.NewWriter(&buf)
|
||||||
|
|
||||||
|
// Создаем поле для файла
|
||||||
|
part, err := writer.CreateFormFile("audio", filepath.Base(audioFilePath))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Копируем содержимое файла
|
||||||
|
_, err = io.Copy(part, file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Закрываем writer
|
||||||
|
err = writer.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Создаем HTTP запрос
|
||||||
|
req, err := http.NewRequest("POST", "/api/transcribe/audio", &buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
return req, writer.FormDataContentType()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateTranscribeJob_Success(t *testing.T) {
|
||||||
|
// Создаем временную директорию для файлов
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
// Создаем структуру директорий для тестов
|
||||||
|
testDataDir := filepath.Join(tempDir, "data", "files")
|
||||||
|
err := os.MkdirAll(testDataDir, 0755)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Временно меняем рабочую директорию для сохранения файлов
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Проверяем результат
|
||||||
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Проверяем размер сохраненного файла
|
||||||
|
fileInfo, err := os.Stat(files[0])
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Greater(t, fileInfo.Size(), int64(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateTranscribeJob_NoFile(t *testing.T) {
|
||||||
|
router, _ := setupTestRouter(t)
|
||||||
|
|
||||||
|
// Создаем запрос без файла
|
||||||
|
req, err := http.NewRequest("POST", "/api/transcribe/audio", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Выполняем запрос
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
// Проверяем результат
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
|
||||||
|
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"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateTranscribeJob_EmptyFile(t *testing.T) {
|
||||||
|
// Создаем временную директорию для файлов
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
// Создаем структуру директорий для тестов
|
||||||
|
testDataDir := filepath.Join(tempDir, "data", "files")
|
||||||
|
err := os.MkdirAll(testDataDir, 0755)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Временно меняем рабочую директорию для сохранения файлов
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Проверяем результат - даже пустой файл должен быть принят
|
||||||
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateTranscribeJob_DifferentFileExtensions(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
filename string
|
||||||
|
expectExt string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "m4a file",
|
||||||
|
filename: "test.m4a",
|
||||||
|
expectExt: ".m4a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mp3 file",
|
||||||
|
filename: "test.mp3",
|
||||||
|
expectExt: ".mp3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wav file",
|
||||||
|
filename: "test.wav",
|
||||||
|
expectExt: ".wav",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file without extension",
|
||||||
|
filename: "test",
|
||||||
|
expectExt: ".audio",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
// Создаем временную директорию для файлов
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
// Создаем структуру директорий для тестов
|
||||||
|
testDataDir := filepath.Join(tempDir, "data", "files")
|
||||||
|
err := os.MkdirAll(testDataDir, 0755)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Временно меняем рабочую директорию для сохранения файлов
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Проверяем результат
|
||||||
|
assert.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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTranscribeJobStatus_Success(t *testing.T) {
|
||||||
|
router, handler := setupTestRouter(t)
|
||||||
|
|
||||||
|
// Создаем тестовую запись в базе данных
|
||||||
|
job := &entity.TranscribeJob{
|
||||||
|
Id: "test-job-id",
|
||||||
|
State: entity.StateCreated,
|
||||||
|
FileID: nil,
|
||||||
|
IsError: false,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := handler.jobRepo.Create(job)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Создаем запрос
|
||||||
|
req, err := http.NewRequest("GET", "/api/transcribe/test-job-id", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Выполняем запрос
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
// Проверяем результат
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
var response GetTranscribeJobResponse
|
||||||
|
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "test-job-id", response.JobID)
|
||||||
|
assert.Equal(t, entity.StateCreated, response.State)
|
||||||
|
assert.NotZero(t, response.CreatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTranscribeJobStatus_NotFound(t *testing.T) {
|
||||||
|
router, _ := setupTestRouter(t)
|
||||||
|
|
||||||
|
// Создаем запрос с несуществующим ID
|
||||||
|
req, err := http.NewRequest("GET", "/api/transcribe/non-existent-id", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Выполняем запрос
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
// Проверяем результат
|
||||||
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
|
||||||
|
var response map[string]string
|
||||||
|
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "Job not found", response["error"])
|
||||||
|
}
|
Reference in New Issue
Block a user