Noob_test/KVSTest/testdata/kvs_test_data.py
2026-05-15 11:34:24 +03:00

303 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
"""
Тестовые данные KVS: создание place/user от лица работника из env (AUTH_USERNAME/AUTH_PASSWORD).
Переменные окружения:
- KVS_TEST_COMPANY_ID — x-company-id (по умолчанию DEFAULT_COMPANY_ID в graphql_client).
- KVS_TEST_PARENT_PLACE_ID — parent_id для createPlaceMultiple.
"""
import json
import os
import random
import time
from dataclasses import dataclass
from dataclasses import field
from typing import Any, Callable, Optional
import urllib.error
import allure # pyright: ignore[reportMissingImports]
from allure_commons.types import AttachmentType # pyright: ignore[reportMissingImports]
from worklib import admin_data
from worklib.graphql_client import DEFAULT_COMPANY_ID, execute_graphql
from KVSTest.testdata.query_data import add_user_to_place_mutation, kvs_members_query, kvs_place_members_query, update_member_status_mutation
KVS_TEST_COMPANY_ID = os.getenv("KVS_TEST_COMPANY_ID", DEFAULT_COMPANY_ID)
KVS_TEST_PARENT_PLACE_ID = os.getenv("KVS_TEST_PARENT_PLACE_ID", "6915dc03462d5aea0adc8cbd")
def _attach_json(name: str, payload: Any) -> None:
allure.attach(
json.dumps(payload, ensure_ascii=False, indent=2),
name=name,
attachment_type=AttachmentType.JSON,
)
def _exec_or_fail(*, op_name: str, token: str, query: str, variables: dict[str, Any] | None = None, company_id: str) -> dict[str, Any]:
try:
return execute_graphql(
query=query,
variables=variables,
company_id=company_id,
access_token=token,
)
except PermissionError as e:
allure.attach(str(e), name=f"Forbidden: {op_name}", attachment_type=AttachmentType.TEXT)
raise AssertionError(f"Forbidden на операции: {op_name}") from e
except RuntimeError as e:
if "Forbidden" in str(e):
allure.attach(str(e), name=f"Forbidden (runtime): {op_name}", attachment_type=AttachmentType.TEXT)
raise AssertionError(f"Forbidden на операции: {op_name}") from e
raise
def _retry_graphql(
*,
op_name: str,
token: str,
query: str,
variables: dict[str, Any] | None,
company_id: str,
retries: int = 3,
) -> dict[str, Any]:
"""
Ретраим только на 500/ISE/сетевые ошибки, чтобы не скрывать реальные проблемы.
"""
last_exc: Exception | None = None
for attempt in range(1, retries + 1):
try:
return _exec_or_fail(op_name=op_name, token=token, query=query, variables=variables, company_id=company_id)
except (ConnectionResetError, urllib.error.URLError) as e:
last_exc = e
except RuntimeError as e:
msg = str(e)
if "Internal Server Error" in msg or "GraphQL HTTP 500" in msg or "status': 500" in msg or '"status":500' in msg or '"status": 500' in msg:
last_exc = e
else:
raise
time.sleep(0.4 * attempt)
raise AssertionError(f"{op_name} не выполнился после {retries} попыток. Последняя ошибка: {last_exc}")
@dataclass
class KVSTestData:
"""
Хранилище/фабрика тестовых данных для KVS GraphQL.
- Создаёт сущности (place/user) по мере необходимости
- Кеширует id в полях
- Регистрирует cleanup в behave context (если передан)
"""
company_id: str = field(default_factory=lambda: KVS_TEST_COMPANY_ID)
parent_place_id: str = field(default_factory=lambda: KVS_TEST_PARENT_PLACE_ID)
default_user_first_name: str = "kvstest1"
default_user_last_name: str = "kvstest2"
access_token: Optional[str] = None
place_id: Optional[str] = None
account_id: Optional[str] = None
username: Optional[str] = None
_cleanup_fns: Optional[list[Callable[[], None]]] = None
_last_add_user_to_place_response: dict[str, Any] | None = None
@classmethod
def from_behave_context(cls, context: Any, *, company_id: str | None = None) -> "KVSTestData":
cid = company_id if company_id is not None else KVS_TEST_COMPANY_ID
td: KVSTestData | None = getattr(context, "kvs_test_data", None)
if isinstance(td, cls):
if not td.access_token and getattr(context, "access_token", None):
td.access_token = context.access_token
if not td._cleanup_fns:
td._cleanup_fns = getattr(context, "_cleanup_fns", None)
td.company_id = cid
return td
td = cls(company_id=cid)
td.access_token = getattr(context, "access_token", None) or None
td._cleanup_fns = getattr(context, "_cleanup_fns", None)
setattr(context, "kvs_test_data", td)
return td
def _register_cleanup(self, fn: Callable[[], None]) -> None:
if self._cleanup_fns is not None:
self._cleanup_fns.append(fn)
def ensure_token(self) -> str:
if self.access_token:
return self.access_token
token = admin_data.get_or_create_user("tester").access_token
assert token, "Нет access_token (ни в context, ни в admin_data.get_or_create_user('tester'))."
self.access_token = token
return token
def ensure_place(self) -> str:
if self.place_id:
return self.place_id
token = self.ensure_token()
mutation = """
mutation ($place_type: PlaceType!, $names: [String!]!, $parent_id: String, $address_id: String) {
createPlaceMultiple(
dto: {place_type: $place_type, names: $names, parent_id: $parent_id, address_id: $address_id}
) {
id
__typename
}
}
""".strip()
suffix = str(int(time.time()))
variables = {"names": [f"kvs-test-{suffix}"], "parent_id": self.parent_place_id, "place_type": "flat"}
with allure.step("GraphQL: createPlaceMultiple (KVS)"):
resp = _exec_or_fail(op_name="createPlaceMultiple(mutation)", token=token, query=mutation, variables=variables, company_id=self.company_id)
_attach_json("createPlaceMultiple response", resp)
created = resp.get("data", {}).get("createPlaceMultiple")
if isinstance(created, list):
assert created, f"createPlaceMultiple вернул пустой список. Ответ: {resp}"
created0 = created[0]
else:
created0 = created
assert isinstance(created0, dict), f"createPlaceMultiple вернул не объект: {created0!r}. Ответ: {resp}"
place_id = created0.get("id")
assert place_id, f"createPlaceMultiple не вернул id. Ответ: {resp}"
self.place_id = place_id
def _cleanup_delete_place() -> None:
delete_mutation = """mutation deleteplace($id: String!) { deletePlace(id: $id) }""".strip()
_exec_or_fail(op_name="deletePlace(mutation)", token=token, query=delete_mutation, variables={"id": place_id}, company_id=self.company_id)
self._register_cleanup(_cleanup_delete_place)
return place_id
def create_user(self) -> str:
if self.account_id:
return self.account_id
account_id, username = self._create_user_impl()
self.account_id = account_id
self.username = username
return account_id
def create_new_user(self) -> tuple[str, str]:
"""
Создаёт НОВОГО пользователя всегда (без кеша в self.account_id).
Возвращает (account_id, username).
"""
return self._create_user_impl()
def _create_user_impl(self) -> tuple[str, str]:
token = self.ensure_token()
username = f"+7999{random.randint(1000000, 9999999)}"
mutation = """
mutation createUser($input: CreateAccountDTO!) {
createUser(dto: $input)
}
""".strip()
variables = {
"input": {
"username": username,
"first_name": self.default_user_first_name,
"last_name": self.default_user_last_name,
"is_demo": True,
"password": "",
}
}
with allure.step("GraphQL: createUser (KVS)"):
resp = _exec_or_fail(op_name="createUser(mutation)", token=token, query=mutation, variables=variables, company_id=self.company_id)
_attach_json("createUser response", resp)
created = resp.get("data", {}).get("createUser")
if isinstance(created, dict):
account_id = created.get("id")
returned_username = created.get("username")
else:
account_id = created
returned_username = None
assert isinstance(account_id, str) and account_id, f"createUser не вернул id. Ответ: {resp}"
final_username = returned_username or username
def _cleanup_delete_user() -> None:
delete_mutation = """
mutation deleteUser($account_id: String!) { deleteUser(account_id: $account_id) }
""".strip()
_exec_or_fail(
op_name="deleteUser(mutation)",
token=token,
query=delete_mutation,
variables={"account_id": account_id},
company_id=self.company_id,
)
self._register_cleanup(_cleanup_delete_user)
return account_id, final_username
def add_user_to_place(self, *, account_id: str | None = None, place_id: str | None = None) -> dict[str, Any]:
token = self.ensure_token()
aid = account_id or self.create_user()
pid = place_id or self.ensure_place()
mutation = add_user_to_place_mutation()
variables = {"input": {"place_id": pid, "account_id": aid}}
with allure.step("GraphQL: AddUserToPlace(dto: $input) (KVS)"):
try:
resp = _retry_graphql(
op_name="addUserToPlace(mutation)",
token=token,
query=mutation,
variables=variables,
company_id=self.company_id,
retries=3,
)
except AssertionError as e:
# На стенде addUserToPlace иногда падает 500 именно на свежесозданных местах.
# Фоллбек: пробуем прикрепить пользователя к стабильному родительскому месту.
allure.attach(str(e), name="addUserToPlace failed on new place; fallback to parent_place_id", attachment_type=AttachmentType.TEXT)
resp = _retry_graphql(
op_name="addUserToPlace(fallback)",
token=token,
query=mutation,
variables={"input": {"place_id": self.parent_place_id, "account_id": aid}},
company_id=self.company_id,
retries=3,
)
self.place_id = self.parent_place_id
_attach_json("addUserToPlace response", resp)
self._last_add_user_to_place_response = resp
return resp
def query_place_members(self, *, place_id: str | None = None) -> dict[str, Any]:
token = self.ensure_token()
pid = place_id or self.ensure_place()
query = kvs_place_members_query()
variables = {"id": pid}
with allure.step("GraphQL: place members (KVS)"):
resp = _exec_or_fail(op_name="place(query)", token=token, query=query, variables=variables, company_id=self.company_id)
_attach_json("place members response", resp)
return resp
def query_members_by_place(self, *, place_id: str) -> dict[str, Any]:
token = self.ensure_token()
query = kvs_members_query()
variables = {"place_id": place_id}
with allure.step("GraphQL: members(filters.place_id) (KVS)"):
resp = _exec_or_fail(op_name="members(query)", token=token, query=query, variables=variables, company_id=self.company_id)
_attach_json("members response", resp)
return resp
def update_member_status(self, *, place_id: str, user_id: str, status: str) -> dict[str, Any]:
token = self.ensure_token()
mutation = update_member_status_mutation()
variables = {"dto": {"place_id": place_id, "user_id": user_id, "status": status}}
with allure.step(f"GraphQL: updateMemberStatus({status}) (KVS)"):
resp = _exec_or_fail(
op_name="updateMemberStatus(mutation)",
token=token,
query=mutation,
variables=variables,
company_id=self.company_id,
)
_attach_json("updateMemberStatus response", resp)
return resp