"""Suite B — Upsell engine link visibility rules (P0).""" import pytest from app.services.session_core.context import compute_interest_score from app.services.session_core.manager import SessionContext from app.services.upsell_engine import UpsellEngine, response_has_intrigue_hook @pytest.fixture def engine(): return UpsellEngine() @pytest.mark.p0 def test_purchase_intent_always_shows_link(engine): ctx = SessionContext(session_id="s1", author_id="a1", turn_count=0) assert engine.should_include_link("purchase_intent", ctx, "DIRECT_CTA") is True @pytest.mark.p0 def test_complaint_never_shows_link(engine): ctx = SessionContext( session_id="s1", author_id="a1", turn_count=5, selected_book_id="book-1", interest_score=0.9, ) strategy = engine.select_strategy("complaint", ctx) assert engine.should_include_link("complaint", ctx, strategy) is False @pytest.mark.p0 def test_turn_two_with_selected_book_shows_link(engine): ctx = SessionContext( session_id="s1", author_id="a1", turn_count=2, selected_book_id="book-1", interest_score=0.2, ) strategy = engine.select_strategy("question", ctx) assert engine.should_include_link("question", ctx, strategy) is True @pytest.mark.p0 def test_greeting_turn_one_no_link(engine): ctx = SessionContext(session_id="s1", author_id="a1", turn_count=0) strategy = engine.select_strategy("greeting", ctx) assert engine.should_include_link("greeting", ctx, strategy) is False @pytest.mark.p0 def test_full_story_request_shows_link(engine): ctx = SessionContext(session_id="s1", author_id="a1", turn_count=0) strategy = engine.select_strategy("full_story_request", ctx) assert strategy == "DIRECT_CTA" assert engine.should_include_link("full_story_request", ctx, strategy) is True @pytest.mark.p0 def test_book_comparison_uses_social_proof_at_medium_engagement(engine): ctx = SessionContext( session_id="s1", author_id="a1", turn_count=5, interest_score=0.5, ) strategy = engine.select_strategy("book_comparison", ctx) assert strategy == "SOCIAL_PROOF" def test_fallback_hook_skipped_when_intrigue_present(engine): text = "She carries a secret — want to know what it cost her?" assert engine.fallback_hook_if_needed(text, "CURIOSITY_GAP", 0.6) is None def test_fallback_hook_when_flat_response(engine): text = "The protagonist is brave." hook = engine.fallback_hook_if_needed(text, "CURIOSITY_GAP", 0.6) assert hook is not None assert response_has_intrigue_hook(hook) or len(hook) > 10 def test_pricing_intent_boosts_score(): cold = compute_interest_score(1, []) hot = compute_interest_score(1, ["pricing"], current_intent="purchase_intent") assert hot > cold assert hot >= 0.75