innercontext/backend/tests/test_products_helpers.py

155 lines
4.9 KiB
Python

import uuid
from datetime import date
from unittest.mock import patch
from sqlmodel import Session
from innercontext.api.products import (
_build_actives_tool_handler,
_build_inci_tool_handler,
_build_safety_rules_tool_handler,
_build_shopping_context,
_build_usage_notes_tool_handler,
_extract_requested_product_ids,
)
from innercontext.models import Product, ProductInventory, SkinConditionSnapshot
def test_build_shopping_context(session: Session):
# Empty context
ctx = _build_shopping_context(session)
assert "(brak danych)" in ctx
assert "POSIADANE PRODUKTY" in ctx
# Add snapshot
snap = SkinConditionSnapshot(
id=uuid.uuid4(),
snapshot_date=date.today(),
overall_state="fair",
skin_type="combination",
hydration_level=3,
sensitivity_level=4,
barrier_state="mildly_compromised",
active_concerns=["redness"],
priorities=["soothing"],
)
session.add(snap)
# Add product
p = Product(
id=uuid.uuid4(),
name="Soothing Serum",
brand="BrandX",
category="serum",
recommended_time="both",
leave_on=True,
targets=["redness"],
product_effect_profile={"soothing_strength": 4, "hydration_immediate": 1},
actives=[{"name": "Centella"}],
)
session.add(p)
session.commit()
# Add inventory
inv = ProductInventory(id=uuid.uuid4(), product_id=p.id, is_opened=True)
session.add(inv)
session.commit()
ctx = _build_shopping_context(session)
assert "Typ skóry: combination" in ctx
assert "Nawilżenie: 3/5" in ctx
assert "Wrażliwość: 4/5" in ctx
assert "Aktywne problemy: redness" in ctx
assert "Priorytety: soothing" in ctx
# Check product
assert "[✓] id=" in ctx
assert "Soothing Serum" in ctx
assert f"id={p.id}" in ctx
assert "BrandX" in ctx
assert "targets: ['redness']" in ctx
assert "actives: ['Centella']" in ctx
assert "effects: {'soothing': 4}" in ctx
def test_suggest_shopping(client, session):
with patch(
"innercontext.api.products.call_gemini_with_function_tools"
) as mock_gemini:
mock_response = type(
"Response",
(),
{
"text": '{"suggestions": [{"category": "cleanser", "product_type": "cleanser", "priority": "high", "key_ingredients": [], "target_concerns": [], "why_needed": "reason", "recommended_time": "am", "frequency": "daily"}], "reasoning": "Test shopping"}'
},
)
mock_gemini.return_value = mock_response
r = client.post("/products/suggest")
assert r.status_code == 200
data = r.json()
assert len(data["suggestions"]) == 1
assert data["suggestions"][0]["product_type"] == "cleanser"
assert data["reasoning"] == "Test shopping"
kwargs = mock_gemini.call_args.kwargs
assert "function_handlers" in kwargs
assert "get_product_inci" in kwargs["function_handlers"]
assert "get_product_safety_rules" in kwargs["function_handlers"]
assert "get_product_actives" in kwargs["function_handlers"]
assert "get_product_usage_notes" in kwargs["function_handlers"]
def test_shopping_context_medication_skip(session: Session):
p = Product(
id=uuid.uuid4(),
name="Epiduo",
brand="Galderma",
category="serum",
recommended_time="pm",
leave_on=True,
is_medication=True,
product_effect_profile={},
)
session.add(p)
session.commit()
ctx = _build_shopping_context(session)
assert "Epiduo" not in ctx
def test_extract_requested_product_ids_dedupes_and_limits():
ids = _extract_requested_product_ids(
{"product_ids": ["a", "b", "a", 1, "c", "d"]},
max_ids=3,
)
assert ids == ["a", "b", "c"]
def test_shopping_tool_handlers_return_payloads(session: Session):
product = Product(
id=uuid.uuid4(),
name="Test Product",
brand="Brand",
category="serum",
recommended_time="both",
leave_on=True,
usage_notes="Use AM and PM on clean skin.",
inci=["Water", "Niacinamide"],
actives=[{"name": "Niacinamide", "percent": 5, "functions": ["niacinamide"]}],
context_rules={"safe_after_shaving": True},
product_effect_profile={},
)
payload = {"product_ids": [str(product.id)]}
inci_data = _build_inci_tool_handler([product])(payload)
assert inci_data["products"][0]["inci"] == ["Water", "Niacinamide"]
actives_data = _build_actives_tool_handler([product])(payload)
assert actives_data["products"][0]["actives"][0]["name"] == "Niacinamide"
notes_data = _build_usage_notes_tool_handler([product])(payload)
assert notes_data["products"][0]["usage_notes"] == "Use AM and PM on clean skin."
safety_data = _build_safety_rules_tool_handler([product])(payload)
assert "context_rules" in safety_data["products"][0]