agent-messenger 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/.claude-plugin/README.md +16 -16
  2. package/.claude-plugin/marketplace.json +29 -29
  3. package/.claude-plugin/plugin.json +5 -5
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +8 -5
  6. package/bun.lock +70 -110
  7. package/bunfig.toml +3 -0
  8. package/dist/package.json +11 -3
  9. package/dist/src/platforms/discordbot/client.js +2 -2
  10. package/dist/src/platforms/discordbot/client.js.map +1 -1
  11. package/dist/src/platforms/kakaotalk/cli.d.ts.map +1 -1
  12. package/dist/src/platforms/kakaotalk/cli.js +2 -1
  13. package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
  14. package/dist/src/platforms/kakaotalk/client.d.ts +2 -1
  15. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  16. package/dist/src/platforms/kakaotalk/client.js +52 -2
  17. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  18. package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -0
  19. package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
  20. package/dist/src/platforms/kakaotalk/commands/index.js +1 -0
  21. package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
  22. package/dist/src/platforms/kakaotalk/commands/profile.d.ts +3 -0
  23. package/dist/src/platforms/kakaotalk/commands/profile.d.ts.map +1 -0
  24. package/dist/src/platforms/kakaotalk/commands/profile.js +19 -0
  25. package/dist/src/platforms/kakaotalk/commands/profile.js.map +1 -0
  26. package/dist/src/platforms/kakaotalk/index.d.ts +2 -2
  27. package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
  28. package/dist/src/platforms/kakaotalk/index.js +1 -1
  29. package/dist/src/platforms/kakaotalk/index.js.map +1 -1
  30. package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
  31. package/dist/src/platforms/kakaotalk/protocol/session.js +2 -1
  32. package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
  33. package/dist/src/platforms/kakaotalk/types.d.ts +16 -0
  34. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  35. package/dist/src/platforms/kakaotalk/types.js +8 -0
  36. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  37. package/dist/src/platforms/line/client.d.ts.map +1 -1
  38. package/dist/src/platforms/line/client.js +9 -36
  39. package/dist/src/platforms/line/client.js.map +1 -1
  40. package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
  41. package/dist/src/platforms/line/commands/auth.js +32 -20
  42. package/dist/src/platforms/line/commands/auth.js.map +1 -1
  43. package/dist/src/platforms/teams/commands/reaction.d.ts.map +1 -1
  44. package/dist/src/platforms/teams/commands/reaction.js +2 -0
  45. package/dist/src/platforms/teams/commands/reaction.js.map +1 -1
  46. package/dist/src/platforms/wechatbot/cli.d.ts +5 -0
  47. package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -0
  48. package/dist/src/platforms/wechatbot/cli.js +18 -0
  49. package/dist/src/platforms/wechatbot/cli.js.map +1 -0
  50. package/dist/src/platforms/wechatbot/client.d.ts +36 -0
  51. package/dist/src/platforms/wechatbot/client.d.ts.map +1 -0
  52. package/dist/src/platforms/wechatbot/client.js +208 -0
  53. package/dist/src/platforms/wechatbot/client.js.map +1 -0
  54. package/dist/src/platforms/wechatbot/commands/auth.d.ts +28 -0
  55. package/dist/src/platforms/wechatbot/commands/auth.d.ts.map +1 -0
  56. package/dist/src/platforms/wechatbot/commands/auth.js +164 -0
  57. package/dist/src/platforms/wechatbot/commands/auth.js.map +1 -0
  58. package/dist/src/platforms/wechatbot/commands/index.d.ts +5 -0
  59. package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -0
  60. package/dist/src/platforms/wechatbot/commands/index.js +5 -0
  61. package/dist/src/platforms/wechatbot/commands/index.js.map +1 -0
  62. package/dist/src/platforms/wechatbot/commands/message.d.ts +18 -0
  63. package/dist/src/platforms/wechatbot/commands/message.d.ts.map +1 -0
  64. package/dist/src/platforms/wechatbot/commands/message.js +80 -0
  65. package/dist/src/platforms/wechatbot/commands/message.js.map +1 -0
  66. package/dist/src/platforms/wechatbot/commands/shared.d.ts +9 -0
  67. package/dist/src/platforms/wechatbot/commands/shared.d.ts.map +1 -0
  68. package/dist/src/platforms/wechatbot/commands/shared.js +13 -0
  69. package/dist/src/platforms/wechatbot/commands/shared.js.map +1 -0
  70. package/dist/src/platforms/wechatbot/commands/template.d.ts +19 -0
  71. package/dist/src/platforms/wechatbot/commands/template.d.ts.map +1 -0
  72. package/dist/src/platforms/wechatbot/commands/template.js +76 -0
  73. package/dist/src/platforms/wechatbot/commands/template.js.map +1 -0
  74. package/dist/src/platforms/wechatbot/commands/user.d.ts +20 -0
  75. package/dist/src/platforms/wechatbot/commands/user.d.ts.map +1 -0
  76. package/dist/src/platforms/wechatbot/commands/user.js +53 -0
  77. package/dist/src/platforms/wechatbot/commands/user.js.map +1 -0
  78. package/dist/src/platforms/wechatbot/credential-manager.d.ts +17 -0
  79. package/dist/src/platforms/wechatbot/credential-manager.d.ts.map +1 -0
  80. package/dist/src/platforms/wechatbot/credential-manager.js +121 -0
  81. package/dist/src/platforms/wechatbot/credential-manager.js.map +1 -0
  82. package/dist/src/platforms/wechatbot/index.d.ts +5 -0
  83. package/dist/src/platforms/wechatbot/index.d.ts.map +1 -0
  84. package/dist/src/platforms/wechatbot/index.js +4 -0
  85. package/dist/src/platforms/wechatbot/index.js.map +1 -0
  86. package/dist/src/platforms/wechatbot/types.d.ts +94 -0
  87. package/dist/src/platforms/wechatbot/types.d.ts.map +1 -0
  88. package/dist/src/platforms/wechatbot/types.js +54 -0
  89. package/dist/src/platforms/wechatbot/types.js.map +1 -0
  90. package/dist/src/platforms/whatsapp/client.d.ts +1 -0
  91. package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
  92. package/dist/src/platforms/whatsapp/client.js +27 -13
  93. package/dist/src/platforms/whatsapp/client.js.map +1 -1
  94. package/dist/src/platforms/whatsapp/commands/auth.d.ts.map +1 -1
  95. package/dist/src/platforms/whatsapp/commands/auth.js +21 -18
  96. package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
  97. package/dist/src/platforms/whatsapp/credential-manager.d.ts.map +1 -1
  98. package/dist/src/platforms/whatsapp/credential-manager.js +14 -8
  99. package/dist/src/platforms/whatsapp/credential-manager.js.map +1 -1
  100. package/docs/content/docs/agent-skills.mdx +4 -4
  101. package/docs/content/docs/cli/channeltalk.mdx +1 -1
  102. package/docs/content/docs/cli/channeltalkbot.mdx +1 -1
  103. package/docs/content/docs/cli/discord.mdx +1 -1
  104. package/docs/content/docs/cli/discordbot.mdx +1 -1
  105. package/docs/content/docs/cli/instagram.mdx +1 -1
  106. package/docs/content/docs/cli/kakaotalk.mdx +1 -1
  107. package/docs/content/docs/cli/line.mdx +1 -1
  108. package/docs/content/docs/cli/meta.json +1 -0
  109. package/docs/content/docs/cli/slack.mdx +1 -1
  110. package/docs/content/docs/cli/slackbot.mdx +1 -1
  111. package/docs/content/docs/cli/teams.mdx +1 -1
  112. package/docs/content/docs/cli/webex.mdx +1 -1
  113. package/docs/content/docs/cli/wechatbot.mdx +179 -0
  114. package/docs/content/docs/cli/whatsapp.mdx +1 -1
  115. package/docs/content/docs/cli/whatsappbot.mdx +1 -1
  116. package/docs/content/docs/sdk/meta.json +1 -1
  117. package/docs/content/docs/sdk/wechatbot.mdx +282 -0
  118. package/docs/content/docs/tui.mdx +1 -1
  119. package/docs/src/app/page.tsx +5 -5
  120. package/package.json +11 -3
  121. package/skills/agent-channeltalk/SKILL.md +1 -1
  122. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  123. package/skills/agent-discord/SKILL.md +1 -1
  124. package/skills/agent-discordbot/SKILL.md +1 -1
  125. package/skills/agent-instagram/SKILL.md +1 -1
  126. package/skills/agent-kakaotalk/SKILL.md +24 -1
  127. package/skills/agent-line/SKILL.md +7 -11
  128. package/skills/agent-line/references/authentication.md +13 -4
  129. package/skills/agent-slack/SKILL.md +1 -1
  130. package/skills/agent-slackbot/SKILL.md +1 -1
  131. package/skills/agent-teams/SKILL.md +1 -1
  132. package/skills/agent-telegram/SKILL.md +1 -1
  133. package/skills/agent-webex/SKILL.md +1 -1
  134. package/skills/agent-wechatbot/SKILL.md +385 -0
  135. package/skills/agent-whatsapp/SKILL.md +12 -1
  136. package/skills/agent-whatsappbot/SKILL.md +1 -1
  137. package/src/platforms/discord/credential-manager.test.ts +18 -1
  138. package/src/platforms/discordbot/client.ts +2 -2
  139. package/src/platforms/instagram/commands/auth.test.ts +216 -0
  140. package/src/platforms/instagram/commands/chat.test.ts +127 -0
  141. package/src/platforms/instagram/commands/message.test.ts +178 -0
  142. package/src/platforms/kakaotalk/cli.ts +2 -1
  143. package/src/platforms/kakaotalk/client.test.ts +157 -0
  144. package/src/platforms/kakaotalk/client.ts +57 -3
  145. package/src/platforms/kakaotalk/commands/auth.test.ts +299 -0
  146. package/src/platforms/kakaotalk/commands/chat.test.ts +97 -0
  147. package/src/platforms/kakaotalk/commands/index.ts +1 -0
  148. package/src/platforms/kakaotalk/commands/message.test.ts +113 -0
  149. package/src/platforms/kakaotalk/commands/profile.test.ts +84 -0
  150. package/src/platforms/kakaotalk/commands/profile.ts +21 -0
  151. package/src/platforms/kakaotalk/index.test.ts +5 -0
  152. package/src/platforms/kakaotalk/index.ts +2 -0
  153. package/src/platforms/kakaotalk/protocol/session.ts +2 -0
  154. package/src/platforms/kakaotalk/types.ts +18 -0
  155. package/src/platforms/line/client.ts +14 -39
  156. package/src/platforms/line/commands/auth.test.ts +141 -0
  157. package/src/platforms/line/commands/auth.ts +28 -19
  158. package/src/platforms/line/commands/chat.test.ts +110 -0
  159. package/src/platforms/line/commands/friend.test.ts +98 -0
  160. package/src/platforms/line/commands/message.test.ts +119 -0
  161. package/src/platforms/line/commands/profile.test.ts +85 -0
  162. package/src/platforms/slackbot/commands/channel.test.ts +139 -0
  163. package/src/platforms/slackbot/commands/message.test.ts +226 -0
  164. package/src/platforms/slackbot/commands/reaction.test.ts +90 -0
  165. package/src/platforms/slackbot/commands/user.test.ts +143 -0
  166. package/src/platforms/teams/commands/reaction.test.ts +45 -61
  167. package/src/platforms/teams/commands/reaction.ts +2 -0
  168. package/src/platforms/telegram/commands/chat.test.ts +125 -0
  169. package/src/platforms/telegram/commands/message.test.ts +92 -0
  170. package/src/platforms/webex/commands/member.test.ts +65 -58
  171. package/src/platforms/webex/commands/message.test.ts +78 -121
  172. package/src/platforms/webex/commands/snapshot.test.ts +59 -46
  173. package/src/platforms/webex/commands/space.test.ts +49 -48
  174. package/src/platforms/wechatbot/cli.ts +24 -0
  175. package/src/platforms/wechatbot/client.test.ts +497 -0
  176. package/src/platforms/wechatbot/client.ts +268 -0
  177. package/src/platforms/wechatbot/commands/auth.test.ts +211 -0
  178. package/src/platforms/wechatbot/commands/auth.ts +203 -0
  179. package/src/platforms/wechatbot/commands/index.ts +4 -0
  180. package/src/platforms/wechatbot/commands/message.test.ts +155 -0
  181. package/src/platforms/wechatbot/commands/message.ts +104 -0
  182. package/src/platforms/wechatbot/commands/shared.ts +22 -0
  183. package/src/platforms/wechatbot/commands/template.test.ts +199 -0
  184. package/src/platforms/wechatbot/commands/template.ts +102 -0
  185. package/src/platforms/wechatbot/commands/user.test.ts +165 -0
  186. package/src/platforms/wechatbot/commands/user.ts +75 -0
  187. package/src/platforms/wechatbot/credential-manager.test.ts +255 -0
  188. package/src/platforms/wechatbot/credential-manager.ts +148 -0
  189. package/src/platforms/wechatbot/index.test.ts +49 -0
  190. package/src/platforms/wechatbot/index.ts +19 -0
  191. package/src/platforms/wechatbot/types.test.ts +223 -0
  192. package/src/platforms/wechatbot/types.ts +107 -0
  193. package/src/platforms/whatsapp/client.ts +24 -13
  194. package/src/platforms/whatsapp/commands/auth.test.ts +311 -0
  195. package/src/platforms/whatsapp/commands/auth.ts +21 -17
  196. package/src/platforms/whatsapp/commands/chat.test.ts +198 -0
  197. package/src/platforms/whatsapp/commands/message.test.ts +231 -0
  198. package/src/platforms/whatsapp/credential-manager.test.ts +20 -0
  199. package/src/platforms/whatsapp/credential-manager.ts +17 -8
  200. package/src/platforms/whatsappbot/commands/auth.test.ts +217 -0
  201. package/src/platforms/whatsappbot/commands/message.test.ts +198 -0
  202. package/src/platforms/whatsappbot/commands/template.test.ts +112 -0
