This commit is contained in:
@@ -578,3 +578,28 @@ def redis_client():
|
||||
|
||||
redis_service = RedisService()
|
||||
return redis_service._client
|
||||
|
||||
|
||||
# Mock для Redis если он недоступен
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_redis_if_unavailable():
|
||||
"""Автоматически мокает Redis если он недоступен"""
|
||||
try:
|
||||
import redis
|
||||
# Пробуем подключиться к Redis
|
||||
r = redis.Redis(host='localhost', port=6379, socket_connect_timeout=1)
|
||||
r.ping()
|
||||
# Redis доступен, не мокаем
|
||||
yield
|
||||
except Exception:
|
||||
# Redis недоступен, мокаем
|
||||
with patch('services.redis.RedisService') as mock_redis:
|
||||
# Создаем базовый mock для Redis методов
|
||||
mock_redis.return_value.get.return_value = None
|
||||
mock_redis.return_value.set.return_value = True
|
||||
mock_redis.return_value.delete.return_value = True
|
||||
mock_redis.return_value.exists.return_value = False
|
||||
mock_redis.return_value.ping.return_value = True
|
||||
mock_redis.return_value.is_connected = False
|
||||
|
||||
yield
|
||||
|
||||
@@ -6,41 +6,81 @@
|
||||
import asyncio
|
||||
import time
|
||||
import os
|
||||
import requests
|
||||
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
|
||||
async def wait_for_server_ready(url: str, timeout: int = 60) -> bool:
|
||||
"""Ждем готовности сервера"""
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
response = requests.get(url, timeout=5)
|
||||
if response.status_code == 200:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
await asyncio.sleep(2)
|
||||
return False
|
||||
|
||||
|
||||
async def test_delete_button(frontend_url):
|
||||
"""Тест поиска кнопки удаления с улучшенной обработкой ошибок"""
|
||||
|
||||
# Проверяем готовность фронтенда
|
||||
print(f"🌐 Проверяем готовность фронтенда {frontend_url}...")
|
||||
if not await wait_for_server_ready(frontend_url):
|
||||
print(f"❌ Фронтенд {frontend_url} не готов в течение 60 секунд")
|
||||
return False
|
||||
|
||||
print(f"✅ Фронтенд {frontend_url} готов")
|
||||
|
||||
async with async_playwright() as p:
|
||||
# Определяем headless режим из переменной окружения
|
||||
headless_mode = os.getenv("PLAYWRIGHT_HEADLESS", "false").lower() == "true"
|
||||
headless_mode = os.getenv("PLAYWRIGHT_HEADLESS", "true").lower() == "true"
|
||||
print(f"🔧 Headless режим: {headless_mode}")
|
||||
|
||||
browser = await p.chromium.launch(headless=headless_mode)
|
||||
browser = await p.chromium.launch(
|
||||
headless=headless_mode,
|
||||
args=['--no-sandbox', '--disable-dev-shm-usage']
|
||||
)
|
||||
page = await browser.new_page()
|
||||
|
||||
# Увеличиваем таймауты для CI
|
||||
page.set_default_timeout(30000) # 30 секунд
|
||||
page.set_default_navigation_timeout(30000)
|
||||
|
||||
try:
|
||||
print(f"🌐 Открываем админ-панель на {frontend_url}...")
|
||||
await page.goto(f"{frontend_url}/login")
|
||||
await page.wait_for_load_state("networkidle")
|
||||
await page.goto(f"{frontend_url}/login", wait_until="networkidle")
|
||||
print("✅ Страница логина загружена")
|
||||
|
||||
print("🔐 Авторизуемся...")
|
||||
# Ждем появления полей ввода
|
||||
await page.wait_for_selector('input[type="email"]', timeout=15000)
|
||||
await page.wait_for_selector('input[type="password"]', timeout=15000)
|
||||
|
||||
await page.fill('input[type="email"]', "test_admin@discours.io")
|
||||
await page.fill('input[type="password"]', "password123")
|
||||
await page.click('button[type="submit"]')
|
||||
|
||||
# Ждем авторизации
|
||||
await page.wait_for_url(f"{frontend_url}/admin/**", timeout=10000)
|
||||
# Ждем авторизации с увеличенным таймаутом
|
||||
await page.wait_for_url(f"{frontend_url}/admin/**", timeout=20000)
|
||||
print("✅ Авторизация успешна")
|
||||
|
||||
print("📋 Переходим на страницу сообществ...")
|
||||
await page.goto(f"{frontend_url}/admin/communities")
|
||||
await page.wait_for_load_state("networkidle")
|
||||
await page.goto(f"{frontend_url}/admin/communities", wait_until="networkidle")
|
||||
print("✅ Страница сообществ загружена")
|
||||
|
||||
print("🔍 Ищем таблицу сообществ...")
|
||||
await page.wait_for_selector("table", timeout=10000)
|
||||
await page.wait_for_selector("table tbody tr", timeout=10000)
|
||||
await page.wait_for_selector("table", timeout=15000)
|
||||
await page.wait_for_selector("table tbody tr", timeout=15000)
|
||||
print("✅ Таблица сообществ найдена")
|
||||
|
||||
# Создаем папку для скриншотов если её нет
|
||||
os.makedirs("test-results", exist_ok=True)
|
||||
|
||||
print("📸 Делаем скриншот таблицы...")
|
||||
await page.screenshot(path="test-results/communities_table_debug.png")
|
||||
|
||||
@@ -112,15 +152,25 @@ async def test_delete_button(frontend_url):
|
||||
class_name = await btn.get_attribute("class")
|
||||
print(f" Кнопка {i}: текст='{text}', класс='{class_name}'")
|
||||
|
||||
return True
|
||||
else:
|
||||
print("❌ Строка с Test Community не найдена")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка: {e}")
|
||||
# Создаем папку для скриншотов если её нет
|
||||
os.makedirs("test-results", exist_ok=True)
|
||||
await page.screenshot(path=f"test-results/error_{int(time.time())}.png")
|
||||
return False
|
||||
finally:
|
||||
await browser.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_delete_button())
|
||||
result = asyncio.run(test_delete_button("http://localhost:3000"))
|
||||
if result:
|
||||
print("✅ Тест завершен успешно")
|
||||
else:
|
||||
print("❌ Тест завершен с ошибками")
|
||||
exit(1)
|
||||
|
||||
@@ -4,41 +4,72 @@
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
import time
|
||||
import requests
|
||||
|
||||
|
||||
def wait_for_server_ready(url: str, timeout: int = 60) -> bool:
|
||||
"""Ждем готовности сервера"""
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
if response.status_code == 200:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
time.sleep(2)
|
||||
return False
|
||||
|
||||
|
||||
def test_delete_new_community():
|
||||
"""Тестируем удаление нового сообщества через API"""
|
||||
|
||||
# Проверяем готовность бэкенда
|
||||
print("🌐 Проверяем готовность бэкенда...")
|
||||
if not wait_for_server_ready("http://localhost:8000"):
|
||||
print("❌ Бэкенд не готов в течение 60 секунд")
|
||||
return False
|
||||
|
||||
print("✅ Бэкенд готов")
|
||||
|
||||
# 1. Авторизуемся как test_admin@discours.io
|
||||
print("🔐 Авторизуемся как test_admin@discours.io...")
|
||||
login_response = requests.post(
|
||||
"http://localhost:8000/graphql",
|
||||
headers={"Content-Type": "application/json"},
|
||||
json={
|
||||
"query": """
|
||||
mutation Login($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password) {
|
||||
success
|
||||
token
|
||||
author {
|
||||
id
|
||||
name
|
||||
email
|
||||
try:
|
||||
login_response = requests.post(
|
||||
"http://localhost:8000/graphql",
|
||||
headers={"Content-Type": "application/json"},
|
||||
json={
|
||||
"query": """
|
||||
mutation Login($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password) {
|
||||
success
|
||||
token
|
||||
author {
|
||||
id
|
||||
name
|
||||
email
|
||||
}
|
||||
error
|
||||
}
|
||||
}
|
||||
error
|
||||
}
|
||||
}
|
||||
""",
|
||||
"variables": {"email": "test_admin@discours.io", "password": "password123"},
|
||||
},
|
||||
)
|
||||
""",
|
||||
"variables": {"email": "test_admin@discours.io", "password": "password123"},
|
||||
},
|
||||
timeout=30 # Увеличиваем таймаут
|
||||
)
|
||||
except requests.exceptions.Timeout:
|
||||
print("❌ Таймаут при авторизации")
|
||||
return False
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("❌ Ошибка подключения к бэкенду")
|
||||
return False
|
||||
|
||||
login_data = login_response.json()
|
||||
if not login_data.get("data", {}).get("login", {}).get("success"):
|
||||
print("❌ Ошибка авторизации test_admin@discours.io")
|
||||
return
|
||||
print(f"Ответ: {json.dumps(login_data, indent=2, ensure_ascii=False)}")
|
||||
return False
|
||||
|
||||
token = login_data["data"]["login"]["token"]
|
||||
user_id = login_data["data"]["login"]["author"]["id"]
|
||||
@@ -46,26 +77,31 @@ def test_delete_new_community():
|
||||
|
||||
# 2. Проверяем, что сообщество существует
|
||||
print("🔍 Проверяем существование сообщества...")
|
||||
communities_response = requests.post(
|
||||
"http://localhost:8000/graphql",
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
json={
|
||||
"query": """
|
||||
query GetCommunities {
|
||||
get_communities_all {
|
||||
id
|
||||
name
|
||||
slug
|
||||
created_by {
|
||||
id
|
||||
name
|
||||
email
|
||||
try:
|
||||
communities_response = requests.post(
|
||||
"http://localhost:8000/graphql",
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
json={
|
||||
"query": """
|
||||
query GetCommunities {
|
||||
get_communities_all {
|
||||
id
|
||||
name
|
||||
slug
|
||||
created_by {
|
||||
id
|
||||
name
|
||||
email
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
},
|
||||
)
|
||||
""",
|
||||
},
|
||||
timeout=30 # Увеличиваем таймаут
|
||||
)
|
||||
except requests.exceptions.Timeout:
|
||||
print("❌ Таймаут при получении списка сообществ")
|
||||
return False
|
||||
|
||||
communities_data = communities_response.json()
|
||||
target_community = None
|
||||
@@ -76,29 +112,37 @@ def test_delete_new_community():
|
||||
|
||||
if not target_community:
|
||||
print("❌ Сообщество test-admin-community-e2e-1754005730 не найдено")
|
||||
return
|
||||
print("Доступные сообщества:")
|
||||
for community in communities_data.get("data", {}).get("get_communities_all", []):
|
||||
print(f" - {community['name']} (slug: {community['slug']})")
|
||||
return False
|
||||
|
||||
print(f"✅ Найдено сообщество: {target_community['name']} (ID: {target_community['id']})")
|
||||
print(f" Создатель: {target_community['created_by']['name']} (ID: {target_community['created_by']['id']})")
|
||||
|
||||
# 3. Пытаемся удалить сообщество
|
||||
print("🗑️ Пытаемся удалить сообщество...")
|
||||
delete_response = requests.post(
|
||||
"http://localhost:8000/graphql",
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
json={
|
||||
"query": """
|
||||
mutation DeleteCommunity($slug: String!) {
|
||||
delete_community(slug: $slug) {
|
||||
success
|
||||
message
|
||||
error
|
||||
}
|
||||
}
|
||||
""",
|
||||
"variables": {"slug": "test-admin-community-e2e-1754005730"},
|
||||
},
|
||||
)
|
||||
try:
|
||||
delete_response = requests.post(
|
||||
"http://localhost:8000/graphql",
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
json={
|
||||
"query": """
|
||||
mutation DeleteCommunity($slug: String!) {
|
||||
delete_community(slug: $slug) {
|
||||
success
|
||||
message
|
||||
error
|
||||
}
|
||||
}
|
||||
""",
|
||||
"variables": {"slug": "test-admin-community-e2e-1754005730"},
|
||||
},
|
||||
timeout=30 # Увеличиваем таймаут
|
||||
)
|
||||
except requests.exceptions.Timeout:
|
||||
print("❌ Таймаут при удалении сообщества")
|
||||
return False
|
||||
|
||||
delete_data = delete_response.json()
|
||||
print(f"📡 Ответ удаления: {json.dumps(delete_data, indent=2, ensure_ascii=False)}")
|
||||
@@ -108,21 +152,26 @@ def test_delete_new_community():
|
||||
|
||||
# 4. Проверяем, что сообщество действительно удалено
|
||||
print("🔍 Проверяем, что сообщество удалено...")
|
||||
check_response = requests.post(
|
||||
"http://localhost:8000/graphql",
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
json={
|
||||
"query": """
|
||||
query GetCommunities {
|
||||
get_communities_all {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
"""
|
||||
},
|
||||
)
|
||||
try:
|
||||
check_response = requests.post(
|
||||
"http://localhost:8000/graphql",
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
json={
|
||||
"query": """
|
||||
query GetCommunities {
|
||||
get_communities_all {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
""",
|
||||
},
|
||||
timeout=30 # Увеличиваем таймаут
|
||||
)
|
||||
except requests.exceptions.Timeout:
|
||||
print("❌ Таймаут при проверке удаления")
|
||||
return False
|
||||
|
||||
check_data = check_response.json()
|
||||
still_exists = False
|
||||
@@ -133,13 +182,20 @@ def test_delete_new_community():
|
||||
|
||||
if still_exists:
|
||||
print("❌ Сообщество все еще существует после удаления")
|
||||
return False
|
||||
else:
|
||||
print("✅ Сообщество успешно удалено из базы данных")
|
||||
return True
|
||||
else:
|
||||
print("❌ Ошибка удаления")
|
||||
error = delete_data.get("data", {}).get("delete_community", {}).get("error")
|
||||
print(f"Ошибка: {error}")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_delete_new_community()
|
||||
if test_delete_new_community():
|
||||
print("✅ Тест завершен успешно")
|
||||
else:
|
||||
print("❌ Тест завершен с ошибками")
|
||||
exit(1)
|
||||
|
||||
87
tests/test_server_health.py
Normal file
87
tests/test_server_health.py
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Тест здоровья серверов для CI
|
||||
"""
|
||||
|
||||
import time
|
||||
import requests
|
||||
import pytest
|
||||
|
||||
|
||||
def test_backend_health():
|
||||
"""Проверяем здоровье бэкенда"""
|
||||
max_retries = 10
|
||||
for attempt in range(1, max_retries + 1):
|
||||
try:
|
||||
response = requests.get("http://localhost:8000/", timeout=10)
|
||||
if response.status_code == 200:
|
||||
print(f"✅ Бэкенд готов (попытка {attempt})")
|
||||
return
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"⚠️ Попытка {attempt}/{max_retries}: Бэкенд не готов - {e}")
|
||||
if attempt < max_retries:
|
||||
time.sleep(3)
|
||||
else:
|
||||
pytest.fail(f"Бэкенд не готов после {max_retries} попыток")
|
||||
|
||||
|
||||
def test_frontend_health():
|
||||
"""Проверяем здоровье фронтенда"""
|
||||
max_retries = 10
|
||||
for attempt in range(1, max_retries + 1):
|
||||
try:
|
||||
response = requests.get("http://localhost:3000/", timeout=10)
|
||||
if response.status_code == 200:
|
||||
print(f"✅ Фронтенд готов (попытка {attempt})")
|
||||
return
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"⚠️ Попытка {attempt}/{max_retries}: Фронтенд не готов - {e}")
|
||||
if attempt < max_retries:
|
||||
time.sleep(3)
|
||||
else:
|
||||
pytest.fail(f"Фронтенд не готов после {max_retries} попыток")
|
||||
|
||||
|
||||
def test_graphql_endpoint():
|
||||
"""Проверяем доступность GraphQL endpoint"""
|
||||
try:
|
||||
response = requests.post(
|
||||
"http://localhost:8000/graphql",
|
||||
headers={"Content-Type": "application/json"},
|
||||
json={"query": "{ __schema { types { name } } }"},
|
||||
timeout=15
|
||||
)
|
||||
if response.status_code == 200:
|
||||
print("✅ GraphQL endpoint доступен")
|
||||
return
|
||||
else:
|
||||
pytest.fail(f"GraphQL endpoint вернул статус {response.status_code}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
pytest.fail(f"GraphQL endpoint недоступен: {e}")
|
||||
|
||||
|
||||
def test_admin_panel_access():
|
||||
"""Проверяем доступность админ-панели"""
|
||||
try:
|
||||
response = requests.get("http://localhost:3000/admin", timeout=15)
|
||||
if response.status_code == 200:
|
||||
print("✅ Админ-панель доступна")
|
||||
return
|
||||
else:
|
||||
pytest.fail(f"Админ-панель вернула статус {response.status_code}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
pytest.fail(f"Админ-панель недоступна: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🧪 Проверяем здоровье серверов...")
|
||||
|
||||
try:
|
||||
test_backend_health()
|
||||
test_frontend_health()
|
||||
test_graphql_endpoint()
|
||||
test_admin_panel_access()
|
||||
print("✅ Все серверы здоровы!")
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка проверки здоровья: {e}")
|
||||
exit(1)
|
||||
Reference in New Issue
Block a user