agent-messenger 2.2.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 (229) 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 +9 -6
  6. package/bun.lock +89 -105
  7. package/bunfig.toml +3 -0
  8. package/dist/package.json +13 -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/commands/auth.d.ts.map +1 -1
  38. package/dist/src/platforms/line/commands/auth.js +32 -20
  39. package/dist/src/platforms/line/commands/auth.js.map +1 -1
  40. package/dist/src/platforms/teams/commands/reaction.d.ts.map +1 -1
  41. package/dist/src/platforms/teams/commands/reaction.js +2 -0
  42. package/dist/src/platforms/teams/commands/reaction.js.map +1 -1
  43. package/dist/src/platforms/webex/client.d.ts +2 -0
  44. package/dist/src/platforms/webex/client.d.ts.map +1 -1
  45. package/dist/src/platforms/webex/client.js +66 -23
  46. package/dist/src/platforms/webex/client.js.map +1 -1
  47. package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
  48. package/dist/src/platforms/webex/commands/auth.js +4 -0
  49. package/dist/src/platforms/webex/commands/auth.js.map +1 -1
  50. package/dist/src/platforms/webex/encryption.d.ts +10 -0
  51. package/dist/src/platforms/webex/encryption.d.ts.map +1 -0
  52. package/dist/src/platforms/webex/encryption.js +49 -0
  53. package/dist/src/platforms/webex/encryption.js.map +1 -0
  54. package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -1
  55. package/dist/src/platforms/webex/ensure-auth.js +4 -0
  56. package/dist/src/platforms/webex/ensure-auth.js.map +1 -1
  57. package/dist/src/platforms/webex/token-extractor.d.ts +6 -5
  58. package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
  59. package/dist/src/platforms/webex/token-extractor.js +92 -43
  60. package/dist/src/platforms/webex/token-extractor.js.map +1 -1
  61. package/dist/src/platforms/webex/types.d.ts +4 -0
  62. package/dist/src/platforms/webex/types.d.ts.map +1 -1
  63. package/dist/src/platforms/webex/types.js +2 -0
  64. package/dist/src/platforms/webex/types.js.map +1 -1
  65. package/dist/src/platforms/wechatbot/cli.d.ts +5 -0
  66. package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -0
  67. package/dist/src/platforms/wechatbot/cli.js +18 -0
  68. package/dist/src/platforms/wechatbot/cli.js.map +1 -0
  69. package/dist/src/platforms/wechatbot/client.d.ts +36 -0
  70. package/dist/src/platforms/wechatbot/client.d.ts.map +1 -0
  71. package/dist/src/platforms/wechatbot/client.js +208 -0
  72. package/dist/src/platforms/wechatbot/client.js.map +1 -0
  73. package/dist/src/platforms/wechatbot/commands/auth.d.ts +28 -0
  74. package/dist/src/platforms/wechatbot/commands/auth.d.ts.map +1 -0
  75. package/dist/src/platforms/wechatbot/commands/auth.js +164 -0
  76. package/dist/src/platforms/wechatbot/commands/auth.js.map +1 -0
  77. package/dist/src/platforms/wechatbot/commands/index.d.ts +5 -0
  78. package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -0
  79. package/dist/src/platforms/wechatbot/commands/index.js +5 -0
  80. package/dist/src/platforms/wechatbot/commands/index.js.map +1 -0
  81. package/dist/src/platforms/wechatbot/commands/message.d.ts +18 -0
  82. package/dist/src/platforms/wechatbot/commands/message.d.ts.map +1 -0
  83. package/dist/src/platforms/wechatbot/commands/message.js +80 -0
  84. package/dist/src/platforms/wechatbot/commands/message.js.map +1 -0
  85. package/dist/src/platforms/wechatbot/commands/shared.d.ts +9 -0
  86. package/dist/src/platforms/wechatbot/commands/shared.d.ts.map +1 -0
  87. package/dist/src/platforms/wechatbot/commands/shared.js +13 -0
  88. package/dist/src/platforms/wechatbot/commands/shared.js.map +1 -0
  89. package/dist/src/platforms/wechatbot/commands/template.d.ts +19 -0
  90. package/dist/src/platforms/wechatbot/commands/template.d.ts.map +1 -0
  91. package/dist/src/platforms/wechatbot/commands/template.js +76 -0
  92. package/dist/src/platforms/wechatbot/commands/template.js.map +1 -0
  93. package/dist/src/platforms/wechatbot/commands/user.d.ts +20 -0
  94. package/dist/src/platforms/wechatbot/commands/user.d.ts.map +1 -0
  95. package/dist/src/platforms/wechatbot/commands/user.js +53 -0
  96. package/dist/src/platforms/wechatbot/commands/user.js.map +1 -0
  97. package/dist/src/platforms/wechatbot/credential-manager.d.ts +17 -0
  98. package/dist/src/platforms/wechatbot/credential-manager.d.ts.map +1 -0
  99. package/dist/src/platforms/wechatbot/credential-manager.js +121 -0
  100. package/dist/src/platforms/wechatbot/credential-manager.js.map +1 -0
  101. package/dist/src/platforms/wechatbot/index.d.ts +5 -0
  102. package/dist/src/platforms/wechatbot/index.d.ts.map +1 -0
  103. package/dist/src/platforms/wechatbot/index.js +4 -0
  104. package/dist/src/platforms/wechatbot/index.js.map +1 -0
  105. package/dist/src/platforms/wechatbot/types.d.ts +94 -0
  106. package/dist/src/platforms/wechatbot/types.d.ts.map +1 -0
  107. package/dist/src/platforms/wechatbot/types.js +54 -0
  108. package/dist/src/platforms/wechatbot/types.js.map +1 -0
  109. package/dist/src/platforms/whatsapp/client.d.ts +1 -0
  110. package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
  111. package/dist/src/platforms/whatsapp/client.js +27 -13
  112. package/dist/src/platforms/whatsapp/client.js.map +1 -1
  113. package/dist/src/platforms/whatsapp/commands/auth.d.ts.map +1 -1
  114. package/dist/src/platforms/whatsapp/commands/auth.js +21 -18
  115. package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
  116. package/dist/src/platforms/whatsapp/credential-manager.d.ts.map +1 -1
  117. package/dist/src/platforms/whatsapp/credential-manager.js +14 -8
  118. package/dist/src/platforms/whatsapp/credential-manager.js.map +1 -1
  119. package/docs/content/docs/agent-skills.mdx +4 -4
  120. package/docs/content/docs/cli/channeltalk.mdx +1 -1
  121. package/docs/content/docs/cli/channeltalkbot.mdx +1 -1
  122. package/docs/content/docs/cli/discord.mdx +1 -1
  123. package/docs/content/docs/cli/discordbot.mdx +1 -1
  124. package/docs/content/docs/cli/instagram.mdx +1 -1
  125. package/docs/content/docs/cli/kakaotalk.mdx +1 -1
  126. package/docs/content/docs/cli/line.mdx +1 -1
  127. package/docs/content/docs/cli/meta.json +1 -0
  128. package/docs/content/docs/cli/slack.mdx +1 -1
  129. package/docs/content/docs/cli/slackbot.mdx +1 -1
  130. package/docs/content/docs/cli/teams.mdx +1 -1
  131. package/docs/content/docs/cli/webex.mdx +5 -3
  132. package/docs/content/docs/cli/wechatbot.mdx +179 -0
  133. package/docs/content/docs/cli/whatsapp.mdx +1 -1
  134. package/docs/content/docs/cli/whatsappbot.mdx +1 -1
  135. package/docs/content/docs/sdk/meta.json +1 -1
  136. package/docs/content/docs/sdk/wechatbot.mdx +282 -0
  137. package/docs/content/docs/tui.mdx +1 -1
  138. package/docs/src/app/page.tsx +5 -5
  139. package/package.json +13 -3
  140. package/skills/agent-channeltalk/SKILL.md +1 -1
  141. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  142. package/skills/agent-discord/SKILL.md +1 -1
  143. package/skills/agent-discordbot/SKILL.md +1 -1
  144. package/skills/agent-instagram/SKILL.md +1 -1
  145. package/skills/agent-kakaotalk/SKILL.md +24 -1
  146. package/skills/agent-line/SKILL.md +7 -11
  147. package/skills/agent-line/references/authentication.md +13 -4
  148. package/skills/agent-slack/SKILL.md +1 -1
  149. package/skills/agent-slackbot/SKILL.md +1 -1
  150. package/skills/agent-teams/SKILL.md +1 -1
  151. package/skills/agent-telegram/SKILL.md +1 -1
  152. package/skills/agent-webex/SKILL.md +1 -1
  153. package/skills/agent-webex/references/authentication.md +4 -3
  154. package/skills/agent-webex/references/common-patterns.md +1 -1
  155. package/skills/agent-wechatbot/SKILL.md +385 -0
  156. package/skills/agent-whatsapp/SKILL.md +12 -1
  157. package/skills/agent-whatsappbot/SKILL.md +1 -1
  158. package/src/platforms/discord/credential-manager.test.ts +18 -1
  159. package/src/platforms/discordbot/client.ts +2 -2
  160. package/src/platforms/instagram/commands/auth.test.ts +216 -0
  161. package/src/platforms/instagram/commands/chat.test.ts +127 -0
  162. package/src/platforms/instagram/commands/message.test.ts +178 -0
  163. package/src/platforms/kakaotalk/cli.ts +2 -1
  164. package/src/platforms/kakaotalk/client.test.ts +157 -0
  165. package/src/platforms/kakaotalk/client.ts +57 -3
  166. package/src/platforms/kakaotalk/commands/auth.test.ts +299 -0
  167. package/src/platforms/kakaotalk/commands/chat.test.ts +97 -0
  168. package/src/platforms/kakaotalk/commands/index.ts +1 -0
  169. package/src/platforms/kakaotalk/commands/message.test.ts +113 -0
  170. package/src/platforms/kakaotalk/commands/profile.test.ts +84 -0
  171. package/src/platforms/kakaotalk/commands/profile.ts +21 -0
  172. package/src/platforms/kakaotalk/index.test.ts +5 -0
  173. package/src/platforms/kakaotalk/index.ts +2 -0
  174. package/src/platforms/kakaotalk/protocol/session.ts +2 -0
  175. package/src/platforms/kakaotalk/types.ts +18 -0
  176. package/src/platforms/line/commands/auth.test.ts +141 -0
  177. package/src/platforms/line/commands/auth.ts +28 -19
  178. package/src/platforms/line/commands/chat.test.ts +110 -0
  179. package/src/platforms/line/commands/friend.test.ts +98 -0
  180. package/src/platforms/line/commands/message.test.ts +119 -0
  181. package/src/platforms/line/commands/profile.test.ts +85 -0
  182. package/src/platforms/slackbot/commands/channel.test.ts +139 -0
  183. package/src/platforms/slackbot/commands/message.test.ts +226 -0
  184. package/src/platforms/slackbot/commands/reaction.test.ts +90 -0
  185. package/src/platforms/slackbot/commands/user.test.ts +143 -0
  186. package/src/platforms/teams/commands/reaction.test.ts +45 -61
  187. package/src/platforms/teams/commands/reaction.ts +2 -0
  188. package/src/platforms/telegram/commands/chat.test.ts +125 -0
  189. package/src/platforms/telegram/commands/message.test.ts +92 -0
  190. package/src/platforms/webex/client.ts +98 -26
  191. package/src/platforms/webex/commands/auth.ts +4 -0
  192. package/src/platforms/webex/commands/member.test.ts +65 -58
  193. package/src/platforms/webex/commands/message.test.ts +78 -121
  194. package/src/platforms/webex/commands/snapshot.test.ts +59 -46
  195. package/src/platforms/webex/commands/space.test.ts +49 -48
  196. package/src/platforms/webex/encryption.ts +53 -0
  197. package/src/platforms/webex/ensure-auth.ts +4 -0
  198. package/src/platforms/webex/token-extractor.ts +107 -40
  199. package/src/platforms/webex/types.ts +4 -0
  200. package/src/platforms/webex/typings/node-jose.d.ts +27 -0
  201. package/src/platforms/wechatbot/cli.ts +24 -0
  202. package/src/platforms/wechatbot/client.test.ts +497 -0
  203. package/src/platforms/wechatbot/client.ts +268 -0
  204. package/src/platforms/wechatbot/commands/auth.test.ts +211 -0
  205. package/src/platforms/wechatbot/commands/auth.ts +203 -0
  206. package/src/platforms/wechatbot/commands/index.ts +4 -0
  207. package/src/platforms/wechatbot/commands/message.test.ts +155 -0
  208. package/src/platforms/wechatbot/commands/message.ts +104 -0
  209. package/src/platforms/wechatbot/commands/shared.ts +22 -0
  210. package/src/platforms/wechatbot/commands/template.test.ts +199 -0
  211. package/src/platforms/wechatbot/commands/template.ts +102 -0
  212. package/src/platforms/wechatbot/commands/user.test.ts +165 -0
  213. package/src/platforms/wechatbot/commands/user.ts +75 -0
  214. package/src/platforms/wechatbot/credential-manager.test.ts +255 -0
  215. package/src/platforms/wechatbot/credential-manager.ts +148 -0
  216. package/src/platforms/wechatbot/index.test.ts +49 -0
  217. package/src/platforms/wechatbot/index.ts +19 -0
  218. package/src/platforms/wechatbot/types.test.ts +223 -0
  219. package/src/platforms/wechatbot/types.ts +107 -0
  220. package/src/platforms/whatsapp/client.ts +24 -13
  221. package/src/platforms/whatsapp/commands/auth.test.ts +311 -0
  222. package/src/platforms/whatsapp/commands/auth.ts +21 -17
  223. package/src/platforms/whatsapp/commands/chat.test.ts +198 -0
  224. package/src/platforms/whatsapp/commands/message.test.ts +231 -0
  225. package/src/platforms/whatsapp/credential-manager.test.ts +20 -0
  226. package/src/platforms/whatsapp/credential-manager.ts +17 -8
  227. package/src/platforms/whatsappbot/commands/auth.test.ts +217 -0
  228. package/src/platforms/whatsappbot/commands/message.test.ts +198 -0
  229. package/src/platforms/whatsappbot/commands/template.test.ts +112 -0