@@ -1,89 +1,73 @@
1
- import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
1
+ import { afterEach, beforeEach, expect, spyOn, test } from 'bun:test'
2
2
 
3
3
  import { TeamsClient } from '../client'
4
4
  import { TeamsCredentialManager } from '../credential-manager'
5
5
  import { addAction, removeAction } from './reaction'
6
6
 
7
- let clientAddReactionSpy: ReturnType<typeof spyOn>
8
- let clientRemoveReactionSpy: ReturnType<typeof spyOn>
9
- let credManagerLoadConfigSpy: ReturnType<typeof spyOn>
7
+ let addReactionSpy: ReturnType<typeof spyOn>
8
+ let removeReactionSpy: ReturnType<typeof spyOn>
9
+ let getTokenWithExpirySpy: ReturnType<typeof spyOn>
10
+ let consoleLogSpy: ReturnType<typeof spyOn>
11
+ let processExitSpy: ReturnType<typeof spyOn>
10
12
 
11
13
  beforeEach(() => {
12
- clientAddReactionSpy = spyOn(TeamsClient.prototype, 'addReaction').mockResolvedValue(undefined)
13
- clientRemoveReactionSpy = spyOn(TeamsClient.prototype, 'removeReaction').mockResolvedValue(undefined)
14
- credManagerLoadConfigSpy = spyOn(TeamsCredentialManager.prototype, 'loadConfig').mockResolvedValue({
15
- current_account: 'work',
16
- accounts: {
17
- work: {
18
- token: 'test-token',
19
- account_type: 'work' as const,
20
- current_team: null,
21
- teams: {},
22
- },
23
- },
14
+ addReactionSpy = spyOn(TeamsClient.prototype, 'addReaction').mockResolvedValue(undefined)
15
+ removeReactionSpy = spyOn(TeamsClient.prototype, 'removeReaction').mockResolvedValue(undefined)
16
+ getTokenWithExpirySpy = spyOn(TeamsCredentialManager.prototype, 'getTokenWithExpiry').mockImplementation(() =>
17
+ Promise.resolve({ token: 'test-token', tokenExpiresAt: undefined }),
18
+ )
19
+
20
+ consoleLogSpy = spyOn(console, 'log').mockImplementation(() => {})
21
+ processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => {
22
+ throw new Error(`process.exit(${_code})`)
24
23
  })
25
24
  })
26
25
 
27
26
  afterEach(() => {
28
- clientAddReactionSpy?.mockRestore()
29
- clientRemoveReactionSpy?.mockRestore()
30
- credManagerLoadConfigSpy?.mockRestore()
27
+ addReactionSpy.mockRestore()
28
+ removeReactionSpy.mockRestore()
29
+ getTokenWithExpirySpy.mockRestore()
30
+ consoleLogSpy.mockRestore()
31
+ processExitSpy.mockRestore()
31
32
  })
32
33
 
33
34
  test('add: sends correct POST request with emoji', async () => {
34
- const consoleSpy = mock((_msg: string) => {})
35
- const originalLog = console.log
36
- console.log = consoleSpy
37
-
38
35
  try {
39
36
  await addAction('team123', 'ch123', 'msg123', 'like', { pretty: false })
40
- expect(consoleSpy).toHaveBeenCalled()
41
- const output = JSON.parse(consoleSpy.mock.calls[0][0])
42
- expect(output.success).toBe(true)
43
- expect(output.team_id).toBe('team123')
44
- expect(output.channel_id).toBe('ch123')
45
- expect(output.message_id).toBe('msg123')
46
- expect(output.emoji).toBe('like')
47
- } finally {
48
- console.log = originalLog
49
- }
37
+ } catch {}
38
+
39
+ expect(consoleLogSpy).toHaveBeenCalled()
40
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
41
+ expect(output.success).toBe(true)
42
+ expect(output.team_id).toBe('team123')
43
+ expect(output.channel_id).toBe('ch123')
44
+ expect(output.message_id).toBe('msg123')
45
+ expect(output.emoji).toBe('like')
50
46
  })
