from __future__ import annotations from decimal import Decimal from pathlib import Path from typing import Callable import pytest from app.catalog import CatalogItem, RawCatalogItem, load_raw_catalog from app.matcher import CatalogMatcher CATALOG_ITEMS = load_raw_catalog( Path(__file__).resolve().parents[1] / "catalog_excel.csv" ) @pytest.mark.parametrize("item", CATALOG_ITEMS, ids=lambda item: item.sku) def test_full_catalog_name_retrieves_its_own_sku( matcher: CatalogMatcher, item: RawCatalogItem ) -> None: result = matcher.match(item.name) assert item.sku in {candidate.sku for candidate in result.candidates} def test_every_canonical_product_type_returns_only_its_own_items( matcher: CatalogMatcher, ) -> None: items_by_sku = {item.sku: item for item in matcher.items} product_types = {item.product_type for item in matcher.items} for product_type in product_types: result = matcher.match(product_type) assert result.candidates, product_type assert all( items_by_sku[candidate.sku].product_type == product_type for candidate in result.candidates ), product_type def test_every_catalog_model_code_returns_only_matching_items( matcher: CatalogMatcher, ) -> None: items_by_sku = {item.sku: item for item in matcher.items} model_codes = { model for item in matcher.items for model in item.attributes.model_codes } for model_code in model_codes: result = matcher.match(model_code) assert result.candidates, model_code assert all( model_code in items_by_sku[candidate.sku].attributes.model_codes for candidate in result.candidates ), model_code def test_every_catalog_brand_returns_only_its_own_items( matcher: CatalogMatcher, ) -> None: items_by_sku = {item.sku: item for item in matcher.items} brands = { item.attributes.brand for item in matcher.items if item.attributes.brand is not None } for brand in brands: result = matcher.match(brand) assert result.candidates, brand assert all( items_by_sku[candidate.sku].attributes.brand == brand for candidate in result.candidates ), brand def _number_text(value: Decimal) -> str: return format(value, "f") def _assert_query_returns_only( matcher: CatalogMatcher, query: str, predicate: Callable[[CatalogItem], bool], ) -> None: items_by_sku = {item.sku: item for item in matcher.items} result = matcher.match(query) assert result.candidates, query assert all( predicate(items_by_sku[candidate.sku]) for candidate in result.candidates ), query def test_every_catalog_millimetre_value_is_searchable_without_product_name( matcher: CatalogMatcher, ) -> None: values = { value for item in matcher.items for value in ( *item.attributes.mm_values, *((item.attributes.dimensions[0],) if item.attributes.dimensions else ()), *((item.attributes.thread_length_mm,) if item.attributes.thread_length_mm else ()), ) } for value in values: _assert_query_returns_only( matcher, f"{_number_text(value)} мм", lambda item, expected=value: ( expected in item.attributes.mm_values or bool( item.attributes.dimensions and item.attributes.dimensions[0] == expected ) or item.attributes.thread_length_mm == expected ), ) def test_every_catalog_dimension_is_searchable_without_product_name( matcher: CatalogMatcher, ) -> None: dimensions = { item.attributes.dimensions for item in matcher.items if item.attributes.dimensions } for dimension in dimensions: query = "x".join(_number_text(value) for value in dimension) _assert_query_returns_only( matcher, query, lambda item, expected=dimension: ( len(item.attributes.dimensions) >= len(expected) and item.attributes.dimensions[: len(expected)] == expected ), ) @pytest.mark.parametrize( ("field", "suffix"), [ ("voltage_v", " В"), ("power_w", " Вт"), ("thread_size", ""), ("grit", ""), ("profile", ""), ("sds_type", ""), ("teeth", " зубьев"), ], ) def test_every_scalar_catalog_attribute_is_searchable_without_product_name( matcher: CatalogMatcher, field: str, suffix: str, ) -> None: values = { getattr(item.attributes, field) for item in matcher.items if getattr(item.attributes, field) is not None } for value in values: if field == "thread_size": query = f"M{_number_text(value)}" elif field == "grit": query = f"P{value}" else: value_text = _number_text(value) if isinstance(value, Decimal) else str(value) query = f"{value_text}{suffix}" _assert_query_returns_only( matcher, query, lambda item, expected=value: getattr(item.attributes, field) == expected, )