import uuid from datetime import date, timedelta from sqlmodel import Session from innercontext.api.routines import ( _build_actives_tool_handler, _build_day_context, _build_grooming_context, _build_inci_tool_handler, _build_objectives_context, _build_products_context, _build_recent_history, _build_safety_rules_tool_handler, _build_skin_context, _build_usage_notes_tool_handler, _contains_minoxidil_text, _ev, _extract_active_names, _extract_requested_product_ids, _get_available_products, _is_minoxidil_product, ) from innercontext.models import ( GroomingSchedule, Product, ProductInventory, Routine, RoutineStep, SkinConditionSnapshot, ) def test_contains_minoxidil_text(): assert _contains_minoxidil_text(None) is False assert _contains_minoxidil_text("") is False assert _contains_minoxidil_text("some random text") is False assert _contains_minoxidil_text("contains MINOXIDIL here") is True assert _contains_minoxidil_text("minoksydyl 5%") is True def test_is_minoxidil_product(): # Setup product p = Product(id=uuid.uuid4(), name="Test", brand="Brand", is_medication=True) assert _is_minoxidil_product(p) is False p.name = "Minoxidil 5%" assert _is_minoxidil_product(p) is True p.name = "Test" p.brand = "Brand with minoksydyl" assert _is_minoxidil_product(p) is True p.brand = "Brand" p.line_name = "Minoxidil Line" assert _is_minoxidil_product(p) is True p.line_name = None p.usage_notes = "Use minoxidil daily" assert _is_minoxidil_product(p) is True p.usage_notes = None p.inci = ["water", "minoxidil"] assert _is_minoxidil_product(p) is True p.inci = None p.actives = [{"name": "minoxidil", "strength": "5%"}] assert _is_minoxidil_product(p) is True # As Pydantic model representation isn't exactly a dict in db sometimes, we just test dict p.actives = [{"name": "Retinol", "strength": "1%"}] assert _is_minoxidil_product(p) is False def test_ev(): class DummyEnum: value = "dummy" assert _ev(None) == "" assert _ev(DummyEnum()) == "dummy" assert _ev("string") == "string" def test_build_skin_context(session: Session): # Empty assert _build_skin_context(session) == "SKIN CONDITION: no data\n" # With data snap = SkinConditionSnapshot( id=uuid.uuid4(), snapshot_date=date.today(), overall_state="good", hydration_level=4, barrier_state="intact", active_concerns=["acne", "dryness"], priorities=["hydration"], notes="Feeling good", ) session.add(snap) session.commit() ctx = _build_skin_context(session) assert "SKIN CONDITION (snapshot from" in ctx assert "Overall state: good" in ctx assert "Hydration: 4/5" in ctx assert "Barrier: intact" in ctx assert "Active concerns: acne, dryness" in ctx assert "Priorities: hydration" in ctx assert "Notes: Feeling good" in ctx def test_build_grooming_context(session: Session): assert _build_grooming_context(session) == "GROOMING SCHEDULE: none\n" sch = GroomingSchedule( id=uuid.uuid4(), day_of_week=0, action="shaving_oneblade", notes="Morning" ) session.add(sch) session.commit() ctx = _build_grooming_context(session) assert "GROOMING SCHEDULE:" in ctx assert "poniedziaƂek: shaving_oneblade (Morning)" in ctx # Test weekdays filter ctx2 = _build_grooming_context(session, weekdays=[1]) # not monday assert "(no entries for specified days)" in ctx2 def test_build_recent_history(session: Session): assert _build_recent_history(session) == "RECENT ROUTINES: none\n" r = Routine(id=uuid.uuid4(), routine_date=date.today(), part_of_day="am") session.add(r) p = Product( id=uuid.uuid4(), name="Cleanser", category="cleanser", brand="Test", recommended_time="both", leave_on=False, product_effect_profile={}, ) session.add(p) session.commit() s1 = RoutineStep(id=uuid.uuid4(), routine_id=r.id, order_index=1, product_id=p.id) s2 = RoutineStep( id=uuid.uuid4(), routine_id=r.id, order_index=2, action_type="shaving_razor" ) # Step with non-existent product s3 = RoutineStep( id=uuid.uuid4(), routine_id=r.id, order_index=3, product_id=uuid.uuid4() ) session.add_all([s1, s2, s3]) session.commit() ctx = _build_recent_history(session) assert "RECENT ROUTINES:" in ctx assert "AM:" in ctx assert "cleanser [" in ctx assert "action: shaving_razor" in ctx assert "unknown [" in ctx def test_build_products_context(session: Session): p1 = Product( id=uuid.uuid4(), name="Regaine", category="serum", is_medication=True, brand="J&J", recommended_time="both", leave_on=True, product_effect_profile={}, ) p2 = Product( id=uuid.uuid4(), name="Sunscreen", category="spf", brand="Test", leave_on=True, recommended_time="am", pao_months=6, product_effect_profile={"hydration_immediate": 2, "exfoliation_strength": 0}, context_rules={"safe_after_shaving": False}, min_interval_hours=12, max_frequency_per_week=7, ) session.add_all([p1, p2]) session.commit() # Inventory inv1 = ProductInventory( id=uuid.uuid4(), product_id=p2.id, is_opened=True, opened_at=date.today() - timedelta(days=10), expiry_date=date.today() + timedelta(days=365), ) inv2 = ProductInventory(id=uuid.uuid4(), product_id=p2.id, is_opened=False) session.add_all([inv1, inv2]) session.commit() # Usage r = Routine(id=uuid.uuid4(), routine_date=date.today(), part_of_day="am") session.add(r) session.commit() s = RoutineStep(id=uuid.uuid4(), routine_id=r.id, order_index=1, product_id=p2.id) session.add(s) session.commit() ctx = _build_products_context( session, time_filter="am", reference_date=date.today() ) # p1 is medication but not minoxidil (wait, Regaine name doesn't contain minoxidil!) -> skipped assert "Regaine" not in ctx # Let's fix p1 to be minoxidil p1.name = "Regaine Minoxidil" session.add(p1) session.commit() ctx = _build_products_context( session, time_filter="am", reference_date=date.today() ) assert "Regaine Minoxidil" in ctx assert "Sunscreen" in ctx assert "inventory_status={active:2,opened:1,sealed:1}" in ctx assert "nearest_open_expiry=" in ctx assert "nearest_open_pao_deadline=" in ctx assert "pao_months=6" in ctx assert "effects={'hydration_immediate': 2}" in ctx assert "context_rules={'safe_after_shaving': False}" in ctx assert "min_interval_hours=12" in ctx assert "max_frequency_per_week=7" in ctx assert "used_in_last_7_days=1" in ctx def test_build_objectives_context(): assert _build_objectives_context(False) == "" assert "improve beard" in _build_objectives_context(True) def test_build_day_context(): assert _build_day_context(None) == "" assert "Leaving home: yes" in _build_day_context(True) assert "Leaving home: no" in _build_day_context(False) def test_get_available_products_respects_filters(session: Session): regular_med = Product( id=uuid.uuid4(), name="Tretinoin", category="serum", is_medication=True, brand="Test", recommended_time="pm", leave_on=True, product_effect_profile={}, ) minoxidil_med = Product( id=uuid.uuid4(), name="Minoxidil 5%", category="serum", is_medication=True, brand="Test", recommended_time="both", leave_on=True, product_effect_profile={}, ) am_product = Product( id=uuid.uuid4(), name="AM SPF", category="spf", brand="Test", recommended_time="am", leave_on=True, product_effect_profile={}, ) pm_product = Product( id=uuid.uuid4(), name="PM Cream", category="moisturizer", brand="Test", recommended_time="pm", leave_on=True, product_effect_profile={}, ) session.add_all([regular_med, minoxidil_med, am_product, pm_product]) session.commit() am_available = _get_available_products(session, time_filter="am") am_names = {p.name for p in am_available} assert "Tretinoin" not in am_names assert "Minoxidil 5%" in am_names assert "AM SPF" in am_names assert "PM Cream" not in am_names def test_build_inci_tool_handler_returns_only_available_ids(session: Session): available = Product( id=uuid.uuid4(), name="Available", category="serum", brand="Test", recommended_time="both", leave_on=True, inci=["Water", "Niacinamide"], product_effect_profile={}, ) unavailable = Product( id=uuid.uuid4(), name="Unavailable", category="serum", brand="Test", recommended_time="both", leave_on=True, inci=["Water", "Retinol"], product_effect_profile={}, ) handler = _build_inci_tool_handler([available]) payload = handler( { "product_ids": [ str(available.id), str(unavailable.id), str(available.id), 123, ] } ) assert "products" in payload products = payload["products"] assert len(products) == 1 assert products[0]["id"] == str(available.id) assert products[0]["name"] == "Available" assert products[0]["inci"] == ["Water", "Niacinamide"] def test_extract_requested_product_ids_dedupes_and_limits(): ids = _extract_requested_product_ids( { "product_ids": [ "id-1", "id-2", "id-1", 3, "id-3", "id-4", ] }, max_ids=3, ) assert ids == ["id-1", "id-2", "id-3"] def test_extract_active_names_uses_compact_distinct_names(session: Session): p = Product( id=uuid.uuid4(), name="Test", category="serum", brand="Test", recommended_time="both", leave_on=True, actives=[ {"name": "Niacinamide", "percent": 10}, {"name": "Niacinamide", "percent": 5}, {"name": "Zinc PCA", "percent": 1}, ], product_effect_profile={}, ) names = _extract_active_names(p) assert names == ["Niacinamide", "Zinc PCA"] def test_additional_tool_handlers_return_product_payloads(session: Session): p = Product( id=uuid.uuid4(), name="Detail Product", category="serum", brand="Test", recommended_time="both", leave_on=True, usage_notes="Apply morning and evening.", actives=[{"name": "Niacinamide", "percent": 5, "functions": ["niacinamide"]}], context_rules={"safe_after_shaving": True}, product_effect_profile={}, ) ids_payload = {"product_ids": [str(p.id)]} actives_out = _build_actives_tool_handler([p])(ids_payload) assert actives_out["products"][0]["actives"][0]["name"] == "Niacinamide" notes_out = _build_usage_notes_tool_handler([p])(ids_payload) assert notes_out["products"][0]["usage_notes"] == "Apply morning and evening." safety_out = _build_safety_rules_tool_handler([p])(ids_payload) assert "context_rules" in safety_out["products"][0]