|
|
|
|
@ -0,0 +1,182 @@
|
|
|
|
|
import unittest
|
|
|
|
|
import requests
|
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
|
|
BASE_URL = "http://localhost:8090"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_error_message(response: requests.Response) -> str:
|
|
|
|
|
"""Извлекает сообщение об ошибке из ответа сервера или возвращает понятную заглушку."""
|
|
|
|
|
try:
|
|
|
|
|
if response.headers.get('Content-Type', '').startswith('application/json'):
|
|
|
|
|
data = response.json()
|
|
|
|
|
return data.get("error", "No 'error' field in JSON response")
|
|
|
|
|
else:
|
|
|
|
|
return f"Non-JSON response: {response.text[:200]}"
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return f"Failed to parse error: {e} | Raw: {response.text[:200]}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestGameShopAPI(unittest.TestCase):
|
|
|
|
|
|
|
|
|
|
valid_token = None
|
|
|
|
|
test_nickname = "autotest_user"
|
|
|
|
|
test_password = "secure_password_123"
|
|
|
|
|
test_email = "autotest@example.com"
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def setUpClass(cls):
|
|
|
|
|
"""Регистрируем пользователя один раз перед всеми тестами."""
|
|
|
|
|
super().setUpClass()
|
|
|
|
|
register_payload = {
|
|
|
|
|
"nickname": cls.test_nickname,
|
|
|
|
|
"password": cls.test_password,
|
|
|
|
|
"email": cls.test_email
|
|
|
|
|
}
|
|
|
|
|
try:
|
|
|
|
|
response = requests.post(f"{BASE_URL}/register/", json=register_payload, timeout=10)
|
|
|
|
|
if response.status_code != 200:
|
|
|
|
|
error_msg = get_error_message(response)
|
|
|
|
|
raise RuntimeError(f"Registration failed ({response.status_code}): {error_msg}")
|
|
|
|
|
data = response.json()
|
|
|
|
|
cls.valid_token = data.get("OK")
|
|
|
|
|
if not cls.valid_token:
|
|
|
|
|
raise ValueError("Response missing 'OK' field with token")
|
|
|
|
|
uuid.UUID(cls.valid_token)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
raise unittest.SkipTest(f"Не удалось зарегистрировать пользователя: {e}")
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def tearDownClass(cls):
|
|
|
|
|
"""Удаляем пользователя после всех тестов."""
|
|
|
|
|
if cls.valid_token:
|
|
|
|
|
try:
|
|
|
|
|
headers = {"Token": cls.valid_token}
|
|
|
|
|
response = requests.post(f"{BASE_URL}/unregister/", headers=headers, timeout=10)
|
|
|
|
|
if response.status_code != 200:
|
|
|
|
|
error_msg = get_error_message(response)
|
|
|
|
|
print(f"⚠️ Внимание: unregister завершился с кодом {response.status_code}: {error_msg}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"⚠️ Ошибка при unregister: {e}")
|
|
|
|
|
super().tearDownClass()
|
|
|
|
|
|
|
|
|
|
def test_1_get_shop_products(self):
|
|
|
|
|
"""GET /shop/ — должен вернуть непустой список товаров"""
|
|
|
|
|
response = requests.get(f"{BASE_URL}/shop/")
|
|
|
|
|
if response.status_code != 200:
|
|
|
|
|
error_msg = get_error_message(response)
|
|
|
|
|
self.fail(f"GET /shop/ failed ({response.status_code}): {error_msg}")
|
|
|
|
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
self.assertIn("OK", data, f"Ответ не содержит 'OK'. Получено: {data}")
|
|
|
|
|
products = data["OK"]
|
|
|
|
|
self.assertIsInstance(products, list, f"'OK' должен быть списком, получено: {type(products)}")
|
|
|
|
|
self.assertGreater(len(products), 0, "Список товаров пуст")
|
|
|
|
|
|
|
|
|
|
product = products[0]
|
|
|
|
|
required = {"id", "name", "count", "reserved", "picture_url", "description", "type", "cost"}
|
|
|
|
|
missing = required - set(product.keys())
|
|
|
|
|
self.assertFalse(missing, f"В товаре отсутствуют поля: {missing}. Товар: {product}")
|
|
|
|
|
self.assertIsInstance(product["cost"], (int, float), f"'cost' должен быть числом, получено: {type(product['cost'])}")
|
|
|
|
|
|
|
|
|
|
def test_2_registration_returns_valid_token(self):
|
|
|
|
|
"""Проверка, что токен — валидный UUID"""
|
|
|
|
|
self.assertIsNotNone(self.valid_token, "Токен не был получен при регистрации")
|
|
|
|
|
try:
|
|
|
|
|
uuid.UUID(self.valid_token)
|
|
|
|
|
except ValueError:
|
|
|
|
|
self.fail(f"Токен не является валидным UUID: {self.valid_token}")
|
|
|
|
|
|
|
|
|
|
def test_3_basket_success(self):
|
|
|
|
|
"""POST /basket/ с валидным токеном → возвращает '[]'"""
|
|
|
|
|
headers = {"Token": self.valid_token}
|
|
|
|
|
response = requests.post(f"{BASE_URL}/basket/", headers=headers)
|
|
|
|
|
if response.status_code != 200:
|
|
|
|
|
error_msg = get_error_message(response)
|
|
|
|
|
self.fail(f"POST /basket/ failed ({response.status_code}): {error_msg}")
|
|
|
|
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
self.assertIn("OK", data, f"Ответ не содержит 'OK'. Получено: {data}")
|
|
|
|
|
self.assertEqual(data["OK"], '[]', f"Ожидалась пустая корзина ('[]'), получено: {data['OK']}")
|
|
|
|
|
|
|
|
|
|
def test_4_history_success(self):
|
|
|
|
|
"""POST /history/ с валидным токеном → возвращает '[]'"""
|
|
|
|
|
headers = {"Token": self.valid_token}
|
|
|
|
|
response = requests.post(f"{BASE_URL}/history/", headers=headers)
|
|
|
|
|
if response.status_code != 200:
|
|
|
|
|
error_msg = get_error_message(response)
|
|
|
|
|
self.fail(f"POST /history/ failed ({response.status_code}): {error_msg}")
|
|
|
|
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
self.assertIn("OK", data, f"Ответ не содержит 'OK'. Получено: {data}")
|
|
|
|
|
self.assertEqual(data["OK"], '[]', f"Ожидалась пустая история ('[]'), получено: {data['OK']}")
|
|
|
|
|
|
|
|
|
|
def test_5_basket_invalid_token(self):
|
|
|
|
|
"""POST /basket/ с невалидным токеном → ошибка 500"""
|
|
|
|
|
headers = {"Token": "invalid-token-12345"}
|
|
|
|
|
response = requests.post(f"{BASE_URL}/basket/", headers=headers)
|
|
|
|
|
if response.status_code != 500:
|
|
|
|
|
error_msg = get_error_message(response)
|
|
|
|
|
self.fail(f"Ожидался статус 500 для невалидного токена, получен {response.status_code}. Ошибка: {error_msg}")
|
|
|
|
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
self.assertIn("error", data, f"Ответ должен содержать 'error', получено: {data}")
|
|
|
|
|
self.assertIsInstance(data["error"], str, f"'error' должен быть строкой, получено: {type(data['error'])}")
|
|
|
|
|
self.assertGreater(len(data["error"]), 0, "Сообщение об ошибке пустое")
|
|
|
|
|
|
|
|
|
|
def test_6_history_invalid_token(self):
|
|
|
|
|
"""POST /history/ с невалидным токеном → ошибка 500"""
|
|
|
|
|
headers = {"Token": str(uuid.uuid4())}
|
|
|
|
|
response = requests.post(f"{BASE_URL}/history/", headers=headers)
|
|
|
|
|
if response.status_code != 500:
|
|
|
|
|
error_msg = get_error_message(response)
|
|
|
|
|
self.fail(f"Ожидался статус 500 для невалидного токена, получен {response.status_code}. Ошибка: {error_msg}")
|
|
|
|
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
self.assertIn("error", data, f"Ответ должен содержать 'error', получено: {data}")
|
|
|
|
|
|
|
|
|
|
def test_7_login_success(self):
|
|
|
|
|
"""POST /login/ — успешная аутентификация зарегистрированного пользователя"""
|
|
|
|
|
login_payload = {
|
|
|
|
|
"email": self.test_email,
|
|
|
|
|
"password": self.test_password
|
|
|
|
|
}
|
|
|
|
|
response = requests.post(f"{BASE_URL}/login/", json=login_payload)
|
|
|
|
|
if response.status_code != 200:
|
|
|
|
|
error_msg = get_error_message(response)
|
|
|
|
|
self.fail(f"POST /login/ failed ({response.status_code}): {error_msg}")
|
|
|
|
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
self.assertIn("OK", data, f"Ответ не содержит 'OK'. Получено: {data}")
|
|
|
|
|
login_token = data["OK"]
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
uuid.UUID(login_token)
|
|
|
|
|
except ValueError:
|
|
|
|
|
self.fail(f"Токен от /login/ не является валидным UUID: {login_token}")
|
|
|
|
|
|
|
|
|
|
# Проверка, что токен работает
|
|
|
|
|
basket_resp = requests.post(f"{BASE_URL}/basket/", headers={"Token": login_token})
|
|
|
|
|
if basket_resp.status_code != 200:
|
|
|
|
|
error_msg = get_error_message(basket_resp)
|
|
|
|
|
self.fail(f"Токен от /login/ не работает: {error_msg}")
|
|
|
|
|
|
|
|
|
|
def test_8_logout_success(self):
|
|
|
|
|
"""POST /logout/ с валидным токеном — успешный выход"""
|
|
|
|
|
headers = {"Token": self.valid_token}
|
|
|
|
|
response = requests.post(f"{BASE_URL}/logout/", headers=headers)
|
|
|
|
|
if response.status_code != 200:
|
|
|
|
|
error_msg = get_error_message(response)
|
|
|
|
|
self.fail(f"POST /logout/ failed ({response.status_code}): {error_msg}")
|
|
|
|
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
self.assertIn("OK", data, f"Ответ не содержит 'OK'. Получено: {data}")
|
|
|
|
|
self.assertEqual(data["OK"], self.valid_token, "В ответе должен быть тот же токен")
|
|
|
|
|
|
|
|
|
|
def test_9_all_scenarios_completed(self):
|
|
|
|
|
"""Финальная проверка: все тесты пройдены"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main(verbosity=2)
|