155 lines
4.9 KiB
Python
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]
|