51
47
 
52
48
  test('remove: sends correct DELETE request with emoji', async () => {
53
- const consoleSpy = mock((_msg: string) => {})
54
- const originalLog = console.log
55
- console.log = consoleSpy
56
-
57
49
  try {
58
50
  await removeAction('team123', 'ch123', 'msg123', 'like', { pretty: false })
59
- expect(consoleSpy).toHaveBeenCalled()
60
- const output = JSON.parse(consoleSpy.mock.calls[0][0])
61
- expect(output.success).toBe(true)
62
- expect(output.team_id).toBe('team123')
63
- expect(output.channel_id).toBe('ch123')
64
- expect(output.message_id).toBe('msg123')
65
- expect(output.emoji).toBe('like')
66
- } finally {
67
- console.log = originalLog
68
- }
51
+ } catch {}
52
+
53
+ expect(consoleLogSpy).toHaveBeenCalled()
54
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
55
+ expect(output.success).toBe(true)
56
+ expect(output.team_id).toBe('team123')
57
+ expect(output.channel_id).toBe('ch123')
58
+ expect(output.message_id).toBe('msg123')
59
+ expect(output.emoji).toBe('like')
69
60
  })
70
61
 
71
62
  test('add: handles missing token gracefully', async () => {
72
- credManagerLoadConfigSpy?.mockResolvedValue(null)
73
-
74
- const consoleSpy = mock((_msg: string) => {})
75
- const originalLog = console.log
76
- const originalExit = process.exit
77
- process.exit = mock(() => {}) as any
78
- console.log = consoleSpy
63
+ getTokenWithExpirySpy.mockImplementation(() => Promise.resolve(null))
79
64
 
80
65
  try {
81
66
  await addAction('team123', 'ch123', 'msg123', 'like', { pretty: false })
82
- expect(consoleSpy).toHaveBeenCalled()
83
- const output = JSON.parse(consoleSpy.mock.calls[0][0])
84
- expect(output.error).toBeDefined()
85
- } finally {
86
- console.log = originalLog
87
- process.exit = originalExit
88
- }
67
+ } catch {}
68
+
69
+ expect(consoleLogSpy).toHaveBeenCalled()
70
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
71
+ expect(output.error).toBeDefined()
72
+ expect(processExitSpy).toHaveBeenCalledWith(1)
89
73
  })
