test: +13 тестов — save_run UPSERT, auth режимы/URL, сценарии

test_db_save_run.py (4 теста): INSERT/UPDATE/UPSERT/без БД
test_auth.py (9 тестов): URL полигона, get_stand, whitelist, режимы
test_scenario_full.py (8 тестов): CRUD + запуск (требует polygon + БД)

Итого: 35 тестов (22 старых + 13 новых)
This commit is contained in:
2026-08-04 08:48:36 +04:00
parent 764592ba60
commit b89c0138e4
3 changed files with 435 additions and 0 deletions
+196
View File
@@ -0,0 +1,196 @@
"""
Интеграционные тесты сценариев с полигоном.
Требует polygon_server (conftest.py, session scope).
"""
import json
import time
# ── Хелперы ──
def _create_scenario(app_client, name, steps):
resp = app_client.post("/api/scenario/definitions",
json={"name": name, "steps": steps},
headers={"Content-Type": "application/json"})
return resp.get_json()
def _delete_scenario(app_client, def_id):
app_client.delete(f"/api/scenario/definitions/{def_id}")
def _run_scenario(app_client, def_id):
return app_client.post("/api/scenario/run",
json={"definition_id": def_id},
headers={"Content-Type": "application/json"})
def _poll_scenario(app_client, run_id, timeout=15):
deadline = time.time() + timeout
while time.time() < deadline:
resp = app_client.get(f"/api/scenario/run/{run_id}")
data = resp.get_json()
if data and data.get("status") != "RUNNING":
return data
time.sleep(0.5)
return {"status": "TIMEOUT"}
# ── Тесты ──
class TestScenarioLifecycle:
"""Полный цикл: создать определение -> запустить -> проверить результат."""
def test_list_scenarios(self, app_client):
resp = app_client.get("/api/scenarios")
assert resp.status_code == 200
data = resp.get_json()
assert isinstance(data, list)
def test_create_and_run_simple_scenario(self, app_client):
"""Сценарий create -> delete. Проверяем что оба шага OK."""
steps = [
{"service_id": 1, "operation": "create", "params": {}, "output": "d1"},
{"service_id": 1, "operation": "delete", "instance_ref": "d1", "params": {}},
]
name = f"test-simple-{int(time.time())}"
created = _create_scenario(app_client, name, steps)
assert "id" in created, f"Create failed: {created}"
resp = _run_scenario(app_client, created["id"])
assert resp.status_code == 202, f"Run failed: {resp.get_json()}"
run_data = resp.get_json()
assert run_data["status"] == "RUNNING"
run_id = run_data["run_id"]
result = _poll_scenario(app_client, run_id)
assert result["status"] == "OK", f"Scenario failed: {result}"
assert result["current_step"] == 2
assert result["total_steps"] == 2
_delete_scenario(app_client, created["id"])
def test_scenario_404_unknown_id(self, app_client):
resp = _run_scenario(app_client, 99999)
assert resp.status_code == 404
def test_scenario_lock_prevents_second_run(self, app_client):
"""Запуск во время выполнения -> 409."""
steps = [
{"service_id": 1, "operation": "create", "params": {}, "output": "lock1"},
{"service_id": 1, "operation": "delete", "instance_ref": "lock1", "params": {}},
]
name = f"test-lock-{int(time.time())}"
created = _create_scenario(app_client, name, steps)
# Первый запуск
resp1 = _run_scenario(app_client, created["id"])
assert resp1.status_code == 202
# Второй запуск — должен получить 409
resp2 = _run_scenario(app_client, created["id"])
assert resp2.status_code == 409, f"Expected 409, got {resp2.status_code}: {resp2.get_json()}"
# Ждём завершения первого
run_id = resp1.get_json()["run_id"]
_poll_scenario(app_client, run_id)
_delete_scenario(app_client, created["id"])
def test_scenario_can_run_after_completion(self, app_client):
"""После завершения можно запустить снова."""
steps = [
{"service_id": 1, "operation": "create", "params": {}, "output": "again"},
{"service_id": 1, "operation": "delete", "instance_ref": "again", "params": {}},
]
name = f"test-again-{int(time.time())}"
created = _create_scenario(app_client, name, steps)
# Первый прогон
resp1 = _run_scenario(app_client, created["id"])
run1_id = resp1.get_json()["run_id"]
result1 = _poll_scenario(app_client, run1_id)
assert result1["status"] == "OK"
# Второй прогон — должен работать
resp2 = _run_scenario(app_client, created["id"])
assert resp2.status_code == 202, f"Second run failed: {resp2.get_json()}"
run2_id = resp2.get_json()["run_id"]
result2 = _poll_scenario(app_client, run2_id)
assert result2["status"] == "OK"
_delete_scenario(app_client, created["id"])
def test_scenario_status_endpoint(self, app_client):
"""GET /api/scenario/status возвращает список."""
resp = app_client.get("/api/scenario/status")
assert resp.status_code == 200
data = resp.get_json()
assert isinstance(data, list)
class TestScenarioCRUD:
"""CRUD определения сценариев."""
def test_create_and_get_definition(self, app_client):
name = f"test-crud-{int(time.time())}"
steps = [{"service_id": 1, "operation": "create", "params": {}}]
created = _create_scenario(app_client, name, steps)
assert "id" in created
resp = app_client.get(f"/api/scenario/definitions/{created['id']}")
data = resp.get_json()
assert data["name"] == name
assert data["version"] == 1
_delete_scenario(app_client, created["id"])
def test_update_increments_version(self, app_client):
name = f"test-ver-{int(time.time())}"
created = _create_scenario(app_client, name,
[{"service_id": 1, "operation": "create", "params": {}}])
resp = app_client.put(f"/api/scenario/definitions/{created['id']}",
json={"name": name, "steps": [
{"service_id": 1, "operation": "create", "params": {}},
{"service_id": 1, "operation": "delete", "params": {}}
], "version": 1},
headers={"Content-Type": "application/json"})
data = resp.get_json()
assert data.get("ok"), f"Update failed: {data}"
assert data["version"] == 2
_delete_scenario(app_client, created["id"])
def test_version_conflict_returns_409(self, app_client):
name = f"test-conflict-{int(time.time())}"
created = _create_scenario(app_client, name,
[{"service_id": 1, "operation": "create", "params": {}}])
# Обновляем с version=1 -> OK
app_client.put(f"/api/scenario/definitions/{created['id']}",
json={"name": name, "steps": [
{"service_id": 1, "operation": "create", "params": {}}
], "version": 1},
headers={"Content-Type": "application/json"})
# Ещё раз с version=1 -> conflict
resp = app_client.put(f"/api/scenario/definitions/{created['id']}",
json={"name": name, "steps": [
{"service_id": 1, "operation": "create", "params": {}}
], "version": 1},
headers={"Content-Type": "application/json"})
data = resp.get_json()
assert data.get("conflict"), f"Expected conflict, got: {data}"
_delete_scenario(app_client, created["id"])
def test_soft_delete(self, app_client):
name = f"test-del-{int(time.time())}"
created = _create_scenario(app_client, name,
[{"service_id": 1, "operation": "create", "params": {}}])
resp = app_client.delete(f"/api/scenario/definitions/{created['id']}")
assert resp.status_code == 200
assert resp.get_json().get("ok")