From d1e45c768617793daff327f3bd53606d45168dae Mon Sep 17 00:00:00 2001 From: valitovgaziz Date: Mon, 8 Jun 2026 01:58:04 +0500 Subject: [PATCH] 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 --- .../yalarba/api_yal/cmd/testrunner/main.go | 141 ++++++++++++------ .../yalarba/api_yal/cmd/testrunner/runner.go | 85 ++++++++--- .../api_yal/tests/integration/account_test.go | 16 +- .../api_yal/tests/integration/appeal_test.go | 34 +++-- .../api_yal/tests/integration/auth_test.go | 15 +- .../api_yal/tests/integration/comment_test.go | 12 ++ .../tests/integration/feedback_test.go | 16 +- .../api_yal/tests/integration/object_test.go | 19 ++- .../api_yal/tests/integration/rating_test.go | 15 +- .../yalarba/api_yal/tests/testutils/client.go | 1 - .../api_yal/tests/testutils/fixtures.go | 59 +++++--- .../yalarba/api_yal/tests/testutils/setup.go | 51 ++++++- 12 files changed, 346 insertions(+), 118 deletions(-) delete mode 100644 main_dc/yalarba/api_yal/tests/testutils/client.go diff --git a/main_dc/yalarba/api_yal/cmd/testrunner/main.go b/main_dc/yalarba/api_yal/cmd/testrunner/main.go index f932d2d..8f99a4f 100644 --- a/main_dc/yalarba/api_yal/cmd/testrunner/main.go +++ b/main_dc/yalarba/api_yal/cmd/testrunner/main.go @@ -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) } -} +} \ No newline at end of file diff --git a/main_dc/yalarba/api_yal/cmd/testrunner/runner.go b/main_dc/yalarba/api_yal/cmd/testrunner/runner.go index cf25d62..7d2cd81 100644 --- a/main_dc/yalarba/api_yal/cmd/testrunner/runner.go +++ b/main_dc/yalarba/api_yal/cmd/testrunner/runner.go @@ -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) diff --git a/main_dc/yalarba/api_yal/tests/integration/account_test.go b/main_dc/yalarba/api_yal/tests/integration/account_test.go index b837f4b..c50c88b 100644 --- a/main_dc/yalarba/api_yal/tests/integration/account_test.go +++ b/main_dc/yalarba/api_yal/tests/integration/account_test.go @@ -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) } }) -} +} \ No newline at end of file diff --git a/main_dc/yalarba/api_yal/tests/integration/appeal_test.go b/main_dc/yalarba/api_yal/tests/integration/appeal_test.go index 68ca8a9..1c5ee5e 100644 --- a/main_dc/yalarba/api_yal/tests/integration/appeal_test.go +++ b/main_dc/yalarba/api_yal/tests/integration/appeal_test.go @@ -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) diff --git a/main_dc/yalarba/api_yal/tests/integration/auth_test.go b/main_dc/yalarba/api_yal/tests/integration/auth_test.go index d0f57db..cf21971 100644 --- a/main_dc/yalarba/api_yal/tests/integration/auth_test.go +++ b/main_dc/yalarba/api_yal/tests/integration/auth_test.go @@ -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 { diff --git a/main_dc/yalarba/api_yal/tests/integration/comment_test.go b/main_dc/yalarba/api_yal/tests/integration/comment_test.go index 2ec49e6..6c97ef2 100644 --- a/main_dc/yalarba/api_yal/tests/integration/comment_test.go +++ b/main_dc/yalarba/api_yal/tests/integration/comment_test.go @@ -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) diff --git a/main_dc/yalarba/api_yal/tests/integration/feedback_test.go b/main_dc/yalarba/api_yal/tests/integration/feedback_test.go index 5c7b0f7..35c393a 100644 --- a/main_dc/yalarba/api_yal/tests/integration/feedback_test.go +++ b/main_dc/yalarba/api_yal/tests/integration/feedback_test.go @@ -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) diff --git a/main_dc/yalarba/api_yal/tests/integration/object_test.go b/main_dc/yalarba/api_yal/tests/integration/object_test.go index f9b539f..4eeb1f0 100644 --- a/main_dc/yalarba/api_yal/tests/integration/object_test.go +++ b/main_dc/yalarba/api_yal/tests/integration/object_test.go @@ -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) } }) -} +} \ No newline at end of file diff --git a/main_dc/yalarba/api_yal/tests/integration/rating_test.go b/main_dc/yalarba/api_yal/tests/integration/rating_test.go index efb24a0..062e2dc 100644 --- a/main_dc/yalarba/api_yal/tests/integration/rating_test.go +++ b/main_dc/yalarba/api_yal/tests/integration/rating_test.go @@ -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) } diff --git a/main_dc/yalarba/api_yal/tests/testutils/client.go b/main_dc/yalarba/api_yal/tests/testutils/client.go deleted file mode 100644 index 8cbf20b..0000000 --- a/main_dc/yalarba/api_yal/tests/testutils/client.go +++ /dev/null @@ -1 +0,0 @@ -package testutils \ No newline at end of file diff --git a/main_dc/yalarba/api_yal/tests/testutils/fixtures.go b/main_dc/yalarba/api_yal/tests/testutils/fixtures.go index 5e22bca..2939f09 100644 --- a/main_dc/yalarba/api_yal/tests/testutils/fixtures.go +++ b/main_dc/yalarba/api_yal/tests/testutils/fixtures.go @@ -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, diff --git a/main_dc/yalarba/api_yal/tests/testutils/setup.go b/main_dc/yalarba/api_yal/tests/testutils/setup.go index 52fb9ef..8b2947a 100644 --- a/main_dc/yalarba/api_yal/tests/testutils/setup.go +++ b/main_dc/yalarba/api_yal/tests/testutils/setup.go @@ -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,