118 lines
3.9 KiB
Markdown
118 lines
3.9 KiB
Markdown
# Стратегия тестирования API
|
|
|
|
---
|
|
|
|
## 9 обязательных сценариев для каждого endpoint
|
|
|
|
### 1. Missing field → 422
|
|
```python
|
|
async def test_create_missing_field(async_client):
|
|
response = await async_client.post("/api/v1/ideas", json={})
|
|
assert response.status_code == 422
|
|
```
|
|
|
|
### 2. Wrong type → 422
|
|
```python
|
|
async def test_create_wrong_type(async_client):
|
|
response = await async_client.post("/api/v1/ideas", json={
|
|
"title": 123, # Должна быть строка
|
|
"content": "test",
|
|
})
|
|
assert response.status_code == 422
|
|
```
|
|
|
|
### 3. Expired/invalid token → 401
|
|
```python
|
|
async def test_unauthorized(async_client):
|
|
response = await async_client.get("/api/v1/ideas", headers={
|
|
"Authorization": "Bearer invalid_token"
|
|
})
|
|
assert response.status_code == 401
|
|
```
|
|
|
|
### 4. Wrong permissions → 403
|
|
```python
|
|
async def test_forbidden(async_client, user_token):
|
|
response = await async_client.get(
|
|
"/api/v1/admin/users",
|
|
headers={"Authorization": f"Bearer {user_token}"},
|
|
)
|
|
assert response.status_code == 403
|
|
```
|
|
|
|
### 5. Not found → 404
|
|
```python
|
|
async def test_not_found(async_client, user_token):
|
|
response = await async_client.get(
|
|
"/api/v1/ideas/nonexistent",
|
|
headers={"Authorization": f"Bearer {user_token}"},
|
|
)
|
|
assert response.status_code == 404
|
|
```
|
|
|
|
### 6. Conflict → 409
|
|
```python
|
|
async def test_duplicate_email(async_client):
|
|
# Создать первого пользователя
|
|
await async_client.post("/api/v1/auth/register", json={...})
|
|
# Попробовать создать с тем же email
|
|
response = await async_client.post("/api/v1/auth/register", json={...})
|
|
assert response.status_code == 409
|
|
```
|
|
|
|
### 7. Success → 200/201
|
|
```python
|
|
async def test_create_success(async_client, user_token):
|
|
response = await async_client.post(
|
|
"/api/v1/ideas",
|
|
json={"title": "Test", "content": "Content"},
|
|
headers={"Authorization": f"Bearer {user_token}"},
|
|
)
|
|
assert response.status_code == 201
|
|
data = response.json()
|
|
assert data["title"] == "Test"
|
|
```
|
|
|
|
### 8. Rate limit → 429 (если реализован)
|
|
```python
|
|
async def test_rate_limit(async_client, user_token):
|
|
for _ in range(100):
|
|
await async_client.get("/api/v1/ideas", headers={...})
|
|
response = await async_client.get("/api/v1/ideas", headers={...})
|
|
assert response.status_code == 429
|
|
```
|
|
|
|
### 9. Idempotency → тот же результат при повторе
|
|
```python
|
|
async def test_idempotent_delete(async_client, user_token, idea_id):
|
|
response1 = await async_client.delete(f"/api/v1/ideas/{idea_id}", headers={...})
|
|
response2 = await async_client.delete(f"/api/v1/ideas/{idea_id}", headers={...})
|
|
assert response1.status_code == 204
|
|
assert response2.status_code == 404 # Уже удалено
|
|
```
|
|
|
|
---
|
|
|
|
## Структура тестов
|
|
|
|
```
|
|
tests/
|
|
├── conftest.py # Глобальные фикстуры
|
|
├── unit/ # изолированные тесты
|
|
├── integration/
|
|
│ ├── conftest.py # Фикстуры для API тестов
|
|
│ ├── test_auth.py # 9 сценариев для auth
|
|
│ ├── test_ideas.py # 9 сценариев для ideas
|
|
│ └── test_admin.py # 9 сценариев для admin
|
|
└── smoke/
|
|
└── test_health.py # smoke-тесты
|
|
```
|
|
|
|
---
|
|
|
|
## [ASK]
|
|
|
|
- Все ли 9 сценариев нужны для каждого endpoint? (рекомендация: да, но можно начать с успех + not found + unauthorized)
|
|
- Нужны ли тесты на idempotency? (да, для DELETE и PATCH)
|
|
- Как часто прогонять? (при каждом PR — обязательно, при каждом push — желательно)
|