117 lines
5.4 KiB
Python
117 lines
5.4 KiB
Python
"""Проверка ответа GraphQL place: структура и значения полей results[ ] + Allure-отчёт."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from typing import Any, Final
|
||
|
||
import allure # pyright: ignore[reportMissingImports]
|
||
import pytest
|
||
from allure_commons.types import AttachmentType # pyright: ignore[reportMissingImports]
|
||
|
||
from worklib.findplaceinfo.find_place_data import fetch_place_members
|
||
from worklib.QueryData import query_data, query_data_place_id_variables
|
||
|
||
# Ожидаемый фрагмент ответа (ключи и значения должны совпадать хотя бы в одной записи results).
|
||
_EXPECTED_RESULT: Final[dict[str, str]] = {
|
||
"id": "682b071a163ac2a0995355be",
|
||
"place_type": "street",
|
||
"name": "ул. Мебельная",
|
||
}
|
||
|
||
ALLURE_EPIC = "Place API"
|
||
ALLURE_FEATURE = "GraphQL place"
|
||
ALLURE_STORY = "Формат ответа и значения полей"
|
||
|
||
|
||
@allure.epic(ALLURE_EPIC)
|
||
@allure.feature(ALLURE_FEATURE)
|
||
@allure.story(ALLURE_STORY)
|
||
@pytest.mark.integration
|
||
class TestFindPlaceInfo:
|
||
"""Интеграционный тест: реальный запрос и сверка формата/значений."""
|
||
|
||
@pytest.fixture(scope="class")
|
||
def place_response(self) -> dict[str, Any]:
|
||
with allure.step("Запрос place через GraphQL (fetch_place_members)"):
|
||
payload = fetch_place_members(
|
||
query=query_data()["query"],
|
||
variables=query_data_place_id_variables()["variables"],
|
||
)
|
||
allure.attach(
|
||
json.dumps(payload, ensure_ascii=False, indent=2),
|
||
name="Ответ GraphQL (raw JSON)",
|
||
attachment_type=AttachmentType.JSON,
|
||
)
|
||
return payload
|
||
|
||
@allure.title("Структура: data → place → results[] с полями id, place_type, name")
|
||
@allure.severity(allure.severity_level.NORMAL)
|
||
def test_response_has_place_shape(self, place_response: dict[str, Any]) -> None:
|
||
with allure.step("Проверка вложенности data.place.results"):
|
||
results = _extract_results(place_response)
|
||
allure.attach(
|
||
json.dumps(results, ensure_ascii=False, indent=2),
|
||
name="results (извлечено)",
|
||
attachment_type=AttachmentType.JSON,
|
||
)
|
||
assert len(results) >= 1, "results должен содержать хотя бы один элемент"
|
||
for key in ("id", "place_type", "name"):
|
||
assert key in results[0], f'В элементе results[0] должен быть ключ "{key}"'
|
||
|
||
@allure.title("Значения id, place_type, name совпадают с эталоном")
|
||
@allure.severity(allure.severity_level.CRITICAL)
|
||
def test_expected_id_place_type_name_match(self, place_response: dict[str, Any]) -> None:
|
||
allure.attach(
|
||
json.dumps(_EXPECTED_RESULT, ensure_ascii=False, indent=2),
|
||
name="Ожидаемый объект в results",
|
||
attachment_type=AttachmentType.JSON,
|
||
)
|
||
with allure.step("Поиск записи с полным совпадением id, place_type, name"):
|
||
results = _extract_results(place_response)
|
||
match = _find_matching_result(results, _EXPECTED_RESULT)
|
||
if match is None:
|
||
allure.attach(
|
||
json.dumps(results, ensure_ascii=False, indent=2),
|
||
name="Фактический results (для отладки)",
|
||
attachment_type=AttachmentType.JSON,
|
||
)
|
||
assert match is not None, (
|
||
f"Ни одна запись в results не совпадает с ожидаемым набором {_EXPECTED_RESULT}. "
|
||
f"Получено: {json.dumps(results, ensure_ascii=False, indent=2)[:2000]}"
|
||
)
|
||
allure.attach(
|
||
json.dumps(match, ensure_ascii=False, indent=2),
|
||
name="Найденная запись",
|
||
attachment_type=AttachmentType.JSON,
|
||
)
|
||
assert match["id"] == _EXPECTED_RESULT["id"]
|
||
assert match["place_type"] == _EXPECTED_RESULT["place_type"]
|
||
assert match["name"] == _EXPECTED_RESULT["name"]
|
||
|
||
|
||
def _extract_results(payload: dict[str, Any]) -> list[dict[str, Any]]:
|
||
assert "data" in payload, f'В ответе нет ключа "data": {json.dumps(payload, ensure_ascii=False)[:500]}'
|
||
data = payload["data"]
|
||
assert isinstance(data, dict), '"data" должен быть объектом'
|
||
|
||
assert "place" in data, f'В data нет "place": keys={list(data.keys())}'
|
||
place = data["place"]
|
||
assert place is not None, '"place" не должен быть null'
|
||
assert isinstance(place, dict), '"place" должен быть объектом'
|
||
|
||
assert "results" in place, f'В place нет "results": keys={list(place.keys())}'
|
||
results = place["results"]
|
||
assert isinstance(results, list), '"results" должен быть массивом'
|
||
|
||
for i, item in enumerate(results):
|
||
assert isinstance(item, dict), f'results[{i}] должен быть объектом, получено {type(item)}'
|
||
return results
|
||
|
||
|
||
def _find_matching_result(results: list[dict[str, Any]], expected: dict[str, str]) -> dict[str, Any] | None:
|
||
for item in results:
|
||
if all(item.get(k) == v for k, v in expected.items()):
|
||
return item
|
||
return None
|