"""Проверка ответа 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 # Ожидаемый фрагмент ответа (ключи и значения должны совпадать хотя бы в одной записи 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() 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