test_message_service.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. from unittest.mock import patch
  2. import pytest
  3. from faker import Faker
  4. from models.model import MessageFeedback
  5. from services.app_service import AppService
  6. from services.errors.message import (
  7. FirstMessageNotExistsError,
  8. LastMessageNotExistsError,
  9. MessageNotExistsError,
  10. SuggestedQuestionsAfterAnswerDisabledError,
  11. )
  12. from services.message_service import MessageService
  13. class TestMessageService:
  14. """Integration tests for MessageService using testcontainers."""
  15. @pytest.fixture
  16. def mock_external_service_dependencies(self):
  17. """Mock setup for external service dependencies."""
  18. with (
  19. patch("services.account_service.FeatureService") as mock_account_feature_service,
  20. patch("services.message_service.ModelManager") as mock_model_manager,
  21. patch("services.message_service.WorkflowService") as mock_workflow_service,
  22. patch("services.message_service.AdvancedChatAppConfigManager") as mock_app_config_manager,
  23. patch("services.message_service.LLMGenerator") as mock_llm_generator,
  24. patch("services.message_service.TraceQueueManager") as mock_trace_manager_class,
  25. patch("services.message_service.TokenBufferMemory") as mock_token_buffer_memory,
  26. ):
  27. # Setup default mock returns
  28. mock_account_feature_service.get_features.return_value.billing.enabled = False
  29. # Mock ModelManager
  30. mock_model_instance = mock_model_manager.return_value.get_default_model_instance.return_value
  31. mock_model_instance.get_tts_voices.return_value = [{"value": "test-voice"}]
  32. # Mock get_model_instance method as well
  33. mock_model_manager.return_value.get_model_instance.return_value = mock_model_instance
  34. # Mock WorkflowService
  35. mock_workflow = mock_workflow_service.return_value.get_published_workflow.return_value
  36. mock_workflow_service.return_value.get_draft_workflow.return_value = mock_workflow
  37. # Mock AdvancedChatAppConfigManager
  38. mock_app_config = mock_app_config_manager.get_app_config.return_value
  39. mock_app_config.additional_features.suggested_questions_after_answer = True
  40. # Mock LLMGenerator
  41. mock_llm_generator.generate_suggested_questions_after_answer.return_value = ["Question 1", "Question 2"]
  42. # Mock TraceQueueManager
  43. mock_trace_manager_instance = mock_trace_manager_class.return_value
  44. # Mock TokenBufferMemory
  45. mock_memory_instance = mock_token_buffer_memory.return_value
  46. mock_memory_instance.get_history_prompt_text.return_value = "Mocked history prompt"
  47. yield {
  48. "account_feature_service": mock_account_feature_service,
  49. "model_manager": mock_model_manager,
  50. "workflow_service": mock_workflow_service,
  51. "app_config_manager": mock_app_config_manager,
  52. "llm_generator": mock_llm_generator,
  53. "trace_manager_class": mock_trace_manager_class,
  54. "trace_manager_instance": mock_trace_manager_instance,
  55. "token_buffer_memory": mock_token_buffer_memory,
  56. # "current_user": mock_current_user,
  57. }
  58. def _create_test_app_and_account(self, db_session_with_containers, mock_external_service_dependencies):
  59. """
  60. Helper method to create a test app and account for testing.
  61. Args:
  62. db_session_with_containers: Database session from testcontainers infrastructure
  63. mock_external_service_dependencies: Mock dependencies
  64. Returns:
  65. tuple: (app, account) - Created app and account instances
  66. """
  67. fake = Faker()
  68. # Setup mocks for account creation
  69. mock_external_service_dependencies[
  70. "account_feature_service"
  71. ].get_system_features.return_value.is_allow_register = True
  72. # Create account and tenant first
  73. from services.account_service import AccountService, TenantService
  74. account = AccountService.create_account(
  75. email=fake.email(),
  76. name=fake.name(),
  77. interface_language="en-US",
  78. password=fake.password(length=12),
  79. )
  80. TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
  81. tenant = account.current_tenant
  82. # Setup app creation arguments
  83. app_args = {
  84. "name": fake.company(),
  85. "description": fake.text(max_nb_chars=100),
  86. "mode": "advanced-chat", # Use advanced-chat mode to use mocked workflow
  87. "icon_type": "emoji",
  88. "icon": "📚",
  89. "icon_background": "#FF6B6B",
  90. "api_rph": 100,
  91. "api_rpm": 10,
  92. }
  93. # Create app
  94. app_service = AppService()
  95. app = app_service.create_app(tenant.id, app_args, account)
  96. # Setup current_user mock
  97. self._mock_current_user(mock_external_service_dependencies, account.id, tenant.id)
  98. return app, account
  99. def _mock_current_user(self, mock_external_service_dependencies, account_id, tenant_id):
  100. """
  101. Helper method to mock the current user for testing.
  102. """
  103. # mock_external_service_dependencies["current_user"].id = account_id
  104. # mock_external_service_dependencies["current_user"].current_tenant_id = tenant_id
  105. def _create_test_conversation(self, app, account, fake):
  106. """
  107. Helper method to create a test conversation with all required fields.
  108. """
  109. from extensions.ext_database import db
  110. from models.model import Conversation
  111. conversation = Conversation(
  112. app_id=app.id,
  113. app_model_config_id=None,
  114. model_provider=None,
  115. model_id="",
  116. override_model_configs=None,
  117. mode=app.mode,
  118. name=fake.sentence(),
  119. inputs={},
  120. introduction="",
  121. system_instruction="",
  122. system_instruction_tokens=0,
  123. status="normal",
  124. invoke_from="console",
  125. from_source="console",
  126. from_end_user_id=None,
  127. from_account_id=account.id,
  128. )
  129. db.session.add(conversation)
  130. db.session.flush()
  131. return conversation
  132. def _create_test_message(self, app, conversation, account, fake):
  133. """
  134. Helper method to create a test message with all required fields.
  135. """
  136. import json
  137. from extensions.ext_database import db
  138. from models.model import Message
  139. message = Message(
  140. app_id=app.id,
  141. model_provider=None,
  142. model_id="",
  143. override_model_configs=None,
  144. conversation_id=conversation.id,
  145. inputs={},
  146. query=fake.sentence(),
  147. message=json.dumps([{"role": "user", "text": fake.sentence()}]),
  148. message_tokens=0,
  149. message_unit_price=0,
  150. message_price_unit=0.001,
  151. answer=fake.text(max_nb_chars=200),
  152. answer_tokens=0,
  153. answer_unit_price=0,
  154. answer_price_unit=0.001,
  155. parent_message_id=None,
  156. provider_response_latency=0,
  157. total_price=0,
  158. currency="USD",
  159. invoke_from="console",
  160. from_source="console",
  161. from_end_user_id=None,
  162. from_account_id=account.id,
  163. )
  164. db.session.add(message)
  165. db.session.commit()
  166. return message
  167. def test_pagination_by_first_id_success(self, db_session_with_containers, mock_external_service_dependencies):
  168. """
  169. Test successful pagination by first ID.
  170. """
  171. fake = Faker()
  172. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  173. # Create a conversation and multiple messages
  174. conversation = self._create_test_conversation(app, account, fake)
  175. messages = []
  176. for i in range(5):
  177. message = self._create_test_message(app, conversation, account, fake)
  178. messages.append(message)
  179. # Test pagination by first ID
  180. result = MessageService.pagination_by_first_id(
  181. app_model=app,
  182. user=account,
  183. conversation_id=conversation.id,
  184. first_id=messages[2].id, # Use middle message as first_id
  185. limit=2,
  186. order="asc",
  187. )
  188. # Verify results
  189. assert result.limit == 2
  190. assert len(result.data) == 2
  191. # total 5, from the middle, no more
  192. assert result.has_more is False
  193. # Verify messages are in ascending order
  194. assert result.data[0].created_at <= result.data[1].created_at
  195. def test_pagination_by_first_id_no_user(self, db_session_with_containers, mock_external_service_dependencies):
  196. """
  197. Test pagination by first ID when no user is provided.
  198. """
  199. fake = Faker()
  200. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  201. # Test pagination with no user
  202. result = MessageService.pagination_by_first_id(
  203. app_model=app, user=None, conversation_id=fake.uuid4(), first_id=None, limit=10
  204. )
  205. # Verify empty result
  206. assert result.limit == 10
  207. assert len(result.data) == 0
  208. assert result.has_more is False
  209. def test_pagination_by_first_id_no_conversation_id(
  210. self, db_session_with_containers, mock_external_service_dependencies
  211. ):
  212. """
  213. Test pagination by first ID when no conversation ID is provided.
  214. """
  215. fake = Faker()
  216. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  217. # Test pagination with no conversation ID
  218. result = MessageService.pagination_by_first_id(
  219. app_model=app, user=account, conversation_id="", first_id=None, limit=10
  220. )
  221. # Verify empty result
  222. assert result.limit == 10
  223. assert len(result.data) == 0
  224. assert result.has_more is False
  225. def test_pagination_by_first_id_invalid_first_id(
  226. self, db_session_with_containers, mock_external_service_dependencies
  227. ):
  228. """
  229. Test pagination by first ID with invalid first_id.
  230. """
  231. fake = Faker()
  232. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  233. # Create a conversation and message
  234. conversation = self._create_test_conversation(app, account, fake)
  235. self._create_test_message(app, conversation, account, fake)
  236. # Test pagination with invalid first_id
  237. with pytest.raises(FirstMessageNotExistsError):
  238. MessageService.pagination_by_first_id(
  239. app_model=app,
  240. user=account,
  241. conversation_id=conversation.id,
  242. first_id=fake.uuid4(), # Non-existent message ID
  243. limit=10,
  244. )
  245. def test_pagination_by_last_id_success(self, db_session_with_containers, mock_external_service_dependencies):
  246. """
  247. Test successful pagination by last ID.
  248. """
  249. fake = Faker()
  250. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  251. # Create a conversation and multiple messages
  252. conversation = self._create_test_conversation(app, account, fake)
  253. messages = []
  254. for i in range(5):
  255. message = self._create_test_message(app, conversation, account, fake)
  256. messages.append(message)
  257. # Test pagination by last ID
  258. result = MessageService.pagination_by_last_id(
  259. app_model=app,
  260. user=account,
  261. last_id=messages[2].id, # Use middle message as last_id
  262. limit=2,
  263. conversation_id=conversation.id,
  264. )
  265. # Verify results
  266. assert result.limit == 2
  267. assert len(result.data) == 2
  268. # total 5, from the middle, no more
  269. assert result.has_more is False
  270. # Verify messages are in descending order
  271. assert result.data[0].created_at >= result.data[1].created_at
  272. def test_pagination_by_last_id_with_include_ids(
  273. self, db_session_with_containers, mock_external_service_dependencies
  274. ):
  275. """
  276. Test pagination by last ID with include_ids filter.
  277. """
  278. fake = Faker()
  279. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  280. # Create a conversation and multiple messages
  281. conversation = self._create_test_conversation(app, account, fake)
  282. messages = []
  283. for i in range(5):
  284. message = self._create_test_message(app, conversation, account, fake)
  285. messages.append(message)
  286. # Test pagination with include_ids
  287. include_ids = [messages[0].id, messages[1].id, messages[2].id]
  288. result = MessageService.pagination_by_last_id(
  289. app_model=app, user=account, last_id=messages[1].id, limit=2, include_ids=include_ids
  290. )
  291. # Verify results
  292. assert result.limit == 2
  293. assert len(result.data) <= 2
  294. # Verify all returned messages are in include_ids
  295. for message in result.data:
  296. assert message.id in include_ids
  297. def test_pagination_by_last_id_no_user(self, db_session_with_containers, mock_external_service_dependencies):
  298. """
  299. Test pagination by last ID when no user is provided.
  300. """
  301. fake = Faker()
  302. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  303. # Test pagination with no user
  304. result = MessageService.pagination_by_last_id(app_model=app, user=None, last_id=None, limit=10)
  305. # Verify empty result
  306. assert result.limit == 10
  307. assert len(result.data) == 0
  308. assert result.has_more is False
  309. def test_pagination_by_last_id_invalid_last_id(
  310. self, db_session_with_containers, mock_external_service_dependencies
  311. ):
  312. """
  313. Test pagination by last ID with invalid last_id.
  314. """
  315. fake = Faker()
  316. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  317. # Create a conversation and message
  318. conversation = self._create_test_conversation(app, account, fake)
  319. self._create_test_message(app, conversation, account, fake)
  320. # Test pagination with invalid last_id
  321. with pytest.raises(LastMessageNotExistsError):
  322. MessageService.pagination_by_last_id(
  323. app_model=app,
  324. user=account,
  325. last_id=fake.uuid4(), # Non-existent message ID
  326. limit=10,
  327. conversation_id=conversation.id,
  328. )
  329. def test_create_feedback_success(self, db_session_with_containers, mock_external_service_dependencies):
  330. """
  331. Test successful creation of feedback.
  332. """
  333. fake = Faker()
  334. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  335. # Create a conversation and message
  336. conversation = self._create_test_conversation(app, account, fake)
  337. message = self._create_test_message(app, conversation, account, fake)
  338. # Create feedback
  339. rating = "like"
  340. content = fake.text(max_nb_chars=100)
  341. feedback = MessageService.create_feedback(
  342. app_model=app, message_id=message.id, user=account, rating=rating, content=content
  343. )
  344. # Verify feedback was created correctly
  345. assert feedback.app_id == app.id
  346. assert feedback.conversation_id == conversation.id
  347. assert feedback.message_id == message.id
  348. assert feedback.rating == rating
  349. assert feedback.content == content
  350. assert feedback.from_source == "admin"
  351. assert feedback.from_account_id == account.id
  352. assert feedback.from_end_user_id is None
  353. def test_create_feedback_no_user(self, db_session_with_containers, mock_external_service_dependencies):
  354. """
  355. Test creating feedback when no user is provided.
  356. """
  357. fake = Faker()
  358. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  359. # Create a conversation and message
  360. conversation = self._create_test_conversation(app, account, fake)
  361. message = self._create_test_message(app, conversation, account, fake)
  362. # Test creating feedback with no user
  363. with pytest.raises(ValueError, match="user cannot be None"):
  364. MessageService.create_feedback(
  365. app_model=app, message_id=message.id, user=None, rating="like", content=fake.text(max_nb_chars=100)
  366. )
  367. def test_create_feedback_update_existing(self, db_session_with_containers, mock_external_service_dependencies):
  368. """
  369. Test updating existing feedback.
  370. """
  371. fake = Faker()
  372. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  373. # Create a conversation and message
  374. conversation = self._create_test_conversation(app, account, fake)
  375. message = self._create_test_message(app, conversation, account, fake)
  376. # Create initial feedback
  377. initial_rating = "like"
  378. initial_content = fake.text(max_nb_chars=100)
  379. feedback = MessageService.create_feedback(
  380. app_model=app, message_id=message.id, user=account, rating=initial_rating, content=initial_content
  381. )
  382. # Update feedback
  383. updated_rating = "dislike"
  384. updated_content = fake.text(max_nb_chars=100)
  385. updated_feedback = MessageService.create_feedback(
  386. app_model=app, message_id=message.id, user=account, rating=updated_rating, content=updated_content
  387. )
  388. # Verify feedback was updated correctly
  389. assert updated_feedback.id == feedback.id
  390. assert updated_feedback.rating == updated_rating
  391. assert updated_feedback.content == updated_content
  392. assert updated_feedback.rating != initial_rating
  393. assert updated_feedback.content != initial_content
  394. def test_create_feedback_delete_existing(self, db_session_with_containers, mock_external_service_dependencies):
  395. """
  396. Test deleting existing feedback by setting rating to None.
  397. """
  398. fake = Faker()
  399. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  400. # Create a conversation and message
  401. conversation = self._create_test_conversation(app, account, fake)
  402. message = self._create_test_message(app, conversation, account, fake)
  403. # Create initial feedback
  404. feedback = MessageService.create_feedback(
  405. app_model=app, message_id=message.id, user=account, rating="like", content=fake.text(max_nb_chars=100)
  406. )
  407. # Delete feedback by setting rating to None
  408. MessageService.create_feedback(app_model=app, message_id=message.id, user=account, rating=None, content=None)
  409. # Verify feedback was deleted
  410. from extensions.ext_database import db
  411. deleted_feedback = db.session.query(MessageFeedback).where(MessageFeedback.id == feedback.id).first()
  412. assert deleted_feedback is None
  413. def test_create_feedback_no_rating_when_not_exists(
  414. self, db_session_with_containers, mock_external_service_dependencies
  415. ):
  416. """
  417. Test creating feedback with no rating when feedback doesn't exist.
  418. """
  419. fake = Faker()
  420. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  421. # Create a conversation and message
  422. conversation = self._create_test_conversation(app, account, fake)
  423. message = self._create_test_message(app, conversation, account, fake)
  424. # Test creating feedback with no rating when no feedback exists
  425. with pytest.raises(ValueError, match="rating cannot be None when feedback not exists"):
  426. MessageService.create_feedback(
  427. app_model=app, message_id=message.id, user=account, rating=None, content=None
  428. )
  429. def test_get_all_messages_feedbacks_success(self, db_session_with_containers, mock_external_service_dependencies):
  430. """
  431. Test successful retrieval of all message feedbacks.
  432. """
  433. fake = Faker()
  434. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  435. # Create multiple conversations and messages with feedbacks
  436. feedbacks = []
  437. for i in range(3):
  438. conversation = self._create_test_conversation(app, account, fake)
  439. message = self._create_test_message(app, conversation, account, fake)
  440. feedback = MessageService.create_feedback(
  441. app_model=app,
  442. message_id=message.id,
  443. user=account,
  444. rating="like" if i % 2 == 0 else "dislike",
  445. content=f"Feedback {i}: {fake.text(max_nb_chars=50)}",
  446. )
  447. feedbacks.append(feedback)
  448. # Get all feedbacks
  449. result = MessageService.get_all_messages_feedbacks(app, page=1, limit=10)
  450. # Verify results
  451. assert len(result) == 3
  452. # Verify feedbacks are ordered by created_at desc
  453. for i in range(len(result) - 1):
  454. assert result[i]["created_at"] >= result[i + 1]["created_at"]
  455. def test_get_all_messages_feedbacks_pagination(
  456. self, db_session_with_containers, mock_external_service_dependencies
  457. ):
  458. """
  459. Test pagination of message feedbacks.
  460. """
  461. fake = Faker()
  462. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  463. # Create multiple conversations and messages with feedbacks
  464. for i in range(5):
  465. conversation = self._create_test_conversation(app, account, fake)
  466. message = self._create_test_message(app, conversation, account, fake)
  467. MessageService.create_feedback(
  468. app_model=app, message_id=message.id, user=account, rating="like", content=f"Feedback {i}"
  469. )
  470. # Get feedbacks with pagination
  471. result_page_1 = MessageService.get_all_messages_feedbacks(app, page=1, limit=3)
  472. result_page_2 = MessageService.get_all_messages_feedbacks(app, page=2, limit=3)
  473. # Verify pagination results
  474. assert len(result_page_1) == 3
  475. assert len(result_page_2) == 2
  476. # Verify no overlap between pages
  477. page_1_ids = {feedback["id"] for feedback in result_page_1}
  478. page_2_ids = {feedback["id"] for feedback in result_page_2}
  479. assert len(page_1_ids.intersection(page_2_ids)) == 0
  480. def test_get_message_success(self, db_session_with_containers, mock_external_service_dependencies):
  481. """
  482. Test successful retrieval of message.
  483. """
  484. fake = Faker()
  485. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  486. # Create a conversation and message
  487. conversation = self._create_test_conversation(app, account, fake)
  488. message = self._create_test_message(app, conversation, account, fake)
  489. # Get message
  490. retrieved_message = MessageService.get_message(app_model=app, user=account, message_id=message.id)
  491. # Verify message was retrieved correctly
  492. assert retrieved_message.id == message.id
  493. assert retrieved_message.app_id == app.id
  494. assert retrieved_message.conversation_id == conversation.id
  495. assert retrieved_message.from_source == "console"
  496. assert retrieved_message.from_account_id == account.id
  497. def test_get_message_not_exists(self, db_session_with_containers, mock_external_service_dependencies):
  498. """
  499. Test getting message that doesn't exist.
  500. """
  501. fake = Faker()
  502. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  503. # Test getting non-existent message
  504. with pytest.raises(MessageNotExistsError):
  505. MessageService.get_message(app_model=app, user=account, message_id=fake.uuid4())
  506. def test_get_message_wrong_user(self, db_session_with_containers, mock_external_service_dependencies):
  507. """
  508. Test getting message with wrong user (different account).
  509. """
  510. fake = Faker()
  511. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  512. # Create a conversation and message
  513. conversation = self._create_test_conversation(app, account, fake)
  514. message = self._create_test_message(app, conversation, account, fake)
  515. # Create another account
  516. from services.account_service import AccountService, TenantService
  517. other_account = AccountService.create_account(
  518. email=fake.email(),
  519. name=fake.name(),
  520. interface_language="en-US",
  521. password=fake.password(length=12),
  522. )
  523. TenantService.create_owner_tenant_if_not_exist(other_account, name=fake.company())
  524. # Test getting message with different user
  525. with pytest.raises(MessageNotExistsError):
  526. MessageService.get_message(app_model=app, user=other_account, message_id=message.id)
  527. def test_get_suggested_questions_after_answer_success(
  528. self, db_session_with_containers, mock_external_service_dependencies
  529. ):
  530. """
  531. Test successful generation of suggested questions after answer.
  532. """
  533. fake = Faker()
  534. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  535. # Create a conversation and message
  536. conversation = self._create_test_conversation(app, account, fake)
  537. message = self._create_test_message(app, conversation, account, fake)
  538. # Mock the LLMGenerator to return specific questions
  539. mock_questions = ["What is AI?", "How does machine learning work?", "Tell me about neural networks"]
  540. mock_external_service_dependencies[
  541. "llm_generator"
  542. ].generate_suggested_questions_after_answer.return_value = mock_questions
  543. # Get suggested questions
  544. from core.app.entities.app_invoke_entities import InvokeFrom
  545. result = MessageService.get_suggested_questions_after_answer(
  546. app_model=app, user=account, message_id=message.id, invoke_from=InvokeFrom.SERVICE_API
  547. )
  548. # Verify results
  549. assert result == mock_questions
  550. # Verify LLMGenerator was called
  551. mock_external_service_dependencies[
  552. "llm_generator"
  553. ].generate_suggested_questions_after_answer.assert_called_once()
  554. # Verify TraceQueueManager was called
  555. mock_external_service_dependencies["trace_manager_instance"].add_trace_task.assert_called_once()
  556. def test_get_suggested_questions_after_answer_no_user(
  557. self, db_session_with_containers, mock_external_service_dependencies
  558. ):
  559. """
  560. Test getting suggested questions when no user is provided.
  561. """
  562. fake = Faker()
  563. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  564. # Create a conversation and message
  565. conversation = self._create_test_conversation(app, account, fake)
  566. message = self._create_test_message(app, conversation, account, fake)
  567. # Test getting suggested questions with no user
  568. from core.app.entities.app_invoke_entities import InvokeFrom
  569. with pytest.raises(ValueError, match="user cannot be None"):
  570. MessageService.get_suggested_questions_after_answer(
  571. app_model=app, user=None, message_id=message.id, invoke_from=InvokeFrom.SERVICE_API
  572. )
  573. def test_get_suggested_questions_after_answer_disabled(
  574. self, db_session_with_containers, mock_external_service_dependencies
  575. ):
  576. """
  577. Test getting suggested questions when feature is disabled.
  578. """
  579. fake = Faker()
  580. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  581. # Create a conversation and message
  582. conversation = self._create_test_conversation(app, account, fake)
  583. message = self._create_test_message(app, conversation, account, fake)
  584. # Mock the feature to be disabled
  585. mock_external_service_dependencies[
  586. "app_config_manager"
  587. ].get_app_config.return_value.additional_features.suggested_questions_after_answer = False
  588. # Test getting suggested questions when feature is disabled
  589. from core.app.entities.app_invoke_entities import InvokeFrom
  590. with pytest.raises(SuggestedQuestionsAfterAnswerDisabledError):
  591. MessageService.get_suggested_questions_after_answer(
  592. app_model=app, user=account, message_id=message.id, invoke_from=InvokeFrom.SERVICE_API
  593. )
  594. def test_get_suggested_questions_after_answer_no_workflow(
  595. self, db_session_with_containers, mock_external_service_dependencies
  596. ):
  597. """
  598. Test getting suggested questions when no workflow exists.
  599. """
  600. fake = Faker()
  601. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  602. # Create a conversation and message
  603. conversation = self._create_test_conversation(app, account, fake)
  604. message = self._create_test_message(app, conversation, account, fake)
  605. # Mock no workflow
  606. mock_external_service_dependencies["workflow_service"].return_value.get_published_workflow.return_value = None
  607. # Get suggested questions (should return empty list)
  608. from core.app.entities.app_invoke_entities import InvokeFrom
  609. result = MessageService.get_suggested_questions_after_answer(
  610. app_model=app, user=account, message_id=message.id, invoke_from=InvokeFrom.SERVICE_API
  611. )
  612. # Verify empty result
  613. assert result == []
  614. def test_get_suggested_questions_after_answer_debugger_mode(
  615. self, db_session_with_containers, mock_external_service_dependencies
  616. ):
  617. """
  618. Test getting suggested questions in debugger mode.
  619. """
  620. fake = Faker()
  621. app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
  622. # Create a conversation and message
  623. conversation = self._create_test_conversation(app, account, fake)
  624. message = self._create_test_message(app, conversation, account, fake)
  625. # Mock questions
  626. mock_questions = ["Debug question 1", "Debug question 2"]
  627. mock_external_service_dependencies[
  628. "llm_generator"
  629. ].generate_suggested_questions_after_answer.return_value = mock_questions
  630. # Get suggested questions in debugger mode
  631. from core.app.entities.app_invoke_entities import InvokeFrom
  632. result = MessageService.get_suggested_questions_after_answer(
  633. app_model=app, user=account, message_id=message.id, invoke_from=InvokeFrom.DEBUGGER
  634. )
  635. # Verify results
  636. assert result == mock_questions
  637. # Verify draft workflow was used instead of published workflow
  638. mock_external_service_dependencies["workflow_service"].return_value.get_draft_workflow.assert_called_once_with(
  639. app_model=app
  640. )
  641. # Verify TraceQueueManager was called
  642. mock_external_service_dependencies["trace_manager_instance"].add_trace_task.assert_called_once()