On branch main

modified:   main_dc/yalarba/api_yal/cmd/testrunner/main.go
	modified:   main_dc/yalarba/api_yal/cmd/testrunner/runner.go
	modified:   main_dc/yalarba/api_yal/tests/integration/account_test.go
	modified:   main_dc/yalarba/api_yal/tests/integration/appeal_test.go
	modified:   main_dc/yalarba/api_yal/tests/integration/auth_test.go
	modified:   main_dc/yalarba/api_yal/tests/integration/comment_test.go
	modified:   main_dc/yalarba/api_yal/tests/integration/feedback_test.go
	modified:   main_dc/yalarba/api_yal/tests/integration/object_test.go
	modified:   main_dc/yalarba/api_yal/tests/integration/rating_test.go
	deleted:    main_dc/yalarba/api_yal/tests/testutils/client.go
	modified:   main_dc/yalarba/api_yal/tests/testutils/fixtures.go
	modified:   main_dc/yalarba/api_yal/tests/testutils/setup.go
write comments for and into test's functions
This commit is contained in:
2026-06-08 01:58:04 +05:00
parent b4574f9df1
commit d1e45c7686
12 changed files with 346 additions and 118 deletions
+98 -43
View File
@@ -15,29 +15,32 @@ import (
"time"
)
// Конфигурация
// Config хранит конфигурацию тестового раннера
type Config struct {
ServerURL string
ServerPort string
TestTimeout time.Duration
Verbose bool
Parallel bool
Coverage bool
TestSuite string
ServerURL string // URL API сервера
ServerPort string // Порт API сервера
TestTimeout time.Duration // Таймаут выполнения тестов
Verbose bool // Флаг подробного вывода
Parallel bool // Флаг параллельного выполнения
Coverage bool // Флаг генерации отчета о покрытии
TestSuite string // Выбранный набор тестов
}
// Цвета для вывода
// Colors хранит ANSI коды цветов для вывода в консоль
type Colors struct {
Reset string
Red string
Green string
Yellow string
Blue string
Cyan string
Reset string // Сброс цвета
Red string // Красный цвет (ошибки)
Green string // Зеленый цвет (успех)
Yellow string // Желтый цвет (предупреждения)
Blue string // Синий цвет (информация)
Cyan string // Бирюзовый цвет (заголовки)
}
// Глобальная переменная с цветами
var colors Colors
// init инициализирует цвета для консольного вывода
// Определяет поддержку цветов для разных ОС
func init() {
// Определяем поддержку цветов
if runtime.GOOS == "windows" {
@@ -62,23 +65,25 @@ func init() {
}
}
// Тестовый набор
// TestSuite представляет набор тестов
type TestSuite struct {
Name string
Pattern string
Timeout time.Duration
Name string // Название набора
Pattern string // Регулярное выражение для фильтрации тестов
Timeout time.Duration // Таймаут для этого набора
}
// Результат теста
// TestResult хранит результат выполнения тестового набора
type TestResult struct {
Suite string
Passed bool
Duration time.Duration
Output string
Error error
Coverage float64
Suite string // Название набора
Passed bool // Флаг успешности
Duration time.Duration // Длительность выполнения
Output string // Вывод тестов
Error error // Ошибка выполнения
Coverage float64 // Процент покрытия кода
}
// main - точка входа в программу
// Обрабатывает аргументы командной строки и запускает соответствующий режим
func main() {
config := parseFlags()
@@ -105,6 +110,8 @@ func main() {
os.Exit(0)
}
// parseFlags парсит аргументы командной строки
// Возвращает указатель на Config с заполненными значениями
func parseFlags() *Config {
config := &Config{}
@@ -121,14 +128,18 @@ func parseFlags() *Config {
return config
}
// printBanner выводит графический баннер программы
func printBanner() {
fmt.Printf("%s╔════════════════════════════════════════════════════════════════╗%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s╔══════════════════════════════════════════════════════════════════╗%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s║ Go Test Runner for API ║%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s║ Version 1.0.0 ║%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s╚════════════════════════════════════════════════════════════════╝%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s╚══════════════════════════════════════════════════════════════════╝%s\n", colors.Cyan, colors.Reset)
fmt.Println()
}
// checkServer проверяет доступность API сервера
// Отправляет GET запрос на /health эндпоинт
// Возвращает true если сервер доступен
func checkServer(config *Config) bool {
fmt.Printf("%s🔍 Checking server status...%s\n", colors.Yellow, colors.Reset)
@@ -151,6 +162,8 @@ func checkServer(config *Config) bool {
return false
}
// cleanTestCache очищает кэш тестов Go
// Выполняет команду 'go clean -testcache'
func cleanTestCache() {
fmt.Printf("%s🧹 Cleaning test cache...%s\n", colors.Yellow, colors.Reset)
cmd := exec.Command("go", "clean", "-testcache")
@@ -160,6 +173,8 @@ func cleanTestCache() {
fmt.Println()
}
// getTestSuites возвращает список доступных наборов тестов
// Возвращает массив TestSuite с предопределенными настройками
func getTestSuites() []TestSuite {
return []TestSuite{
{Name: "All", Pattern: "", Timeout: 30 * time.Minute},
@@ -173,6 +188,10 @@ func getTestSuites() []TestSuite {
}
}
// runTests запускает выбранные наборы тестов
// Параметры:
// - config: конфигурация запуска
// Возвращает: массив результатов тестов
func runTests(config *Config) []TestResult {
suites := getTestSuites()
var selectedSuites []TestSuite
@@ -212,6 +231,12 @@ func runTests(config *Config) []TestResult {
return results
}
// runTestsParallel запускает тесты параллельно
// Использует горутины и WaitGroup для синхронизации
// Параметры:
// - suites: список наборов тестов
// - config: конфигурация запуска
// Возвращает: массив результатов тестов
func runTestsParallel(suites []TestSuite, config *Config) []TestResult {
fmt.Printf("%s🚀 Running tests in parallel mode...%s\n\n", colors.Cyan, colors.Reset)
@@ -238,6 +263,12 @@ func runTestsParallel(suites []TestSuite, config *Config) []TestResult {
return results
}
// runTestSuite запускает один набор тестов
// Выполняет команду 'go test' с соответствующими параметрами
// Параметры:
// - suite: набор тестов для запуска
// - config: конфигурация запуска
// Возвращает: результат выполнения тестов
func runTestSuite(suite TestSuite, config *Config) TestResult {
startTime := time.Now()
@@ -307,6 +338,9 @@ func runTestSuite(suite TestSuite, config *Config) TestResult {
return result
}
// extractCoverage извлекает процент покрытия кода из файла coverage.out
// Выполняет команду 'go tool cover -func=coverage.out'
// Возвращает: процент покрытия (0 если не удалось извлечь)
func extractCoverage() float64 {
// Проверяем наличие файла покрытия
if _, err := os.Stat("coverage.out"); os.IsNotExist(err) {
@@ -337,11 +371,14 @@ func extractCoverage() float64 {
return 0
}
// printResults выводит сводную таблицу результатов тестов
// Параметры:
// - results: массив результатов тестов
func printResults(results []TestResult) {
fmt.Println()
fmt.Printf("%s╔════════════════════════════════════════════════════════════════╗%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s╔══════════════════════════════════════════════════════════════════╗%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s║ Test Results ║%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s╚════════════════════════════════════════════════════════════════╝%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s╚══════════════════════════════════════════════════════════════════╝%s\n", colors.Cyan, colors.Reset)
fmt.Println()
// Таблица результатов
@@ -388,6 +425,8 @@ func printResults(results []TestResult) {
fmt.Println()
}
// generateHTMLReport генерирует HTML отчет о покрытии кода
// Создает файл coverage.html и открывает его в браузере
func generateHTMLReport() {
fmt.Printf("%s📊 Generating HTML coverage report...%s\n", colors.Yellow, colors.Reset)
@@ -415,6 +454,10 @@ func generateHTMLReport() {
}
}
// hasFailures проверяет, есть ли неудачные тесты
// Параметры:
// - results: массив результатов тестов
// Возвращает: true если есть хотя бы один проваленный тест
func hasFailures(results []TestResult) bool {
for _, result := range results {
if !result.Passed {
@@ -424,7 +467,8 @@ func hasFailures(results []TestResult) bool {
return false
}
// Интерактивный режим
// interactiveMode запускает интерактивный режим с меню выбора
// Позволяет пользователю выбирать тесты через консольное меню
func interactiveMode() {
reader := bufio.NewReader(os.Stdin)
@@ -468,10 +512,11 @@ func interactiveMode() {
}
}
// printMenu выводит интерактивное меню выбора тестов
func printMenu() {
fmt.Printf("\n%s╔════════════════════════════════════════════════════════════════╗%s\n", colors.Cyan, colors.Reset)
fmt.Printf("\n%s╔══════════════════════════════════════════════════════════════════╗%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s║ Interactive Menu ║%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s╚════════════════════════════════════════════════════════════════╝%s\n", colors.Cyan, colors.Reset)
fmt.Printf("%s╚══════════════════════════════════════════════════════════════════╝%s\n", colors.Cyan, colors.Reset)
fmt.Println()
fmt.Printf("%s1.%s Run all tests\n", colors.Green, colors.Reset)
fmt.Printf("%s2.%s Run auth tests only\n", colors.Green, colors.Reset)
@@ -487,6 +532,10 @@ func printMenu() {
fmt.Println()
}
// runWithConfig запускает тесты с заданной конфигурацией
// Используется в интерактивном режиме
// Параметры:
// - config: конфигурация запуска тестов
func runWithConfig(config *Config) {
config.ServerURL = "http://localhost:8088"
config.TestTimeout = 30 * time.Minute
@@ -502,9 +551,12 @@ func runWithConfig(config *Config) {
printResults(results)
}
// Функция для мониторинга сервера
// watchServer запускает мониторинг состояния сервера
// Периодически проверяет доступность сервера через health check
// Параметры:
// - config: конфигурация с адресом сервера
func watchServer(config *Config) {
fmt.Printf("%s👁 Starting server health monitor...%s\n", colors.Yellow, colors.Reset)
fmt.Printf("%s👋 Starting server health monitor...%s\n", colors.Yellow, colors.Reset)
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
@@ -516,15 +568,17 @@ func watchServer(config *Config) {
}
}
// Структура для бенчмарков
// BenchmarkResult хранит результат бенчмарк теста
type BenchmarkResult struct {
Name string
Ops int64
NsPerOp time.Duration
Allocs int64
Bytes int64
Name string // Название бенчмарка
Ops int64 // Количество операций
NsPerOp time.Duration // Наносекунд на операцию
Allocs int64 // Количество аллокаций
Bytes int64 // Количество байт
}
// runBenchmarks запускает бенчмарк тесты
// Выполняет команду 'go test -bench=. -benchmem'
func runBenchmarks() {
fmt.Printf("%s🏃 Running benchmarks...%s\n\n", colors.Yellow, colors.Reset)
@@ -538,7 +592,8 @@ func runBenchmarks() {
fmt.Println(string(output))
}
// Точка входа с поддержкой аргументов командной строки
// mainWithArgs - альтернативная точка входа с поддержкой режимов
// Обрабатывает специальные команды: interactive, bench, watch
func mainWithArgs() {
if len(os.Args) > 1 && os.Args[1] == "interactive" {
interactiveMode()
@@ -571,4 +626,4 @@ func mainWithArgs() {
if hasFailures(results) {
os.Exit(1)
}
}
}
@@ -1,4 +1,4 @@
// cmd/testrunner/runner.go - Дополнительные утилиты
// cmd/testrunner/runner.go - Дополнительные утилиты для тестового раннера
package main
@@ -12,16 +12,20 @@ import (
"time"
)
// Сохранение результатов в JSON
// saveResultsToJSON сохраняет результаты тестов в JSON файл
// Параметры:
// - results: массив результатов тестов
// - filename: имя файла для сохранения
// Возвращает: ошибку при сохранении
func saveResultsToJSON(results []TestResult, filename string) error {
type JSONResult struct {
Timestamp string `json:"timestamp"`
Results []TestResult `json:"results"`
Timestamp string `json:"timestamp"` // Время сохранения отчета
Results []TestResult `json:"results"` // Результаты тестов
Summary struct {
Total int `json:"total"`
Passed int `json:"passed"`
Failed int `json:"failed"`
PassRate float64 `json:"pass_rate"`
Total int `json:"total"` // Общее количество тестов
Passed int `json:"passed"` // Количество пройденных
Failed int `json:"failed"` // Количество проваленных
PassRate float64 `json:"pass_rate"` // Процент прохождения
} `json:"summary"`
}
@@ -29,6 +33,7 @@ func saveResultsToJSON(results []TestResult, filename string) error {
jsonResult.Timestamp = time.Now().Format(time.RFC3339)
jsonResult.Results = results
// Подсчет статистики
for _, r := range results {
jsonResult.Summary.Total++
if r.Passed {
@@ -38,6 +43,7 @@ func saveResultsToJSON(results []TestResult, filename string) error {
}
}
// Расчет процента прохождения
if jsonResult.Summary.Total > 0 {
jsonResult.Summary.PassRate = float64(jsonResult.Summary.Passed) / float64(jsonResult.Summary.Total) * 100
}
@@ -50,30 +56,38 @@ func saveResultsToJSON(results []TestResult, filename string) error {
return os.WriteFile(filename, data, 0644)
}
// Создание отчета в формате JUnit XML (для CI/CD)
// generateJUnitReport создает отчет в формате JUnit XML для CI/CD систем
// Параметры:
// - results: массив результатов тестов
// - filename: имя файла для сохранения
// Возвращает: ошибку при сохранении
func generateJUnitReport(results []TestResult, filename string) error {
// TestCase представляет один тестовый случай в JUnit формате
type TestCase struct {
Name string `xml:"name,attr"`
Classname string `xml:"classname,attr"`
Time float64 `xml:"time,attr"`
Failure *string `xml:"failure,omitempty"`
Name string `xml:"name,attr"` // Название теста
Classname string `xml:"classname,attr"` // Класс теста
Time float64 `xml:"time,attr"` // Время выполнения
Failure *string `xml:"failure,omitempty"` // Информация об ошибке (опционально)
}
// TestSuite представляет набор тестов в JUnit формате
type TestSuite struct {
Name string `xml:"name,attr"`
Tests int `xml:"tests,attr"`
Failures int `xml:"failures,attr"`
Time float64 `xml:"time,attr"`
TestCases []TestCase `xml:"testcase"`
Name string `xml:"name,attr"` // Название набора
Tests int `xml:"tests,attr"` // Количество тестов
Failures int `xml:"failures,attr"` // Количество провалов
Time float64 `xml:"time,attr"` // Общее время
TestCases []TestCase `xml:"testcase"` // Список тестов
}
// TestSuites корневой элемент JUnit отчета
type TestSuites struct {
XMLName string `xml:"testsuites"`
TestSuites []TestSuite `xml:"testsuite"`
XMLName string `xml:"testsuites"` // Имя корневого элемента
TestSuites []TestSuite `xml:"testsuite"` // Список наборов тестов
}
var suites TestSuites
// Конвертируем результаты в JUnit формат
for _, result := range results {
suite := TestSuite{
Name: result.Suite,
@@ -87,6 +101,7 @@ func generateJUnitReport(results []TestResult, filename string) error {
Time: result.Duration.Seconds(),
}
// Добавляем информацию об ошибке если тест провален
if !result.Passed {
suite.Failures = 1
failureMsg := result.Error.Error()
@@ -97,6 +112,7 @@ func generateJUnitReport(results []TestResult, filename string) error {
suites.TestSuites = append(suites.TestSuites, suite)
}
// Маршалинг в XML и сохранение
xmlData, err := xml.MarshalIndent(suites, "", " ")
if err != nil {
return err
@@ -105,7 +121,12 @@ func generateJUnitReport(results []TestResult, filename string) error {
return os.WriteFile(filename, append([]byte(xml.Header), xmlData...), 0644)
}
// Функция для запуска определенных тестов по тегам
// runTestsByTag запускает тесты с определенным тегом (build tag)
// Позволяет фильтровать тесты по тегам
// Параметры:
// - tag: тег для фильтрации тестов
// - config: конфигурация запуска
// Возвращает: массив результатов тестов
func runTestsByTag(tag string, config *Config) []TestResult {
fmt.Printf("%s🏷️ Running tests with tag: %s%s\n", colors.Yellow, tag, colors.Reset)
@@ -128,7 +149,8 @@ func runTestsByTag(tag string, config *Config) []TestResult {
return []TestResult{result}
}
// Создание скрипта для CI/CD
// generateCIScript создает bash скрипт для запуска тестов в CI/CD
// Скрипт автоматически запускает сервер, выполняет тесты и останавливает сервер
func generateCIScript() {
ciScript := `#!/bin/bash
# CI/CD script for API tests
@@ -153,16 +175,28 @@ kill $SERVER_PID
echo "Tests completed"
`
// Создаем директорию scripts если ее нет
if err := os.MkdirAll("scripts", 0755); err != nil {
fmt.Printf("Failed to create scripts directory: %v\n", err)
return
}
// Сохраняем скрипт и делаем его исполняемым
if err := os.WriteFile("scripts/ci_test.sh", []byte(ciScript), 0755); err != nil {
fmt.Printf("Failed to generate CI script: %v\n", err)
}
}
// Функция для сравнения результатов тестов
// compareResults сравнивает результаты текущего запуска с предыдущим
// Показывает изменения в прохождении тестов и времени выполнения
// Параметры:
// - current: текущие результаты тестов
// - previous: предыдущие результаты для сравнения
func compareResults(current, previous []TestResult) {
fmt.Printf("%s📊 Test Result Comparison%s\n", colors.Cyan, colors.Reset)
fmt.Println(strings.Repeat("-", 50))
// Создаем карты для быстрого доступа к результатам
currentMap := make(map[string]TestResult)
for _, r := range current {
currentMap[r.Suite] = r
@@ -173,13 +207,16 @@ func compareResults(current, previous []TestResult) {
prevMap[r.Suite] = r
}
// Сравниваем каждый набор тестов
for name, currentResult := range currentMap {
prevResult, exists := prevMap[name]
if !exists {
// Новый набор тестов
fmt.Printf("%s New test suite: %s%s\n", colors.Green, name, colors.Reset)
continue
}
// Сравниваем статус прохождения
if currentResult.Passed != prevResult.Passed {
if currentResult.Passed {
fmt.Printf("%s✅ Fixed: %s (was failing)%s\n", colors.Green, name, colors.Reset)
@@ -187,7 +224,7 @@ func compareResults(current, previous []TestResult) {
fmt.Printf("%s❌ Regression: %s (was passing)%s\n", colors.Red, name, colors.Reset)
}
} else if currentResult.Passed {
// Сравнение времени выполнения
// Сравнение времени выполнения для успешных тестов
diff := currentResult.Duration - prevResult.Duration
if diff > 0 {
fmt.Printf("%s⚠️ Slower: %s (+%v)%s\n", colors.Yellow, name, diff.Round(time.Millisecond), colors.Reset)
@@ -5,9 +5,12 @@ import (
"testing"
)
// TestAccountEndpoints тестирует все эндпоинты управления аккаунтом пользователя
// Включает получение профиля, обновление данных, смену пароля и удаление аккаунта
func TestAccountEndpoints(t *testing.T) {
config := testutils.NewTestConfig()
// GetProfile тестирует получение профиля авторизованного пользователя
t.Run("GetProfile", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -27,6 +30,7 @@ func TestAccountEndpoints(t *testing.T) {
t.Fatalf("Failed to parse response: %v", err)
}
// Проверяем наличие всех обязательных полей в ответе
expectedFields := []string{"id", "email", "full_name", "first_name", "last_name", "role", "stats"}
for _, field := range expectedFields {
if _, ok := profile[field]; !ok {
@@ -35,6 +39,7 @@ func TestAccountEndpoints(t *testing.T) {
}
})
// GetOwnAccount тестирует получение данных собственного аккаунта
t.Run("GetOwnAccount", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -50,6 +55,7 @@ func TestAccountEndpoints(t *testing.T) {
}
})
// UpdateAccount тестирует обновление данных аккаунта (телефон, город)
t.Run("UpdateAccount", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -74,11 +80,13 @@ func TestAccountEndpoints(t *testing.T) {
t.Fatalf("Failed to parse response: %v", err)
}
// Проверяем, что номер телефона обновился корректно
if phone, ok := updatedAccount["phone"].(string); !ok || phone != "+79998887766" {
t.Error("Phone number not updated correctly")
}
})
// ChangePassword тестирует успешную смену пароля
t.Run("ChangePassword", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -107,6 +115,7 @@ func TestAccountEndpoints(t *testing.T) {
}
})
// ChangePasswordWrongCurrent тестирует смену пароля с неверным текущим паролем
t.Run("ChangePasswordWrongCurrent", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -120,11 +129,13 @@ func TestAccountEndpoints(t *testing.T) {
}
defer resp.Body.Close()
// Ожидаем ошибку 400 или 401 при неверном текущем пароле
if resp.StatusCode != 400 && resp.StatusCode != 401 {
t.Errorf("Expected status 400 or 401, got %d", resp.StatusCode)
}
})
// DeleteAccount тестирует удаление аккаунта
t.Run("DeleteAccount", func(t *testing.T) {
user := config.CreateTestUser(t)
@@ -138,13 +149,14 @@ func TestAccountEndpoints(t *testing.T) {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
// Проверяем, что больше нельзя войти
// Проверяем, что больше нельзя войти после удаления аккаунта
_, err = config.GetAuthToken(user.Email, user.Password)
if err == nil {
t.Error("Should not be able to login after account deletion")
}
})
// UnauthorizedAccess тестирует доступ к защищенным эндпоинтам без токена
t.Run("UnauthorizedAccess", func(t *testing.T) {
resp, err := config.Request("GET", "/account/profile", nil, "")
if err != nil {
@@ -156,4 +168,4 @@ func TestAccountEndpoints(t *testing.T) {
t.Errorf("Expected status 401, got %d", resp.StatusCode)
}
})
}
}
@@ -5,19 +5,22 @@ import (
"api_yal/tests/testutils"
)
// TestAppealEndpoints тестирует все эндпоинты для работы с обращениями пользователей
// Включает создание, получение, обновление, удаление обращений и получение списка своих обращений
func TestAppealEndpoints(t *testing.T) {
config := testutils.NewTestConfig()
// CreateAppeal тестирует создание нового обращения
t.Run("CreateAppeal", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
appealData := map[string]interface{}{
"type": "complaint",
"title": "Test Appeal",
"message": "This is a test appeal message for testing purposes",
"priority": "high",
"contact_name": "Test User",
"type": "complaint",
"title": "Test Appeal",
"message": "This is a test appeal message for testing purposes",
"priority": "high",
"contact_name": "Test User",
"contact_email": user.Email,
}
@@ -27,6 +30,7 @@ func TestAppealEndpoints(t *testing.T) {
}
defer resp.Body.Close()
// Ожидаем статус 201 Created
if resp.StatusCode != 201 {
t.Errorf("Expected status 201, got %d", resp.StatusCode)
}
@@ -36,20 +40,22 @@ func TestAppealEndpoints(t *testing.T) {
t.Fatalf("Failed to parse response: %v", err)
}
// Проверяем, что ID обращения присутствует в ответе
if _, ok := createdAppeal["id"]; !ok {
t.Error("Appeal ID not found")
}
})
// GetAppealByID тестирует получение обращения по ID
t.Run("GetAppealByID", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
// Создаем обращение
appealData := map[string]interface{}{
"type": "question",
"title": "Test Question",
"message": "This is a test question",
"type": "question",
"title": "Test Question",
"message": "This is a test question",
"contact_email": user.Email,
}
resp, err := config.Request("POST", "/appeals", appealData, user.Token)
@@ -76,15 +82,16 @@ func TestAppealEndpoints(t *testing.T) {
}
})
// UpdateAppeal тестирует обновление существующего обращения
t.Run("UpdateAppeal", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
// Создаем обращение
appealData := map[string]interface{}{
"type": "suggestion",
"title": "Original Title",
"message": "Original message for testing",
"type": "suggestion",
"title": "Original Title",
"message": "Original message for testing",
"contact_email": user.Email,
}
resp, err := config.Request("POST", "/appeals", appealData, user.Token)
@@ -117,6 +124,7 @@ func TestAppealEndpoints(t *testing.T) {
}
})
// MyAppeals тестирует получение списка обращений текущего пользователя
t.Run("MyAppeals", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -145,6 +153,7 @@ func TestAppealEndpoints(t *testing.T) {
}
})
// DeleteAppeal тестирует удаление обращения
t.Run("DeleteAppeal", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -174,11 +183,12 @@ func TestAppealEndpoints(t *testing.T) {
}
defer deleteResp.Body.Close()
// Ожидаем статус 204 No Content при успешном удалении
if deleteResp.StatusCode != 204 {
t.Errorf("Expected status 204, got %d", deleteResp.StatusCode)
}
// Проверяем, что обращение удалено
// Проверяем, что обращение удалено - должно вернуть 404
getResp, err := config.Request("GET", "/appeals/"+string(rune(appealID)), nil, user.Token)
if err != nil {
t.Fatalf("Failed to get appeal: %v", err)
@@ -5,9 +5,12 @@ import (
"api_yal/tests/testutils"
)
// TestAuthFlow тестирует полный поток аутентификации пользователя
// Включает регистрацию, логин, обновление токена, выход, сброс пароля и мобильную авторизацию
func TestAuthFlow(t *testing.T) {
config := testutils.NewTestConfig()
// Register тестирует регистрацию нового пользователя
t.Run("Register", func(t *testing.T) {
testData := map[string]interface{}{
"email": "testflow@example.com",
@@ -22,6 +25,7 @@ func TestAuthFlow(t *testing.T) {
}
defer resp.Body.Close()
// Ожидаем статус 201 Created при успешной регистрации
if resp.StatusCode != 201 {
t.Errorf("Expected status 201, got %d", resp.StatusCode)
}
@@ -31,6 +35,7 @@ func TestAuthFlow(t *testing.T) {
t.Fatalf("Failed to parse response: %v", err)
}
// Проверяем наличие токена и данных пользователя в ответе
if _, ok := result["token"]; !ok {
t.Error("Token not found in response")
}
@@ -40,6 +45,7 @@ func TestAuthFlow(t *testing.T) {
}
})
// Login тестирует вход существующего пользователя
t.Run("Login", func(t *testing.T) {
// Сначала создаем пользователя
user := config.CreateTestUser(t)
@@ -82,6 +88,7 @@ func TestAuthFlow(t *testing.T) {
}
})
// RefreshToken тестирует обновление access токена через refresh token
t.Run("RefreshToken", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -107,6 +114,7 @@ func TestAuthFlow(t *testing.T) {
}
})
// Logout тестирует выход пользователя из системы
t.Run("Logout", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -126,11 +134,13 @@ func TestAuthFlow(t *testing.T) {
t.Fatalf("Failed to parse response: %v", err)
}
// Проверяем сообщение о успешном выходе
if msg, ok := result["message"]; !ok || msg != "Successfully logged out" {
t.Error("Logout message not as expected")
}
})
// PasswordReset тестирует полный поток сброса пароля
t.Run("PasswordReset", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -153,12 +163,13 @@ func TestAuthFlow(t *testing.T) {
t.Fatalf("Failed to parse response: %v", err)
}
// Получаем токен сброса из ответа (в реальном API он приходит на email)
resetToken, ok := result["token"].(string)
if !ok {
t.Fatal("Reset token not found in response")
}
// Подтверждение сброса пароля
// Подтверждение сброса пароля с новым паролем
newPassword := "newpassword789"
resp, err = config.Request("POST", "/auth/password-reset/confirm", map[string]interface{}{
"token": resetToken,
@@ -183,6 +194,7 @@ func TestAuthFlow(t *testing.T) {
}
})
// MobileLogin тестирует мобильную авторизацию (возвращает access и refresh токены)
t.Run("MobileLogin", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -205,6 +217,7 @@ func TestAuthFlow(t *testing.T) {
t.Fatalf("Failed to parse response: %v", err)
}
// Для мобильной авторизации ожидаем специальные поля
requiredFields := []string{"access_token", "refresh_token", "expires_at", "user"}
for _, field := range requiredFields {
if _, ok := result[field]; !ok {
@@ -5,9 +5,12 @@ import (
"api_yal/tests/testutils"
)
// TestCommentEndpoints тестирует все эндпоинты для работы с комментариями к отзывам
// Включает создание, получение, обновление, удаление комментариев и получение статистики
func TestCommentEndpoints(t *testing.T) {
config := testutils.NewTestConfig()
// CreateComment тестирует создание нового комментария к отзыву
t.Run("CreateComment", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -40,6 +43,7 @@ func TestCommentEndpoints(t *testing.T) {
}
})
// GetCommentByID тестирует получение комментария по ID
t.Run("GetCommentByID", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -65,6 +69,7 @@ func TestCommentEndpoints(t *testing.T) {
commentID := createdComment["id"].(float64)
// Получение комментария доступно без авторизации
getResp, err := config.Request("GET", "/comments/"+string(rune(commentID)), nil, "")
if err != nil {
t.Fatalf("Failed to get comment: %v", err)
@@ -76,6 +81,7 @@ func TestCommentEndpoints(t *testing.T) {
}
})
// UpdateComment тестирует обновление текста комментария автором
t.Run("UpdateComment", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -116,6 +122,7 @@ func TestCommentEndpoints(t *testing.T) {
}
})
// ListComments тестирует получение списка комментариев с пагинацией
t.Run("ListComments", func(t *testing.T) {
resp, err := config.Request("GET", "/comments?page=1&page_size=20", nil, "")
if err != nil {
@@ -128,6 +135,7 @@ func TestCommentEndpoints(t *testing.T) {
}
})
// MyComments тестирует получение комментариев текущего пользователя
t.Run("MyComments", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -143,6 +151,7 @@ func TestCommentEndpoints(t *testing.T) {
}
})
// CommentsByFeedback тестирует получение комментариев для конкретного отзыва
t.Run("CommentsByFeedback", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -161,6 +170,7 @@ func TestCommentEndpoints(t *testing.T) {
}
})
// CommentStats тестирует получение статистики по комментариям
t.Run("CommentStats", func(t *testing.T) {
resp, err := config.Request("GET", "/comments/stats", nil, "")
if err != nil {
@@ -177,6 +187,7 @@ func TestCommentEndpoints(t *testing.T) {
t.Fatalf("Failed to parse response: %v", err)
}
// Проверяем наличие полей статистики
expectedFields := []string{"total_comments", "verified_comments", "unverified_comments"}
for _, field := range expectedFields {
if _, ok := stats[field]; !ok {
@@ -185,6 +196,7 @@ func TestCommentEndpoints(t *testing.T) {
}
})
// DeleteComment тестирует удаление комментария автором
t.Run("DeleteComment", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -5,9 +5,12 @@ import (
"api_yal/tests/testutils"
)
// TestFeedbackEndpoints тестирует все эндпоинты для работы с отзывами
// Включает создание, получение, обновление, удаление отзывов, поиск и статистику
func TestFeedbackEndpoints(t *testing.T) {
config := testutils.NewTestConfig()
// CreateFeedback тестирует создание нового отзыва
t.Run("CreateFeedback", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -41,6 +44,7 @@ func TestFeedbackEndpoints(t *testing.T) {
}
})
// GetFeedbackByID тестирует получение отзыва по ID
t.Run("GetFeedbackByID", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -59,6 +63,7 @@ func TestFeedbackEndpoints(t *testing.T) {
}
})
// UpdateFeedback тестирует обновление отзыва автором
t.Run("UpdateFeedback", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -91,6 +96,7 @@ func TestFeedbackEndpoints(t *testing.T) {
}
})
// ListFeedbacks тестирует получение списка отзывов с пагинацией
t.Run("ListFeedbacks", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -106,6 +112,7 @@ func TestFeedbackEndpoints(t *testing.T) {
}
})
// MyFeedbacks тестирует получение отзывов текущего пользователя
t.Run("MyFeedbacks", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -121,6 +128,7 @@ func TestFeedbackEndpoints(t *testing.T) {
}
})
// FeedbacksByObject тестирует получение отзывов для конкретного объекта
t.Run("FeedbacksByObject", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -138,6 +146,7 @@ func TestFeedbackEndpoints(t *testing.T) {
}
})
// FeedbacksByPlatform тестирует получение отзывов по платформе
t.Run("FeedbacksByPlatform", func(t *testing.T) {
resp, err := config.Request("GET", "/feedbacks/platform/tourist", nil, "")
if err != nil {
@@ -150,6 +159,7 @@ func TestFeedbackEndpoints(t *testing.T) {
}
})
// SearchFeedbacks тестирует поиск отзывов по тексту
t.Run("SearchFeedbacks", func(t *testing.T) {
resp, err := config.Request("GET", "/feedbacks/search?q=excellent", nil, "")
if err != nil {
@@ -162,6 +172,7 @@ func TestFeedbackEndpoints(t *testing.T) {
}
})
// FeedbackStats тестирует получение статистики по отзывам
t.Run("FeedbackStats", func(t *testing.T) {
resp, err := config.Request("GET", "/feedbacks/stats", nil, "")
if err != nil {
@@ -178,6 +189,7 @@ func TestFeedbackEndpoints(t *testing.T) {
t.Fatalf("Failed to parse response: %v", err)
}
// Проверяем наличие полей статистики
expectedFields := []string{"total_feedbacks", "average_rating", "rating_distribution", "platform_stats"}
for _, field := range expectedFields {
if _, ok := stats[field]; !ok {
@@ -186,6 +198,7 @@ func TestFeedbackEndpoints(t *testing.T) {
}
})
// DeleteFeedback тестирует удаление отзыва
t.Run("DeleteFeedback", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -199,11 +212,12 @@ func TestFeedbackEndpoints(t *testing.T) {
}
defer resp.Body.Close()
// Ожидаем статус 204 No Content при успешном удалении
if resp.StatusCode != 204 {
t.Errorf("Expected status 204, got %d", resp.StatusCode)
}
// Проверяем, что отзыв удален
// Проверяем, что отзыв удален - должен вернуть 404
getResp, err := config.Request("GET", "/feedbacks/"+string(rune(feedbackID)), nil, "")
if err != nil {
t.Fatalf("Failed to get feedback: %v", err)
@@ -5,9 +5,12 @@ import (
"testing"
)
// TestObjectEndpoints тестирует все эндпоинты для работы с объектами (местами, заведениями)
// Включает создание, получение, обновление, удаление объектов, поиск и геопоиск
func TestObjectEndpoints(t *testing.T) {
config := testutils.NewTestConfig()
// CreateObject тестирует создание нового объекта пользователем
t.Run("CreateObject", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -52,6 +55,7 @@ func TestObjectEndpoints(t *testing.T) {
}
})
// GetObjectByID тестирует получение объекта по ID
t.Run("GetObjectByID", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -78,6 +82,7 @@ func TestObjectEndpoints(t *testing.T) {
}
})
// GetNonExistentObject тестирует получение несуществующего объекта
t.Run("GetNonExistentObject", func(t *testing.T) {
resp, err := config.Request("GET", "/objects/999999", nil, "")
if err != nil {
@@ -90,6 +95,7 @@ func TestObjectEndpoints(t *testing.T) {
}
})
// UpdateObject тестирует обновление объекта его владельцем
t.Run("UpdateObject", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -122,6 +128,7 @@ func TestObjectEndpoints(t *testing.T) {
}
})
// UpdateObjectUnauthorized тестирует попытку обновления чужого объекта
t.Run("UpdateObjectUnauthorized", func(t *testing.T) {
user1 := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user1)
@@ -131,6 +138,7 @@ func TestObjectEndpoints(t *testing.T) {
objectID := config.CreateTestObject(t, user1.Token)
// Пользователь user2 пытается обновить объект user1
resp, err := config.Request("PUT", "/objects/"+string(rune(objectID)), map[string]interface{}{
"short_name": "Hacked Name",
}, user2.Token)
@@ -139,11 +147,13 @@ func TestObjectEndpoints(t *testing.T) {
}
defer resp.Body.Close()
// Ожидаем ошибку 403 Forbidden
if resp.StatusCode != 403 {
t.Errorf("Expected status 403, got %d", resp.StatusCode)
}
})
// ListObjects тестирует получение списка объектов с пагинацией
t.Run("ListObjects", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -173,6 +183,7 @@ func TestObjectEndpoints(t *testing.T) {
}
})
// SearchObjects тестирует поиск объектов по тексту
t.Run("SearchObjects", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -195,9 +206,10 @@ func TestObjectEndpoints(t *testing.T) {
}
t.Logf("Search results: %+v", searchResults)
_ = objectID
_ = objectID // Используем переменную для избежания предупреждения
})
// NearbyObjects тестирует поиск объектов рядом с заданными координатами
t.Run("NearbyObjects", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -213,6 +225,7 @@ func TestObjectEndpoints(t *testing.T) {
}
})
// DeleteObject тестирует удаление объекта
t.Run("DeleteObject", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -229,7 +242,7 @@ func TestObjectEndpoints(t *testing.T) {
t.Errorf("Expected status 204, got %d", resp.StatusCode)
}
// Проверяем, что объект удален
// Проверяем, что объект удален - должен вернуть 404
getResp, err := config.Request("GET", "/objects/"+string(rune(objectID)), nil, "")
if err != nil {
t.Fatalf("Failed to get object: %v", err)
@@ -240,4 +253,4 @@ func TestObjectEndpoints(t *testing.T) {
t.Errorf("Expected status 404 for deleted object, got %d", getResp.StatusCode)
}
})
}
}
@@ -5,9 +5,12 @@ import (
"api_yal/tests/testutils"
)
// TestRatingEndpoints тестирует все эндпоинты для работы с рейтингами и голосованием
// Включает создание рейтинга, голосование, изменение голоса и получение статистики
func TestRatingEndpoints(t *testing.T) {
config := testutils.NewTestConfig()
// CreateRating тестирует создание нового рейтинга для объекта
t.Run("CreateRating", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -30,6 +33,7 @@ func TestRatingEndpoints(t *testing.T) {
}
})
// CastVote тестирует голосование в рейтинге
t.Run("CastVote", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -54,7 +58,7 @@ func TestRatingEndpoints(t *testing.T) {
ratingID := createdRating["id"].(float64)
// Голосуем
// Голосуем с оценкой 5
voteData := map[string]interface{}{
"score": 5,
}
@@ -70,6 +74,7 @@ func TestRatingEndpoints(t *testing.T) {
}
})
// GetMyVote тестирует получение голоса текущего пользователя
t.Run("GetMyVote", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -118,11 +123,13 @@ func TestRatingEndpoints(t *testing.T) {
t.Fatalf("Failed to parse response: %v", err)
}
// Проверяем, что пользователь уже голосовал
if hasVoted, ok := voteInfo["has_voted"].(bool); !ok || !hasVoted {
t.Error("has_voted should be true")
}
})
// UpdateMyVote тестирует обновление голоса пользователя
t.Run("UpdateMyVote", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -155,7 +162,7 @@ func TestRatingEndpoints(t *testing.T) {
t.Fatalf("Failed to cast vote: %v", err)
}
// Обновляем голос
// Обновляем голос с оценки 5 на 4
updateData := map[string]interface{}{
"score": 4,
}
@@ -170,6 +177,7 @@ func TestRatingEndpoints(t *testing.T) {
}
})
// GetRatingStats тестирует получение статистики по рейтингам
t.Run("GetRatingStats", func(t *testing.T) {
resp, err := config.Request("GET", "/ratings/stats", nil, "")
if err != nil {
@@ -186,6 +194,7 @@ func TestRatingEndpoints(t *testing.T) {
t.Fatalf("Failed to parse response: %v", err)
}
// Проверяем наличие полей статистики
expectedFields := []string{"total_ratings", "total_votes", "platform_distribution"}
for _, field := range expectedFields {
if _, ok := stats[field]; !ok {
@@ -194,6 +203,7 @@ func TestRatingEndpoints(t *testing.T) {
}
})
// DeleteMyVote тестирует удаление голоса пользователя
t.Run("DeleteMyVote", func(t *testing.T) {
user := config.CreateTestUser(t)
defer config.CleanupTestUser(t, user)
@@ -233,6 +243,7 @@ func TestRatingEndpoints(t *testing.T) {
}
defer deleteResp.Body.Close()
// Ожидаем статус 204 No Content при успешном удалении
if deleteResp.StatusCode != 204 {
t.Errorf("Expected status 204, got %d", deleteResp.StatusCode)
}
@@ -1 +0,0 @@
package testutils
@@ -2,36 +2,46 @@ package testutils
import "testing"
// CreateObjectRequest представляет структуру запроса для создания объекта
// Используется в тестах для передачи данных при создании нового объекта
type CreateObjectRequest struct {
ShortName string `json:"short_name"`
LongName string `json:"long_name"`
Type string `json:"type"`
Phone string `json:"phone"`
Email string `json:"email"`
Site string `json:"site"`
ShortDescription string `json:"short_description"`
Description string `json:"description"`
Address string `json:"address"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
IsActive bool `json:"is_active"`
IsVerified bool `json:"is_verified"`
ShortName string `json:"short_name"` // Короткое название объекта
LongName string `json:"long_name"` // Полное название объекта
Type string `json:"type"` // Тип объекта (cafe, restaurant, museum и т.д.)
Phone string `json:"phone"` // Контактный телефон
Email string `json:"email"` // Email для связи
Site string `json:"site"` // Веб-сайт объекта
ShortDescription string `json:"short_description"` // Краткое описание
Description string `json:"description"` // Полное описание
Address string `json:"address"` // Физический адрес
Latitude float64 `json:"latitude"` // Географическая широта
Longitude float64 `json:"longitude"` // Географическая долгота
IsActive bool `json:"is_active"` // Активен ли объект
IsVerified bool `json:"is_verified"` // Подтвержден ли объект
}
// CreateFeedbackRequest представляет структуру запроса для создания отзыва
type CreateFeedbackRequest struct {
ObjectID uint `json:"object_id"`
Rating int `json:"rating"`
Text string `json:"text"`
Platform string `json:"platform"`
MediaURLs []string `json:"media_urls"`
ObjectID uint `json:"object_id"` // ID объекта, на который оставляется отзыв
Rating int `json:"rating"` // Оценка от 1 до 5
Text string `json:"text"` // Текст отзыва
Platform string `json:"platform"` // Платформа, с которой оставлен отзыв (tourist, expert, local)
MediaURLs []string `json:"media_urls"` // Ссылки на медиафайлы (фото, видео)
}
// CreateCommentRequest представляет структуру запроса для создания комментария
type CreateCommentRequest struct {
FeedbackID uint `json:"feedback_id"`
Text string `json:"text"`
ParentID *uint `json:"parent_id"`
FeedbackID uint `json:"feedback_id"` // ID отзыва, к которому оставляется комментарий
Text string `json:"text"` // Текст комментария
ParentID *uint `json:"parent_id"` // ID родительского комментария (для вложенных комментариев)
}
// CreateTestObject создает тестовый объект и возвращает его ID
// Используется в тестах для подготовки тестовых данных
// Параметры:
// - t: указатель на тест для логирования ошибок
// - token: JWT токен авторизованного пользователя
// Возвращает: ID созданного объекта
func (c *TestConfig) CreateTestObject(t *testing.T, token string) uint {
req := CreateObjectRequest{
ShortName: "Test Object",
@@ -67,6 +77,13 @@ func (c *TestConfig) CreateTestObject(t *testing.T, token string) uint {
return 0
}
// CreateTestFeedback создает тестовый отзыв и возвращает его ID
// Используется в тестах для подготовки тестовых данных
// Параметры:
// - t: указатель на тест для логирования ошибок
// - token: JWT токен авторизованного пользователя
// - objectID: ID объекта, к которому создается отзыв
// Возвращает: ID созданного отзыва
func (c *TestConfig) CreateTestFeedback(t *testing.T, token string, objectID uint) uint {
req := CreateFeedbackRequest{
ObjectID: objectID,
@@ -11,20 +11,26 @@ import (
"time"
)
// TestConfig хранит конфигурацию для тестов
// Содержит базовый URL API и HTTP клиент с поддержкой cookies
type TestConfig struct {
BaseURL string
Client *http.Client
BaseURL string // Базовый URL API сервера
Client *http.Client // HTTP клиент с поддержкой cookies
}
// TestUser хранит данные тестового пользователя
type TestUser struct {
Email string
Password string
FirstName string
LastName string
Token string
UserID uint
Email string // Email пользователя
Password string // Пароль пользователя
FirstName string // Имя пользователя
LastName string // Фамилия пользователя
Token string // JWT токен доступа
UserID uint // ID пользователя в системе
}
// NewTestConfig создает новую конфигурацию для тестов
// Настраивает HTTP клиент с поддержкой cookies и таймаутом 30 секунд
// Возвращает указатель на TestConfig
func NewTestConfig() *TestConfig {
jar, _ := cookiejar.New(nil)
return &TestConfig{
@@ -36,6 +42,13 @@ func NewTestConfig() *TestConfig {
}
}
// Request выполняет HTTP запрос к API
// Параметры:
// - method: HTTP метод (GET, POST, PUT, DELETE)
// - path: путь эндпоинта (относительно BaseURL)
// - body: тело запроса (будет сериализовано в JSON)
// - token: JWT токен для авторизации (может быть пустым)
// Возвращает: HTTP ответ и ошибку
func (c *TestConfig) Request(method, path string, body interface{}, token string) (*http.Response, error) {
var reqBody io.Reader
if body != nil {
@@ -59,6 +72,11 @@ func (c *TestConfig) Request(method, path string, body interface{}, token string
return c.Client.Do(req)
}
// ParseResponse парсит JSON ответ HTTP запроса в указанную структуру
// Параметры:
// - resp: HTTP ответ для парсинга
// - target: указатель на структуру, в которую нужно распарсить JSON
// Возвращает: ошибку парсинга
func (c *TestConfig) ParseResponse(resp *http.Response, target interface{}) error {
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
@@ -68,6 +86,11 @@ func (c *TestConfig) ParseResponse(resp *http.Response, target interface{}) erro
return json.Unmarshal(body, target)
}
// CreateTestUser создает тестового пользователя через API регистрации
// Автоматически генерирует уникальный email на основе timestamp
// Параметры:
// - t: указатель на тест для логирования ошибок
// Возвращает: указатель на созданного TestUser с заполненными полями (включая токен)
func (c *TestConfig) CreateTestUser(t *testing.T) *TestUser {
user := &TestUser{
Email: fmt.Sprintf("test_%d@example.com", time.Now().UnixNano()),
@@ -92,9 +115,11 @@ func (c *TestConfig) CreateTestUser(t *testing.T) *TestUser {
t.Fatalf("Failed to parse response: %v", err)
}
// Извлекаем токен из ответа
if token, ok := result["token"].(string); ok {
user.Token = token
}
// Извлекаем ID пользователя из ответа
if userData, ok := result["user"].(map[string]interface{}); ok {
if id, ok := userData["id"].(float64); ok {
user.UserID = uint(id)
@@ -104,6 +129,11 @@ func (c *TestConfig) CreateTestUser(t *testing.T) *TestUser {
return user
}
// CleanupTestUser удаляет тестового пользователя через API
// Вызывается через defer после создания пользователя для очистки
// Параметры:
// - t: указатель на тест для логирования предупреждений
// - user: тестовый пользователь для удаления
func (c *TestConfig) CleanupTestUser(t *testing.T, user *TestUser) {
if user.Token != "" {
_, err := c.Request("DELETE", "/account", nil, user.Token)
@@ -113,6 +143,11 @@ func (c *TestConfig) CleanupTestUser(t *testing.T, user *TestUser) {
}
}
// GetAuthToken выполняет вход пользователя и возвращает JWT токен
// Параметры:
// - email: email пользователя
// - password: пароль пользователя
// Возвращает: JWT токен и ошибку
func (c *TestConfig) GetAuthToken(email, password string) (string, error) {
resp, err := c.Request("POST", "/auth/login", map[string]interface{}{
"email": email,