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

268 lines
12 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
import json
import time
from dataclasses import dataclass
from typing import Any, Callable, Optional
import allure # pyright: ignore[reportMissingImports]
from allure_commons.types import AttachmentType # pyright: ignore[reportMissingImports]
from worklib.graphql_client import execute_graphql
from KVSTest.testdata.kvs_test_data import KVS_TEST_COMPANY_ID, KVSTestData
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:
# На части стендов Forbidden иногда приходит как GraphQL error со статусом 500.
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}. Проверьте, что пользователь из env "
"AUTH_USERNAME/AUTH_PASSWORD имеет права на subscription операции."
) from e
raise
@dataclass
class SubscriptionTestData:
"""
Данные для теста подписок:
service -> plan -> subscription -> invoices -> delete subscription
"""
company_id: str = KVS_TEST_COMPANY_ID
kvs: KVSTestData | None = None
service_id: Optional[str] = None
plan_id: Optional[str] = None
subscription_id: Optional[str] = None
_cleanup_fns: Optional[list[Callable[[], None]]] = None
@classmethod
def from_behave_context(cls, context: Any, *, company_id: str | None = None) -> "SubscriptionTestData":
cid = company_id if company_id is not None else KVS_TEST_COMPANY_ID
td: SubscriptionTestData | None = getattr(context, "kvs_subscription_test_data", None)
if isinstance(td, cls):
if not td._cleanup_fns:
td._cleanup_fns = getattr(context, "_cleanup_fns", None)
if not td.kvs:
td.kvs = KVSTestData.from_behave_context(context, company_id=cid)
td.company_id = cid
return td
td = cls(company_id=cid)
td._cleanup_fns = getattr(context, "_cleanup_fns", None)
td.kvs = KVSTestData.from_behave_context(context, company_id=cid)
setattr(context, "kvs_subscription_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_service(self) -> str:
if self.service_id:
return self.service_id
assert self.kvs is not None
token = self.kvs.ensure_token()
mutation = """
mutation createservice($title: String!, $type: String!) {
createService(dto: { title: $title, type: $type }) {
id
title
type
}
}
""".strip()
suffix = str(int(time.time()))
# API валидирует type: camera|intercom|access
variables = {"title": f"kvs-service-{suffix}", "type": "access"}
with allure.step("GraphQL: createService"):
resp = _exec_or_fail(op_name="createService(mutation)", token=token, query=mutation, variables=variables, company_id=self.company_id)
_attach_json("createService response", resp)
service = resp.get("data", {}).get("createService")
assert isinstance(service, dict) and service.get("id"), f"createService не вернул id. Ответ: {resp}"
service_id = service["id"]
self.service_id = service_id
def _cleanup_delete_service() -> None:
delete_mutation = """mutation { deleteService(id: "%s") }""".strip() % service_id
_exec_or_fail(op_name="deleteService(mutation)", token=token, query=delete_mutation, variables=None, company_id=self.company_id)
self._register_cleanup(_cleanup_delete_service)
return service_id
def ensure_plan(self) -> str:
if self.plan_id:
return self.plan_id
assert self.kvs is not None
token = self.kvs.ensure_token()
service_id = self.ensure_service()
place_id = self.kvs.ensure_place()
mutation = """
mutation ($input: CreatePlanDTO!) {
createPlan(dto: $input) {
id
service_ids
bundle_ids
place_id
place_ids
price
title
discount
payment_interval
price_without_discount
}
}
""".strip()
suffix = str(int(time.time()))
variables = {
"input": {
"services": [service_id],
"place_ids": [place_id],
"price": 200,
"payment_interval": 1,
"discount": 0,
"title": f"plan-kvs-{suffix}",
}
}
with allure.step("GraphQL: createPlan"):
resp = _exec_or_fail(op_name="createPlan(mutation)", token=token, query=mutation, variables=variables, company_id=self.company_id)
_attach_json("createPlan response", resp)
plan = resp.get("data", {}).get("createPlan")
assert isinstance(plan, dict) and plan.get("id"), f"createPlan не вернул id. Ответ: {resp}"
# Проверяем, что service действительно включён в plan (как просили)
service_ids = plan.get("service_ids")
assert isinstance(service_ids, list) and service_id in service_ids, (
f"Созданный service_id не попал в plan.service_ids. service_id={service_id!r}, service_ids={service_ids!r}"
)
plan_id = plan["id"]
self.plan_id = plan_id
def _cleanup_delete_plan() -> None:
delete_mutation = """mutation { deletePlan(id: "%s") }""".strip() % plan_id
_exec_or_fail(op_name="deletePlan(mutation)", token=token, query=delete_mutation, variables=None, company_id=self.company_id)
self._register_cleanup(_cleanup_delete_plan)
return plan_id
def create_subscription(self) -> str:
if self.subscription_id:
return self.subscription_id
assert self.kvs is not None
token = self.kvs.ensure_token()
plan_id = self.ensure_plan()
service_id = self.ensure_service()
place_id = self.kvs.ensure_place()
subscriber_id = self.kvs.create_user()
# Пользователь должен быть участником места (как в addUserToPlace тесте)
add_resp = self.kvs.add_user_to_place(account_id=subscriber_id, place_id=place_id)
_attach_json("addUserToPlace (for subscription) response", add_resp)
payload = add_resp.get("data", {}).get("addUserToPlace")
assert isinstance(payload, dict) and payload.get("member_id"), f"addUserToPlace не вернул member_id: {add_resp!r}"
members_resp = self.kvs.query_place_members(place_id=place_id)
_attach_json("place members (after addUserToPlace) response", members_resp)
results = members_resp.get("data", {}).get("place", {}).get("results", [])
assert isinstance(results, list) and results, f"place.results пустой: {members_resp!r}"
members = results[0].get("members") if isinstance(results[0], dict) else None
assert isinstance(members, list), f"members не list: {members!r}"
user_ids = []
for m in members:
if isinstance(m, dict) and isinstance(m.get("user"), dict):
uid = m["user"].get("id")
if isinstance(uid, str):
user_ids.append(uid)
assert subscriber_id in user_ids, f"subscriber_id не найден в place.members.user.id. subscriber_id={subscriber_id!r}, got={user_ids!r}"
mutation = """
mutation createsub($plan_id: String!, $subscriber_id: String!, $place_id: String!, $service_id: String!) {
createSubscription(dto: { plan_id: $plan_id, subscriber_id: $subscriber_id, place_id: $place_id, service_id: $service_id }) {
id
services { id title }
user { id data { first_name last_name } }
plan { id title }
place_id
}
}
""".strip()
variables = {"plan_id": plan_id, "subscriber_id": subscriber_id, "place_id": place_id, "service_id": service_id}
with allure.step("GraphQL: createSubscription"):
resp = _exec_or_fail(op_name="createSubscription(mutation)", token=token, query=mutation, variables=variables, company_id=self.company_id)
_attach_json("createSubscription response", resp)
sub = resp.get("data", {}).get("createSubscription")
assert isinstance(sub, dict) and sub.get("id"), f"createSubscription не вернул id. Ответ: {resp}"
# Проверяем, что service в subscription совпадает с созданным
services = sub.get("services")
assert isinstance(services, list) and any(isinstance(s, dict) and s.get("id") == service_id for s in services), (
f"service_id не найден в subscription.services. service_id={service_id!r}, services={services!r}"
)
plan_obj = sub.get("plan")
assert isinstance(plan_obj, dict) and plan_obj.get("id") == plan_id, f"subscription.plan.id не совпал: {plan_obj!r}"
assert sub.get("place_id") == place_id, f"subscription.place_id не совпал: {sub!r}"
user_obj = sub.get("user")
assert isinstance(user_obj, dict) and user_obj.get("id") == subscriber_id, f"subscription.user.id не совпал: {user_obj!r}"
subscription_id = sub["id"]
self.subscription_id = subscription_id
def _cleanup_delete_subscription() -> None:
del_mut = """mutation($id: String!) { deleteSubscription(id: $id) }""".strip()
_exec_or_fail(op_name="deleteSubscription(mutation)", token=token, query=del_mut, variables={"id": subscription_id}, company_id=self.company_id)
self._register_cleanup(_cleanup_delete_subscription)
return subscription_id
def query_invoices(self) -> dict[str, Any]:
assert self.kvs is not None
token = self.kvs.ensure_token()
place_id = self.kvs.ensure_place()
query = """
query invoicesInfo($place_id: String!) {
invoices(place_id: $place_id, status: pending) {
id
price
status
subscriptions
place_id
}
}
""".strip()
with allure.step("GraphQL: invoices (pending)"):
resp = _exec_or_fail(op_name="invoices(query)", token=token, query=query, variables={"place_id": place_id}, company_id=self.company_id)
_attach_json("invoices response", resp)
return resp
def delete_subscription_now(self) -> dict[str, Any]:
assert self.kvs is not None
token = self.kvs.ensure_token()
sub_id = self.subscription_id
assert isinstance(sub_id, str) and sub_id, "Нет subscription_id."
mutation = """mutation($id: String!) { deleteSubscription(id: $id) }""".strip()
with allure.step("GraphQL: deleteSubscription"):
resp = _exec_or_fail(op_name="deleteSubscription(mutation)", token=token, query=mutation, variables={"id": sub_id}, company_id=self.company_id)
_attach_json("deleteSubscription response", resp)
return resp