Arag / tests /unit /test_objection_intelligence.py
AuthorBot
Add objection intelligence: typed concern counters, personal anchors, stage-aware persuasion.
e0756f1
Raw
History Blame Contribute Delete
4.29 kB
"""Objection intelligence β€” detection, typed counters, stage-aware persuasion."""
from app.services.objection import count_prior_objections, detect_objection
from app.services.prompter import (
OBJECTION_HANDLING_INSTRUCTIONS,
get_conversation_stage_directive,
get_objection_instruction,
get_reading_anchor,
)
# ── Detection ─────────────────────────────────────────────────────────────────
def test_price_objection_detected():
assert detect_objection("this seems too expensive for me") == "price"
assert detect_objection("I can't afford another book right now") == "price"
def test_time_objection_detected():
assert detect_objection("i don't have time to read these days") == "time"
assert detect_objection("looks too long, I'd never finish it") == "time"
def test_relevance_objection_detected():
assert detect_objection("i have no interest in politics") == "relevance"
assert detect_objection("this is not my thing honestly") == "relevance"
def test_skepticism_objection_detected():
assert detect_objection("is it actually good or just hype?") == "skepticism"
assert detect_objection("what makes it different from other books") == "skepticism"
def test_genre_objection_detected():
assert detect_objection("i only read romance novels") == "genre"
def test_alternatives_objection_detected():
assert detect_objection("i already read something similar last year") == "alternatives"
def test_generic_value_challenge_maps_to_relevance():
assert detect_objection("so why should i buy this") == "relevance"
assert detect_objection("convince me") == "relevance"
def test_normal_question_is_not_objection():
assert detect_objection("who is the main character?") is None
assert detect_objection("what is this book about") is None
def test_prior_objections_counted_from_history():
history = [
{"role": "user", "content": "what is this about"},
{"role": "assistant", "content": "It explores..."},
{"role": "user", "content": "why should i buy this, i have no interest in politics"},
{"role": "assistant", "content": "Fair question..."},
]
assert count_prior_objections(history) == 1
# ── Instructions ──────────────────────────────────────────────────────────────
def test_each_objection_type_has_instruction():
for obj_type in ("price", "time", "relevance", "skepticism", "genre", "alternatives"):
assert obj_type in OBJECTION_HANDLING_INSTRUCTIONS
assert len(OBJECTION_HANDLING_INSTRUCTIONS[obj_type]) > 40
def test_objection_instruction_empty_when_no_objection():
assert get_objection_instruction(None) == ""
def test_repeat_objection_softens_the_counter():
first = get_objection_instruction("price", prior_objections=0)
repeat = get_objection_instruction("price", prior_objections=1)
assert len(repeat) > len(first)
assert "do not repeat" in repeat.lower()
# ── Personal anchor ───────────────────────────────────────────────────────────
def test_reading_anchor_uses_most_recent_tag():
anchor = get_reading_anchor(["genre", "characters"])
assert "characters" in anchor.lower() or "who they will meet" in anchor.lower()
def test_reading_anchor_empty_without_tags():
assert get_reading_anchor([]) == ""
# ── Conversation stage ────────────────────────────────────────────────────────
def test_stage_discovery_on_early_turns():
directive = get_conversation_stage_directive(0, 0.0)
assert "DISCOVERY" in directive
def test_stage_decision_when_hot():
directive = get_conversation_stage_directive(6, 0.9)
assert "DECISION" in directive
def test_stage_consideration_mid_journey():
directive = get_conversation_stage_directive(4, 0.6)
assert "CONSIDERATION" in directive