369 lines
10 KiB
Go
369 lines
10 KiB
Go
package http
|
||
|
||
import (
|
||
"bytes"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"io"
|
||
"mime/multipart"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"os"
|
||
"path"
|
||
"path/filepath"
|
||
"runtime"
|
||
"testing"
|
||
"time"
|
||
|
||
"git.vakhrushev.me/av/transcriber/internal/adapter/ffmpeg"
|
||
"git.vakhrushev.me/av/transcriber/internal/adapter/s3"
|
||
"git.vakhrushev.me/av/transcriber/internal/adapter/sqlite"
|
||
"git.vakhrushev.me/av/transcriber/internal/entity"
|
||
"git.vakhrushev.me/av/transcriber/internal/service"
|
||
"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/pressly/goose/v3"
|
||
"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)
|
||
|
||
err = goose.SetDialect("sqlite3")
|
||
require.NoError(t, err)
|
||
|
||
_, b, _, _ := runtime.Caller(0)
|
||
|
||
migpath, err := filepath.Abs(path.Join(b, "../../../../migrations"))
|
||
require.NoError(t, err)
|
||
|
||
err = goose.Up(db, migpath)
|
||
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)
|
||
|
||
converter := ffmpeg.NewFfmpegConverter()
|
||
s3Service := &s3.TestS3service{}
|
||
|
||
trsService := service.NewTranscribeService(jobRepo, fileRepo, converter, s3Service)
|
||
|
||
handler := NewTranscribeHandler(jobRepo, trsService)
|
||
|
||
router := gin.New()
|
||
router.MaxMultipartMemory = 32 << 20 // 32 MiB
|
||
|
||
api := router.Group("/api")
|
||
{
|
||
api.POST("/audio", handler.CreateTranscribeJob)
|
||
api.GET("/status/: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/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/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/status/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/status/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"])
|
||
}
|