1362 lines
48 KiB
Go
1362 lines
48 KiB
Go
package e2e
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"testing"
|
||
"time"
|
||
|
||
"git.kirlllll.ru/volontery/backend/internal/api"
|
||
"git.kirlllll.ru/volontery/backend/internal/config"
|
||
"git.kirlllll.ru/volontery/backend/internal/database"
|
||
"git.kirlllll.ru/volontery/backend/internal/pkg/jwt"
|
||
"git.kirlllll.ru/volontery/backend/internal/repository"
|
||
"git.kirlllll.ru/volontery/backend/internal/service"
|
||
"github.com/jackc/pgx/v5/pgtype"
|
||
"github.com/jackc/pgx/v5/pgxpool"
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
"github.com/stretchr/testify/suite"
|
||
)
|
||
|
||
// APITestSuite - набор E2E тестов для API
|
||
type APITestSuite struct {
|
||
suite.Suite
|
||
server *api.Server
|
||
db *pgxpool.Pool
|
||
queries *database.Queries
|
||
cfg *config.Config
|
||
accessToken string
|
||
userID int64
|
||
}
|
||
|
||
// SetupSuite запускается один раз перед всеми тестами
|
||
func (suite *APITestSuite) SetupSuite() {
|
||
// Загрузка конфигурации
|
||
cfg, err := config.Load()
|
||
require.NoError(suite.T(), err)
|
||
suite.cfg = cfg
|
||
|
||
// Подключение к БД
|
||
ctx := context.Background()
|
||
db, err := pgxpool.New(ctx, cfg.DatabaseURL)
|
||
require.NoError(suite.T(), err)
|
||
suite.db = db
|
||
|
||
suite.queries = database.New(db)
|
||
|
||
// Инициализация зависимостей
|
||
jwtManager := jwt.NewManager(cfg.JWTSecret, cfg.JWTAccessTokenTTL, cfg.JWTRefreshTokenTTL)
|
||
require.NoError(suite.T(), err)
|
||
|
||
authRepo := repository.NewAuthRepository(suite.queries)
|
||
userRepo := repository.NewUserRepository(suite.queries)
|
||
rbacRepo := repository.NewRBACRepository(suite.queries)
|
||
authService := service.NewAuthService(userRepo, authRepo, rbacRepo, jwtManager)
|
||
userService := service.NewUserService(userRepo, rbacRepo)
|
||
requestRepo := repository.NewRequestRepository(suite.queries)
|
||
requestService := service.NewRequestService(requestRepo)
|
||
|
||
// Создание сервера
|
||
suite.server = api.NewServer(cfg, authService, userService, requestService, jwtManager)
|
||
}
|
||
|
||
// TearDownSuite запускается один раз после всех тестов
|
||
func (suite *APITestSuite) TearDownSuite() {
|
||
if suite.db != nil {
|
||
suite.db.Close()
|
||
}
|
||
}
|
||
|
||
// TestAPITestSuite - точка входа для запуска набора тестов
|
||
func TestAPITestSuite(t *testing.T) {
|
||
suite.Run(t, new(APITestSuite))
|
||
}
|
||
|
||
// Helper функции
|
||
|
||
// createModerator создает пользователя с ролью moderator
|
||
func (suite *APITestSuite) createModerator() (userID int64, token string) {
|
||
// Создаем пользователя
|
||
email := fmt.Sprintf("moderator_%d@example.com", time.Now().UnixNano())
|
||
registerReq := map[string]interface{}{
|
||
"email": email,
|
||
"password": "SecureP@ssw0rd123",
|
||
"first_name": "Moderator",
|
||
"last_name": "User",
|
||
}
|
||
|
||
_, regBody := suite.makeRequest("POST", "/api/v1/auth/register", registerReq, "")
|
||
var authResp map[string]interface{}
|
||
err := json.Unmarshal(regBody, &authResp)
|
||
require.NoError(suite.T(), err)
|
||
|
||
token = authResp["access_token"].(string)
|
||
user := authResp["user"].(map[string]interface{})
|
||
userID = int64(user["id"].(float64))
|
||
|
||
// Получаем роль moderator
|
||
role, err := suite.queries.GetRoleByName(context.Background(), "moderator")
|
||
require.NoError(suite.T(), err)
|
||
|
||
// Назначаем роль модератора
|
||
_, err = suite.queries.AssignRoleToUser(context.Background(), database.AssignRoleToUserParams{
|
||
UserID: userID,
|
||
RoleID: role.ID,
|
||
AssignedBy: pgtype.Int8{
|
||
Int64: userID,
|
||
Valid: true,
|
||
}, // self-assigned for testing
|
||
})
|
||
require.NoError(suite.T(), err)
|
||
|
||
return userID, token
|
||
}
|
||
|
||
// createApprovedRequest создает заявку и одобряет её
|
||
func (suite *APITestSuite) createApprovedRequest(requesterToken string) int64 {
|
||
// Получаем типы заявок
|
||
_, body := suite.makeRequest("GET", "/api/v1/request-types", nil, "")
|
||
var types []map[string]interface{}
|
||
json.Unmarshal(body, &types)
|
||
require.NotEmpty(suite.T(), types)
|
||
|
||
requestTypeID := int64(types[0]["id"].(float64))
|
||
|
||
// Создаем заявку
|
||
createReq := map[string]interface{}{
|
||
"request_type_id": requestTypeID,
|
||
"title": fmt.Sprintf("Test Request %d", time.Now().UnixNano()),
|
||
"description": "Test description",
|
||
"latitude": 55.751244,
|
||
"longitude": 37.618423,
|
||
"address": "Test Address",
|
||
"city": "Москва",
|
||
"urgency": "medium",
|
||
}
|
||
|
||
_, respBody := suite.makeRequest("POST", "/api/v1/requests", createReq, requesterToken)
|
||
var request map[string]interface{}
|
||
json.Unmarshal(respBody, &request)
|
||
requestID := int64(request["id"].(float64))
|
||
|
||
// Создаем модератора и одобряем заявку
|
||
moderatorID, moderatorToken := suite.createModerator()
|
||
url := fmt.Sprintf("/api/v1/moderation/requests/%d/approve", requestID)
|
||
approveReq := map[string]interface{}{
|
||
"comment": "Approved for testing",
|
||
}
|
||
resp, _ := suite.makeRequest("POST", url, approveReq, moderatorToken)
|
||
require.Equal(suite.T(), http.StatusOK, resp.StatusCode, "Failed to approve request, moderator_id=%d", moderatorID)
|
||
|
||
return requestID
|
||
}
|
||
|
||
// createVolunteerWithResponse создает волонтера и отклик
|
||
func (suite *APITestSuite) createVolunteerWithResponse(requestID int64) (volunteerID int64, responseID int64, token string) {
|
||
// Создаем волонтера
|
||
email := fmt.Sprintf("volunteer_%d@example.com", time.Now().UnixNano())
|
||
registerReq := map[string]interface{}{
|
||
"email": email,
|
||
"password": "SecureP@ssw0rd123",
|
||
"first_name": "Volunteer",
|
||
"last_name": "User",
|
||
}
|
||
|
||
_, regBody := suite.makeRequest("POST", "/api/v1/auth/register", registerReq, "")
|
||
var authResp map[string]interface{}
|
||
json.Unmarshal(regBody, &authResp)
|
||
token = authResp["access_token"].(string)
|
||
user := authResp["user"].(map[string]interface{})
|
||
volunteerID = int64(user["id"].(float64))
|
||
|
||
// Создаем отклик
|
||
responseReq := map[string]interface{}{
|
||
"message": "I can help with this request",
|
||
}
|
||
url := fmt.Sprintf("/api/v1/requests/%d/responses", requestID)
|
||
_, respBody := suite.makeRequest("POST", url, responseReq, token)
|
||
var response map[string]interface{}
|
||
json.Unmarshal(respBody, &response)
|
||
responseID = int64(response["id"].(float64))
|
||
|
||
return volunteerID, responseID, token
|
||
}
|
||
|
||
// checkModeratorAction проверяет наличие записи в moderator_actions
|
||
func (suite *APITestSuite) checkModeratorAction(moderatorID, requestID int64, actionType string) {
|
||
actions, err := suite.queries.GetModeratorActionsByRequest(context.Background(), pgtype.Int8{
|
||
Int64: requestID,
|
||
Valid: true,
|
||
})
|
||
require.NoError(suite.T(), err)
|
||
|
||
found := false
|
||
for _, action := range actions {
|
||
if action.ModeratorID == moderatorID && string(action.ActionType) == actionType {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
assert.True(suite.T(), found, "Moderator action not found: moderator_id=%d, request_id=%d, action_type=%s", moderatorID, requestID, actionType)
|
||
}
|
||
|
||
// checkRequestStatusHistory проверяет историю статусов
|
||
func (suite *APITestSuite) checkRequestStatusHistory(requestID int64, expectedStatuses []string) {
|
||
history, err := suite.queries.GetRequestStatusHistory(context.Background(), requestID)
|
||
require.NoError(suite.T(), err)
|
||
|
||
// Получаем список статусов из истории (в обратном порядке - от новых к старым)
|
||
var actualStatuses []string
|
||
for i := len(history) - 1; i >= 0; i-- {
|
||
actualStatuses = append(actualStatuses, string(history[i].ToStatus))
|
||
}
|
||
|
||
// Проверяем, что все ожидаемые статусы присутствуют
|
||
for _, expectedStatus := range expectedStatuses {
|
||
found := false
|
||
for _, actualStatus := range actualStatuses {
|
||
if actualStatus == expectedStatus {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
assert.True(suite.T(), found, "Expected status %s not found in history for request %d. Actual: %v", expectedStatus, requestID, actualStatuses)
|
||
}
|
||
}
|
||
|
||
func (suite *APITestSuite) makeRequest(method, path string, body interface{}, token string) (*http.Response, []byte) {
|
||
var bodyReader io.Reader
|
||
if body != nil {
|
||
jsonBody, err := json.Marshal(body)
|
||
require.NoError(suite.T(), err)
|
||
bodyReader = bytes.NewReader(jsonBody)
|
||
}
|
||
|
||
req := httptest.NewRequest(method, path, bodyReader)
|
||
if bodyReader != nil {
|
||
req.Header.Set("Content-Type", "application/json")
|
||
}
|
||
if token != "" {
|
||
req.Header.Set("Authorization", "Bearer "+token)
|
||
}
|
||
|
||
w := httptest.NewRecorder()
|
||
suite.server.ServeHTTP(w, req)
|
||
|
||
resp := w.Result()
|
||
respBody, err := io.ReadAll(resp.Body)
|
||
require.NoError(suite.T(), err)
|
||
defer resp.Body.Close()
|
||
|
||
return resp, respBody
|
||
}
|
||
|
||
// Test 1: Health Check
|
||
func (suite *APITestSuite) Test01_HealthCheck() {
|
||
suite.T().Log("Testing: GET /health")
|
||
|
||
resp, body := suite.makeRequest("GET", "/health", nil, "")
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
assert.Equal(suite.T(), "OK", string(body))
|
||
}
|
||
|
||
// Test 2: User Registration
|
||
func (suite *APITestSuite) Test02_Register() {
|
||
suite.T().Log("Testing: POST /api/v1/auth/register")
|
||
|
||
registerReq := map[string]interface{}{
|
||
"email": fmt.Sprintf("test_%d@example.com", time.Now().Unix()),
|
||
"password": "SecureP@ssw0rd123",
|
||
"first_name": "Test",
|
||
"last_name": "User",
|
||
"phone": "+79001234567",
|
||
"latitude": 55.751244,
|
||
"longitude": 37.618423,
|
||
"city": "Москва",
|
||
"bio": "Test bio",
|
||
}
|
||
|
||
resp, body := suite.makeRequest("POST", "/api/v1/auth/register", registerReq, "")
|
||
|
||
assert.Equal(suite.T(), http.StatusCreated, resp.StatusCode)
|
||
assert.Equal(suite.T(), "application/json", resp.Header.Get("Content-Type"))
|
||
|
||
var authResp map[string]interface{}
|
||
err := json.Unmarshal(body, &authResp)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), authResp, "access_token")
|
||
assert.Contains(suite.T(), authResp, "refresh_token")
|
||
assert.Contains(suite.T(), authResp, "expires_in")
|
||
assert.Contains(suite.T(), authResp, "user")
|
||
|
||
// Сохраняем токен для последующих тестов
|
||
suite.accessToken = authResp["access_token"].(string)
|
||
|
||
user := authResp["user"].(map[string]interface{})
|
||
suite.userID = int64(user["id"].(float64))
|
||
}
|
||
|
||
// Test 3: Login
|
||
func (suite *APITestSuite) Test03_Login() {
|
||
suite.T().Log("Testing: POST /api/v1/auth/login")
|
||
|
||
// Сначала регистрируем пользователя
|
||
email := fmt.Sprintf("login_test_%d@example.com", time.Now().Unix())
|
||
password := "SecureP@ssw0rd123"
|
||
|
||
registerReq := map[string]interface{}{
|
||
"email": email,
|
||
"password": password,
|
||
"first_name": "Login",
|
||
"last_name": "Test User",
|
||
}
|
||
|
||
suite.makeRequest("POST", "/api/v1/auth/register", registerReq, "")
|
||
|
||
// Теперь логинимся
|
||
loginReq := map[string]interface{}{
|
||
"email": email,
|
||
"password": password,
|
||
}
|
||
|
||
resp, body := suite.makeRequest("POST", "/api/v1/auth/login", loginReq, "")
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var authResp map[string]interface{}
|
||
err := json.Unmarshal(body, &authResp)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), authResp, "access_token")
|
||
assert.Contains(suite.T(), authResp, "refresh_token")
|
||
}
|
||
|
||
// Test 4: Login with wrong credentials
|
||
func (suite *APITestSuite) Test04_Login_WrongCredentials() {
|
||
suite.T().Log("Testing: POST /api/v1/auth/login (invalid credentials)")
|
||
|
||
loginReq := map[string]interface{}{
|
||
"email": "nonexistent@example.com",
|
||
"password": "wrongpassword",
|
||
}
|
||
|
||
resp, body := suite.makeRequest("POST", "/api/v1/auth/login", loginReq, "")
|
||
|
||
assert.Equal(suite.T(), http.StatusUnauthorized, resp.StatusCode)
|
||
|
||
var errorResp map[string]interface{}
|
||
err := json.Unmarshal(body, &errorResp)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), errorResp, "error")
|
||
}
|
||
|
||
// Test 5: Get current user info
|
||
func (suite *APITestSuite) Test05_AuthMe() {
|
||
suite.T().Log("Testing: GET /api/v1/auth/me")
|
||
|
||
resp, body := suite.makeRequest("GET", "/api/v1/auth/me", nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var userInfo map[string]interface{}
|
||
err := json.Unmarshal(body, &userInfo)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), userInfo, "id")
|
||
assert.Contains(suite.T(), userInfo, "email")
|
||
assert.Equal(suite.T(), float64(suite.userID), userInfo["id"].(float64))
|
||
}
|
||
|
||
// Test 6: Get current user profile
|
||
func (suite *APITestSuite) Test06_GetMyProfile() {
|
||
suite.T().Log("Testing: GET /api/v1/users/me")
|
||
|
||
resp, body := suite.makeRequest("GET", "/api/v1/users/me", nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var profile map[string]interface{}
|
||
err := json.Unmarshal(body, &profile)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), profile, "id")
|
||
assert.Contains(suite.T(), profile, "email")
|
||
assert.Contains(suite.T(), profile, "first_name")
|
||
assert.Contains(suite.T(), profile, "last_name")
|
||
assert.Contains(suite.T(), profile, "created_at")
|
||
}
|
||
|
||
// Test 7: Update profile
|
||
func (suite *APITestSuite) Test07_UpdateProfile() {
|
||
suite.T().Log("Testing: PATCH /api/v1/users/me")
|
||
|
||
updateReq := map[string]interface{}{
|
||
"first_name": "Updated",
|
||
"last_name": "Name",
|
||
"city": "Санкт-Петербург",
|
||
}
|
||
|
||
resp, body := suite.makeRequest("PATCH", "/api/v1/users/me", updateReq, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var result map[string]interface{}
|
||
err := json.Unmarshal(body, &result)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), result, "message")
|
||
}
|
||
|
||
// Test 8: Update location
|
||
func (suite *APITestSuite) Test08_UpdateLocation() {
|
||
suite.T().Log("Testing: POST /api/v1/users/me/location")
|
||
|
||
locationReq := map[string]interface{}{
|
||
"latitude": 59.9343,
|
||
"longitude": 30.3351,
|
||
}
|
||
|
||
resp, body := suite.makeRequest("POST", "/api/v1/users/me/location", locationReq, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var result map[string]interface{}
|
||
err := json.Unmarshal(body, &result)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), result, "message")
|
||
}
|
||
|
||
// Test 9: Get user roles
|
||
func (suite *APITestSuite) Test09_GetMyRoles() {
|
||
suite.T().Log("Testing: GET /api/v1/users/me/roles")
|
||
|
||
resp, body := suite.makeRequest("GET", "/api/v1/users/me/roles", nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var roles []map[string]interface{}
|
||
err := json.Unmarshal(body, &roles)
|
||
require.NoError(suite.T(), err)
|
||
|
||
// Пользователь должен иметь хотя бы одну роль
|
||
assert.NotEmpty(suite.T(), roles)
|
||
}
|
||
|
||
// Test 10: Get user permissions
|
||
func (suite *APITestSuite) Test10_GetMyPermissions() {
|
||
suite.T().Log("Testing: GET /api/v1/users/me/permissions")
|
||
|
||
resp, body := suite.makeRequest("GET", "/api/v1/users/me/permissions", nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var permissions []map[string]interface{}
|
||
err := json.Unmarshal(body, &permissions)
|
||
require.NoError(suite.T(), err)
|
||
|
||
// Пользователь должен иметь разрешения
|
||
assert.NotEmpty(suite.T(), permissions)
|
||
}
|
||
|
||
// Test 11: Get request types
|
||
func (suite *APITestSuite) Test11_GetRequestTypes() {
|
||
suite.T().Log("Testing: GET /api/v1/request-types")
|
||
|
||
resp, body := suite.makeRequest("GET", "/api/v1/request-types", nil, "")
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var types []map[string]interface{}
|
||
err := json.Unmarshal(body, &types)
|
||
require.NoError(suite.T(), err)
|
||
|
||
// Должны быть типы заявок в БД
|
||
assert.NotEmpty(suite.T(), types)
|
||
|
||
// Проверяем структуру
|
||
if len(types) > 0 {
|
||
assert.Contains(suite.T(), types[0], "id")
|
||
assert.Contains(suite.T(), types[0], "name")
|
||
}
|
||
}
|
||
|
||
// Test 12: Create request
|
||
func (suite *APITestSuite) Test12_CreateRequest() {
|
||
suite.T().Log("Testing: POST /api/v1/requests")
|
||
|
||
// Получаем типы заявок
|
||
_, body := suite.makeRequest("GET", "/api/v1/request-types", nil, "")
|
||
var types []map[string]interface{}
|
||
json.Unmarshal(body, &types)
|
||
require.NotEmpty(suite.T(), types)
|
||
|
||
requestTypeID := int64(types[0]["id"].(float64))
|
||
|
||
createReq := map[string]interface{}{
|
||
"request_type_id": requestTypeID,
|
||
"title": "Нужна помощь с покупкой продуктов",
|
||
"description": "Прошу помочь купить продукты в ближайшем магазине. Список прилагается.",
|
||
"latitude": 55.751244,
|
||
"longitude": 37.618423,
|
||
"address": "ул. Тверская, д. 1, кв. 10",
|
||
"city": "Москва",
|
||
"urgency": "high",
|
||
"contact_phone": "+79001234567",
|
||
"contact_notes": "Код домофона: 123",
|
||
"desired_completion_date": time.Now().Add(24 * time.Hour).Format(time.RFC3339),
|
||
}
|
||
|
||
resp, body := suite.makeRequest("POST", "/api/v1/requests", createReq, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusCreated, resp.StatusCode)
|
||
|
||
var request map[string]interface{}
|
||
err := json.Unmarshal(body, &request)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), request, "id")
|
||
assert.Contains(suite.T(), request, "title")
|
||
assert.Contains(suite.T(), request, "status")
|
||
}
|
||
|
||
// Test 13: Get my requests
|
||
func (suite *APITestSuite) Test13_GetMyRequests() {
|
||
suite.T().Log("Testing: GET /api/v1/requests/my")
|
||
|
||
resp, body := suite.makeRequest("GET", "/api/v1/requests/my?limit=10&offset=0", nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var requests []map[string]interface{}
|
||
err := json.Unmarshal(body, &requests)
|
||
require.NoError(suite.T(), err)
|
||
|
||
// Должна быть хотя бы одна заявка (созданная в предыдущем тесте)
|
||
assert.NotEmpty(suite.T(), requests)
|
||
}
|
||
|
||
// Test 14: Find nearby requests
|
||
func (suite *APITestSuite) Test14_FindNearbyRequests() {
|
||
suite.T().Log("Testing: GET /api/v1/requests/nearby")
|
||
|
||
url := "/api/v1/requests/nearby?lat=55.751244&lon=37.618423&radius=5000&limit=10&offset=0"
|
||
resp, body := suite.makeRequest("GET", url, nil, suite.accessToken)
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
suite.T().Logf("Error response body: %s", string(body))
|
||
}
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var requests []map[string]interface{}
|
||
err := json.Unmarshal(body, &requests)
|
||
require.NoError(suite.T(), err)
|
||
|
||
// Проверяем наличие distance_meters в ответе
|
||
if len(requests) > 0 {
|
||
assert.Contains(suite.T(), requests[0], "distance_meters")
|
||
}
|
||
}
|
||
|
||
// Test 15: Find requests in bounds
|
||
func (suite *APITestSuite) Test15_FindRequestsInBounds() {
|
||
suite.T().Log("Testing: GET /api/v1/requests/bounds")
|
||
|
||
url := "/api/v1/requests/bounds?min_lon=37.6&min_lat=55.7&max_lon=37.7&max_lat=55.8"
|
||
resp, body := suite.makeRequest("GET", url, nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var requests []map[string]interface{}
|
||
err := json.Unmarshal(body, &requests)
|
||
require.NoError(suite.T(), err)
|
||
|
||
// Ответ должен быть массивом
|
||
assert.NotNil(suite.T(), requests)
|
||
}
|
||
|
||
// Test 16: Get request by ID
|
||
func (suite *APITestSuite) Test16_GetRequestByID() {
|
||
suite.T().Log("Testing: GET /api/v1/requests/{id}")
|
||
|
||
// Получаем список своих заявок
|
||
_, body := suite.makeRequest("GET", "/api/v1/requests/my", nil, suite.accessToken)
|
||
var requests []map[string]interface{}
|
||
json.Unmarshal(body, &requests)
|
||
require.NotEmpty(suite.T(), requests)
|
||
|
||
requestID := int64(requests[0]["id"].(float64))
|
||
|
||
// Получаем заявку по ID
|
||
url := fmt.Sprintf("/api/v1/requests/%d", requestID)
|
||
resp, body := suite.makeRequest("GET", url, nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var request map[string]interface{}
|
||
err := json.Unmarshal(body, &request)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Equal(suite.T(), float64(requestID), request["id"].(float64))
|
||
assert.Contains(suite.T(), request, "title")
|
||
assert.Contains(suite.T(), request, "description")
|
||
}
|
||
|
||
// Test 17: Create volunteer response
|
||
func (suite *APITestSuite) Test17_CreateVolunteerResponse() {
|
||
suite.T().Log("Testing: POST /api/v1/requests/{id}/responses")
|
||
|
||
// Создаем нового волонтера
|
||
email := fmt.Sprintf("volunteer_%d@example.com", time.Now().Unix())
|
||
registerReq := map[string]interface{}{
|
||
"email": email,
|
||
"password": "SecureP@ssw0rd123",
|
||
"first_name": "Volunteer",
|
||
"last_name": "User",
|
||
}
|
||
|
||
_, regBody := suite.makeRequest("POST", "/api/v1/auth/register", registerReq, "")
|
||
var authResp map[string]interface{}
|
||
json.Unmarshal(regBody, &authResp)
|
||
volunteerToken := authResp["access_token"].(string)
|
||
|
||
// Получаем список заявок
|
||
_, body := suite.makeRequest("GET", "/api/v1/requests/my", nil, suite.accessToken)
|
||
var requests []map[string]interface{}
|
||
json.Unmarshal(body, &requests)
|
||
require.NotEmpty(suite.T(), requests)
|
||
|
||
requestID := int64(requests[0]["id"].(float64))
|
||
|
||
// Создаем отклик
|
||
responseReq := map[string]interface{}{
|
||
"message": "Готов помочь завтра после 15:00",
|
||
}
|
||
|
||
url := fmt.Sprintf("/api/v1/requests/%d/responses", requestID)
|
||
resp, body := suite.makeRequest("POST", url, responseReq, volunteerToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusCreated, resp.StatusCode)
|
||
|
||
var response map[string]interface{}
|
||
err := json.Unmarshal(body, &response)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), response, "id")
|
||
assert.Contains(suite.T(), response, "volunteer_id")
|
||
assert.Contains(suite.T(), response, "status")
|
||
}
|
||
|
||
// Test 18: Get request responses
|
||
func (suite *APITestSuite) Test18_GetRequestResponses() {
|
||
suite.T().Log("Testing: GET /api/v1/requests/{id}/responses")
|
||
|
||
// Получаем список своих заявок
|
||
_, body := suite.makeRequest("GET", "/api/v1/requests/my", nil, suite.accessToken)
|
||
var requests []map[string]interface{}
|
||
json.Unmarshal(body, &requests)
|
||
require.NotEmpty(suite.T(), requests)
|
||
|
||
requestID := int64(requests[0]["id"].(float64))
|
||
|
||
// Получаем отклики
|
||
url := fmt.Sprintf("/api/v1/requests/%d/responses", requestID)
|
||
resp, body := suite.makeRequest("GET", url, nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var responses []map[string]interface{}
|
||
err := json.Unmarshal(body, &responses)
|
||
require.NoError(suite.T(), err)
|
||
|
||
// Должен быть хотя бы один отклик (созданный в предыдущем тесте)
|
||
assert.NotEmpty(suite.T(), responses)
|
||
|
||
if len(responses) > 0 {
|
||
assert.Contains(suite.T(), responses[0], "volunteer_id")
|
||
assert.Contains(suite.T(), responses[0], "status")
|
||
}
|
||
}
|
||
|
||
// Test 19: Refresh token
|
||
func (suite *APITestSuite) Test19_RefreshToken() {
|
||
suite.T().Log("Testing: POST /api/v1/auth/refresh")
|
||
|
||
// Регистрируем нового пользователя
|
||
email := fmt.Sprintf("refresh_test_%d@example.com", time.Now().Unix())
|
||
registerReq := map[string]interface{}{
|
||
"email": email,
|
||
"password": "SecureP@ssw0rd123",
|
||
"first_name": "Refresh",
|
||
"last_name": "Test User",
|
||
}
|
||
|
||
_, regBody := suite.makeRequest("POST", "/api/v1/auth/register", registerReq, "")
|
||
var authResp map[string]interface{}
|
||
json.Unmarshal(regBody, &authResp)
|
||
refreshToken := authResp["refresh_token"].(string)
|
||
|
||
// Обновляем токен
|
||
refreshReq := map[string]interface{}{
|
||
"refresh_token": refreshToken,
|
||
}
|
||
|
||
resp, body := suite.makeRequest("POST", "/api/v1/auth/refresh", refreshReq, "")
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var newAuthResp map[string]interface{}
|
||
err := json.Unmarshal(body, &newAuthResp)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), newAuthResp, "access_token")
|
||
assert.Contains(suite.T(), newAuthResp, "refresh_token")
|
||
assert.NotEqual(suite.T(), refreshToken, newAuthResp["refresh_token"].(string))
|
||
}
|
||
|
||
// Test 20: Logout
|
||
func (suite *APITestSuite) Test20_Logout() {
|
||
suite.T().Log("Testing: POST /api/v1/auth/logout")
|
||
|
||
resp, body := suite.makeRequest("POST", "/api/v1/auth/logout", nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var result map[string]interface{}
|
||
err := json.Unmarshal(body, &result)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), result, "message")
|
||
}
|
||
|
||
// Test 21: Unauthorized access
|
||
func (suite *APITestSuite) Test21_UnauthorizedAccess() {
|
||
suite.T().Log("Testing: Unauthorized access to protected endpoint")
|
||
|
||
resp, body := suite.makeRequest("GET", "/api/v1/users/me", nil, "")
|
||
|
||
assert.Equal(suite.T(), http.StatusUnauthorized, resp.StatusCode)
|
||
|
||
var errorResp map[string]interface{}
|
||
err := json.Unmarshal(body, &errorResp)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), errorResp, "error")
|
||
}
|
||
|
||
// Test 22: Invalid token
|
||
func (suite *APITestSuite) Test22_InvalidToken() {
|
||
suite.T().Log("Testing: Invalid token")
|
||
|
||
resp, body := suite.makeRequest("GET", "/api/v1/users/me", nil, "invalid.token.here")
|
||
|
||
assert.Equal(suite.T(), http.StatusUnauthorized, resp.StatusCode)
|
||
|
||
var errorResp map[string]interface{}
|
||
err := json.Unmarshal(body, &errorResp)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), errorResp, "error")
|
||
}
|
||
|
||
// Test 23: Get user by ID
|
||
func (suite *APITestSuite) Test23_GetUserByID() {
|
||
suite.T().Log("Testing: GET /api/v1/users/{id}")
|
||
|
||
url := fmt.Sprintf("/api/v1/users/%d", suite.userID)
|
||
resp, body := suite.makeRequest("GET", url, nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var profile map[string]interface{}
|
||
err := json.Unmarshal(body, &profile)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Equal(suite.T(), float64(suite.userID), profile["id"].(float64))
|
||
assert.Contains(suite.T(), profile, "email")
|
||
assert.Contains(suite.T(), profile, "first_name")
|
||
assert.Contains(suite.T(), profile, "last_name")
|
||
}
|
||
|
||
// Test 24: Invalid request body
|
||
func (suite *APITestSuite) Test24_InvalidRequestBody() {
|
||
suite.T().Log("Testing: Invalid request body")
|
||
|
||
// Пытаемся зарегистрироваться с невалидными данными
|
||
registerReq := map[string]interface{}{
|
||
"email": "invalid-email", // невалидный email
|
||
}
|
||
|
||
resp, body := suite.makeRequest("POST", "/api/v1/auth/register", registerReq, "")
|
||
|
||
assert.Equal(suite.T(), http.StatusBadRequest, resp.StatusCode)
|
||
|
||
var errorResp map[string]interface{}
|
||
err := json.Unmarshal(body, &errorResp)
|
||
require.NoError(suite.T(), err)
|
||
|
||
assert.Contains(suite.T(), errorResp, "error")
|
||
}
|
||
|
||
// Test 25: Pagination
|
||
func (suite *APITestSuite) Test25_Pagination() {
|
||
suite.T().Log("Testing: Pagination parameters")
|
||
|
||
// Тест с разными параметрами пагинации
|
||
testCases := []struct {
|
||
limit int
|
||
offset int
|
||
}{
|
||
{10, 0},
|
||
{5, 5},
|
||
{20, 0},
|
||
}
|
||
|
||
for _, tc := range testCases {
|
||
url := fmt.Sprintf("/api/v1/requests/my?limit=%d&offset=%d", tc.limit, tc.offset)
|
||
resp, _ := suite.makeRequest("GET", url, nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// ТЕСТЫ МОДЕРАЦИИ (Test26-Test32)
|
||
// ============================================================================
|
||
|
||
// Test 26: Full moderation workflow
|
||
func (suite *APITestSuite) Test26_ModerationWorkflow() {
|
||
suite.T().Log("Testing: Full moderation workflow")
|
||
|
||
// 1. Создаем заявку (автоматически pending_moderation)
|
||
_, body := suite.makeRequest("GET", "/api/v1/request-types", nil, "")
|
||
var types []map[string]interface{}
|
||
json.Unmarshal(body, &types)
|
||
require.NotEmpty(suite.T(), types)
|
||
|
||
requestTypeID := int64(types[0]["id"].(float64))
|
||
createReq := map[string]interface{}{
|
||
"request_type_id": requestTypeID,
|
||
"title": "Moderation Test Request",
|
||
"description": "This request needs moderation",
|
||
"latitude": 55.751244,
|
||
"longitude": 37.618423,
|
||
"address": "Test Address",
|
||
}
|
||
|
||
_, respBody := suite.makeRequest("POST", "/api/v1/requests", createReq, suite.accessToken)
|
||
var request map[string]interface{}
|
||
json.Unmarshal(respBody, &request)
|
||
requestID := int64(request["id"].(float64))
|
||
|
||
// status - это объект NullRequestStatus {request_status: "...", valid: true}
|
||
statusObj := request["status"].(map[string]interface{})
|
||
assert.Equal(suite.T(), "pending_moderation", statusObj["request_status"])
|
||
|
||
// 2. Создаем модератора
|
||
moderatorID, moderatorToken := suite.createModerator()
|
||
|
||
// 3. Получаем список заявок на модерации
|
||
resp, body := suite.makeRequest("GET", "/api/v1/moderation/requests/pending?limit=10&offset=0", nil, moderatorToken)
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var pendingRequests []map[string]interface{}
|
||
json.Unmarshal(body, &pendingRequests)
|
||
assert.NotEmpty(suite.T(), pendingRequests)
|
||
|
||
// 4. Одобряем заявку
|
||
url := fmt.Sprintf("/api/v1/moderation/requests/%d/approve", requestID)
|
||
approveReq := map[string]interface{}{
|
||
"comment": "Request looks good",
|
||
}
|
||
resp, _ = suite.makeRequest("POST", url, approveReq, moderatorToken)
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
// 5. Проверяем статус заявки
|
||
url = fmt.Sprintf("/api/v1/requests/%d", requestID)
|
||
resp, body = suite.makeRequest("GET", url, nil, suite.accessToken)
|
||
json.Unmarshal(body, &request)
|
||
statusObj = request["status"].(map[string]interface{})
|
||
assert.Equal(suite.T(), "approved", statusObj["request_status"])
|
||
|
||
// 6. Проверяем запись в moderator_actions
|
||
suite.checkModeratorAction(moderatorID, requestID, "approve_request")
|
||
|
||
// 7. Проверяем историю статусов
|
||
suite.checkRequestStatusHistory(requestID, []string{"pending_moderation", "approved"})
|
||
}
|
||
|
||
// Test 27: Approve request
|
||
func (suite *APITestSuite) Test27_ModerationApprove() {
|
||
suite.T().Log("Testing: POST /api/v1/moderation/requests/{id}/approve")
|
||
|
||
// Создаем заявку
|
||
_, body := suite.makeRequest("GET", "/api/v1/request-types", nil, "")
|
||
var types []map[string]interface{}
|
||
json.Unmarshal(body, &types)
|
||
requestTypeID := int64(types[0]["id"].(float64))
|
||
|
||
createReq := map[string]interface{}{
|
||
"request_type_id": requestTypeID,
|
||
"title": "Approve Test",
|
||
"description": "Test",
|
||
"latitude": 55.751244,
|
||
"longitude": 37.618423,
|
||
"address": "Test",
|
||
}
|
||
_, respBody := suite.makeRequest("POST", "/api/v1/requests", createReq, suite.accessToken)
|
||
var request map[string]interface{}
|
||
json.Unmarshal(respBody, &request)
|
||
requestID := int64(request["id"].(float64))
|
||
|
||
// Модератор одобряет
|
||
moderatorID, moderatorToken := suite.createModerator()
|
||
url := fmt.Sprintf("/api/v1/moderation/requests/%d/approve", requestID)
|
||
approveReq := map[string]interface{}{}
|
||
resp, body := suite.makeRequest("POST", url, approveReq, moderatorToken)
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
suite.T().Logf("Approve failed with status %d, body: %s", resp.StatusCode, string(body))
|
||
}
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
// Проверяем статус
|
||
url = fmt.Sprintf("/api/v1/requests/%d", requestID)
|
||
_, body = suite.makeRequest("GET", url, nil, suite.accessToken)
|
||
json.Unmarshal(body, &request)
|
||
statusObj := request["status"].(map[string]interface{})
|
||
assert.Equal(suite.T(), "approved", statusObj["request_status"])
|
||
|
||
suite.checkModeratorAction(moderatorID, requestID, "approve_request")
|
||
}
|
||
|
||
// Test 28: Reject request
|
||
func (suite *APITestSuite) Test28_ModerationReject() {
|
||
suite.T().Log("Testing: POST /api/v1/moderation/requests/{id}/reject")
|
||
|
||
// Создаем заявку
|
||
_, body := suite.makeRequest("GET", "/api/v1/request-types", nil, "")
|
||
var types []map[string]interface{}
|
||
json.Unmarshal(body, &types)
|
||
requestTypeID := int64(types[0]["id"].(float64))
|
||
|
||
createReq := map[string]interface{}{
|
||
"request_type_id": requestTypeID,
|
||
"title": "Reject Test",
|
||
"description": "Test",
|
||
"latitude": 55.751244,
|
||
"longitude": 37.618423,
|
||
"address": "Test",
|
||
}
|
||
_, respBody := suite.makeRequest("POST", "/api/v1/requests", createReq, suite.accessToken)
|
||
var request map[string]interface{}
|
||
json.Unmarshal(respBody, &request)
|
||
requestID := int64(request["id"].(float64))
|
||
|
||
// Модератор отклоняет
|
||
moderatorID, moderatorToken := suite.createModerator()
|
||
url := fmt.Sprintf("/api/v1/moderation/requests/%d/reject", requestID)
|
||
rejectReq := map[string]interface{}{
|
||
"comment": "Does not meet requirements",
|
||
}
|
||
resp, _ := suite.makeRequest("POST", url, rejectReq, moderatorToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
// Проверяем статус
|
||
url = fmt.Sprintf("/api/v1/requests/%d", requestID)
|
||
_, body = suite.makeRequest("GET", url, nil, suite.accessToken)
|
||
json.Unmarshal(body, &request)
|
||
statusObj := request["status"].(map[string]interface{})
|
||
assert.Equal(suite.T(), "rejected", statusObj["request_status"])
|
||
|
||
suite.checkModeratorAction(moderatorID, requestID, "reject_request")
|
||
}
|
||
|
||
// Test 29: Reject without comment (should fail)
|
||
func (suite *APITestSuite) Test29_ModerationRejectWithoutComment() {
|
||
suite.T().Log("Testing: Reject request without comment")
|
||
|
||
// Создаем заявку
|
||
_, body := suite.makeRequest("GET", "/api/v1/request-types", nil, "")
|
||
var types []map[string]interface{}
|
||
json.Unmarshal(body, &types)
|
||
requestTypeID := int64(types[0]["id"].(float64))
|
||
|
||
createReq := map[string]interface{}{
|
||
"request_type_id": requestTypeID,
|
||
"title": "Reject No Comment Test",
|
||
"description": "Test",
|
||
"latitude": 55.751244,
|
||
"longitude": 37.618423,
|
||
"address": "Test",
|
||
}
|
||
_, respBody := suite.makeRequest("POST", "/api/v1/requests", createReq, suite.accessToken)
|
||
var request map[string]interface{}
|
||
json.Unmarshal(respBody, &request)
|
||
requestID := int64(request["id"].(float64))
|
||
|
||
// Модератор пытается отклонить без комментария
|
||
_, moderatorToken := suite.createModerator()
|
||
url := fmt.Sprintf("/api/v1/moderation/requests/%d/reject", requestID)
|
||
rejectReq := map[string]interface{}{
|
||
"comment": "",
|
||
}
|
||
resp, _ := suite.makeRequest("POST", url, rejectReq, moderatorToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusBadRequest, resp.StatusCode)
|
||
}
|
||
|
||
// Test 30: Moderation without permission (should fail)
|
||
func (suite *APITestSuite) Test30_ModerationWithoutPermission() {
|
||
suite.T().Log("Testing: Moderation without permission")
|
||
|
||
// Создаем заявку
|
||
_, body := suite.makeRequest("GET", "/api/v1/request-types", nil, "")
|
||
var types []map[string]interface{}
|
||
json.Unmarshal(body, &types)
|
||
requestTypeID := int64(types[0]["id"].(float64))
|
||
|
||
createReq := map[string]interface{}{
|
||
"request_type_id": requestTypeID,
|
||
"title": "No Permission Test",
|
||
"description": "Test",
|
||
"latitude": 55.751244,
|
||
"longitude": 37.618423,
|
||
"address": "Test",
|
||
}
|
||
_, respBody := suite.makeRequest("POST", "/api/v1/requests", createReq, suite.accessToken)
|
||
var request map[string]interface{}
|
||
json.Unmarshal(respBody, &request)
|
||
requestID := int64(request["id"].(float64))
|
||
|
||
// Обычный пользователь пытается модерировать
|
||
url := fmt.Sprintf("/api/v1/moderation/requests/%d/approve", requestID)
|
||
resp, _ := suite.makeRequest("POST", url, nil, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusForbidden, resp.StatusCode)
|
||
}
|
||
|
||
// Test 31: Get pending moderation requests
|
||
func (suite *APITestSuite) Test31_GetPendingModerationRequests() {
|
||
suite.T().Log("Testing: GET /api/v1/moderation/requests/pending")
|
||
|
||
// Создаем несколько заявок
|
||
_, body := suite.makeRequest("GET", "/api/v1/request-types", nil, "")
|
||
var types []map[string]interface{}
|
||
json.Unmarshal(body, &types)
|
||
requestTypeID := int64(types[0]["id"].(float64))
|
||
|
||
for i := 0; i < 3; i++ {
|
||
createReq := map[string]interface{}{
|
||
"request_type_id": requestTypeID,
|
||
"title": fmt.Sprintf("Pending Request %d", i),
|
||
"description": "Test",
|
||
"latitude": 55.751244,
|
||
"longitude": 37.618423,
|
||
"address": "Test",
|
||
}
|
||
suite.makeRequest("POST", "/api/v1/requests", createReq, suite.accessToken)
|
||
}
|
||
|
||
// Модератор получает список
|
||
_, moderatorToken := suite.createModerator()
|
||
resp, body := suite.makeRequest("GET", "/api/v1/moderation/requests/pending?limit=10&offset=0", nil, moderatorToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var requests []map[string]interface{}
|
||
json.Unmarshal(body, &requests)
|
||
assert.NotEmpty(suite.T(), requests)
|
||
|
||
// Проверяем, что все заявки в статусе pending_moderation
|
||
for _, req := range requests {
|
||
statusObj := req["status"].(map[string]interface{})
|
||
assert.Equal(suite.T(), "pending_moderation", statusObj["request_status"])
|
||
}
|
||
}
|
||
|
||
// Test 32: Get my moderated requests
|
||
func (suite *APITestSuite) Test32_GetMyModeratedRequests() {
|
||
suite.T().Log("Testing: GET /api/v1/moderation/requests/my")
|
||
|
||
// Создаем модератора
|
||
moderatorID, moderatorToken := suite.createModerator()
|
||
|
||
// Создаем и модерируем несколько заявок
|
||
_, body := suite.makeRequest("GET", "/api/v1/request-types", nil, "")
|
||
var types []map[string]interface{}
|
||
json.Unmarshal(body, &types)
|
||
requestTypeID := int64(types[0]["id"].(float64))
|
||
|
||
moderatedCount := 0
|
||
for i := 0; i < 2; i++ {
|
||
createReq := map[string]interface{}{
|
||
"request_type_id": requestTypeID,
|
||
"title": fmt.Sprintf("Moderated Request %d", i),
|
||
"description": "Test",
|
||
"latitude": 55.751244,
|
||
"longitude": 37.618423,
|
||
"address": "Test",
|
||
}
|
||
_, respBody := suite.makeRequest("POST", "/api/v1/requests", createReq, suite.accessToken)
|
||
var request map[string]interface{}
|
||
json.Unmarshal(respBody, &request)
|
||
requestID := int64(request["id"].(float64))
|
||
|
||
// Одобряем
|
||
url := fmt.Sprintf("/api/v1/moderation/requests/%d/approve", requestID)
|
||
approveReq := map[string]interface{}{}
|
||
resp, _ := suite.makeRequest("POST", url, approveReq, moderatorToken)
|
||
if resp.StatusCode == http.StatusOK {
|
||
moderatedCount++
|
||
}
|
||
}
|
||
|
||
// Получаем историю модерации
|
||
resp, body := suite.makeRequest("GET", "/api/v1/moderation/requests/my?limit=10&offset=0", nil, moderatorToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var requests []map[string]interface{}
|
||
json.Unmarshal(body, &requests)
|
||
assert.NotEmpty(suite.T(), requests)
|
||
|
||
// Проверяем, что все заявки модерированы этим модератором
|
||
for _, req := range requests {
|
||
if req["moderated_by"] != nil {
|
||
assert.Equal(suite.T(), float64(moderatorID), req["moderated_by"].(float64))
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// Тесты хранимых процедур
|
||
// ============================================================================
|
||
|
||
// Test33_AcceptVolunteerResponse проверяет успешное принятие отклика волонтера
|
||
func (suite *APITestSuite) Test33_AcceptVolunteerResponse() {
|
||
// Создаем заявку и одобряем её
|
||
requestID := suite.createApprovedRequest(suite.accessToken)
|
||
|
||
// Создаем волонтера и отклик
|
||
volunteerID, responseID, _ := suite.createVolunteerWithResponse(requestID)
|
||
|
||
// Принимаем отклик через процедуру
|
||
url := fmt.Sprintf("/api/v1/requests/%d/responses/%d/accept", requestID, responseID)
|
||
resp, body := suite.makeRequest("POST", url, nil, suite.accessToken)
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
suite.T().Logf("Accept response failed. Status: %d, Body: %s", resp.StatusCode, string(body))
|
||
}
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var result map[string]interface{}
|
||
err := json.Unmarshal(body, &result)
|
||
require.NoError(suite.T(), err)
|
||
|
||
// Проверяем результат процедуры
|
||
assert.True(suite.T(), result["success"].(bool))
|
||
assert.Contains(suite.T(), result["message"].(string), "successfully")
|
||
assert.Equal(suite.T(), float64(requestID), result["request_id"].(float64))
|
||
assert.Equal(suite.T(), float64(volunteerID), result["volunteer_id"].(float64))
|
||
|
||
// Проверяем, что заявка перешла в статус in_progress
|
||
resp2, body2 := suite.makeRequest("GET", fmt.Sprintf("/api/v1/requests/%d", requestID), nil, suite.accessToken)
|
||
assert.Equal(suite.T(), http.StatusOK, resp2.StatusCode)
|
||
|
||
var request map[string]interface{}
|
||
json.Unmarshal(body2, &request)
|
||
statusObj := request["status"].(map[string]interface{})
|
||
assert.Equal(suite.T(), "in_progress", statusObj["request_status"])
|
||
assert.Equal(suite.T(), float64(volunteerID), request["assigned_volunteer_id"].(float64))
|
||
|
||
// Проверяем историю статусов
|
||
suite.checkRequestStatusHistory(requestID, []string{"approved", "in_progress"})
|
||
}
|
||
|
||
// Test34_AcceptVolunteerResponseValidation проверяет валидацию процедуры accept_volunteer_response
|
||
func (suite *APITestSuite) Test34_AcceptVolunteerResponseValidation() {
|
||
// Создаем заявку от первого пользователя
|
||
requestID := suite.createApprovedRequest(suite.accessToken)
|
||
|
||
// Создаем волонтера с откликом
|
||
_, responseID, _ := suite.createVolunteerWithResponse(requestID)
|
||
|
||
// Создаем второго пользователя (не владелец заявки)
|
||
email := fmt.Sprintf("other_user_%d@example.com", time.Now().UnixNano())
|
||
registerReq := map[string]interface{}{
|
||
"email": email,
|
||
"password": "SecureP@ssw0rd123",
|
||
"first_name": "Other",
|
||
"last_name": "User",
|
||
}
|
||
_, regBody := suite.makeRequest("POST", "/api/v1/auth/register", registerReq, "")
|
||
var authResp map[string]interface{}
|
||
json.Unmarshal(regBody, &authResp)
|
||
otherUserToken := authResp["access_token"].(string)
|
||
|
||
// Попытка принять отклик другим пользователем (не владельцем заявки)
|
||
url := fmt.Sprintf("/api/v1/requests/%d/responses/%d/accept", requestID, responseID)
|
||
resp, body := suite.makeRequest("POST", url, nil, otherUserToken)
|
||
|
||
// Должна быть ошибка, так как пользователь не владелец заявки
|
||
assert.Equal(suite.T(), http.StatusBadRequest, resp.StatusCode)
|
||
|
||
var result map[string]interface{}
|
||
json.Unmarshal(body, &result)
|
||
|
||
// Процедура возвращает success: false
|
||
if result["success"] != nil {
|
||
assert.False(suite.T(), result["success"].(bool))
|
||
assert.Contains(suite.T(), result["message"].(string), "not the owner")
|
||
} else {
|
||
// Или ошибка от обработчика
|
||
assert.Contains(suite.T(), result["error"].(string), "owner")
|
||
}
|
||
}
|
||
|
||
// Test35_CompleteRequestWithRating проверяет успешное завершение заявки с рейтингом
|
||
func (suite *APITestSuite) Test35_CompleteRequestWithRating() {
|
||
// Создаем заявку, одобряем, принимаем отклик волонтера
|
||
requestID := suite.createApprovedRequest(suite.accessToken)
|
||
volunteerID, responseID, _ := suite.createVolunteerWithResponse(requestID)
|
||
|
||
// Принимаем отклик
|
||
acceptURL := fmt.Sprintf("/api/v1/requests/%d/responses/%d/accept", requestID, responseID)
|
||
suite.makeRequest("POST", acceptURL, nil, suite.accessToken)
|
||
|
||
// Завершаем заявку с рейтингом
|
||
completeReq := map[string]interface{}{
|
||
"rating": 5,
|
||
"comment": "Отличная работа!",
|
||
}
|
||
completeURL := fmt.Sprintf("/api/v1/requests/%d/complete", requestID)
|
||
resp, body := suite.makeRequest("POST", completeURL, completeReq, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusOK, resp.StatusCode)
|
||
|
||
var result map[string]interface{}
|
||
err := json.Unmarshal(body, &result)
|
||
require.NoError(suite.T(), err)
|
||
|
||
// Проверяем результат процедуры
|
||
assert.True(suite.T(), result["success"].(bool))
|
||
assert.Contains(suite.T(), result["message"].(string), "successfully")
|
||
assert.NotNil(suite.T(), result["rating_id"])
|
||
|
||
// Проверяем, что заявка завершена
|
||
resp2, body2 := suite.makeRequest("GET", fmt.Sprintf("/api/v1/requests/%d", requestID), nil, suite.accessToken)
|
||
assert.Equal(suite.T(), http.StatusOK, resp2.StatusCode)
|
||
|
||
var request map[string]interface{}
|
||
json.Unmarshal(body2, &request)
|
||
statusObj := request["status"].(map[string]interface{})
|
||
assert.Equal(suite.T(), "completed", statusObj["request_status"])
|
||
|
||
// completed_at может быть null, если поле не заполнилось
|
||
if request["completed_at"] == nil {
|
||
suite.T().Logf("Request response: %s", string(body2))
|
||
}
|
||
assert.NotNil(suite.T(), request["completed_at"])
|
||
|
||
// Проверяем, что рейтинг создан
|
||
ratingID := int64(result["rating_id"].(float64))
|
||
rating, err := suite.queries.GetRatingByResponseID(context.Background(), responseID)
|
||
require.NoError(suite.T(), err)
|
||
assert.Equal(suite.T(), ratingID, rating.ID)
|
||
assert.Equal(suite.T(), int32(5), rating.Rating)
|
||
assert.Equal(suite.T(), volunteerID, rating.VolunteerID)
|
||
|
||
// Проверяем историю статусов
|
||
suite.checkRequestStatusHistory(requestID, []string{"approved", "in_progress", "completed"})
|
||
}
|
||
|
||
// Test36_CompleteRequestWithRatingValidation проверяет валидацию рейтинга
|
||
func (suite *APITestSuite) Test36_CompleteRequestWithRatingValidation() {
|
||
// Создаем заявку в статусе in_progress
|
||
requestID := suite.createApprovedRequest(suite.accessToken)
|
||
_, responseID, _ := suite.createVolunteerWithResponse(requestID)
|
||
|
||
acceptURL := fmt.Sprintf("/api/v1/requests/%d/responses/%d/accept", requestID, responseID)
|
||
suite.makeRequest("POST", acceptURL, nil, suite.accessToken)
|
||
|
||
// Попытка завершить с неправильным рейтингом (вне диапазона 1-5)
|
||
completeReq := map[string]interface{}{
|
||
"rating": 10, // Неверный рейтинг
|
||
}
|
||
completeURL := fmt.Sprintf("/api/v1/requests/%d/complete", requestID)
|
||
resp, body := suite.makeRequest("POST", completeURL, completeReq, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusBadRequest, resp.StatusCode)
|
||
|
||
var errorResp map[string]interface{}
|
||
json.Unmarshal(body, &errorResp)
|
||
assert.Contains(suite.T(), errorResp["error"].(string), "between 1 and 5")
|
||
|
||
// Попытка с рейтингом 0
|
||
completeReq2 := map[string]interface{}{
|
||
"rating": 0,
|
||
}
|
||
resp2, body2 := suite.makeRequest("POST", completeURL, completeReq2, suite.accessToken)
|
||
|
||
assert.Equal(suite.T(), http.StatusBadRequest, resp2.StatusCode)
|
||
|
||
var errorResp2 map[string]interface{}
|
||
json.Unmarshal(body2, &errorResp2)
|
||
assert.Contains(suite.T(), errorResp2["error"].(string), "between 1 and 5")
|
||
}
|
||
|
||
// Test37_CompleteRequestNotInProgress проверяет, что нельзя завершить заявку не в статусе in_progress
|
||
func (suite *APITestSuite) Test37_CompleteRequestNotInProgress() {
|
||
// Создаем заявку (будет в статусе pending_moderation)
|
||
_, body := suite.makeRequest("GET", "/api/v1/request-types", nil, "")
|
||
var types []map[string]interface{}
|
||
json.Unmarshal(body, &types)
|
||
requestTypeID := int64(types[0]["id"].(float64))
|
||
|
||
createReq := map[string]interface{}{
|
||
"request_type_id": requestTypeID,
|
||
"title": "Request to complete",
|
||
"description": "Test",
|
||
"latitude": 55.751244,
|
||
"longitude": 37.618423,
|
||
"address": "Test",
|
||
}
|
||
_, respBody := suite.makeRequest("POST", "/api/v1/requests", createReq, suite.accessToken)
|
||
var request map[string]interface{}
|
||
json.Unmarshal(respBody, &request)
|
||
requestID := int64(request["id"].(float64))
|
||
|
||
// Попытка завершить заявку в статусе pending_moderation
|
||
completeReq := map[string]interface{}{
|
||
"rating": 5,
|
||
}
|
||
completeURL := fmt.Sprintf("/api/v1/requests/%d/complete", requestID)
|
||
resp, body := suite.makeRequest("POST", completeURL, completeReq, suite.accessToken)
|
||
|
||
// Процедура должна вернуть ошибку
|
||
assert.Equal(suite.T(), http.StatusBadRequest, resp.StatusCode)
|
||
|
||
var result map[string]interface{}
|
||
json.Unmarshal(body, &result)
|
||
|
||
// Хранимая процедура возвращает success: false, которое обработчик преобразует в error
|
||
if result["error"] != nil {
|
||
// Проверяем сообщение об ошибке содержит информацию о статусе
|
||
assert.Contains(suite.T(), result["error"].(string), "in_progress")
|
||
} else {
|
||
suite.T().Logf("Response: %s", string(body))
|
||
suite.T().Fatal("Expected error in response")
|
||
}
|
||
}
|