88 lines
4.7 KiB
Python
88 lines
4.7 KiB
Python
from copy import deepcopy
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
from backend.prospecting import execution
|
|
from backend.prospecting.contracts import ProspectingBrief
|
|
|
|
|
|
@pytest.fixture
|
|
def task(monkeypatch):
|
|
monkeypatch.setattr(execution, "get_llm_config", lambda: {"provider": "fixture", "model": "fixture-model"})
|
|
return {"id": "incremental-fixture", "inputs": {"_goal_version": 1}, "artifacts": {"sources": [
|
|
{"source_id": "note", "source_type": "note", "content": "正在准备装修", "source_url": "https://example.test/n"},
|
|
{"source_id": "reply", "source_type": "reply", "content": "我也想找设计师", "source_url": "https://example.test/n", "parent_source_id": "note"},
|
|
]}}
|
|
|
|
|
|
def analyzer():
|
|
async def analyze(brief, sources, *, context_sources=None):
|
|
return {"decisions": [{"source_id": row["source_id"], "source_type": row["source_type"], "decision": "keep", "quote": row["content"], "reason": "本人表达需求"} for row in sources], "analysis_model": "fixture-model"}
|
|
return AsyncMock(side_effect=analyze)
|
|
|
|
|
|
async def test_identical_input_reuses_decisions_and_force_is_explicit(task):
|
|
brief = ProspectingBrief(target_candidates=2, target_customer="准备装修的人")
|
|
analyze = analyzer()
|
|
first = await execution.analyze_evidence(task, brief, analyze)
|
|
task["artifacts"].update(first)
|
|
second = await execution.analyze_evidence(task, brief, analyze)
|
|
assert second["analysis_cached"] and second["analysis_new_count"] == 0
|
|
assert len(second["leads"]) == 2
|
|
analyze.assert_awaited_once()
|
|
await execution.analyze_evidence(task, brief, analyze, force=True)
|
|
assert analyze.await_count == 2
|
|
|
|
|
|
async def test_only_new_sources_are_analyzed_with_full_parent_context(task):
|
|
brief = ProspectingBrief(target_candidates=3, target_customer="准备装修的人")
|
|
analyze = analyzer()
|
|
task["artifacts"].update(await execution.analyze_evidence(task, brief, analyze))
|
|
task["artifacts"]["sources"].append({"source_id": "new", "source_type": "reply", "source_url": "https://example.test/n", "parent_source_id": "reply", "content": "可以先沟通方案吗"})
|
|
result = await execution.analyze_evidence(task, brief, analyze)
|
|
assert [row["source_id"] for row in analyze.await_args.args[1]] == ["new"]
|
|
assert len(analyze.await_args.kwargs["context_sources"]) == 3
|
|
assert result["analysis_reused_count"] == 2 and len(result["leads"]) == 3
|
|
|
|
|
|
async def test_changed_parent_or_user_correction_invalidates_related_judgment(task):
|
|
brief = ProspectingBrief(target_candidates=3, target_customer="准备装修的人")
|
|
analyze = analyzer()
|
|
task["artifacts"].update(await execution.analyze_evidence(task, brief, analyze))
|
|
task["artifacts"]["sources"][0]["content"] = "装修已完成,不需要服务"
|
|
result = await execution.analyze_evidence(task, brief, analyze)
|
|
assert result["analysis_new_count"] == 2
|
|
task["artifacts"].update(result)
|
|
task["artifacts"]["corrections"] = [{"source_id": "reply", "text": "这是同行自荐"}]
|
|
await execution.analyze_evidence(task, brief, analyze)
|
|
assert analyze.await_args.args[1][0]["user_correction"] == "这是同行自荐"
|
|
|
|
|
|
async def test_changing_only_people_target_keeps_evidence_judgments(task):
|
|
brief = ProspectingBrief(target_candidates=3, target_customer="准备装修的人")
|
|
analyze = analyzer()
|
|
task["artifacts"].update(await execution.analyze_evidence(task, brief, analyze))
|
|
changed = brief.model_copy(update={"target_candidates": 8})
|
|
assert execution.analysis_is_current(task, changed)
|
|
assert (await execution.analyze_evidence(task, changed, analyze))["analysis_cached"]
|
|
assert not execution.analysis_is_current(task, changed.model_copy(update={"region": "杭州"}))
|
|
|
|
|
|
@pytest.mark.parametrize("explicit_note_id", [True, False])
|
|
async def test_signed_note_url_changes_preserve_context_and_invalidate_discussion(task, explicit_note_id):
|
|
from backend.prospecting.analysis import _related_context
|
|
note, reply = task["artifacts"]["sources"]
|
|
note["source_url"] = "https://www.xiaohongshu.com/explore/abc123?xsec_token=old-fixture"
|
|
reply["source_url"] = "https://www.xiaohongshu.com/discovery/item/abc123?xsec_token=new-fixture"
|
|
reply.pop("parent_source_id")
|
|
if explicit_note_id:
|
|
note["note_id"] = reply["note_id"] = "abc123"
|
|
assert _related_context([reply], [note, reply]) == [note]
|
|
brief = ProspectingBrief(target_candidates=2, target_customer="准备装修的人")
|
|
analyze = analyzer()
|
|
task["artifacts"].update(await execution.analyze_evidence(task, brief, analyze))
|
|
note["content"] = "已装修完了,先前是找客户的广告"
|
|
result = await execution.analyze_evidence(task, brief, analyze)
|
|
assert result["analysis_new_count"] == 2
|