from datetime import UTC, datetime import pytest from app.models.entities import Conversation, ConversationMessage from app.services.conversations import ConversationService def test_conversation_message_data_reads_persisted_risk_result() -> None: message = ConversationMessage( id=10, conversation_id=3, role="assistant", content="已完成风险预估。", structured_data={ "riskResult": { "subjectId": "软管新品", "score": 82, "level": "low", "modelVersion": "baseline-safety-v2", "factors": [], } }, created_at=datetime.now(UTC), ) result = ConversationService._message(message) assert result.role == "assistant" assert result.risk_result is not None assert result.risk_result.score == 82 assert result.risk_result.level.value == "low" def test_conversation_data_includes_all_messages() -> None: conversation = Conversation( id=3, user_id=1, title="软管新品评估", created_at=datetime.now(UTC), updated_at=datetime.now(UTC), messages=[ ConversationMessage( id=1, conversation_id=3, role="user", content="评估软管新品", created_at=datetime.now(UTC), ), ConversationMessage( id=2, conversation_id=3, role="assistant", content="已完成分析", created_at=datetime.now(UTC), ), ], ) result = ConversationService._data(conversation) assert result.message_count == 2 assert [message.role for message in result.messages] == ["user", "assistant"] def test_only_explicit_risk_requests_generate_risk_cards() -> None: assert ConversationService._is_risk_request( "你好", {"intent": "greet", "risk_requested": False}, ) is False assert ConversationService._is_risk_request( "请只回复:AI 连通成功。", {"intent": "general", "risk_requested": False}, ) is False assert ConversationService._is_risk_request( "请评估这个产品的风险", {"intent": "assessment", "risk_requested": True}, ) is True def test_legacy_general_semantics_hide_old_risk_card() -> None: messages = [ ConversationMessage( id=1, conversation_id=1, role="user", content="你好", created_at=datetime.now(UTC), ), ConversationMessage( id=2, conversation_id=1, role="assistant", content="你好,我可以帮你做什么?", structured_data={ "semantic": {"intent": "general"}, "riskResult": { "subjectId": "误判", "score": 61, "level": "moderately_low", "modelVersion": "baseline-safety-v2", "factors": [], }, }, created_at=datetime.now(UTC), ), ] assert ConversationService._risk_result_for_message(messages, 1) is None assert ConversationService._is_risk_request( "我要做一款脱毛膏新品,100g,PE软管包装,加工厂准备使用瑞沣", {"intent": "general", "risk_requested": False}, ) is True def test_product_assessment_semantics_override_nlu_miss() -> None: normalized = ConversationService._normalize_semantics( "我要做一款脱毛膏新品,100g,PE软管包装,加工厂准备使用瑞沣", {"intent": "general", "risk_requested": False, "category": "脱毛膏"}, ) assert normalized["intent"] == "assessment" assert normalized["risk_requested"] is True def test_short_known_supplier_follow_up_is_routed_to_supplier_card() -> None: assert ConversationService._infer_supplier_follow_up( "佳亿", ["佳亿", "祥印", "瑞沣"], ) == "佳亿" assert ConversationService._infer_supplier_follow_up( "这个供应商", ["佳亿", "祥印"], ) is None def test_unknown_standalone_name_is_treated_as_new_supplier() -> None: assert ConversationService._infer_unknown_supplier_follow_up("德盛") == "德盛" assert ConversationService._infer_unknown_supplier_follow_up("新供应商A") is None assert ConversationService._infer_unknown_supplier_follow_up("测试供应商") is None assert ConversationService._infer_unknown_supplier_follow_up("新供应商B") is None assert ConversationService._infer_unknown_supplier_follow_up("洗发水") is None assert ConversationService._infer_unknown_supplier_follow_up("帮我查德盛") is None assert ConversationService._response_type("测试供应商", {"intent": "supplier"}) == "general" assert ConversationService._response_type("新供应商A", {"intent": "supplier"}) == "general" @pytest.mark.asyncio async def test_any_product_without_history_uses_solution_card(monkeypatch: pytest.MonkeyPatch) -> None: service = object.__new__(ConversationService) async def no_history(_: str) -> int: return 0 monkeypatch.setattr(service, "_count_category_anomalies", no_history) response_type = ConversationService._response_type( "评估防晒霜新品,50g,软管包装", {"intent": "assessment", "risk_requested": True, "category": "防晒霜"}, ) result = await service._use_solution_card_without_history( response_type, "评估防晒霜新品,50g,软管包装", {"intent": "assessment", "risk_requested": True, "category": "防晒霜"}, ) assert response_type == "risk_assessment" assert result == "solution" @pytest.mark.asyncio async def test_unknown_product_without_category_uses_solution_card() -> None: service = object.__new__(ConversationService) result = await service._use_solution_card_without_history( "risk_assessment", "评估这个新产品的风险", {"intent": "assessment", "risk_requested": True}, ) assert result == "solution"