@@ -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
  })
@@ -1,18 +1,14 @@
1
- import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
1
+ import { afterAll, afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
2
2
 
3
- import { WebexClient } from '../client'
4
3
  import { WebexError } from '../types'
5
- import { deleteAction, dmAction, editAction, getAction, listAction, sendAction } from './message'
6
4
 
7
- let clientSendMessageSpy: ReturnType<typeof spyOn>
8
- let clientSendDirectMessageSpy: ReturnType<typeof spyOn>
9
- let clientListMessagesSpy: ReturnType<typeof spyOn>
10
- let clientGetMessageSpy: ReturnType<typeof spyOn>
11
- let clientDeleteMessageSpy: ReturnType<typeof spyOn>
12
- let clientEditMessageSpy: ReturnType<typeof spyOn>
13
- let clientLoginSpy: ReturnType<typeof spyOn>
14
- const originalConsoleLog = console.log
15
- const originalConsoleError = console.error
5
+ const mockHandleError = mock((err: Error) => {
6
+ throw err
7
+ })
8
+
9
+ mock.module('@/shared/utils/error-handler', () => ({
10
+ handleError: mockHandleError,
11
+ }))
16
12
 
17
13
  const mockMessage = {
18
14
  id: 'msg_123',
@@ -34,202 +30,163 @@ const mockMessage2 = {
34
30
  created: '2025-01-29T10:01:00.000Z',
35
31
  }
36
32
 
37
- beforeEach(() => {
38
- clientLoginSpy = spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient() as any)
33
+ const mockSendMessage = mock(() => Promise.resolve(mockMessage))
34
+ const mockSendDirectMessage = mock(() => Promise.resolve(mockMessage))
35
+ const mockListMessages = mock(() => Promise.resolve([mockMessage, mockMessage2]))
36
+ const mockGetMessage = mock(() => Promise.resolve(mockMessage))
37
+ const mockDeleteMessage = mock(() => Promise.resolve(undefined))
38
+ const mockEditMessage = mock(() => Promise.resolve({ ...mockMessage, text: 'Updated message' }))
39
+
40
+ const mockClient = {
41
+ sendMessage: mockSendMessage,
42
+ sendDirectMessage: mockSendDirectMessage,
43
+ listMessages: mockListMessages,
44
+ getMessage: mockGetMessage,
45
+ deleteMessage: mockDeleteMessage,
46
+ editMessage: mockEditMessage,
47
+ }
39
48
 
40
- clientSendMessageSpy = spyOn(WebexClient.prototype, 'sendMessage').mockResolvedValue(mockMessage)
49
+ const mockLogin = mock(() => Promise.resolve(mockClient))
41
50
 
42
- clientSendDirectMessageSpy = spyOn(WebexClient.prototype, 'sendDirectMessage').mockResolvedValue(mockMessage)
51
+ mock.module('../client', () => ({
52
+ WebexClient: class {
53
+ login = mockLogin
54
+ },
55
+ }))
43
56
 
44
- clientListMessagesSpy = spyOn(WebexClient.prototype, 'listMessages').mockResolvedValue([
45
- mockMessage,
46
- mockMessage2,
47
- ])
57
+ import { deleteAction, dmAction, editAction, getAction, listAction, sendAction } from './message'
48
58
 
49
- clientGetMessageSpy = spyOn(WebexClient.prototype, 'getMessage').mockResolvedValue(mockMessage)
59
+ afterAll(() => {
60
+ mock.restore()
61
+ })
50
62
 
51
- clientDeleteMessageSpy = spyOn(WebexClient.prototype, 'deleteMessage').mockResolvedValue(
52
- undefined,
53
- )
63
+ let consoleLogSpy: ReturnType<typeof spyOn>
54
64
 
55
- clientEditMessageSpy = spyOn(WebexClient.prototype, 'editMessage').mockResolvedValue({
56
- ...mockMessage,
57
- text: 'Updated message',
65
+ beforeEach(() => {
66
+ mockSendMessage.mockReset().mockImplementation(() => Promise.resolve(mockMessage))
67
+ mockSendDirectMessage.mockReset().mockImplementation(() => Promise.resolve(mockMessage))
68
+ mockListMessages.mockReset().mockImplementation(() => Promise.resolve([mockMessage, mockMessage2]))
69
+ mockGetMessage.mockReset().mockImplementation(() => Promise.resolve(mockMessage))
70
+ mockDeleteMessage.mockReset().mockImplementation(() => Promise.resolve(undefined))
71
+ mockEditMessage.mockReset().mockImplementation(() => Promise.resolve({ ...mockMessage, text: 'Updated message' }))
72
+ mockLogin.mockReset().mockImplementation(() => Promise.resolve(mockClient))
73
+ mockHandleError.mockReset().mockImplementation((err: Error) => {
74
+ throw err
58
75
  })
76
+
77
+ consoleLogSpy = spyOn(console, 'log').mockImplementation(() => {})
59
78
  })
60
79
 
61
80
  afterEach(() => {
62
- clientLoginSpy?.mockRestore()
63
- clientSendMessageSpy?.mockRestore()
64
- clientSendDirectMessageSpy?.mockRestore()
65
- clientListMessagesSpy?.mockRestore()
66
- clientGetMessageSpy?.mockRestore()
67
- clientDeleteMessageSpy?.mockRestore()
68
- clientEditMessageSpy?.mockRestore()
69
- console.log = originalConsoleLog
70
- console.error = originalConsoleError
81
+ consoleLogSpy.mockRestore()
71
82
  })
72
83
 
73
84
  test('send: calls sendMessage with correct args and outputs result', async () => {
74
- const consoleSpy = mock((_msg: string) => {})
75
- console.log = consoleSpy
76
-
77
85
  await sendAction('space_456', 'Hello world', { pretty: false })
78
86
 
79
- expect(clientSendMessageSpy).toHaveBeenCalledWith('space_456', 'Hello world', {
87
+ expect(mockSendMessage).toHaveBeenCalledWith('space_456', 'Hello world', {
80
88
  markdown: undefined,
81
89
  })
82
- expect(consoleSpy).toHaveBeenCalled()
83
- const output = consoleSpy.mock.calls[0][0]
90
+ expect(consoleLogSpy).toHaveBeenCalled()
91
+ const output = consoleLogSpy.mock.calls[0][0]
84
92
  expect(output).toContain('msg_123')
85
93
  expect(output).toContain('space_456')
86
94
  expect(output).toContain('user@example.com')
87
95
  })
88
96
 
89
97
  test('send: with --markdown passes markdown option', async () => {
90
- const consoleSpy = mock((_msg: string) => {})
91
- console.log = consoleSpy
92
-
93
98
  await sendAction('space_456', '**bold**', { markdown: true, pretty: false })
94
99
 
95
- expect(clientSendMessageSpy).toHaveBeenCalledWith('space_456', '**bold**', { markdown: true })
100
+ expect(mockSendMessage).toHaveBeenCalledWith('space_456', '**bold**', { markdown: true })
96
101
  })
97
102
 
98
103
  test('send: not authenticated shows error', async () => {
99
- clientLoginSpy.mockRejectedValue(new WebexError('No Webex credentials found.', 'no_credentials'))
100
-
101
- let stderrOutput = ''
102
- const origWrite = process.stderr.write
103
- process.stderr.write = ((chunk: string | Uint8Array) => {
104
- stderrOutput += typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk)
105
- return true
106
- }) as typeof process.stderr.write
104
+ mockLogin.mockImplementation(async () => {
105
+ throw new WebexError('No Webex credentials found.', 'no_credentials')
106
+ })
107
107
 
108
- const originalExit = process.exit
109
- process.exit = mock((_code?: number) => {
110
- throw new Error('process.exit called')
111
- }) as never
108
+ await expect(sendAction('space_456', 'Hello', { pretty: false })).rejects.toThrow('No Webex credentials found.')
112
109
 
113
- try {
114
- await sendAction('space_456', 'Hello', { pretty: false })
115
- } catch {
116
- } finally {
117
- process.exit = originalExit
118
- process.stderr.write = origWrite
119
- }
120
-
121
- expect(stderrOutput).toContain('No Webex credentials found')
110
+ expect(mockHandleError).toHaveBeenCalledWith(expect.any(WebexError))
122
111
  })
123
112
 
124
113
  test('dm: calls sendDirectMessage with email and text', async () => {
125
- const consoleSpy = mock((_msg: string) => {})
126
- console.log = consoleSpy
127
-
128
114
  await dmAction('alice@example.com', 'Hello!', { pretty: false })
129
115
 
130
- expect(clientSendDirectMessageSpy).toHaveBeenCalledWith('alice@example.com', 'Hello!', {
116
+ expect(mockSendDirectMessage).toHaveBeenCalledWith('alice@example.com', 'Hello!', {
131
117
  markdown: undefined,
132
118
  })
133
- expect(consoleSpy).toHaveBeenCalled()
134
- const output = consoleSpy.mock.calls[0][0]
119
+ expect(consoleLogSpy).toHaveBeenCalled()
120
+ const output = consoleLogSpy.mock.calls[0][0]
135
121
  expect(output).toContain('msg_123')
136
122
  })
137
123
 
138
124
  test('dm: with --markdown passes markdown option', async () => {
139
- const consoleSpy = mock((_msg: string) => {})
140
- console.log = consoleSpy
141
-
142
125
  await dmAction('alice@example.com', '**bold**', { markdown: true, pretty: false })
143
126
 
144
- expect(clientSendDirectMessageSpy).toHaveBeenCalledWith('alice@example.com', '**bold**', {
127
+ expect(mockSendDirectMessage).toHaveBeenCalledWith('alice@example.com', '**bold**', {
145
128
  markdown: true,
146
129
  })
147
130
  })
148
131
 
149
132
  test('list: calls listMessages with limit and outputs array', async () => {
150
- const consoleSpy = mock((_msg: string) => {})
151
- console.log = consoleSpy
152
-
153
133
  await listAction('space_456', { limit: 50, pretty: false })
154
134
 
155
- expect(clientListMessagesSpy).toHaveBeenCalledWith('space_456', { max: 50 })
156
- expect(consoleSpy).toHaveBeenCalled()
157
- const output = consoleSpy.mock.calls[0][0]
135
+ expect(mockListMessages).toHaveBeenCalledWith('space_456', { max: 50 })
136
+ expect(consoleLogSpy).toHaveBeenCalled()
137
+ const output = consoleLogSpy.mock.calls[0][0]
158
138
  expect(output).toContain('msg_123')
159
139
  expect(output).toContain('msg_124')
160
140
  })
161
141
 
162
142
  test('get: calls getMessage with correct id and outputs result', async () => {
163
- const consoleSpy = mock((_msg: string) => {})
164
- console.log = consoleSpy
165
-
166
143
  await getAction('msg_123', { pretty: false })
167
144
 
168
- expect(clientGetMessageSpy).toHaveBeenCalledWith('msg_123')
169
- expect(consoleSpy).toHaveBeenCalled()
170
- const output = consoleSpy.mock.calls[0][0]
145
+ expect(mockGetMessage).toHaveBeenCalledWith('msg_123')
146
+ expect(consoleLogSpy).toHaveBeenCalled()
147
+ const output = consoleLogSpy.mock.calls[0][0]
171
148
  expect(output).toContain('msg_123')
172
149
  expect(output).toContain('user@example.com')
173
150
  })
174
151
 
175
152
  test('delete: with --force calls deleteMessage and outputs deleted id', async () => {
176
- const consoleSpy = mock((_msg: string) => {})
177
- console.log = consoleSpy
178
-
179
153
  await deleteAction('msg_123', { force: true, pretty: false })
180
154
 
181
- expect(clientDeleteMessageSpy).toHaveBeenCalledWith('msg_123')
182
- expect(consoleSpy).toHaveBeenCalled()
183
- const output = consoleSpy.mock.calls[0][0]
155
+ expect(mockDeleteMessage).toHaveBeenCalledWith('msg_123')
156
+ expect(consoleLogSpy).toHaveBeenCalled()
157
+ const output = consoleLogSpy.mock.calls[0][0]
184
158
  expect(output).toContain('deleted')
185
159
  expect(output).toContain('msg_123')
186
160
  })
187
161
 
188
162
  test('delete: without --force shows warning and does not delete', async () => {
189
- const consoleSpy = mock((_msg: string) => {})
190
- console.log = consoleSpy
191
-
192
- const originalExit = process.exit
193
- process.exit = mock((_code?: number) => {
194
- throw new Error('process.exit called')
195
- }) as never
196
-
197
163
  try {
198
164
  await deleteAction('msg_123', { force: false, pretty: false })
199
- } catch {
200
- } finally {
201
- process.exit = originalExit
202
- }
203
-
204
- expect(clientDeleteMessageSpy).not.toHaveBeenCalled()
205
- expect(consoleSpy).toHaveBeenCalled()
206
- const output = consoleSpy.mock.calls[0][0]
165
+ } catch {}
166
+
167
+ expect(mockDeleteMessage).not.toHaveBeenCalled()
168
+ expect(consoleLogSpy).toHaveBeenCalled()
169
+ const output = consoleLogSpy.mock.calls[0][0]
207
170
  expect(output).toContain('warning')
208
171
  expect(output).toContain('--force')
209
172
  })
210
173
 
211
174
  test('edit: calls editMessage with roomId in args and outputs result', async () => {
212
- const consoleSpy = mock((_msg: string) => {})
213
- console.log = consoleSpy
214
-
215
175
  await editAction('msg_123', 'space_456', 'Updated message', { pretty: false })
216
176
 
217
- expect(clientEditMessageSpy).toHaveBeenCalledWith('msg_123', 'space_456', 'Updated message', {
177
+ expect(mockEditMessage).toHaveBeenCalledWith('msg_123', 'space_456', 'Updated message', {
218
178
  markdown: undefined,
219
179
  })
220
- expect(consoleSpy).toHaveBeenCalled()
221
- const output = consoleSpy.mock.calls[0][0]
180
+ expect(consoleLogSpy).toHaveBeenCalled()
181
+ const output = consoleLogSpy.mock.calls[0][0]
222
182
  expect(output).toContain('msg_123')
223
183
  expect(output).toContain('Updated message')
224
184
  })
225
185
 
226
186
  test('edit: with --markdown passes markdown option', async () => {
227
- const consoleSpy = mock((_msg: string) => {})
228
- console.log = consoleSpy
229
-
230
187
  await editAction('msg_123', 'space_456', '**updated**', { markdown: true, pretty: false })
231
188
 
232
- expect(clientEditMessageSpy).toHaveBeenCalledWith('msg_123', 'space_456', '**updated**', {
189
+ expect(mockEditMessage).toHaveBeenCalledWith('msg_123', 'space_456', '**updated**', {
233
190
  markdown: true,
234
191
  })
235
192
  })
@@ -1,44 +1,67 @@
1
- import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
2
- import { WebexClient } from '../client'
1
+ import { afterAll, afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
3
2
  import { WebexError } from '../types'
4
- import { snapshotAction } from './snapshot'
5
3
 
6
- describe('snapshot command', () => {
7
- let consoleSpy: ReturnType<typeof spyOn>
8
- let consoleErrorSpy: ReturnType<typeof spyOn>
9
- let stderrOutput: string
10
- let origStderrWrite: typeof process.stderr.write
4
+ const mockHandleError = mock((err: Error) => {
5
+ throw err
6
+ })
7
+
8
+ mock.module('@/shared/utils/error-handler', () => ({
9
+ handleError: mockHandleError,
10
+ }))
11
11
 
12
- const mockSpaces = [
13
- { id: 'space-1', title: 'General', type: 'group', isLocked: false, lastActivity: '2024-01-15T00:00:00.000Z', created: '2024-01-01T00:00:00.000Z', creatorId: 'person-1' },
14
- ]
12
+ const mockSpaces = [
13
+ { id: 'space-1', title: 'General', type: 'group', isLocked: false, lastActivity: '2024-01-15T00:00:00.000Z', created: '2024-01-01T00:00:00.000Z', creatorId: 'person-1' },
14
+ ]
15
15
 
16
- const mockMessages = [
17
- { id: 'msg-1', roomId: 'space-1', roomType: 'group', text: 'Hello', personId: 'person-1', personEmail: 'alice@example.com', created: '2024-01-15T00:00:00.000Z' },
18
- ]
16
+ const mockMessages = [
17
+ { id: 'msg-1', roomId: 'space-1', roomType: 'group', text: 'Hello', personId: 'person-1', personEmail: 'alice@example.com', created: '2024-01-15T00:00:00.000Z' },
18
+ ]
19
19
 
20
- const mockMembers = [
21
- { id: 'mem-1', roomId: 'space-1', personId: 'person-1', personEmail: 'alice@example.com', personDisplayName: 'Alice', isModerator: true, created: '2024-01-01T00:00:00.000Z' },
22
- ]
20
+ const mockMembers = [
21
+ { id: 'mem-1', roomId: 'space-1', personId: 'person-1', personEmail: 'alice@example.com', personDisplayName: 'Alice', isModerator: true, created: '2024-01-01T00:00:00.000Z' },
22
+ ]
23
+
24
+ const mockListSpaces = mock(() => Promise.resolve(mockSpaces as any))
25
+ const mockListMessages = mock(() => Promise.resolve(mockMessages as any))
26
+ const mockListMemberships = mock(() => Promise.resolve(mockMembers as any))
27
+
28
+ const mockClient = {
29
+ listSpaces: mockListSpaces,
30
+ listMessages: mockListMessages,
31
+ listMemberships: mockListMemberships,
32
+ }
33
+
34
+ const mockLogin = mock(() => Promise.resolve(mockClient))
35
+
36
+ mock.module('../client', () => ({
37
+ WebexClient: class {
38
+ login = mockLogin
39
+ },
40
+ }))
41
+
42
+ import { snapshotAction } from './snapshot'
43
+
44
+ afterAll(() => {
45
+ mock.restore()
46
+ })
47
+
48
+ describe('snapshot command', () => {
49
+ let consoleSpy: ReturnType<typeof spyOn>
23
50
 
24
51
  beforeEach(() => {
52
+ mockListSpaces.mockReset().mockImplementation(() => Promise.resolve(mockSpaces as any))
53
+ mockListMessages.mockReset().mockImplementation(() => Promise.resolve(mockMessages as any))
54
+ mockListMemberships.mockReset().mockImplementation(() => Promise.resolve(mockMembers as any))
55
+ mockLogin.mockReset().mockImplementation(() => Promise.resolve(mockClient))
56
+ mockHandleError.mockReset().mockImplementation((err: Error) => {
57
+ throw err
58
+ })
59
+
25
60
  consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
26
- consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
27
- stderrOutput = ''
28
- origStderrWrite = process.stderr.write
29
- process.stderr.write = ((chunk: string | Uint8Array) => {
30
- stderrOutput += typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk)
31
- return true
32
- }) as typeof process.stderr.write
33
- spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient() as any)
34
- spyOn(WebexClient.prototype, 'listSpaces').mockResolvedValue(mockSpaces as any)
35
- spyOn(WebexClient.prototype, 'listMessages').mockResolvedValue(mockMessages as any)
36
- spyOn(WebexClient.prototype, 'listMemberships').mockResolvedValue(mockMembers as any)
37
61
  })
38
62
 
39
63
  afterEach(() => {
40
- process.stderr.write = origStderrWrite
41
- mock.restore()
64
+ consoleSpy.mockRestore()
42
65
  })
43
66
 
44
67
  test('full snapshot includes spaces, recent_messages, members', async () => {
@@ -78,28 +101,18 @@ describe('snapshot command', () => {
78
101
  })
79
102
 
80
103
  test('not authenticated outputs error', async () => {
81
- spyOn(WebexClient.prototype, 'login').mockRejectedValue(
82
- new WebexError('No Webex credentials found.', 'no_credentials'),
83
- )
104
+ mockLogin.mockImplementation(async () => {
105
+ throw new WebexError('No Webex credentials found.', 'no_credentials')
106
+ })
84
107
 
85
- const originalExit = process.exit
86
- process.exit = mock((_code?: number) => { throw new Error('process.exit called') }) as never
108
+ await expect(snapshotAction({})).rejects.toThrow('No Webex credentials found.')
87
109
 
88
- try {
89
- await snapshotAction({})
90
- } catch {
91
- } finally {
92
- process.exit = originalExit
93
- }
94
-
95
- expect(stderrOutput).toContain('No Webex credentials found')
110
+ expect(mockHandleError).toHaveBeenCalledWith(expect.any(WebexError))
96
111
  })
97
112
 
98
113
  test('passes limit option to listMessages', async () => {
99
- const listMessagesSpy = spyOn(WebexClient.prototype, 'listMessages').mockResolvedValue(mockMessages as any)
100
-
101
114
  await snapshotAction({ limit: 5 })
102
115
 
103
- expect(listMessagesSpy).toHaveBeenCalledWith('space-1', { max: 5 })
116
+ expect(mockListMessages).toHaveBeenCalledWith('space-1', { max: 5 })
104
117
  })
105
118
  })