""" Полное моделирование поведения пользователя автотеста. Бьёт в atest.pythonk8s.dev.nubes.ru, эмуляция + облако, все стенды, ручные операции + сценарии + переключения + краевые случаи. Запуск: python3 tests/test_user_simulation.py """ import time, requests, json, sys BASE = "https://atest.pythonk8s.dev.nubes.ru" PASS = FAIL = 0 def cookie(mode="polygon", stand="test"): return {"mode": mode, "polygon_stand": stand} def log(ok, msg): global PASS, FAIL mark = "OK" if ok else "FAIL" if ok: PASS += 1 else: FAIL += 1 print(f" [{mark}] {msg}") if not ok: sys.stdout.flush() def get(path, cookies=None, code=200): r = requests.get(f"{BASE}{path}", cookies=cookies or cookie(), timeout=30) assert r.status_code == code, f"GET {path}: {r.status_code}" return r.json() if r.text.strip() else {} def post(path, data, cookies=None, code=200): r = requests.post(f"{BASE}{path}", json=data, cookies=cookies or cookie(), timeout=15) assert r.status_code == code, f"POST {path}: {r.status_code} {r.text[:100]}" return r.json() if r.text.strip() else {} def poll(op_uid, cookies=None, timeout=15): deadline = time.time() + timeout while time.time() < deadline: d = get(f"/api/test/status/{op_uid}", cookies) if d.get("done") or d.get("status") in ("OK", "FAIL"): return d time.sleep(0.5) return {"status": "TIMEOUT"} def create_instance(sid, name=None, cookies=None): r = post("/api/test", {"serviceId": sid, "operation": "create", "svcOperationId": 18, "params": {}, "displayName": name or f"autotest-user-{sid}"}, cookies) return r["opUid"], r.get("instanceUid") def run_scenario(def_id, cookies=None): r = post("/api/scenario/run", {"definition_id": def_id}, cookies, code=202) return r["run_id"] def poll_scenario(run_id, cookies=None, timeout=20): deadline = time.time() + timeout while time.time() < deadline: d = get(f"/api/scenario/run/{run_id}", cookies) if d.get("status") != "RUNNING": return d time.sleep(1) return {"status": "TIMEOUT"} # ═══════════════════════════════════════════════════════════ print("=" * 60) print("ТЕСТ 1: Загрузка страницы, сервисы, эмуляция") print("=" * 60) # 1.1 Главная страница рендерится r = requests.get(BASE, cookies=cookie(), timeout=10) log(r.status_code == 200, "GET / -> 200") log("Эмуляция" in r.text, "страница содержит 'Эмуляция'") log("v1.2" in r.text, "версия присутствует") # 1.2 Список сервисов svc = get("/api/services") log(len(svc) > 30, f"сервисов: {len(svc)}") log(any(s["svcId"] == 1 for s in svc), "Болванка есть") # 1.3 Инстансы inst = get("/api/instances/list") log(isinstance(inst, list), f"инстансов: {len(inst)}") print(f"\n{'='*60}") print("ТЕСТ 2: Ручные операции (create + modify + delete)") print("=" * 60) for sid in [1, 90, 91, 92, 93, 28, 21, 25, 99]: sname = next((s["svc"] for s in svc if s["svcId"] == sid), "?") try: op_uid, iuid = create_instance(sid, f"autotest-user-{sid}-{int(time.time())}") result = poll(op_uid) log(result.get("status") == "OK", f"create {sid} {sname} -> {iuid[:8] if iuid else '?'}") except Exception as e: log(False, f"create {sid} {sname} -> {str(e)[:60]}") print(f"\n{'='*60}") print("ТЕСТ 3: Переключение стендов в эмуляции") print("=" * 60) for stand in ["dev", "test", "prod"]: svc_s = get("/api/services", cookie("polygon", stand)) cnt = len(svc_s) log(cnt > 0, f"стенд {stand}: {cnt} сервисов") print(f"\n{'='*60}") print("ТЕСТ 4: Переключение Облако / Эмуляция") print("=" * 60) # 4.1 Переключиться в облако r = requests.post(BASE, data={"action": "set_mode", "mode": "cloud", "polygon_stand": "test"}, cookies=cookie(), allow_redirects=False, timeout=10) log(r.status_code in (302, 200), "POST set_mode cloud -> редирект") # 4.2 В облаке без токена — ошибка c = cookie("cloud", "test") r = requests.get(BASE, cookies=c, timeout=10) log("Токен невалиден" in r.text or "Token" in r.text or r.status_code in (200, 302), "облако без токена: ожидаемая ошибка") # 4.3 Обратно в эмуляцию r = requests.post(BASE, data={"action": "set_mode", "mode": "polygon", "polygon_stand": "test"}, cookies=c, allow_redirects=False, timeout=10) log(r.status_code in (302, 200), "POST set_mode polygon -> редирект") # 4.4 После возврата — сервисы снова есть svc2 = get("/api/services", cookie()) log(len(svc2) > 30, f"после возврата: {len(svc2)} сервисов") print(f"\n{'='*60}") print("ТЕСТ 5: Сценарии — CRUD + запуск") print("=" * 60) # 5.1 Список сценариев scenarios = get("/api/scenarios") log(len(scenarios) > 0, f"сценариев: {len(scenarios)}") # 5.2 Создать сценарий name = f"user-test-{int(time.time())}" steps = [ {"service_id": 1, "operation": "create", "params": {}, "output": "u1"}, {"service_id": 1, "operation": "modify", "instance_ref": "u1", "params": {}}, {"service_id": 1, "operation": "delete", "instance_ref": "u1", "params": {}}, ] r = requests.post(f"{BASE}/api/scenario/definitions", json={"name": name, "steps": steps}, cookies=cookie(), timeout=10) sc_def = r.json() log("id" in sc_def, f"создан сценарий {name} (id={sc_def.get('id')})") if "id" in sc_def: # 5.3 Запустить run_id = run_scenario(sc_def["id"]) log(run_id is not None, f"запущен run_id={run_id}") # 5.4 Поллинг result = poll_scenario(run_id) log(result.get("status") == "OK", f"сценарий: {result.get('status')} (шаг {result.get('current_step')}/{result.get('total_steps')})") # 5.5 Запустить ещё раз (после завершения должно работать) run_id2 = run_scenario(sc_def["id"]) result2 = poll_scenario(run_id2) log(result2.get("status") == "OK", f"повторный запуск: {result2.get('status')}") # 5.6 Удалить r = requests.delete(f"{BASE}/api/scenario/definitions/{sc_def['id']}", cookies=cookie(), timeout=10) log(r.status_code == 200, f"удалён сценарий {sc_def['id']}") # 5.7 Seed-сценарий запустить seed = next((s for s in scenarios if s.get("is_seed")), None) if seed: run_id = run_scenario(seed["id"]) result = poll_scenario(run_id, timeout=20) log(result.get("status") == "OK", f"seed-сценарий '{seed['name']}': {result.get('status')}") else: # Ищем dummy_test среди обычных for s in scenarios: if "dummy" in s.get("name", "").lower(): run_id = run_scenario(s["id"]) result = poll_scenario(run_id, timeout=20) log(result.get("status") == "OK", f"сценарий '{s['name']}': {result.get('status')}") break else: log(False, "нет сценария для запуска") print(f"\n{'='*60}") print("ТЕСТ 6: История") print("=" * 60) rows = get("/api/history") log(len(rows) > 0, f"записей в истории: {len(rows)}") log(any(r["status"] == "OK" for r in rows), "есть OK-записи") # Проверка что нет дубликатов RUNNING/OK (фикс v1.2.39) op_uids = [r["op_uid"] for r in rows if r.get("op_uid")] dup_count = len(op_uids) - len(set(op_uids)) log(dup_count == 0, f"дубликатов op_uid: {dup_count} (0 = хорошо)") print(f"\n{'='*60}") print("ТЕСТ 7: Логи") print("=" * 60) logs = get("/api/log") log(isinstance(logs, list), f"строк лога: {len(logs)}") log(any("api_test" in l or "save_run" in l or "OK" in l for l in logs), "логи содержат операции") print(f"\n{'='*60}") print("ТЕСТ 8: Валидация входных данных") print("=" * 60) # 8.1 Невалидный serviceId r = requests.post(f"{BASE}/api/test", json={"serviceId": "abc", "operation": "create", "svcOperationId": 18, "params": {}}, cookies=cookie(), timeout=10) log(r.status_code == 400, f"serviceId='abc' -> 400") # 8.2 Пустой operation r = requests.post(f"{BASE}/api/test", json={"serviceId": 1, "operation": "", "svcOperationId": 18, "params": {}}, cookies=cookie(), timeout=10) log(r.status_code == 400, f"operation='' -> 400") # 8.3 Невалидный instanceUid r = requests.post(f"{BASE}/api/test", json={"serviceId": 1, "operation": "delete", "svcOperationId": 71, "instanceUid": "not-a-uuid", "params": {}}, cookies=cookie(), timeout=10) log(r.status_code == 400, f"instanceUid='not-a-uuid' -> 400") # 8.4 Невалидный определение сценария r = requests.post(f"{BASE}/api/scenario/run", json={"definition_id": 99999}, cookies=cookie(), timeout=10) log(r.status_code == 404, f"run definition_id=99999 -> 404") print(f"\n{'='*60}") print("ТЕСТ 9: PostgreSQL — create + create_user + create_database + delete") print("=" * 60) try: # 9.1 Создать PostgreSQL pg_name = f"autotest-pg-{int(time.time())}" r = post("/api/test", {"serviceId": 90, "operation": "create", "svcOperationId": 19, "params": {}, "displayName": pg_name}) pg_op = r["opUid"] pg_uid = r.get("instanceUid") result = poll(pg_op, timeout=15) log(result.get("status") == "OK", f"create PostgreSQL -> {pg_uid[:8] if pg_uid else '?'}") if pg_uid: # 9.2 Создать пользователя user_name = f"testuser_{int(time.time()) % 10000}" r = post("/api/test", {"serviceId": 90, "operation": "create_user", "svcOperationId": 241, "instanceUid": pg_uid, "params": {"733": user_name, "740": "app_user", "1096": "false"}}) user_op = r["opUid"] result = poll(user_op) log(result.get("status") == "OK", f"create_user '{user_name}' -> {result.get('status')}") # 9.3 Создать базу данных db_name = f"testdb_{int(time.time()) % 10000}" r = post("/api/test", {"serviceId": 90, "operation": "create_database", "svcOperationId": 245, "instanceUid": pg_uid, "params": {"741": db_name, "742": user_name}}) db_op = r["opUid"] result = poll(db_op) log(result.get("status") == "OK", f"create_database '{db_name}' -> {result.get('status')}") # 9.4 Удалить PostgreSQL r = post("/api/test", {"serviceId": 90, "operation": "delete", "svcOperationId": 20, "instanceUid": pg_uid, "params": {}}) del_op = r["opUid"] result = poll(del_op) log(result.get("status") == "OK", f"delete PostgreSQL -> {result.get('status')}") except Exception as e: log(False, f"PostgreSQL test: {str(e)[:80]}") print(f"\n{'='*60}") print("ТЕСТ 10: Сценарий PostgreSQL — create -> create_user -> create_database -> delete") print("=" * 60) try: pg_scenario_name = f"pg-scenario-{int(time.time())}" pg_steps = [ {"service_id": 90, "operation": "create", "params": {}, "output": "pg"}, {"service_id": 90, "operation": "create_user", "instance_ref": "pg", "params": {"username": "scenario_user", "role": "app_user", "mtlsAccess": "false"}}, {"service_id": 90, "operation": "create_database", "instance_ref": "pg", "params": {"dbName": "scenario_db", "dbOwner": "scenario_user"}}, {"service_id": 90, "operation": "delete", "instance_ref": "pg", "params": {}}, ] r = requests.post(f"{BASE}/api/scenario/definitions", json={"name": pg_scenario_name, "steps": pg_steps}, cookies=cookie(), timeout=10) pg_def = r.json() log("id" in pg_def, f"создан сценарий PostgreSQL (id={pg_def.get('id')})") if "id" in pg_def: pg_run_id = run_scenario(pg_def["id"]) pg_result = poll_scenario(pg_run_id, timeout=30) log(pg_result.get("status") == "OK", f"запуск PG сценария: {pg_result.get('status')} (шаг {pg_result.get('current_step')}/{pg_result.get('total_steps')})") requests.delete(f"{BASE}/api/scenario/definitions/{pg_def['id']}", cookies=cookie(), timeout=10) except Exception as e: log(False, f"PG scenario: {str(e)[:80]}") print(f"\n{'='*60}") print(f"ИТОГО: PASS={PASS} FAIL={FAIL}") print(f"{'='*60}") sys.exit(0 if FAIL == 0 else 1)