Files
chat/tests/full_api_test.sh
Andrew K. Choi 24c1a0c85c
All checks were successful
continuous-integration/drone/push Build is passing
pre-prod deploy
2025-09-26 06:43:56 +09:00

618 lines
24 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Настройка логирования
LOG_FILE="/tmp/api_test_output"
rm -f "$LOG_FILE" 2>/dev/null
exec > >(tee -a "$LOG_FILE") 2>&1
echo -e "${YELLOW}=======================================${NC}"
echo -e "${YELLOW}🔍 Полное тестирование API приложения ${NC}"
echo -e "${YELLOW}=======================================${NC}"
# Функция для логирования тестовых результатов
log_test_result() {
local status=$1
local message=$2
if [[ $status == 0 ]]; then
echo -e "${GREEN}✓ УСПЕШНО: ${message}${NC}"
else
echo -e "${RED}✗ ОШИБКА: ${message}${NC}"
fi
}
# Функция для проверки ответа API
check_api_response() {
local response=$1
local expected_field=$2
if echo "$response" | jq -e ".$expected_field" > /dev/null; then
return 0
else
return 1
fi
}
# 1. Проверка доступности сервисов
echo -e "\n${GREEN}1. Проверка доступности сервисов${NC}"
services=(
"http://192.168.0.103:8000"
"http://192.168.0.103:8001"
"http://192.168.0.103:8002"
"http://192.168.0.103:8003"
"http://192.168.0.103:8004"
"http://192.168.0.103:8005"
)
service_names=(
"API Gateway"
"User Service"
"Emergency Service"
"Location Service"
"Calendar Service"
"Notification Service"
)
for i in "${!services[@]}"; do
echo -n "Проверка ${service_names[$i]} (${services[$i]})... "
if curl -s "${services[$i]}/health" | grep -q "status.*healthy" || curl -s "${services[$i]}/api/v1/health" | grep -q "status.*healthy"; then
echo -e "${GREEN}OK${NC}"
log_test_result 0 "Сервис ${service_names[$i]} доступен"
else
echo -e "${RED}НЕДОСТУПНО${NC}"
log_test_result 1 "Сервис ${service_names[$i]} недоступен"
echo "Продолжение тестирования может вызвать ошибки. Хотите продолжить? (y/n)"
read -r continue_test
if [[ $continue_test != "y" ]]; then
exit 1
fi
fi
done
# 2. Регистрация и авторизация
echo -e "\n${GREEN}2. Регистрация и авторизация${NC}"
echo "Регистрация тестового пользователя..."
# Сначала удалим пользователя, если он уже существует
echo -e "${YELLOW}Удаление существующего пользователя apitest, если он существует...${NC}"
curl -s -X DELETE http://localhost:8001/api/v1/admin/users/by-username/apitest \
-H "Content-Type: application/json" \
-H "X-Admin-Key: super-secret-admin-key" > /dev/null
REGISTER_RESPONSE=$(curl -s -X POST http://localhost:8001/api/v1/users/register \
-H "Content-Type: application/json" \
-d '{
"username": "apitest",
"email": "apitest@example.com",
"password": "ApiTest123!",
"full_name": "API Test User",
"phone_number": "+79997776655"
}')
echo "$REGISTER_RESPONSE" | jq
if check_api_response "$REGISTER_RESPONSE" "id"; then
log_test_result 0 "Регистрация пользователя"
else
log_test_result 1 "Регистрация пользователя"
fi
# Тест на ошибку при повторной регистрации с тем же именем пользователя
echo -e "\n${YELLOW}Проверка дублирующейся регистрации...${NC}"
DUPLICATE_RESPONSE=$(curl -s -X POST http://localhost:8001/api/v1/users/register \
-H "Content-Type: application/json" \
-d '{
"username": "apitest",
"email": "another@example.com",
"password": "ApiTest123!",
"full_name": "Duplicate User",
"phone_number": "+79997776655"
}')
echo "$DUPLICATE_RESPONSE" | jq
if echo "$DUPLICATE_RESPONSE" | grep -q "error"; then
log_test_result 0 "Проверка на дублирование пользователя"
else
log_test_result 1 "Проверка на дублирование пользователя (должна быть ошибка)"
fi
# Авторизация
echo -e "\n${YELLOW}Авторизация тестового пользователя...${NC}"
LOGIN_RESPONSE=$(curl -s -X POST http://localhost:8001/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "apitest",
"password": "ApiTest123!"
}')
echo "$LOGIN_RESPONSE" | jq
TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.access_token')
if [[ $TOKEN == "null" || -z $TOKEN ]]; then
log_test_result 1 "Получение токена авторизации"
echo -e "${RED}Не удалось получить токен авторизации. Тестирование будет остановлено.${NC}"
exit 1
else
log_test_result 0 "Получение токена авторизации"
echo -e "${GREEN}Успешно получен токен авторизации${NC}"
fi
# Тест на авторизацию с неверным паролем
echo -e "\n${YELLOW}Проверка авторизации с неверными данными...${NC}"
FAILED_LOGIN=$(curl -s -X POST http://localhost:8001/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "apitest",
"password": "WrongPassword123!"
}')
echo "$FAILED_LOGIN" | jq
if echo "$FAILED_LOGIN" | grep -q "error"; then
log_test_result 0 "Проверка авторизации с неверным паролем"
else
log_test_result 1 "Проверка авторизации с неверным паролем (должна быть ошибка)"
fi
# 3. Получение текущего профиля через /me эндпоинт
echo -e "\n${GREEN}3. Получение текущего профиля пользователя${NC}"
PROFILE_RESPONSE=$(curl -s -X GET http://localhost:8001/api/v1/users/me \
-H "Authorization: Bearer $TOKEN")
echo "$PROFILE_RESPONSE" | jq
if check_api_response "$PROFILE_RESPONSE" "username"; then
log_test_result 0 "Получение профиля пользователя"
else
log_test_result 1 "Получение профиля пользователя"
fi
# Тестирование получения профиля через альтернативные эндпоинты
echo -e "\n${YELLOW}Тестирование альтернативного эндпоинта /api/v1/profile${NC}"
PROFILE_ALT=$(curl -s -X GET http://localhost:8001/api/v1/profile \
-H "Authorization: Bearer $TOKEN")
echo "$PROFILE_ALT" | jq
if check_api_response "$PROFILE_ALT" "username"; then
log_test_result 0 "Получение профиля через альтернативный эндпоинт"
else
log_test_result 1 "Получение профиля через альтернативный эндпоинт"
fi
# 4. Обновление профиля
echo -e "\n${GREEN}4. Обновление профиля пользователя${NC}"
UPDATE_RESPONSE=$(curl -s -X PATCH http://localhost:8001/api/v1/users/me \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"full_name": "Обновленное Имя",
"phone_number": "+79997776655"
}')
echo "$UPDATE_RESPONSE" | jq
if check_api_response "$UPDATE_RESPONSE" "full_name"; then
log_test_result 0 "Обновление профиля пользователя"
else
log_test_result 1 "Обновление профиля пользователя"
fi
# Проверка обновления профиля
echo -e "\n${YELLOW}Проверка применения изменений профиля${NC}"
PROFILE_AFTER=$(curl -s -X GET http://localhost:8001/api/v1/users/me \
-H "Authorization: Bearer $TOKEN")
if [[ $(echo "$PROFILE_AFTER" | jq -r '.full_name') == "Обновленное Имя" ]]; then
log_test_result 0 "Проверка обновления имени в профиле"
else
log_test_result 1 "Проверка обновления имени в профиле"
fi
# 5. Добавление контактов экстренной связи
echo -e "\n${GREEN}5. Добавление контакта для экстренной связи${NC}"
CONTACT_RESPONSE=$(curl -s -X POST http://localhost:8001/api/v1/users/me/emergency-contacts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Экстренный контакт",
"phone_number": "+79991112233",
"relationship": "Родственник"
}')
echo "$CONTACT_RESPONSE" | jq
CONTACT_ID=$(echo "$CONTACT_RESPONSE" | jq -r '.id')
if [[ $CONTACT_ID != "null" && -n $CONTACT_ID ]]; then
log_test_result 0 "Добавление экстренного контакта"
else
log_test_result 1 "Добавление экстренного контакта"
fi
# 6. Получение списка контактов
echo -e "\n${GREEN}6. Получение списка экстренных контактов${NC}"
CONTACTS_RESPONSE=$(curl -s -X GET http://localhost:8001/api/v1/users/me/emergency-contacts \
-H "Authorization: Bearer $TOKEN")
echo "$CONTACTS_RESPONSE" | jq
CONTACTS_COUNT=$(echo "$CONTACTS_RESPONSE" | jq '. | length')
if [[ $CONTACTS_COUNT -gt 0 ]]; then
log_test_result 0 "Получение списка экстренных контактов (найдено: $CONTACTS_COUNT)"
else
log_test_result 1 "Получение списка экстренных контактов"
fi
# Обновление экстренного контакта
if [[ $CONTACT_ID != "null" && -n $CONTACT_ID ]]; then
echo -e "\n${YELLOW}Обновление экстренного контакта${NC}"
UPDATE_CONTACT_RESPONSE=$(curl -s -X PATCH http://localhost:8001/api/v1/users/me/emergency-contacts/$CONTACT_ID \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Обновленный экстренный контакт",
"relationship": "Друг"
}')
echo "$UPDATE_CONTACT_RESPONSE" | jq
if [[ $(echo "$UPDATE_CONTACT_RESPONSE" | jq -r '.name') == "Обновленный экстренный контакт" ]]; then
log_test_result 0 "Обновление экстренного контакта"
else
log_test_result 1 "Обновление экстренного контакта"
fi
fi
# 7. Обновление местоположения пользователя
echo -e "\n${GREEN}7. Обновление местоположения пользователя${NC}"
LOCATION_UPDATE_RESPONSE=$(curl -s -X POST http://localhost:8003/api/v1/locations/update \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"latitude": 55.7558,
"longitude": 37.6173,
"accuracy": 10.0
}')
echo "$LOCATION_UPDATE_RESPONSE" | jq
if check_api_response "$LOCATION_UPDATE_RESPONSE" "id"; then
log_test_result 0 "Обновление местоположения"
else
log_test_result 1 "Обновление местоположения"
fi
# 8. Получение последнего местоположения
echo -e "\n${GREEN}8. Получение последнего местоположения${NC}"
LAST_LOCATION=$(curl -s -X GET http://localhost:8003/api/v1/locations/last \
-H "Authorization: Bearer $TOKEN")
echo "$LAST_LOCATION" | jq
if check_api_response "$LAST_LOCATION" "latitude"; then
log_test_result 0 "Получение последнего местоположения"
else
log_test_result 1 "Получение последнего местоположения"
fi
# Получение истории местоположений
echo -e "\n${YELLOW}Получение истории местоположений${NC}"
LOCATION_HISTORY=$(curl -s -X GET "http://localhost:8003/api/v1/locations/history?limit=5" \
-H "Authorization: Bearer $TOKEN")
echo "$LOCATION_HISTORY" | jq
if [[ $(echo "$LOCATION_HISTORY" | jq '. | length') -gt 0 ]]; then
log_test_result 0 "Получение истории местоположений"
else
log_test_result 1 "Получение истории местоположений"
fi
# 9. Создание экстренного оповещения
echo -e "\n${GREEN}9. Создание экстренного оповещения${NC}"
ALERT_RESPONSE=$(curl -s -X POST http://localhost:8002/api/v1/emergency/alerts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"latitude": 55.7558,
"longitude": 37.6173,
"alert_type": "SOS",
"message": "Тестовое экстренное оповещение"
}')
echo "$ALERT_RESPONSE" | jq
ALERT_ID=$(echo "$ALERT_RESPONSE" | jq -r '.id')
if [[ $ALERT_ID != "null" && -n $ALERT_ID ]]; then
log_test_result 0 "Создание экстренного оповещения"
else
log_test_result 1 "Создание экстренного оповещения"
fi
# 10. Получение активных оповещений
echo -e "\n${GREEN}10. Получение активных оповещений пользователя${NC}"
ACTIVE_ALERTS=$(curl -s -X GET http://localhost:8002/api/v1/emergency/alerts/my \
-H "Authorization: Bearer $TOKEN")
echo "$ACTIVE_ALERTS" | jq
ALERTS_COUNT=$(echo "$ACTIVE_ALERTS" | jq '. | length')
if [[ $ALERTS_COUNT -gt 0 ]]; then
log_test_result 0 "Получение активных оповещений (найдено: $ALERTS_COUNT)"
else
log_test_result 1 "Получение активных оповещений"
fi
# 11. Получение статистики по оповещениям
echo -e "\n${YELLOW}Получение статистики по экстренным оповещениям${NC}"
ALERTS_STATS=$(curl -s -X GET http://localhost:8002/api/v1/emergency/stats \
-H "Authorization: Bearer $TOKEN")
echo "$ALERTS_STATS" | jq
if check_api_response "$ALERTS_STATS" "total_alerts"; then
log_test_result 0 "Получение статистики по оповещениям"
else
log_test_result 1 "Получение статистики по оповещениям"
fi
# 12. Отмена экстренного оповещения
if [[ $ALERT_ID != "null" && -n $ALERT_ID ]]; then
echo -e "\n${GREEN}12. Отмена экстренного оповещения${NC}"
CANCEL_RESPONSE=$(curl -s -X PATCH http://localhost:8002/api/v1/emergency/alerts/$ALERT_ID/cancel \
-H "Authorization: Bearer $TOKEN")
echo "$CANCEL_RESPONSE" | jq
if [[ $(echo "$CANCEL_RESPONSE" | jq -r '.status') == "CANCELED" ]]; then
log_test_result 0 "Отмена экстренного оповещения"
else
log_test_result 1 "Отмена экстренного оповещения"
fi
fi
# 13. Создание записи в календаре
echo -e "\n${GREEN}13. Создание записи в календаре${NC}"
CALENDAR_RESPONSE=$(curl -s -X POST http://localhost:8004/api/v1/calendar/entries \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"entry_date": "2025-10-01",
"cycle_day": 1,
"symptoms": ["HEADACHE", "FATIGUE"],
"mood": "NORMAL",
"notes": "Тестовая запись в календаре"
}')
echo "$CALENDAR_RESPONSE" | jq
CALENDAR_ENTRY_ID=$(echo "$CALENDAR_RESPONSE" | jq -r '.id')
if [[ $CALENDAR_ENTRY_ID != "null" && -n $CALENDAR_ENTRY_ID ]]; then
log_test_result 0 "Создание записи в календаре"
else
log_test_result 1 "Создание записи в календаре"
fi
# 14. Получение записей календаря
echo -e "\n${GREEN}14. Получение записей календаря${NC}"
CALENDAR_ENTRIES=$(curl -s -X GET "http://localhost:8004/api/v1/calendar/entries?start_date=2025-09-01&end_date=2025-10-31" \
-H "Authorization: Bearer $TOKEN")
echo "$CALENDAR_ENTRIES" | jq
ENTRIES_COUNT=$(echo "$CALENDAR_ENTRIES" | jq '. | length')
if [[ $ENTRIES_COUNT -gt 0 ]]; then
log_test_result 0 "Получение записей календаря (найдено: $ENTRIES_COUNT)"
else
log_test_result 1 "Получение записей календаря"
fi
# Обновление записи в календаре
if [[ $CALENDAR_ENTRY_ID != "null" && -n $CALENDAR_ENTRY_ID ]]; then
echo -e "\n${YELLOW}Обновление записи в календаре${NC}"
UPDATE_ENTRY=$(curl -s -X PATCH "http://localhost:8004/api/v1/calendar/entries/$CALENDAR_ENTRY_ID" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"notes": "Обновленная запись в календаре",
"mood": "GOOD"
}')
echo "$UPDATE_ENTRY" | jq
if [[ $(echo "$UPDATE_ENTRY" | jq -r '.mood') == "GOOD" ]]; then
log_test_result 0 "Обновление записи в календаре"
else
log_test_result 1 "Обновление записи в календаре"
fi
fi
# 15. Получение прогноза цикла
echo -e "\n${GREEN}15. Получение прогноза цикла${NC}"
CYCLE_PREDICTION=$(curl -s -X GET http://localhost:8004/api/v1/calendar/prediction \
-H "Authorization: Bearer $TOKEN")
echo "$CYCLE_PREDICTION" | jq
if check_api_response "$CYCLE_PREDICTION" "next_period_date"; then
log_test_result 0 "Получение прогноза цикла"
else
log_test_result 1 "Получение прогноза цикла"
fi
# 16. Регистрация устройства для уведомлений
echo -e "\n${GREEN}16. Регистрация устройства для уведомлений${NC}"
DEVICE_RESPONSE=$(curl -s -X POST http://localhost:8005/api/v1/notifications/devices \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"device_token": "fcm-test-token-123",
"device_type": "ANDROID",
"app_version": "1.0.0"
}')
echo "$DEVICE_RESPONSE" | jq
DEVICE_ID=$(echo "$DEVICE_RESPONSE" | jq -r '.id')
if [[ $DEVICE_ID != "null" && -n $DEVICE_ID ]]; then
log_test_result 0 "Регистрация устройства для уведомлений"
else
log_test_result 1 "Регистрация устройства для уведомлений"
fi
# 17. Настройка предпочтений уведомлений
echo -e "\n${GREEN}17. Настройка предпочтений уведомлений${NC}"
PREFS_RESPONSE=$(curl -s -X POST http://localhost:8005/api/v1/notifications/preferences \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"emergency_alerts": true,
"nearby_incidents": true,
"calendar_reminders": true,
"system_notifications": true
}')
echo "$PREFS_RESPONSE" | jq
if check_api_response "$PREFS_RESPONSE" "emergency_alerts"; then
log_test_result 0 "Настройка предпочтений уведомлений"
else
log_test_result 1 "Настройка предпочтений уведомлений"
fi
# 18. Получение текущих предпочтений уведомлений
echo -e "\n${YELLOW}Получение текущих предпочтений уведомлений${NC}"
CURRENT_PREFS=$(curl -s -X GET http://localhost:8005/api/v1/notifications/preferences \
-H "Authorization: Bearer $TOKEN")
echo "$CURRENT_PREFS" | jq
if check_api_response "$CURRENT_PREFS" "calendar_reminders"; then
log_test_result 0 "Получение предпочтений уведомлений"
else
log_test_result 1 "Получение предпочтений уведомлений"
fi
# 19. Тестирование отправки тестового уведомления
echo -e "\n${GREEN}19. Отправка тестового уведомления${NC}"
TEST_NOTIFICATION=$(curl -s -X POST http://localhost:8005/api/v1/notifications/test \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"title": "Тестовое уведомление",
"body": "Это тестовое уведомление для проверки API"
}')
echo "$TEST_NOTIFICATION" | jq
if check_api_response "$TEST_NOTIFICATION" "status"; then
log_test_result 0 "Отправка тестового уведомления"
else
log_test_result 1 "Отправка тестового уведомления"
fi
# 20. Получение данных пользователя через Gateway
echo -e "\n${GREEN}20. Получение данных пользователя через Gateway${NC}"
DASHBOARD=$(curl -s -X GET http://localhost:8000/api/v1/users/dashboard \
-H "Authorization: Bearer $TOKEN")
echo "$DASHBOARD" | jq
if check_api_response "$DASHBOARD" "user"; then
log_test_result 0 "Получение данных пользователя через API Gateway"
else
log_test_result 1 "Получение данных пользователя через API Gateway"
fi
# 21. Тестирование документации Swagger
echo -e "\n${GREEN}21. Проверка документации Swagger${NC}"
SWAGGER_RESPONSE=$(curl -s -X GET http://localhost:8000/openapi.json)
echo "$SWAGGER_RESPONSE" | jq -C '.info'
if check_api_response "$SWAGGER_RESPONSE" "paths"; then
log_test_result 0 "Получение OpenAPI схемы"
else
log_test_result 1 "Получение OpenAPI схемы"
fi
# 22. Проверка наличия схем данных в Swagger
echo -e "\n${YELLOW}Проверка наличия схем данных в Swagger${NC}"
COMPONENTS_SCHEMAS=$(echo "$SWAGGER_RESPONSE" | jq '.components.schemas')
SCHEMAS_COUNT=$(echo "$COMPONENTS_SCHEMAS" | jq 'length')
if [[ $SCHEMAS_COUNT -gt 0 ]]; then
log_test_result 0 "Проверка наличия схем данных (найдено: $SCHEMAS_COUNT)"
echo -e "${GREEN}Доступные схемы:${NC}"
echo "$COMPONENTS_SCHEMAS" | jq 'keys'
else
log_test_result 1 "Проверка наличия схем данных"
fi
# Очистка ресурсов
echo -e "\n${GREEN}Очистка тестовых данных...${NC}"
# Удаление созданных ресурсов
if [[ $DEVICE_ID != "null" && -n $DEVICE_ID ]]; then
curl -s -X DELETE http://localhost:8005/api/v1/notifications/devices/$DEVICE_ID \
-H "Authorization: Bearer $TOKEN" > /dev/null
echo "Удалено устройство для уведомлений"
fi
if [[ $CONTACT_ID != "null" && -n $CONTACT_ID ]]; then
curl -s -X DELETE http://localhost:8001/api/v1/users/me/emergency-contacts/$CONTACT_ID \
-H "Authorization: Bearer $TOKEN" > /dev/null
echo "Удалены экстренные контакты"
fi
if [[ $CALENDAR_ENTRY_ID != "null" && -n $CALENDAR_ENTRY_ID ]]; then
curl -s -X DELETE http://localhost:8004/api/v1/calendar/entries/$CALENDAR_ENTRY_ID \
-H "Authorization: Bearer $TOKEN" > /dev/null
echo "Удалены записи в календаре"
fi
# Сбор статистики
PASSED_TESTS=$(grep -c "✓ УСПЕШНО" /tmp/api_test_output 2>/dev/null || echo "0")
FAILED_TESTS=$(grep -c "✗ ОШИБКА" /tmp/api_test_output 2>/dev/null || echo "0")
TOTAL_TESTS=$((PASSED_TESTS + FAILED_TESTS))
echo -e "\n${YELLOW}=======================================${NC}"
echo -e "${YELLOW}📊 Итоги тестирования API${NC}"
echo -e "${YELLOW}=======================================${NC}"
echo -e "Всего тестов: ${TOTAL_TESTS}"
echo -e "${GREEN}✓ Успешно: ${PASSED_TESTS}${NC}"
echo -e "${RED}✗ Ошибок: ${FAILED_TESTS}${NC}"
if [[ $FAILED_TESTS -eq 0 ]]; then
echo -e "\n${GREEN}✅ Тестирование API завершено успешно!${NC}"
else
echo -e "\n${YELLOW}⚠️ Тестирование API завершено с ошибками!${NC}"
fi
echo -e "${YELLOW}=======================================${NC}"
# Сохранение результатов в файл
{
echo "# Результаты тестирования API"
echo "Дата: $(date '+%Y-%m-%d %H:%M:%S')"
echo "## Итоги"
echo "- Всего тестов: ${TOTAL_TESTS}"
echo "- Успешно: ${PASSED_TESTS}"
echo "- Ошибок: ${FAILED_TESTS}"
echo ""
echo "## Подробности"
grep -E "✓ УСПЕШНО|✗ ОШИБКА" /tmp/api_test_output 2>/dev/null || echo "Нет деталей"
} > api_test_results.md
echo "Подробные результаты сохранены в файле api_test_results.md"