BusinessAssistant.test.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import { fireEvent, render, screen, waitFor } from '@testing-library/react';
  2. import { beforeEach, expect, test, vi } from 'vitest';
  3. import BusinessAssistantPage from '@/pages/BusinessAssistant';
  4. import { apiRequest } from '@/services/api';
  5. vi.mock('@/services/api', () => ({
  6. apiRequest: vi.fn(),
  7. jsonOptions: vi.fn((method, body) => ({ method, body: JSON.stringify(body) })),
  8. }));
  9. vi.mock('@/contexts/BotAccess', () => ({
  10. useBotAccess: () => ({
  11. bot: {
  12. bot_id: 'primary',
  13. label: 'Test Bot',
  14. enabled: true,
  15. bot_token_configured: true,
  16. sudo_users_id: [],
  17. log_group_id: '0',
  18. gban_log_group_id: '0',
  19. message_dump_chat: '0',
  20. role_ids: ['business_assistant_manager'],
  21. permissions: ['business_assistant.manage'],
  22. identity: {
  23. id: '999',
  24. username: 'business_bot',
  25. name: 'Business Bot',
  26. can_connect_to_business: true,
  27. },
  28. business_assistant: {
  29. base_url: 'https://models.example.com/v1',
  30. api_key_configured: true,
  31. model: 'support-model',
  32. timeout_seconds: 30,
  33. max_output_tokens: 600,
  34. configured: true,
  35. },
  36. ready_to_connect: true,
  37. runtime: { state: 'running' },
  38. },
  39. }),
  40. }));
  41. const settings = {
  42. connection_id: 'conn-a',
  43. assistant_enabled: true,
  44. system_prompt: '只依据知识库回答。',
  45. language: 'zh-CN',
  46. tone: 'professional',
  47. account_daily_limit: 200,
  48. customer_daily_limit: 20,
  49. human_pause_hours: 24,
  50. notification_destination: 'both',
  51. ops_group_id: '-100123',
  52. timezone: 'Asia/Shanghai',
  53. digest_enabled: false,
  54. digest_time: '09:00',
  55. handoff_message: '请稍候,已转人工。',
  56. unsupported_message: '该消息需要人工处理。',
  57. };
  58. beforeEach(() => {
  59. vi.mocked(apiRequest).mockClear();
  60. vi.mocked(apiRequest).mockImplementation(async (path) => {
  61. if (path === '/business-assistant/status') {
  62. return {
  63. polling_state: 'running',
  64. business_mode_supported: true,
  65. webhook_conflict: false,
  66. webhook_url: '',
  67. model_configured: true,
  68. usage: { ai_calls: 8, customers: 3, conversations: 3, handoffs: 1, human_paused: 1 },
  69. };
  70. }
  71. if (path.startsWith('/business-assistant/connections')) {
  72. return {
  73. items: [
  74. {
  75. connection_id: 'conn-a',
  76. user: { id: '900', username: 'owner', first_name: '店主' },
  77. user_chat_id: '900',
  78. rights: { can_reply: true, can_read_messages: true },
  79. is_enabled: true,
  80. last_event_at: '2026-08-14T08:00:00Z',
  81. settings,
  82. },
  83. ],
  84. total: 1,
  85. page: 1,
  86. page_size: 100,
  87. };
  88. }
  89. if (path.startsWith('/business-assistant/settings')) return settings;
  90. if (path.startsWith('/business-assistant/knowledge-sources')) {
  91. return {
  92. items: [
  93. {
  94. source_id: 'source-1',
  95. connection_id: 'conn-a',
  96. source_type: 'channel',
  97. chat_id: '-100100',
  98. title: '官方公告',
  99. publication_mode: 'auto',
  100. author_policy: 'admins_or_allowlist',
  101. allowed_user_ids: [],
  102. include_linked_chat: true,
  103. linked_chat_id: '-100101',
  104. linked_chat_title: '官方讨论群',
  105. backfill_days: 30,
  106. backfill_limit: 100,
  107. enabled: true,
  108. sync_status: 'completed',
  109. last_sync_processed: 12,
  110. },
  111. ],
  112. total: 1,
  113. page: 1,
  114. page_size: 20,
  115. };
  116. }
  117. if (path.startsWith('/business-assistant/knowledge-candidates')) {
  118. return {
  119. items: [
  120. {
  121. candidate_id: 'candidate-1',
  122. connection_id: 'conn-a',
  123. source_id: 'source-1',
  124. event_id: 'event-1',
  125. question: '配送范围?',
  126. aliases: [],
  127. keywords: ['配送'],
  128. answer: '仅支持市区配送。',
  129. tags: ['配送'],
  130. confidence: 0.92,
  131. status: 'pending',
  132. source_snapshot: {
  133. title: '官方公告',
  134. source_type: 'channel',
  135. chat_id: '-100100',
  136. message_id: '8',
  137. author_id: '900',
  138. },
  139. },
  140. ],
  141. total: 1,
  142. page: 1,
  143. page_size: 20,
  144. };
  145. }
  146. if (path.startsWith('/business-assistant/knowledge')) {
  147. return {
  148. items: [
  149. {
  150. entry_id: 'entry-1',
  151. connection_id: 'conn-a',
  152. question: '营业时间是什么?',
  153. aliases: ['几点营业'],
  154. keywords: ['营业时间'],
  155. answer: '每天 09:00 至 18:00 营业。',
  156. tags: ['营业'],
  157. priority: 10,
  158. enabled: true,
  159. },
  160. ],
  161. total: 1,
  162. page: 1,
  163. page_size: 20,
  164. };
  165. }
  166. if (path.startsWith('/business-assistant/conversations')) {
  167. return {
  168. items: [
  169. {
  170. conversation_id: 'conversation-1',
  171. connection_id: 'conn-a',
  172. chat_id: '501',
  173. customer: { id: '501', first_name: '客户甲' },
  174. status: 'handoff',
  175. summary: '客户询问退款。',
  176. handoff_reason: 'sensitive_request',
  177. last_message_at: '2026-08-14T08:01:00Z',
  178. },
  179. ],
  180. total: 1,
  181. page: 1,
  182. page_size: 20,
  183. };
  184. }
  185. if (path.startsWith('/business-assistant/usage')) {
  186. return { ai_calls: 8, customers: 3, conversations: 3, handoffs: 1, human_paused: 1 };
  187. }
  188. throw new Error(`Unexpected request: ${path}`);
  189. });
  190. });
  191. test('展示运行状态、连接权限,并用最新搜索词查询知识库', async () => {
  192. render(<BusinessAssistantPage />);
  193. expect(screen.getByRole('heading', { name: '智能接待' })).toBeInTheDocument();
  194. expect((await screen.findAllByText('店主')).length).toBeGreaterThan(0);
  195. expect(screen.getByText('允许回复')).toBeInTheDocument();
  196. expect(screen.getByText('允许标记已读')).toBeInTheDocument();
  197. fireEvent.click(await screen.findByRole('tab', { name: /知识库/ }));
  198. expect(await screen.findByText('营业时间是什么?')).toBeInTheDocument();
  199. const search = screen.getByLabelText('搜索知识库');
  200. fireEvent.change(search, { target: { value: '配送' } });
  201. fireEvent.keyDown(search, { key: 'Enter', code: 'Enter' });
  202. await waitFor(() => {
  203. expect(
  204. vi.mocked(apiRequest).mock.calls.some(([path]) =>
  205. String(path).includes('query=%E9%85%8D%E9%80%81'),
  206. ),
  207. ).toBe(true);
  208. });
  209. });
  210. test('展示多源知识与待审核候选', async () => {
  211. render(<BusinessAssistantPage />);
  212. await screen.findAllByText('店主');
  213. fireEvent.click(screen.getByRole('tab', { name: '知识来源' }));
  214. expect(await screen.findByText('官方公告')).toBeInTheDocument();
  215. expect(screen.getByText('讨论群:官方讨论群')).toBeInTheDocument();
  216. expect(screen.getByRole('button', { name: '同步来源 官方公告' })).toBeInTheDocument();
  217. fireEvent.click(screen.getByRole('tab', { name: '知识候选' }));
  218. expect(await screen.findByText('配送范围?')).toBeInTheDocument();
  219. expect(screen.getByText('仅支持市区配送。')).toBeInTheDocument();
  220. expect(screen.getByRole('button', { name: '批准' })).toBeInTheDocument();
  221. });