@@ -20,6 +20,7 @@ export async function addAction(
20
20
  if (!cred) {
21
21
  console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
22
22
  process.exit(1)
23
+ return
23
24
  }
24
25
 
25
26
  const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
@@ -56,6 +57,7 @@ export async function removeAction(
56
57
  if (!cred) {
57
58
  console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
58
59
  process.exit(1)
60
+ return
59
61
  }
60
62
 
61
63
  const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
@@ -0,0 +1,125 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
2
+
3
+ const mockListChats = mock(() =>
4
+ Promise.resolve([
5
+ { id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 },
6
+ { id: 'chat-2', title: 'Random', type: 'group', unread_count: 5 },
7
+ ]),
8
+ )
9
+
10
+ const mockSearchChats = mock(() =>
11
+ Promise.resolve([{ id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 }]),
12
+ )
13
+
14
+ const mockGetChat = mock(() =>
15
+ Promise.resolve({ id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 }),
16
+ )
17
+
18
+ const mockClient = {
19
+ listChats: mockListChats,
20
+ searchChats: mockSearchChats,
21
+ getChat: mockGetChat,
22
+ }
23
+
24
+ mock.module('./shared', () => ({
25
+ withTelegramClient: async (_opts: unknown, fn: (client: typeof mockClient) => Promise<unknown>) =>
26
+ fn(mockClient),
27
+ }))
28
+
29
+ import { chatCommand } from './chat'
30
+
31
+ describe('chat commands', () => {
32
+ let consoleSpy: ReturnType<typeof spyOn>
33
+ let processExitSpy: ReturnType<typeof spyOn>
34
+
35
+ beforeEach(() => {
36
+ mockListChats.mockReset()
37
+ mockListChats.mockImplementation(() =>
38
+ Promise.resolve([
39
+ { id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 },
40
+ { id: 'chat-2', title: 'Random', type: 'group', unread_count: 5 },
41
+ ]),
42
+ )
43
+ mockSearchChats.mockReset()
44
+ mockSearchChats.mockImplementation(() =>
45
+ Promise.resolve([{ id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 }]),
46
+ )
47
+ mockGetChat.mockReset()
48
+ mockGetChat.mockImplementation(() =>
49
+ Promise.resolve({ id: 'chat-1', title: 'General', type: 'supergroup', unread_count: 0 }),
50
+ )
51
+ consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
52
+ processExitSpy = spyOn(process, 'exit').mockImplementation((() => {}) as (code?: number) => never)
53
+ })
54
+
55
+ afterEach(() => {
56
+ consoleSpy.mockRestore()
57
+ processExitSpy.mockRestore()
58
+ })
59
+
60
+ describe('list subcommand', () => {
61
+ test('calls listChats with default limit', async () => {
62
+ await chatCommand.parseAsync(['list'], { from: 'user' })
63
+
64
+ expect(mockListChats).toHaveBeenCalledWith(20)
65
+ })
66
+
67
+ test('calls listChats with custom limit', async () => {
68
+ await chatCommand.parseAsync(['list', '--limit', '5'], { from: 'user' })
69
+
70
+ expect(mockListChats).toHaveBeenCalledWith(5)
71
+ })
72
+
73
+ test('outputs JSON array to console', async () => {
74
+ await chatCommand.parseAsync(['list'], { from: 'user' })
75
+
76
+ expect(consoleSpy).toHaveBeenCalled()
77
+ const output = consoleSpy.mock.calls[0][0] as string
78
+ const parsed = JSON.parse(output)
79
+ expect(parsed).toBeArray()
80
+ expect(parsed).toHaveLength(2)
81
+ })
82
+ })
83
+
84
+ describe('search subcommand', () => {
85
+ test('calls searchChats with query and default limit', async () => {
86
+ await chatCommand.parseAsync(['search', 'General'], { from: 'user' })
87
+
88
+ expect(mockSearchChats).toHaveBeenCalledWith('General', 20)
89
+ })
90
+
91
+ test('calls searchChats with query and custom limit', async () => {
92
+ await chatCommand.parseAsync(['search', 'General', '--limit', '3'], { from: 'user' })
93
+
94
+ expect(mockSearchChats).toHaveBeenCalledWith('General', 3)
95
+ })
96
+
97
+ test('outputs JSON array to console', async () => {
98
+ await chatCommand.parseAsync(['search', 'General'], { from: 'user' })
99
+
100
+ expect(consoleSpy).toHaveBeenCalled()
101
+ const output = consoleSpy.mock.calls[0][0] as string
102
+ const parsed = JSON.parse(output)
103
+ expect(parsed).toBeArray()
104
+ expect(parsed).toHaveLength(1)
105
+ })
106
+ })
107
+
108
+ describe('get subcommand', () => {
109
+ test('calls getChat with chat reference', async () => {
110
+ await chatCommand.parseAsync(['get', 'chat-1'], { from: 'user' })
111
+
112
+ expect(mockGetChat).toHaveBeenCalledWith('chat-1')
113
+ })
114
+
115
+ test('outputs JSON object to console', async () => {
116
+ await chatCommand.parseAsync(['get', 'chat-1'], { from: 'user' })
117
+
118
+ expect(consoleSpy).toHaveBeenCalled()
119
+ const output = consoleSpy.mock.calls[0][0] as string
120
+ const parsed = JSON.parse(output)
121
+ expect(parsed).toBeObject()
122
+ expect(parsed.id).toBe('chat-1')
123
+ })
124
+ })
125
+ })
@@ -0,0 +1,92 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
2
+
3
+ const mockListMessages = mock(() =>
4
+ Promise.resolve([
5
+ { id: 1, text: 'Hello', sender_id: 'user-1', date: 1000 },
6
+ { id: 2, text: 'World', sender_id: 'user-2', date: 2000 },
7
+ ]),
8
+ )
9
+
10
+ const mockSendMessage = mock(() =>
11
+ Promise.resolve({ id: 3, text: 'Sent message', sender_id: 'user-1', date: 3000 }),
12
+ )
13
+
14
+ const mockClient = {
15
+ listMessages: mockListMessages,
16
+ sendMessage: mockSendMessage,
17
+ }
18
+
19
+ mock.module('./shared', () => ({
20
+ withTelegramClient: async (_opts: unknown, fn: (client: typeof mockClient) => Promise<unknown>) =>
21
+ fn(mockClient),
22
+ }))
23
+
24
+ import { messageCommand } from './message'
25
+
26
+ describe('message commands', () => {
27
+ let consoleSpy: ReturnType<typeof spyOn>
28
+ let processExitSpy: ReturnType<typeof spyOn>
29
+
30
+ beforeEach(() => {
31
+ mockListMessages.mockReset()
32
+ mockListMessages.mockImplementation(() =>
33
+ Promise.resolve([
34
+ { id: 1, text: 'Hello', sender_id: 'user-1', date: 1000 },
35
+ { id: 2, text: 'World', sender_id: 'user-2', date: 2000 },
36
+ ]),
37
+ )
38
+ mockSendMessage.mockReset()
39
+ mockSendMessage.mockImplementation(() =>
40
+ Promise.resolve({ id: 3, text: 'Sent message', sender_id: 'user-1', date: 3000 }),
41
+ )
42
+ consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
43
+ processExitSpy = spyOn(process, 'exit').mockImplementation((() => {}) as (code?: number) => never)
44
+ })
45
+
46
+ afterEach(() => {
47
+ consoleSpy.mockRestore()
48
+ processExitSpy.mockRestore()
49
+ })
50
+
51
+ describe('list subcommand', () => {
52
+ test('calls listMessages with chat reference and default limit', async () => {
53
+ await messageCommand.parseAsync(['list', 'chat-123'], { from: 'user' })
54
+
55
+ expect(mockListMessages).toHaveBeenCalledWith('chat-123', 20)
56
+ })
57
+
58
+ test('calls listMessages with custom limit', async () => {
59
+ await messageCommand.parseAsync(['list', 'chat-123', '--limit', '10'], { from: 'user' })
60
+
61
+ expect(mockListMessages).toHaveBeenCalledWith('chat-123', 10)
62
+ })
63
+
64
+ test('outputs JSON to console', async () => {
65
+ await messageCommand.parseAsync(['list', 'chat-123'], { from: 'user' })
66
+
67
+ expect(consoleSpy).toHaveBeenCalled()
68
+ const output = consoleSpy.mock.calls[0][0] as string
69
+ const parsed = JSON.parse(output)
70
+ expect(parsed).toBeArray()
71
+ expect(parsed).toHaveLength(2)
72
+ })
73
+ })
74
+
75
+ describe('send subcommand', () => {
76
+ test('calls sendMessage with chat reference and text', async () => {
77
+ await messageCommand.parseAsync(['send', 'chat-123', 'Hello there'], { from: 'user' })
78
+
79
+ expect(mockSendMessage).toHaveBeenCalledWith('chat-123', 'Hello there')
80
+ })
81
+
82
+ test('outputs JSON to console', async () => {
83
+ await messageCommand.parseAsync(['send', 'chat-123', 'Hello there'], { from: 'user' })
84
+
85
+ expect(consoleSpy).toHaveBeenCalled()
86
+ const output = consoleSpy.mock.calls[0][0] as string
87
+ const parsed = JSON.parse(output)
88
+ expect(parsed).toBeObject()
89
+ expect(parsed.id).toBe(3)
90
+ })
91
+ })
92
+ })
@@ -1,65 +1,74 @@
1
- import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
1
+ import { afterAll, afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
2
2
 
3
- import { WebexClient } from '../client'
4
3
  import { WebexError } from '../types'
4
+
5
+ const mockHandleError = mock((err: Error) => {
6
+ throw err
7
+ })
8
+
9
+ mock.module('@/shared/utils/error-handler', () => ({
10
+ handleError: mockHandleError,
11
+ }))
12
+
13
+ const mockMembers = [
14
+ {
15
+ id: 'mem-1',
16
+ roomId: 'room-1',
17
+ personId: 'person-1',
18
+ personEmail: 'alice@example.com',
19
+ personDisplayName: 'Alice',
20
+ isModerator: true,
21
+ created: '2024-01-01T00:00:00.000Z',
22
+ },
23
+ {
24
+ id: 'mem-2',
25
+ roomId: 'room-1',
26
+ personId: 'person-2',
27
+ personEmail: 'bob@example.com',
28
+ personDisplayName: 'Bob',
29
+ isModerator: false,
30
+ created: '2024-01-02T00:00:00.000Z',
31
+ },
32
+ ]
33
+
34
+ const mockListMemberships = mock(() => Promise.resolve(mockMembers))
35
+ const mockLogin = mock(() => Promise.resolve({ listMemberships: mockListMemberships }))
36
+
37
+ mock.module('../client', () => ({
38
+ WebexClient: class {
39
+ login = mockLogin
40
+ },
41
+ }))
42
+
5
43
  import { listAction } from './member'
6
44
 
45
+ afterAll(() => {
46
+ mock.restore()
47
+ })
48
+
7
49
  describe('member commands', () => {
8
50
  let consoleSpy: ReturnType<typeof spyOn>
9
- let consoleErrorSpy: ReturnType<typeof spyOn>
10
- let processExitSpy: ReturnType<typeof spyOn>
11
- let stderrOutput: string
12
- let origStderrWrite: typeof process.stderr.write
13
- const mockMembers = [
14
- {
15
- id: 'mem-1',
16
- roomId: 'room-1',
17
- personId: 'person-1',
18
- personEmail: 'alice@example.com',
19
- personDisplayName: 'Alice',
20
- isModerator: true,
21
- created: '2024-01-01T00:00:00.000Z',
22
- },
23
- {
24
- id: 'mem-2',
25
- roomId: 'room-1',
26
- personId: 'person-2',
27
- personEmail: 'bob@example.com',
28
- personDisplayName: 'Bob',
29
- isModerator: false,
30
- created: '2024-01-02T00:00:00.000Z',
31
- },
32
- ]
33
51
 
34
52
  beforeEach(() => {
35
- consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
36
- consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
37
- processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => {
38
- throw new Error(`process.exit(${_code})`)
53
+ mockListMemberships.mockReset().mockImplementation(() => Promise.resolve(mockMembers))
54
+ mockLogin.mockReset().mockImplementation(() =>
55
+ Promise.resolve({ listMemberships: mockListMemberships }),
56
+ )
57
+ mockHandleError.mockReset().mockImplementation((err: Error) => {
58
+ throw err
39
59
  })
40
- stderrOutput = ''
41
- origStderrWrite = process.stderr.write
42
- process.stderr.write = ((chunk: string | Uint8Array) => {
43
- stderrOutput += typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk)
44
- return true
45
- }) as typeof process.stderr.write
46
- spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient() as any)
47
- spyOn(WebexClient.prototype, 'listMemberships').mockResolvedValue(mockMembers)
60
+
61
+ consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
48
62
  })
49
63
 
50
64
  afterEach(() => {
51
- process.stderr.write = origStderrWrite
52
- mock.restore()
65
+ consoleSpy.mockRestore()
53
66
  })
54
67
 
55
68
  test('listAction calls listMemberships with spaceId and outputs mapped members', async () => {
56
- const listMembershipsSpy = spyOn(WebexClient.prototype, 'listMemberships').mockResolvedValue(
57
- mockMembers,
58
- )
59
-
60
69
  await listAction('room-1', {})
61
70
 
62
- expect(listMembershipsSpy).toHaveBeenCalledWith('room-1', { max: undefined })
71
+ expect(mockListMemberships).toHaveBeenCalledWith('room-1', { max: undefined })
63
72
  expect(consoleSpy).toHaveBeenCalledWith(
64
73
  JSON.stringify([
65
74
  {
@@ -83,30 +92,28 @@ describe('member commands', () => {
83
92
  })
84
93
 
85
94
  test('listAction passes limit option to listMemberships', async () => {
86
- const listMembershipsSpy = spyOn(WebexClient.prototype, 'listMemberships').mockResolvedValue(
87
- mockMembers,
88
- )
89
-
90
95
  await listAction('room-1', { limit: 25 })
91
96
 
92
- expect(listMembershipsSpy).toHaveBeenCalledWith('room-1', { max: 25 })
97
+ expect(mockListMemberships).toHaveBeenCalledWith('room-1', { max: 25 })
93
98
  })
94
99
 
95
100
  test('listAction handles not-authenticated case', async () => {
96
- spyOn(WebexClient.prototype, 'login').mockRejectedValue(
97
- new WebexError('No Webex credentials found.', 'no_credentials'),
98
- )
101
+ mockLogin.mockImplementation(async () => {
102
+ throw new WebexError('No Webex credentials found.', 'no_credentials')
103
+ })
99
104
 
100
- await expect(listAction('room-1', {})).rejects.toThrow('process.exit(1)')
105
+ await expect(listAction('room-1', {})).rejects.toThrow('No Webex credentials found.')
101
106
 
102
- expect(stderrOutput).toContain('No Webex credentials found')
107
+ expect(mockHandleError).toHaveBeenCalledWith(expect.any(WebexError))
103
108
  })
104
109
 
105
110
  test('listAction handles API error', async () => {
106
- spyOn(WebexClient.prototype, 'listMemberships').mockRejectedValue(new Error('API failure'))
111
+ mockListMemberships.mockImplementation(async () => {
112
+ throw new Error('API failure')
113
+ })
107
114
 
108
- await expect(listAction('room-1', {})).rejects.toThrow('process.exit(1)')
115
+ await expect(listAction('room-1', {})).rejects.toThrow('API failure')
109
116
 
110
- expect(processExitSpy).toHaveBeenCalledWith(1)
117
+ expect(mockHandleError).toHaveBeenCalledWith(expect.any(Error))
111
118
  })
112
119
  })