backend apis updation
This commit is contained in:
113
tests/test_brand_registry.py
Normal file
113
tests/test_brand_registry.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
Guard rails for brand -> parent-brand resolution.
|
||||
|
||||
`resolve_parent_brand` decides which `brand_*` table, which S3 folder, and
|
||||
which seed catalog file a product lands in. An edit to BRAND_ALIASES that
|
||||
quietly changes an existing brand's answer would relocate live rows, so the
|
||||
first test pins the answer for every realistic input: all alias keys, all
|
||||
parent values, and the `brand` field of every bundled seed catalog.
|
||||
|
||||
No database, no network - the registry is a plain dict.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.services.brand_registry import BRAND_ALIASES, resolve_parent_brand
|
||||
from app.services.vector_store import _sanitize_name
|
||||
|
||||
SEED_DIR = Path(__file__).resolve().parents[1] / "data" / "seed_catalogs"
|
||||
|
||||
|
||||
def _seed_brand_fields() -> list[str]:
|
||||
"""The `brand` value of every seed catalog (skipping non-catalog files)."""
|
||||
brands = []
|
||||
for path in sorted(SEED_DIR.glob("*.json")):
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8-sig"))
|
||||
except Exception:
|
||||
continue
|
||||
brand = data.get("brand")
|
||||
if brand:
|
||||
brands.append(brand)
|
||||
return brands
|
||||
|
||||
|
||||
# Every seed catalog and the table it must continue to feed. `tata` mapping to
|
||||
# hindustan_unilever is not a typo: alias "hul tata tea" claims it, which is
|
||||
# where all 121 of that file's products already live.
|
||||
EXPECTED_SEED_TABLES = {
|
||||
"amul": "brand_amul",
|
||||
"cadbury": "brand_cadbury",
|
||||
"cavinkare": "brand_cavinkare",
|
||||
"coca-cola": "brand_coca_cola",
|
||||
"colgate-palmolive": "brand_colgate_palmolive",
|
||||
"dabur": "brand_dabur",
|
||||
"godrej": "brand_godrej",
|
||||
"grb": "brand_grb",
|
||||
"hindustan unilever": "brand_hindustan_unilever",
|
||||
"lion dates": "brand_lion_dates",
|
||||
"Manna": "brand_manna",
|
||||
"milky mist": "brand_milky_mist",
|
||||
"naga": "brand_naga",
|
||||
"Nestle": "brand_nestle",
|
||||
"p&g": "brand_p_g",
|
||||
"pepsico": "brand_pepsico",
|
||||
"tata": "brand_hindustan_unilever",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("alias,parent", sorted(BRAND_ALIASES.items()))
|
||||
def test_every_alias_resolves_to_its_declared_parent(alias: str, parent: str) -> None:
|
||||
assert resolve_parent_brand(alias) == parent
|
||||
|
||||
|
||||
# Every parent brand resolves to itself except "sunfeast", which the alias
|
||||
# "itc sunfeast" claims for ITC. That collapse predates the word-boundary
|
||||
# change and is pinned here so it stays a deliberate quirk rather than drift.
|
||||
_NON_SELF_PARENTS = {"sunfeast": "itc"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("parent", sorted(set(BRAND_ALIASES.values())))
|
||||
def test_parent_brands_resolve_to_their_pinned_answer(parent: str) -> None:
|
||||
assert resolve_parent_brand(parent) == _NON_SELF_PARENTS.get(parent, parent)
|
||||
|
||||
|
||||
def test_seed_catalogs_keep_their_current_tables() -> None:
|
||||
"""The load-bearing assertion: no seed file changes destination table."""
|
||||
actual = {
|
||||
brand: f"brand_{_sanitize_name(resolve_parent_brand(brand))}"
|
||||
for brand in _seed_brand_fields()
|
||||
}
|
||||
assert actual == EXPECTED_SEED_TABLES
|
||||
|
||||
|
||||
def test_known_sub_brands_still_route_to_their_family() -> None:
|
||||
"""Word-boundary matching must not break legitimate sub-brand routing."""
|
||||
assert resolve_parent_brand("Dove") == "hindustan unilever"
|
||||
assert resolve_parent_brand("Silk") == "cadbury"
|
||||
assert resolve_parent_brand("Bingo") == "itc"
|
||||
assert resolve_parent_brand("Sunfeast") == "itc"
|
||||
|
||||
|
||||
def test_new_brand_is_not_swallowed_by_a_fragment_match() -> None:
|
||||
"""
|
||||
"sun" appears inside "hul sunsilk" but is not a word there, so a new brand
|
||||
called Sun must stay Sun. Under the old substring fallback it silently
|
||||
became "hindustan unilever" - the bug this guards.
|
||||
"""
|
||||
assert resolve_parent_brand("Sun") == "Sun"
|
||||
assert resolve_parent_brand("Bing") == "Bing"
|
||||
|
||||
|
||||
def test_unknown_brand_passes_through_unchanged() -> None:
|
||||
assert resolve_parent_brand("Totally Made Up Brand") == "Totally Made Up Brand"
|
||||
|
||||
|
||||
def test_new_brand_round_trips_to_its_own_table() -> None:
|
||||
"""ITC is the worked example: it must own brand_itc, not merge elsewhere."""
|
||||
assert resolve_parent_brand("ITC") == "itc"
|
||||
assert _sanitize_name(resolve_parent_brand("ITC")) == "itc"
|
||||
Reference in New Issue
Block a user