agent-messenger 2.19.0 → 2.19.1
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.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/package.json +1 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +51 -1
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/package.json +1 -1
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +1 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/kakaotalk/client.test.ts +39 -0
- package/src/platforms/kakaotalk/client.ts +59 -1
- package/src/platforms/webex/commands/auth.test.ts +8 -2
- package/src/platforms/webex/commands/member.test.ts +29 -27
- package/src/platforms/webex/commands/message.test.ts +36 -38
- package/src/platforms/webex/commands/snapshot.test.ts +25 -26
- package/src/platforms/webex/commands/space.test.ts +31 -29
- package/src/platforms/webex/commands/whoami.test.ts +3 -1
- package/src/platforms/webex/credential-manager.test.ts +3 -0
- package/src/platforms/whatsapp/commands/auth.test.ts +14 -20
- package/src/platforms/whatsapp/commands/chat.test.ts +17 -24
- package/src/platforms/whatsapp/commands/message.test.ts +31 -41
|
@@ -17,6 +17,7 @@ describe('auth commands', () => {
|
|
|
17
17
|
let consoleSpy: ReturnType<typeof spyOn>
|
|
18
18
|
let consoleErrorSpy: ReturnType<typeof spyOn>
|
|
19
19
|
let execSpy: ReturnType<typeof spyOn>
|
|
20
|
+
let stderrWriteSpy: ReturnType<typeof spyOn>
|
|
20
21
|
const protoSpies: ReturnType<typeof spyOn>[] = []
|
|
21
22
|
let originalStdinTTY: boolean | undefined
|
|
22
23
|
let originalStdoutTTY: boolean | undefined
|
|
@@ -44,16 +45,23 @@ describe('auth commands', () => {
|
|
|
44
45
|
consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
|
|
45
46
|
consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
|
|
46
47
|
execSpy = spyOn(childProcess, 'exec').mockImplementation((() => {}) as any)
|
|
48
|
+
stderrWriteSpy = spyOn(process.stderr, 'write').mockImplementation(() => true)
|
|
47
49
|
originalStdinTTY = process.stdin.isTTY
|
|
48
50
|
originalStdoutTTY = process.stdout.isTTY
|
|
49
51
|
// Default to interactive TTY for existing tests; non-interactive tests override.
|
|
50
52
|
setTTY(true)
|
|
53
|
+
// Fail loudly instead of running the real 300s network polling loop; tests that
|
|
54
|
+
// exercise the Device Grant flow override this with a resolved value.
|
|
55
|
+
protoSpy(WebexCredentialManager.prototype, 'pollDeviceToken').mockImplementation(() => {
|
|
56
|
+
throw new Error('Unexpected real device polling in test')
|
|
57
|
+
})
|
|
51
58
|
})
|
|
52
59
|
|
|
53
60
|
afterEach(() => {
|
|
54
61
|
consoleSpy.mockRestore()
|
|
55
62
|
consoleErrorSpy.mockRestore()
|
|
56
63
|
execSpy.mockRestore()
|
|
64
|
+
stderrWriteSpy.mockRestore()
|
|
57
65
|
for (const s of protoSpies) s.mockRestore()
|
|
58
66
|
protoSpies.length = 0
|
|
59
67
|
setTTY(originalStdinTTY)
|
|
@@ -447,7 +455,6 @@ describe('auth commands', () => {
|
|
|
447
455
|
protoSpy(WebexCredentialManager.prototype, 'refreshToken').mockResolvedValue(null)
|
|
448
456
|
protoSpy(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
|
|
449
457
|
protoSpy(WebexClient.prototype, 'testAuth').mockRejectedValue(new Error('Network error'))
|
|
450
|
-
const stderrWriteSpy = protoSpy(process.stderr, 'write').mockImplementation(() => true)
|
|
451
458
|
const exitSpy = protoSpy(process, 'exit').mockImplementation((code?: string | number | null) => {
|
|
452
459
|
throw new ProcessExit(code)
|
|
453
460
|
})
|
|
@@ -467,7 +474,6 @@ describe('auth commands', () => {
|
|
|
467
474
|
})
|
|
468
475
|
protoSpy(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
|
|
469
476
|
protoSpy(WebexClient.prototype, 'testAuth').mockRejectedValue(new Error('Network error'))
|
|
470
|
-
const stderrWriteSpy = protoSpy(process.stderr, 'write').mockImplementation(() => true)
|
|
471
477
|
const exitSpy = protoSpy(process, 'exit').mockImplementation((code?: string | number | null) => {
|
|
472
478
|
throw new ProcessExit(code)
|
|
473
479
|
})
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect,
|
|
2
|
-
|
|
3
|
-
import * as errorHandler from '@/shared/utils/error-handler'
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, spyOn, it } from 'bun:test'
|
|
4
2
|
|
|
5
3
|
import { WebexClient } from '../client'
|
|
6
4
|
import { WebexError } from '../types'
|
|
@@ -26,31 +24,39 @@ const mockMembers = [
|
|
|
26
24
|
},
|
|
27
25
|
]
|
|
28
26
|
|
|
29
|
-
const mockListMemberships = mock(() => Promise.resolve(mockMembers))
|
|
30
|
-
|
|
31
27
|
import { listAction } from './member'
|
|
32
28
|
|
|
33
29
|
describe('member commands', () => {
|
|
30
|
+
let mockListMemberships: ReturnType<typeof spyOn>
|
|
31
|
+
let mockLogin: ReturnType<typeof spyOn>
|
|
34
32
|
let consoleSpy: ReturnType<typeof spyOn>
|
|
35
|
-
let
|
|
36
|
-
let
|
|
33
|
+
let consoleErrorSpy: ReturnType<typeof spyOn>
|
|
34
|
+
let processExitSpy: ReturnType<typeof spyOn>
|
|
35
|
+
const protoSpies: ReturnType<typeof spyOn>[] = []
|
|
36
|
+
|
|
37
|
+
function protoSpy(method: keyof WebexClient) {
|
|
38
|
+
const s = spyOn(WebexClient.prototype, method as never)
|
|
39
|
+
protoSpies.push(s)
|
|
40
|
+
return s
|
|
41
|
+
}
|
|
37
42
|
|
|
38
43
|
beforeEach(() => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
throw err
|
|
44
|
+
mockLogin = protoSpy('login').mockImplementation(async function (this: WebexClient) {
|
|
45
|
+
return this
|
|
42
46
|
})
|
|
47
|
+
mockListMemberships = protoSpy('listMemberships').mockResolvedValue(mockMembers)
|
|
43
48
|
|
|
44
|
-
loginSpy = spyOn(WebexClient.prototype, 'login').mockResolvedValue(
|
|
45
|
-
Object.assign(new WebexClient(), { listMemberships: mockListMemberships }),
|
|
46
|
-
)
|
|
47
49
|
consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
|
|
50
|
+
consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
|
|
51
|
+
processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => undefined as never)
|
|
48
52
|
})
|
|
49
53
|
|
|
50
54
|
afterEach(() => {
|
|
51
|
-
loginSpy.mockRestore()
|
|
52
|
-
handleErrorSpy.mockRestore()
|
|
53
55
|
consoleSpy.mockRestore()
|
|
56
|
+
consoleErrorSpy.mockRestore()
|
|
57
|
+
processExitSpy.mockRestore()
|
|
58
|
+
for (const s of protoSpies) s.mockRestore()
|
|
59
|
+
protoSpies.length = 0
|
|
54
60
|
})
|
|
55
61
|
|
|
56
62
|
it('calls listMemberships with spaceId and outputs mapped members', async () => {
|
|
@@ -85,23 +91,19 @@ describe('member commands', () => {
|
|
|
85
91
|
expect(mockListMemberships).toHaveBeenCalledWith('room-1', { max: 25 })
|
|
86
92
|
})
|
|
87
93
|
|
|
88
|
-
it('
|
|
89
|
-
|
|
90
|
-
throw new WebexError('No Webex credentials found.', 'no_credentials')
|
|
91
|
-
})
|
|
94
|
+
it('exits with code 1 when not authenticated', async () => {
|
|
95
|
+
mockLogin.mockRejectedValue(new WebexError('No Webex credentials found.', 'no_credentials'))
|
|
92
96
|
|
|
93
|
-
await
|
|
97
|
+
await listAction('room-1', {})
|
|
94
98
|
|
|
95
|
-
expect(
|
|
99
|
+
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
96
100
|
})
|
|
97
101
|
|
|
98
|
-
it('
|
|
99
|
-
mockListMemberships.
|
|
100
|
-
throw new Error('API failure')
|
|
101
|
-
})
|
|
102
|
+
it('exits with code 1 on API error', async () => {
|
|
103
|
+
mockListMemberships.mockRejectedValue(new Error('API failure'))
|
|
102
104
|
|
|
103
|
-
await
|
|
105
|
+
await listAction('room-1', {})
|
|
104
106
|
|
|
105
|
-
expect(
|
|
107
|
+
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
106
108
|
})
|
|
107
109
|
})
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { afterEach, beforeEach, expect,
|
|
2
|
-
|
|
3
|
-
import * as errorHandler from '@/shared/utils/error-handler'
|
|
1
|
+
import { afterEach, beforeEach, expect, spyOn, it } from 'bun:test'
|
|
4
2
|
|
|
5
3
|
import { WebexClient } from '../client'
|
|
6
4
|
import { WebexError } from '../types'
|
|
@@ -25,47 +23,48 @@ const mockMessage2 = {
|
|
|
25
23
|
created: '2025-01-29T10:01:00.000Z',
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
const mockSendMessage = mock(() => Promise.resolve(mockMessage))
|
|
29
|
-
const mockSendDirectMessage = mock(() => Promise.resolve(mockMessage))
|
|
30
|
-
const mockListMessages = mock(() => Promise.resolve([mockMessage, mockMessage2]))
|
|
31
|
-
const mockGetMessage = mock(() => Promise.resolve(mockMessage))
|
|
32
|
-
const mockDeleteMessage = mock(() => Promise.resolve(undefined))
|
|
33
|
-
const mockEditMessage = mock(() => Promise.resolve({ ...mockMessage, text: 'Updated message' }))
|
|
34
|
-
|
|
35
|
-
const mockClient = {
|
|
36
|
-
sendMessage: mockSendMessage,
|
|
37
|
-
sendDirectMessage: mockSendDirectMessage,
|
|
38
|
-
listMessages: mockListMessages,
|
|
39
|
-
getMessage: mockGetMessage,
|
|
40
|
-
deleteMessage: mockDeleteMessage,
|
|
41
|
-
editMessage: mockEditMessage,
|
|
42
|
-
}
|
|
43
|
-
|
|
44
26
|
import { deleteAction, dmAction, editAction, getAction, listAction, sendAction } from './message'
|
|
45
27
|
|
|
28
|
+
let mockSendMessage: ReturnType<typeof spyOn>
|
|
29
|
+
let mockSendDirectMessage: ReturnType<typeof spyOn>
|
|
30
|
+
let mockListMessages: ReturnType<typeof spyOn>
|
|
31
|
+
let mockGetMessage: ReturnType<typeof spyOn>
|
|
32
|
+
let mockDeleteMessage: ReturnType<typeof spyOn>
|
|
33
|
+
let mockEditMessage: ReturnType<typeof spyOn>
|
|
34
|
+
let mockLogin: ReturnType<typeof spyOn>
|
|
46
35
|
let consoleLogSpy: ReturnType<typeof spyOn>
|
|
47
|
-
let
|
|
48
|
-
let
|
|
36
|
+
let consoleErrorSpy: ReturnType<typeof spyOn>
|
|
37
|
+
let processExitSpy: ReturnType<typeof spyOn>
|
|
38
|
+
const protoSpies: ReturnType<typeof spyOn>[] = []
|
|
39
|
+
|
|
40
|
+
function protoSpy(method: keyof WebexClient) {
|
|
41
|
+
const s = spyOn(WebexClient.prototype, method as never)
|
|
42
|
+
protoSpies.push(s)
|
|
43
|
+
return s
|
|
44
|
+
}
|
|
49
45
|
|
|
50
46
|
beforeEach(() => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
mockListMessages.mockReset().mockImplementation(() => Promise.resolve([mockMessage, mockMessage2]))
|
|
54
|
-
mockGetMessage.mockReset().mockImplementation(() => Promise.resolve(mockMessage))
|
|
55
|
-
mockDeleteMessage.mockReset().mockImplementation(() => Promise.resolve(undefined))
|
|
56
|
-
mockEditMessage.mockReset().mockImplementation(() => Promise.resolve({ ...mockMessage, text: 'Updated message' }))
|
|
57
|
-
handleErrorSpy = spyOn(errorHandler, 'handleError').mockImplementation((err: Error) => {
|
|
58
|
-
throw err
|
|
47
|
+
mockLogin = protoSpy('login').mockImplementation(async function (this: WebexClient) {
|
|
48
|
+
return this
|
|
59
49
|
})
|
|
50
|
+
mockSendMessage = protoSpy('sendMessage').mockResolvedValue(mockMessage)
|
|
51
|
+
mockSendDirectMessage = protoSpy('sendDirectMessage').mockResolvedValue(mockMessage)
|
|
52
|
+
mockListMessages = protoSpy('listMessages').mockResolvedValue([mockMessage, mockMessage2])
|
|
53
|
+
mockGetMessage = protoSpy('getMessage').mockResolvedValue(mockMessage)
|
|
54
|
+
mockDeleteMessage = protoSpy('deleteMessage').mockResolvedValue(undefined)
|
|
55
|
+
mockEditMessage = protoSpy('editMessage').mockResolvedValue({ ...mockMessage, text: 'Updated message' })
|
|
60
56
|
|
|
61
|
-
loginSpy = spyOn(WebexClient.prototype, 'login').mockResolvedValue(Object.assign(new WebexClient(), mockClient))
|
|
62
57
|
consoleLogSpy = spyOn(console, 'log').mockImplementation(() => {})
|
|
58
|
+
consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
|
|
59
|
+
processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => undefined as never)
|
|
63
60
|
})
|
|
64
61
|
|
|
65
62
|
afterEach(() => {
|
|
66
|
-
loginSpy.mockRestore()
|
|
67
|
-
handleErrorSpy.mockRestore()
|
|
68
63
|
consoleLogSpy.mockRestore()
|
|
64
|
+
consoleErrorSpy.mockRestore()
|
|
65
|
+
processExitSpy.mockRestore()
|
|
66
|
+
for (const s of protoSpies) s.mockRestore()
|
|
67
|
+
protoSpies.length = 0
|
|
69
68
|
})
|
|
70
69
|
|
|
71
70
|
it('calls sendMessage with correct args and outputs result', async () => {
|
|
@@ -87,14 +86,13 @@ it('passes markdown option when --markdown flag is set on send', async () => {
|
|
|
87
86
|
expect(mockSendMessage).toHaveBeenCalledWith('space_456', '**bold**', { markdown: true })
|
|
88
87
|
})
|
|
89
88
|
|
|
90
|
-
it('
|
|
91
|
-
|
|
92
|
-
throw new WebexError('No Webex credentials found.', 'no_credentials')
|
|
93
|
-
})
|
|
89
|
+
it('exits with code 1 when not authenticated on send', async () => {
|
|
90
|
+
mockLogin.mockRejectedValue(new WebexError('No Webex credentials found.', 'no_credentials'))
|
|
94
91
|
|
|
95
|
-
await
|
|
92
|
+
await sendAction('space_456', 'Hello', { pretty: false })
|
|
96
93
|
|
|
97
|
-
expect(
|
|
94
|
+
expect(mockSendMessage).not.toHaveBeenCalled()
|
|
95
|
+
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
98
96
|
})
|
|
99
97
|
|
|
100
98
|
it('calls sendDirectMessage with email and text', async () => {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect,
|
|
2
|
-
|
|
3
|
-
import * as errorHandler from '@/shared/utils/error-handler'
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, spyOn, it } from 'bun:test'
|
|
4
2
|
|
|
5
3
|
import { WebexClient } from '../client'
|
|
6
4
|
import { WebexError } from '../types'
|
|
@@ -38,36 +36,39 @@ const mockMyMemberships = [
|
|
|
38
36
|
},
|
|
39
37
|
]
|
|
40
38
|
|
|
41
|
-
const mockListSpaces = mock(() => Promise.resolve(mockSpaces as any))
|
|
42
|
-
const mockListMyMemberships = mock(() => Promise.resolve(mockMyMemberships as any))
|
|
43
|
-
|
|
44
|
-
const mockClient = {
|
|
45
|
-
listSpaces: mockListSpaces,
|
|
46
|
-
listMyMemberships: mockListMyMemberships,
|
|
47
|
-
}
|
|
48
|
-
|
|
49
39
|
import { snapshotAction } from './snapshot'
|
|
50
40
|
|
|
51
41
|
describe('snapshot command', () => {
|
|
42
|
+
let mockLogin: ReturnType<typeof spyOn>
|
|
52
43
|
let consoleSpy: ReturnType<typeof spyOn>
|
|
53
|
-
let
|
|
54
|
-
let
|
|
44
|
+
let consoleErrorSpy: ReturnType<typeof spyOn>
|
|
45
|
+
let processExitSpy: ReturnType<typeof spyOn>
|
|
46
|
+
const protoSpies: ReturnType<typeof spyOn>[] = []
|
|
47
|
+
|
|
48
|
+
function protoSpy(method: keyof WebexClient) {
|
|
49
|
+
const s = spyOn(WebexClient.prototype, method as never)
|
|
50
|
+
protoSpies.push(s)
|
|
51
|
+
return s
|
|
52
|
+
}
|
|
55
53
|
|
|
56
54
|
beforeEach(() => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
handleErrorSpy = spyOn(errorHandler, 'handleError').mockImplementation((err: Error) => {
|
|
60
|
-
throw err
|
|
55
|
+
mockLogin = protoSpy('login').mockImplementation(async function (this: WebexClient) {
|
|
56
|
+
return this
|
|
61
57
|
})
|
|
58
|
+
protoSpy('listSpaces').mockResolvedValue(mockSpaces as any)
|
|
59
|
+
protoSpy('listMyMemberships').mockResolvedValue(mockMyMemberships as any)
|
|
62
60
|
|
|
63
|
-
loginSpy = spyOn(WebexClient.prototype, 'login').mockResolvedValue(Object.assign(new WebexClient(), mockClient))
|
|
64
61
|
consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
|
|
62
|
+
consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
|
|
63
|
+
processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => undefined as never)
|
|
65
64
|
})
|
|
66
65
|
|
|
67
66
|
afterEach(() => {
|
|
68
|
-
loginSpy.mockRestore()
|
|
69
|
-
handleErrorSpy.mockRestore()
|
|
70
67
|
consoleSpy.mockRestore()
|
|
68
|
+
consoleErrorSpy.mockRestore()
|
|
69
|
+
processExitSpy.mockRestore()
|
|
70
|
+
for (const s of protoSpies) s.mockRestore()
|
|
71
|
+
protoSpies.length = 0
|
|
71
72
|
})
|
|
72
73
|
|
|
73
74
|
it('returns spaces with id and title only in brief mode', async () => {
|
|
@@ -103,13 +104,11 @@ describe('snapshot command', () => {
|
|
|
103
104
|
expect(output.spaces[0].id).toBe('space-1')
|
|
104
105
|
})
|
|
105
106
|
|
|
106
|
-
it('
|
|
107
|
-
|
|
108
|
-
throw new WebexError('No Webex credentials found.', 'no_credentials')
|
|
109
|
-
})
|
|
107
|
+
it('exits with code 1 when not authenticated', async () => {
|
|
108
|
+
mockLogin.mockRejectedValue(new WebexError('No Webex credentials found.', 'no_credentials'))
|
|
110
109
|
|
|
111
|
-
await
|
|
110
|
+
await snapshotAction({})
|
|
112
111
|
|
|
113
|
-
expect(
|
|
112
|
+
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
114
113
|
})
|
|
115
114
|
})
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect,
|
|
2
|
-
|
|
3
|
-
import * as errorHandler from '@/shared/utils/error-handler'
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, spyOn, it } from 'bun:test'
|
|
4
2
|
|
|
5
3
|
import { WebexClient } from '../client'
|
|
6
4
|
import { WebexError } from '../types'
|
|
@@ -37,32 +35,40 @@ const mockSpace = {
|
|
|
37
35
|
creatorId: 'person-1',
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
const mockListSpaces = mock(() => Promise.resolve(mockSpaces))
|
|
41
|
-
const mockGetSpace = mock(() => Promise.resolve(mockSpace))
|
|
42
|
-
|
|
43
38
|
import { infoAction, listAction } from './space'
|
|
44
39
|
|
|
40
|
+
let mockListSpaces: ReturnType<typeof spyOn>
|
|
41
|
+
let mockGetSpace: ReturnType<typeof spyOn>
|
|
42
|
+
let mockLogin: ReturnType<typeof spyOn>
|
|
45
43
|
let consoleLogSpy: ReturnType<typeof spyOn>
|
|
46
|
-
let
|
|
47
|
-
let
|
|
44
|
+
let consoleErrorSpy: ReturnType<typeof spyOn>
|
|
45
|
+
let processExitSpy: ReturnType<typeof spyOn>
|
|
46
|
+
const protoSpies: ReturnType<typeof spyOn>[] = []
|
|
47
|
+
|
|
48
|
+
function protoSpy(method: keyof WebexClient) {
|
|
49
|
+
const s = spyOn(WebexClient.prototype, method as never)
|
|
50
|
+
protoSpies.push(s)
|
|
51
|
+
return s
|
|
52
|
+
}
|
|
48
53
|
|
|
49
54
|
beforeEach(() => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
handleErrorSpy = spyOn(errorHandler, 'handleError').mockImplementation((err: Error) => {
|
|
53
|
-
throw err
|
|
55
|
+
mockLogin = protoSpy('login').mockImplementation(async function (this: WebexClient) {
|
|
56
|
+
return this
|
|
54
57
|
})
|
|
58
|
+
mockListSpaces = protoSpy('listSpaces').mockResolvedValue(mockSpaces)
|
|
59
|
+
mockGetSpace = protoSpy('getSpace').mockResolvedValue(mockSpace)
|
|
55
60
|
|
|
56
|
-
loginSpy = spyOn(WebexClient.prototype, 'login').mockResolvedValue(
|
|
57
|
-
Object.assign(new WebexClient(), { listSpaces: mockListSpaces, getSpace: mockGetSpace }),
|
|
58
|
-
)
|
|
59
61
|
consoleLogSpy = spyOn(console, 'log').mockImplementation(() => {})
|
|
62
|
+
consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
|
|
63
|
+
processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => undefined as never)
|
|
60
64
|
})
|
|
61
65
|
|
|
62
66
|
afterEach(() => {
|
|
63
|
-
loginSpy.mockRestore()
|
|
64
|
-
handleErrorSpy.mockRestore()
|
|
65
67
|
consoleLogSpy.mockRestore()
|
|
68
|
+
consoleErrorSpy.mockRestore()
|
|
69
|
+
processExitSpy.mockRestore()
|
|
70
|
+
for (const s of protoSpies) s.mockRestore()
|
|
71
|
+
protoSpies.length = 0
|
|
66
72
|
})
|
|
67
73
|
|
|
68
74
|
describe('listAction', () => {
|
|
@@ -129,15 +135,13 @@ describe('listAction', () => {
|
|
|
129
135
|
)
|
|
130
136
|
})
|
|
131
137
|
|
|
132
|
-
it('
|
|
133
|
-
|
|
134
|
-
throw new WebexError('No Webex credentials found.', 'no_credentials')
|
|
135
|
-
})
|
|
138
|
+
it('exits with code 1 when not authenticated', async () => {
|
|
139
|
+
mockLogin.mockRejectedValue(new WebexError('No Webex credentials found.', 'no_credentials'))
|
|
136
140
|
|
|
137
|
-
await
|
|
141
|
+
await listAction({})
|
|
138
142
|
|
|
139
143
|
expect(mockListSpaces).not.toHaveBeenCalled()
|
|
140
|
-
expect(
|
|
144
|
+
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
141
145
|
})
|
|
142
146
|
})
|
|
143
147
|
|
|
@@ -192,14 +196,12 @@ describe('infoAction', () => {
|
|
|
192
196
|
)
|
|
193
197
|
})
|
|
194
198
|
|
|
195
|
-
it('
|
|
196
|
-
|
|
197
|
-
throw new WebexError('No Webex credentials found.', 'no_credentials')
|
|
198
|
-
})
|
|
199
|
+
it('exits with code 1 when not authenticated', async () => {
|
|
200
|
+
mockLogin.mockRejectedValue(new WebexError('No Webex credentials found.', 'no_credentials'))
|
|
199
201
|
|
|
200
|
-
await
|
|
202
|
+
await infoAction('space-1', {})
|
|
201
203
|
|
|
202
204
|
expect(mockGetSpace).not.toHaveBeenCalled()
|
|
203
|
-
expect(
|
|
205
|
+
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
204
206
|
})
|
|
205
207
|
})
|
|
@@ -23,7 +23,9 @@ let consoleLogSpy: ReturnType<typeof spyOn>
|
|
|
23
23
|
let processExitSpy: ReturnType<typeof spyOn>
|
|
24
24
|
|
|
25
25
|
beforeEach(() => {
|
|
26
|
-
loginSpy = spyOn(WebexClient.prototype, 'login').
|
|
26
|
+
loginSpy = spyOn(WebexClient.prototype, 'login').mockImplementation(async function (this: WebexClient) {
|
|
27
|
+
return this
|
|
28
|
+
})
|
|
27
29
|
testAuthSpy = spyOn(WebexClient.prototype, 'testAuth').mockResolvedValue(mockUser)
|
|
28
30
|
consoleLogSpy = spyOn(console, 'log').mockImplementation(() => {})
|
|
29
31
|
processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => undefined as never)
|
|
@@ -8,6 +8,7 @@ import { WebexCredentialManager } from './credential-manager'
|
|
|
8
8
|
describe('WebexCredentialManager', () => {
|
|
9
9
|
let tempDir: string
|
|
10
10
|
let credManager: WebexCredentialManager
|
|
11
|
+
const realFetch = globalThis.fetch
|
|
11
12
|
|
|
12
13
|
beforeEach(async () => {
|
|
13
14
|
tempDir = await mkdtemp(join(tmpdir(), 'webex-cred-test-'))
|
|
@@ -16,6 +17,8 @@ describe('WebexCredentialManager', () => {
|
|
|
16
17
|
|
|
17
18
|
afterEach(async () => {
|
|
18
19
|
await rm(tempDir, { recursive: true, force: true })
|
|
20
|
+
// Guarantee fetch restoration even if a test throws before its own restore line.
|
|
21
|
+
globalThis.fetch = realFetch
|
|
19
22
|
})
|
|
20
23
|
|
|
21
24
|
it('loadConfig returns null when no file exists', async () => {
|
|
@@ -2,12 +2,6 @@ import { afterEach, beforeEach, describe, expect, mock, spyOn, it } from 'bun:te
|
|
|
2
2
|
|
|
3
3
|
const originalConsoleLog = console.log
|
|
4
4
|
|
|
5
|
-
mock.module('@/shared/utils/error-handler', () => ({
|
|
6
|
-
handleError: (err: Error) => {
|
|
7
|
-
throw err
|
|
8
|
-
},
|
|
9
|
-
}))
|
|
10
|
-
|
|
11
5
|
const mockGetAccount = mock(() => Promise.resolve(null))
|
|
12
6
|
const mockListAccounts = mock(() => Promise.resolve([]))
|
|
13
7
|
const mockSetCurrent = mock(() => Promise.resolve(false))
|
|
@@ -46,6 +40,7 @@ import { authCommand } from './auth'
|
|
|
46
40
|
|
|
47
41
|
describe('auth commands', () => {
|
|
48
42
|
let consoleLogSpy: ReturnType<typeof mock>
|
|
43
|
+
let consoleErrorSpy: ReturnType<typeof spyOn>
|
|
49
44
|
let processExitSpy: ReturnType<typeof spyOn>
|
|
50
45
|
|
|
51
46
|
beforeEach(() => {
|
|
@@ -73,14 +68,14 @@ describe('auth commands', () => {
|
|
|
73
68
|
|
|
74
69
|
consoleLogSpy = mock((..._args: unknown[]) => {})
|
|
75
70
|
console.log = consoleLogSpy
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
})
|
|
71
|
+
consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
|
|
72
|
+
processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => undefined as never)
|
|
79
73
|
processExitSpy.mockClear()
|
|
80
74
|
})
|
|
81
75
|
|
|
82
76
|
afterEach(() => {
|
|
83
77
|
console.log = originalConsoleLog
|
|
78
|
+
consoleErrorSpy.mockRestore()
|
|
84
79
|
processExitSpy.mockRestore()
|
|
85
80
|
})
|
|
86
81
|
|
|
@@ -153,7 +148,7 @@ describe('auth commands', () => {
|
|
|
153
148
|
it('outputs error and exits when account not found', async () => {
|
|
154
149
|
mockSetCurrent.mockImplementation(() => Promise.resolve(false))
|
|
155
150
|
|
|
156
|
-
await
|
|
151
|
+
await authCommand.parseAsync(['use', 'nonexistent'], { from: 'user' })
|
|
157
152
|
|
|
158
153
|
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
159
154
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
@@ -185,7 +180,7 @@ describe('auth commands', () => {
|
|
|
185
180
|
it('outputs error and exits when no account configured', async () => {
|
|
186
181
|
mockGetAccount.mockImplementation(() => Promise.resolve(null))
|
|
187
182
|
|
|
188
|
-
await
|
|
183
|
+
await authCommand.parseAsync(['status'], { from: 'user' })
|
|
189
184
|
|
|
190
185
|
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
191
186
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
@@ -217,9 +212,7 @@ describe('auth commands', () => {
|
|
|
217
212
|
it('outputs error for specific missing account', async () => {
|
|
218
213
|
mockGetAccount.mockImplementation(() => Promise.resolve(null))
|
|
219
214
|
|
|
220
|
-
await
|
|
221
|
-
'process.exit(1)',
|
|
222
|
-
)
|
|
215
|
+
await authCommand.parseAsync(['status', '--account', 'missing-id'], { from: 'user' })
|
|
223
216
|
|
|
224
217
|
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
225
218
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
@@ -241,8 +234,9 @@ describe('auth commands', () => {
|
|
|
241
234
|
)
|
|
242
235
|
mockRemoveAccount.mockImplementation(() => Promise.resolve(true))
|
|
243
236
|
|
|
244
|
-
await
|
|
237
|
+
await authCommand.parseAsync(['logout'], { from: 'user' })
|
|
245
238
|
|
|
239
|
+
expect(processExitSpy).toHaveBeenCalledWith(0)
|
|
246
240
|
expect(mockRemoveAccount).toHaveBeenCalledWith('plus-12025551234')
|
|
247
241
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
248
242
|
expect(output.success).toBe(true)
|
|
@@ -253,7 +247,7 @@ describe('auth commands', () => {
|
|
|
253
247
|
it('outputs error and exits when no account configured', async () => {
|
|
254
248
|
mockGetAccount.mockImplementation(() => Promise.resolve(null))
|
|
255
249
|
|
|
256
|
-
await
|
|
250
|
+
await authCommand.parseAsync(['logout'], { from: 'user' })
|
|
257
251
|
|
|
258
252
|
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
259
253
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
@@ -273,8 +267,9 @@ describe('auth commands', () => {
|
|
|
273
267
|
mockConnect.mockImplementation(() => Promise.reject(new Error('Connection failed')))
|
|
274
268
|
mockRemoveAccount.mockImplementation(() => Promise.resolve(true))
|
|
275
269
|
|
|
276
|
-
await
|
|
270
|
+
await authCommand.parseAsync(['logout'], { from: 'user' })
|
|
277
271
|
|
|
272
|
+
expect(processExitSpy).toHaveBeenCalledWith(0)
|
|
278
273
|
expect(mockRemoveAccount).toHaveBeenCalledWith('plus-12025551234')
|
|
279
274
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
280
275
|
expect(output.success).toBe(true)
|
|
@@ -295,10 +290,9 @@ describe('auth commands', () => {
|
|
|
295
290
|
})
|
|
296
291
|
mockRemoveAccount.mockImplementation(() => Promise.resolve(true))
|
|
297
292
|
|
|
298
|
-
await
|
|
299
|
-
authCommand.parseAsync(['logout', '--account', 'plus-19995551234'], { from: 'user' }),
|
|
300
|
-
).rejects.toThrow('process.exit(0)')
|
|
293
|
+
await authCommand.parseAsync(['logout', '--account', 'plus-19995551234'], { from: 'user' })
|
|
301
294
|
|
|
295
|
+
expect(processExitSpy).toHaveBeenCalledWith(0)
|
|
302
296
|
expect(mockRemoveAccount).toHaveBeenCalledWith('plus-19995551234')
|
|
303
297
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
304
298
|
expect(output.success).toBe(true)
|