digital-tools 2.1.3 → 2.3.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.
- package/CHANGELOG.md +9 -0
- package/README.md +2 -0
- package/dist/client.d.ts +109 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +69 -0
- package/dist/client.js.map +1 -0
- package/dist/define.d.ts +2 -2
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +21 -11
- package/dist/define.js.map +1 -1
- package/dist/function-ref.d.ts +229 -0
- package/dist/function-ref.d.ts.map +1 -0
- package/dist/function-ref.js +28 -0
- package/dist/function-ref.js.map +1 -0
- package/dist/function-sugar.d.ts +57 -0
- package/dist/function-sugar.d.ts.map +1 -0
- package/dist/function-sugar.js +79 -0
- package/dist/function-sugar.js.map +1 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -4
- package/dist/index.js.map +1 -1
- package/dist/providers/analytics/mixpanel.d.ts.map +1 -1
- package/dist/providers/analytics/mixpanel.js +21 -18
- package/dist/providers/analytics/mixpanel.js.map +1 -1
- package/dist/providers/calendar/cal-com.d.ts.map +1 -1
- package/dist/providers/calendar/cal-com.js +10 -10
- package/dist/providers/calendar/cal-com.js.map +1 -1
- package/dist/providers/calendar/google-calendar.d.ts.map +1 -1
- package/dist/providers/calendar/google-calendar.js +4 -4
- package/dist/providers/calendar/google-calendar.js.map +1 -1
- package/dist/providers/crm/hubspot.d.ts.map +1 -1
- package/dist/providers/crm/hubspot.js +107 -85
- package/dist/providers/crm/hubspot.js.map +1 -1
- package/dist/providers/development/github.d.ts.map +1 -1
- package/dist/providers/development/github.js +40 -43
- package/dist/providers/development/github.js.map +1 -1
- package/dist/providers/ecommerce/shopify.d.ts.map +1 -1
- package/dist/providers/ecommerce/shopify.js +79 -62
- package/dist/providers/ecommerce/shopify.js.map +1 -1
- package/dist/providers/email/resend.d.ts.map +1 -1
- package/dist/providers/email/resend.js +20 -16
- package/dist/providers/email/resend.js.map +1 -1
- package/dist/providers/email/sendgrid.d.ts.map +1 -1
- package/dist/providers/email/sendgrid.js +12 -9
- package/dist/providers/email/sendgrid.js.map +1 -1
- package/dist/providers/finance/stripe.d.ts.map +1 -1
- package/dist/providers/finance/stripe.js +44 -42
- package/dist/providers/finance/stripe.js.map +1 -1
- package/dist/providers/forms/typeform.d.ts.map +1 -1
- package/dist/providers/forms/typeform.js +68 -58
- package/dist/providers/forms/typeform.js.map +1 -1
- package/dist/providers/knowledge/notion.d.ts.map +1 -1
- package/dist/providers/knowledge/notion.js +75 -41
- package/dist/providers/knowledge/notion.js.map +1 -1
- package/dist/providers/marketing/mailchimp.d.ts.map +1 -1
- package/dist/providers/marketing/mailchimp.js +74 -61
- package/dist/providers/marketing/mailchimp.js.map +1 -1
- package/dist/providers/media/cloudinary.d.ts.map +1 -1
- package/dist/providers/media/cloudinary.js +30 -28
- package/dist/providers/media/cloudinary.js.map +1 -1
- package/dist/providers/messaging/slack.d.ts.map +1 -1
- package/dist/providers/messaging/slack.js +75 -58
- package/dist/providers/messaging/slack.js.map +1 -1
- package/dist/providers/messaging/twilio-sms.d.ts.map +1 -1
- package/dist/providers/messaging/twilio-sms.js +33 -15
- package/dist/providers/messaging/twilio-sms.js.map +1 -1
- package/dist/providers/project-management/linear.d.ts.map +1 -1
- package/dist/providers/project-management/linear.js +31 -27
- package/dist/providers/project-management/linear.js.map +1 -1
- package/dist/providers/spreadsheet/google-sheets.d.ts.map +1 -1
- package/dist/providers/spreadsheet/google-sheets.js +21 -18
- package/dist/providers/spreadsheet/google-sheets.js.map +1 -1
- package/dist/providers/spreadsheet/xlsx.d.ts.map +1 -1
- package/dist/providers/spreadsheet/xlsx.js +4 -4
- package/dist/providers/spreadsheet/xlsx.js.map +1 -1
- package/dist/providers/storage/index.js +1 -0
- package/dist/providers/storage/index.js.map +1 -1
- package/dist/providers/storage/s3.d.ts.map +1 -1
- package/dist/providers/storage/s3.js +36 -27
- package/dist/providers/storage/s3.js.map +1 -1
- package/dist/providers/support/zendesk.d.ts.map +1 -1
- package/dist/providers/support/zendesk.js +24 -25
- package/dist/providers/support/zendesk.js.map +1 -1
- package/dist/providers/tasks/todoist.d.ts.map +1 -1
- package/dist/providers/tasks/todoist.js +18 -18
- package/dist/providers/tasks/todoist.js.map +1 -1
- package/dist/providers/video-conferencing/google-meet.d.ts.map +1 -1
- package/dist/providers/video-conferencing/google-meet.js +11 -11
- package/dist/providers/video-conferencing/google-meet.js.map +1 -1
- package/dist/providers/video-conferencing/jitsi.js +14 -14
- package/dist/providers/video-conferencing/jitsi.js.map +1 -1
- package/dist/providers/video-conferencing/teams.d.ts.map +1 -1
- package/dist/providers/video-conferencing/teams.js +9 -7
- package/dist/providers/video-conferencing/teams.js.map +1 -1
- package/dist/providers/video-conferencing/zoom.d.ts.map +1 -1
- package/dist/providers/video-conferencing/zoom.js +26 -24
- package/dist/providers/video-conferencing/zoom.js.map +1 -1
- package/dist/tools/data.d.ts.map +1 -1
- package/dist/tools/data.js +5 -12
- package/dist/tools/data.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/system.d.ts +289 -0
- package/dist/tools/system.d.ts.map +1 -0
- package/dist/tools/system.js +752 -0
- package/dist/tools/system.js.map +1 -0
- package/dist/tools/web.d.ts.map +1 -1
- package/dist/tools/web.js +22 -10
- package/dist/tools/web.js.map +1 -1
- package/dist/track-record.d.ts +101 -0
- package/dist/track-record.d.ts.map +1 -0
- package/dist/track-record.js +17 -0
- package/dist/track-record.js.map +1 -0
- package/dist/types.d.ts +210 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/verb-registration.d.ts +122 -0
- package/dist/verb-registration.d.ts.map +1 -0
- package/dist/verb-registration.js +176 -0
- package/dist/verb-registration.js.map +1 -0
- package/dist/worker.d.ts +93 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +315 -0
- package/dist/worker.js.map +1 -0
- package/dist/wrap.d.ts +89 -0
- package/dist/wrap.d.ts.map +1 -0
- package/dist/wrap.js +225 -0
- package/dist/wrap.js.map +1 -0
- package/package.json +31 -14
- package/src/client.ts +136 -0
- package/src/define.ts +30 -24
- package/src/function-ref.ts +264 -0
- package/src/function-sugar.ts +134 -0
- package/src/index.ts +132 -10
- package/src/providers/analytics/mixpanel.ts +19 -18
- package/src/providers/calendar/cal-com.ts +29 -18
- package/src/providers/calendar/google-calendar.ts +20 -14
- package/src/providers/crm/hubspot.ts +225 -99
- package/src/providers/development/github.ts +206 -135
- package/src/providers/ecommerce/shopify.ts +250 -89
- package/src/providers/email/resend.ts +101 -28
- package/src/providers/email/sendgrid.ts +12 -9
- package/src/providers/finance/stripe.ts +128 -49
- package/src/providers/forms/typeform.ts +74 -58
- package/src/providers/knowledge/notion.ts +340 -88
- package/src/providers/marketing/mailchimp.ts +86 -70
- package/src/providers/media/cloudinary.ts +99 -41
- package/src/providers/messaging/slack.ts +283 -85
- package/src/providers/messaging/twilio-sms.ts +35 -15
- package/src/providers/project-management/linear.ts +143 -55
- package/src/providers/spreadsheet/google-sheets.ts +222 -56
- package/src/providers/spreadsheet/xlsx.ts +47 -16
- package/src/providers/storage/s3.ts +119 -47
- package/src/providers/support/zendesk.ts +196 -46
- package/src/providers/tasks/todoist.ts +20 -26
- package/src/providers/video-conferencing/google-meet.ts +17 -20
- package/src/providers/video-conferencing/jitsi.ts +14 -14
- package/src/providers/video-conferencing/teams.ts +14 -13
- package/src/providers/video-conferencing/zoom.ts +54 -49
- package/src/tools/data.ts +6 -16
- package/src/tools/index.ts +1 -0
- package/src/tools/system.ts +887 -0
- package/src/tools/web.ts +22 -10
- package/src/track-record.ts +106 -0
- package/src/types.ts +241 -13
- package/src/verb-registration.ts +197 -0
- package/src/worker.ts +370 -0
- package/src/wrap.ts +260 -0
- package/test/client.test.ts +146 -0
- package/test/communication-tools-extended.test.ts +734 -0
- package/test/data-tools-extended.test.ts +743 -0
- package/test/define-extended.test.ts +819 -0
- package/test/define.test.ts +150 -41
- package/test/entities.test.ts +623 -0
- package/test/extended-entities.test.ts +1228 -0
- package/test/provider-implementations.test.ts +725 -0
- package/test/provider-registry-extended.test.ts +583 -0
- package/test/providers/google-sheets.test.ts +851 -0
- package/test/providers/helpers.ts +554 -0
- package/test/providers/hubspot.test.ts +576 -0
- package/test/providers/slack.test.ts +932 -0
- package/test/providers/stripe.test.ts +701 -0
- package/test/providers.test.ts +578 -0
- package/test/system-tools-extended.test.ts +632 -0
- package/test/system.test.ts +673 -0
- package/test/tools.test.ts +15 -11
- package/test/types.test.ts +402 -0
- package/test/verb-registration.test.ts +395 -0
- package/test/web-tools.test.ts +553 -0
- package/test/worker-extended.test.ts +699 -0
- package/test/worker.test.ts +576 -0
- package/test/wrap.test.ts +366 -0
- package/tsconfig.json +3 -13
- package/vitest.config.ts +37 -0
- package/wrangler.jsonc +9 -0
- package/.turbo/turbo-build.log +0 -4
- package/LICENSE +0 -21
- package/dist/providers/voice/vapi.d.ts +0 -27
- package/dist/providers/voice/vapi.d.ts.map +0 -1
- package/dist/providers/voice/vapi.js +0 -440
- package/dist/providers/voice/vapi.js.map +0 -1
- package/src/define.js +0 -259
- package/src/entities/advertising.js +0 -999
- package/src/entities/ai.js +0 -756
- package/src/entities/analytics.js +0 -1588
- package/src/entities/automation.js +0 -601
- package/src/entities/communication.js +0 -1150
- package/src/entities/crm.js +0 -1386
- package/src/entities/design.js +0 -546
- package/src/entities/development.js +0 -2212
- package/src/entities/document.js +0 -874
- package/src/entities/ecommerce.js +0 -1429
- package/src/entities/experiment.js +0 -1039
- package/src/entities/finance.js +0 -3478
- package/src/entities/forms.js +0 -1892
- package/src/entities/hr.js +0 -661
- package/src/entities/identity.js +0 -997
- package/src/entities/index.js +0 -282
- package/src/entities/infrastructure.js +0 -1153
- package/src/entities/knowledge.js +0 -1438
- package/src/entities/marketing.js +0 -1610
- package/src/entities/media.js +0 -1634
- package/src/entities/notification.js +0 -1199
- package/src/entities/presentation.js +0 -1274
- package/src/entities/productivity.js +0 -1317
- package/src/entities/project-management.js +0 -1136
- package/src/entities/recruiting.js +0 -736
- package/src/entities/shipping.js +0 -509
- package/src/entities/signature.js +0 -1102
- package/src/entities/site.js +0 -222
- package/src/entities/spreadsheet.js +0 -1341
- package/src/entities/storage.js +0 -1198
- package/src/entities/support.js +0 -1166
- package/src/entities/video-conferencing.js +0 -1750
- package/src/entities/video.js +0 -950
- package/src/entities.js +0 -1663
- package/src/index.js +0 -74
- package/src/providers/analytics/index.js +0 -17
- package/src/providers/analytics/mixpanel.js +0 -255
- package/src/providers/calendar/cal-com.js +0 -303
- package/src/providers/calendar/google-calendar.js +0 -335
- package/src/providers/calendar/index.js +0 -20
- package/src/providers/crm/hubspot.js +0 -566
- package/src/providers/crm/index.js +0 -17
- package/src/providers/development/github.js +0 -472
- package/src/providers/development/index.js +0 -17
- package/src/providers/ecommerce/index.js +0 -17
- package/src/providers/ecommerce/shopify.js +0 -378
- package/src/providers/email/index.js +0 -20
- package/src/providers/email/resend.js +0 -258
- package/src/providers/email/sendgrid.js +0 -161
- package/src/providers/finance/index.js +0 -17
- package/src/providers/finance/stripe.js +0 -549
- package/src/providers/forms/index.js +0 -17
- package/src/providers/forms/typeform.js +0 -500
- package/src/providers/index.js +0 -123
- package/src/providers/knowledge/index.js +0 -17
- package/src/providers/knowledge/notion.js +0 -389
- package/src/providers/marketing/index.js +0 -17
- package/src/providers/marketing/mailchimp.js +0 -443
- package/src/providers/media/cloudinary.js +0 -318
- package/src/providers/media/index.js +0 -17
- package/src/providers/messaging/index.js +0 -20
- package/src/providers/messaging/slack.js +0 -393
- package/src/providers/messaging/twilio-sms.js +0 -249
- package/src/providers/project-management/index.js +0 -17
- package/src/providers/project-management/linear.js +0 -575
- package/src/providers/registry.js +0 -86
- package/src/providers/spreadsheet/google-sheets.js +0 -375
- package/src/providers/spreadsheet/index.js +0 -20
- package/src/providers/spreadsheet/xlsx.js +0 -423
- package/src/providers/storage/index.js +0 -24
- package/src/providers/storage/s3.js +0 -419
- package/src/providers/support/index.js +0 -17
- package/src/providers/support/zendesk.js +0 -373
- package/src/providers/tasks/index.js +0 -17
- package/src/providers/tasks/todoist.js +0 -286
- package/src/providers/types.js +0 -9
- package/src/providers/video-conferencing/google-meet.js +0 -286
- package/src/providers/video-conferencing/index.js +0 -31
- package/src/providers/video-conferencing/jitsi.js +0 -254
- package/src/providers/video-conferencing/teams.js +0 -270
- package/src/providers/video-conferencing/zoom.js +0 -332
- package/src/registry.js +0 -128
- package/src/tools/communication.js +0 -184
- package/src/tools/data.js +0 -205
- package/src/tools/index.js +0 -11
- package/src/tools/web.js +0 -137
- package/src/types.js +0 -10
- package/test/define.test.js +0 -306
- package/test/registry.test.js +0 -357
- package/test/tools.test.js +0 -363
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Messaging Provider Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the Slack messaging provider implementation covering:
|
|
5
|
+
* - Provider initialization with access/bot tokens
|
|
6
|
+
* - Message sending, editing, and deletion
|
|
7
|
+
* - Channel operations
|
|
8
|
+
* - Member and workspace management
|
|
9
|
+
* - Reactions and presence
|
|
10
|
+
* - Error handling
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, beforeEach, afterEach, vi, type MockInstance } from 'vitest'
|
|
14
|
+
import { createSlackProvider, slackInfo } from '../../src/providers/messaging/slack.js'
|
|
15
|
+
import type { MessagingProvider } from '../../src/providers/types.js'
|
|
16
|
+
import {
|
|
17
|
+
setupMockFetch,
|
|
18
|
+
resetMockFetch,
|
|
19
|
+
mockJsonResponse,
|
|
20
|
+
mockNetworkError,
|
|
21
|
+
getLastFetchCall,
|
|
22
|
+
getFetchCall,
|
|
23
|
+
parseFetchJsonBody,
|
|
24
|
+
slackMocks,
|
|
25
|
+
} from './helpers.js'
|
|
26
|
+
|
|
27
|
+
describe('Slack Messaging Provider', () => {
|
|
28
|
+
let mockFetch: MockInstance
|
|
29
|
+
let provider: MessagingProvider
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
mockFetch = setupMockFetch()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
resetMockFetch(mockFetch)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// ===========================================================================
|
|
40
|
+
// Provider Initialization Tests
|
|
41
|
+
// ===========================================================================
|
|
42
|
+
|
|
43
|
+
describe('initialization', () => {
|
|
44
|
+
it('should have correct provider info', () => {
|
|
45
|
+
const provider = createSlackProvider({})
|
|
46
|
+
expect(provider.info).toBe(slackInfo)
|
|
47
|
+
expect(provider.info.id).toBe('messaging.slack')
|
|
48
|
+
expect(provider.info.name).toBe('Slack')
|
|
49
|
+
expect(provider.info.category).toBe('messaging')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should require access token for initialization', async () => {
|
|
53
|
+
provider = createSlackProvider({})
|
|
54
|
+
await expect(provider.initialize({})).rejects.toThrow('token is required')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should initialize successfully with access token', async () => {
|
|
58
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
59
|
+
await expect(provider.initialize({ accessToken: 'xoxb-test-token' })).resolves.toBeUndefined()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should initialize successfully with bot token', async () => {
|
|
63
|
+
provider = createSlackProvider({ botToken: 'xoxb-bot-token' })
|
|
64
|
+
await expect(provider.initialize({ botToken: 'xoxb-bot-token' })).resolves.toBeUndefined()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should include requiredConfig in provider info', () => {
|
|
68
|
+
provider = createSlackProvider({})
|
|
69
|
+
expect(provider.info.requiredConfig).toContain('accessToken')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should include optionalConfig in provider info', () => {
|
|
73
|
+
provider = createSlackProvider({})
|
|
74
|
+
expect(provider.info.optionalConfig).toContain('botToken')
|
|
75
|
+
expect(provider.info.optionalConfig).toContain('signingSecret')
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// ===========================================================================
|
|
80
|
+
// Health Check Tests
|
|
81
|
+
// ===========================================================================
|
|
82
|
+
|
|
83
|
+
describe('healthCheck', () => {
|
|
84
|
+
beforeEach(async () => {
|
|
85
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
86
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should return healthy status on successful auth.test', async () => {
|
|
90
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse({ user: 'testbot' })))
|
|
91
|
+
|
|
92
|
+
const health = await provider.healthCheck()
|
|
93
|
+
|
|
94
|
+
expect(health.healthy).toBe(true)
|
|
95
|
+
expect(health.message).toBe('Connected as testbot')
|
|
96
|
+
expect(health.latencyMs).toBeGreaterThanOrEqual(0)
|
|
97
|
+
expect(health.checkedAt).toBeInstanceOf(Date)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('should call auth.test endpoint for health check', async () => {
|
|
101
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse({ user: 'bot' })))
|
|
102
|
+
|
|
103
|
+
await provider.healthCheck()
|
|
104
|
+
|
|
105
|
+
const { url } = getLastFetchCall(mockFetch)
|
|
106
|
+
expect(url).toContain('auth.test')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should return unhealthy status on API error', async () => {
|
|
110
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.errorResponse('invalid_auth')))
|
|
111
|
+
|
|
112
|
+
const health = await provider.healthCheck()
|
|
113
|
+
|
|
114
|
+
expect(health.healthy).toBe(false)
|
|
115
|
+
expect(health.message).toBe('invalid_auth')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('should return unhealthy status on network error', async () => {
|
|
119
|
+
mockFetch.mockRejectedValueOnce(mockNetworkError('Connection timeout'))
|
|
120
|
+
|
|
121
|
+
const health = await provider.healthCheck()
|
|
122
|
+
|
|
123
|
+
expect(health.healthy).toBe(false)
|
|
124
|
+
expect(health.message).toBe('Connection timeout')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// ===========================================================================
|
|
129
|
+
// Message Sending Tests
|
|
130
|
+
// ===========================================================================
|
|
131
|
+
|
|
132
|
+
describe('send', () => {
|
|
133
|
+
beforeEach(async () => {
|
|
134
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
135
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('should send message to channel', async () => {
|
|
139
|
+
mockFetch.mockResolvedValueOnce(
|
|
140
|
+
mockJsonResponse(slackMocks.postMessageResponse('1234567890.123456', 'C123'))
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
const result = await provider.send({
|
|
144
|
+
channel: 'C123',
|
|
145
|
+
text: 'Hello Slack!',
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
expect(result.success).toBe(true)
|
|
149
|
+
expect(result.messageId).toBe('1234567890.123456')
|
|
150
|
+
expect(result.channel).toBe('C123')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should format request body correctly', async () => {
|
|
154
|
+
mockFetch.mockResolvedValueOnce(
|
|
155
|
+
mockJsonResponse(slackMocks.postMessageResponse('ts', 'C123'))
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
await provider.send({
|
|
159
|
+
channel: 'C123',
|
|
160
|
+
text: 'Test message',
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const body = parseFetchJsonBody(mockFetch) as { text: string; channel: string }
|
|
164
|
+
expect(body.text).toBe('Test message')
|
|
165
|
+
expect(body.channel).toBe('C123')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should send DM by opening conversation first', async () => {
|
|
169
|
+
mockFetch
|
|
170
|
+
.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse({ channel: { id: 'D123' } })))
|
|
171
|
+
.mockResolvedValueOnce(mockJsonResponse(slackMocks.postMessageResponse('ts', 'D123')))
|
|
172
|
+
|
|
173
|
+
const result = await provider.send({
|
|
174
|
+
userId: 'U456',
|
|
175
|
+
text: 'Direct message',
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
expect(result.success).toBe(true)
|
|
179
|
+
expect(result.channel).toBe('D123')
|
|
180
|
+
expect(mockFetch).toHaveBeenCalledTimes(2)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should return error when DM open fails', async () => {
|
|
184
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.errorResponse('user_not_found')))
|
|
185
|
+
|
|
186
|
+
const result = await provider.send({
|
|
187
|
+
userId: 'U999',
|
|
188
|
+
text: 'Message',
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
expect(result.success).toBe(false)
|
|
192
|
+
expect(result.error?.code).toBe('user_not_found')
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('should return error when neither channel nor userId provided', async () => {
|
|
196
|
+
const result = await provider.send({
|
|
197
|
+
text: 'No target',
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
expect(result.success).toBe(false)
|
|
201
|
+
expect(result.error?.code).toBe('MISSING_TARGET')
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('should include thread_ts for threaded replies', async () => {
|
|
205
|
+
mockFetch.mockResolvedValueOnce(
|
|
206
|
+
mockJsonResponse(slackMocks.postMessageResponse('ts', 'C123'))
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
await provider.send({
|
|
210
|
+
channel: 'C123',
|
|
211
|
+
text: 'Reply',
|
|
212
|
+
threadId: '1234567890.000000',
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const body = parseFetchJsonBody(mockFetch) as { thread_ts: string }
|
|
216
|
+
expect(body.thread_ts).toBe('1234567890.000000')
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('should include blocks when provided', async () => {
|
|
220
|
+
mockFetch.mockResolvedValueOnce(
|
|
221
|
+
mockJsonResponse(slackMocks.postMessageResponse('ts', 'C123'))
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
const blocks = [{ type: 'section', text: { type: 'mrkdwn', text: '*Bold*' } }]
|
|
225
|
+
await provider.send({
|
|
226
|
+
channel: 'C123',
|
|
227
|
+
text: 'Fallback',
|
|
228
|
+
blocks,
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
const body = parseFetchJsonBody(mockFetch) as { blocks: unknown[] }
|
|
232
|
+
expect(body.blocks).toEqual(blocks)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('should include metadata when provided', async () => {
|
|
236
|
+
mockFetch.mockResolvedValueOnce(
|
|
237
|
+
mockJsonResponse(slackMocks.postMessageResponse('ts', 'C123'))
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
await provider.send({
|
|
241
|
+
channel: 'C123',
|
|
242
|
+
text: 'Message',
|
|
243
|
+
metadata: { key: 'value' },
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
const body = parseFetchJsonBody(mockFetch) as { metadata: unknown }
|
|
247
|
+
expect(body.metadata).toBeDefined()
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('should handle API error response', async () => {
|
|
251
|
+
mockFetch.mockResolvedValueOnce(
|
|
252
|
+
mockJsonResponse(slackMocks.errorResponse('channel_not_found'))
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
const result = await provider.send({
|
|
256
|
+
channel: 'C999',
|
|
257
|
+
text: 'Message',
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
expect(result.success).toBe(false)
|
|
261
|
+
expect(result.error?.code).toBe('channel_not_found')
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// ===========================================================================
|
|
266
|
+
// Message Editing Tests
|
|
267
|
+
// ===========================================================================
|
|
268
|
+
|
|
269
|
+
describe('edit', () => {
|
|
270
|
+
beforeEach(async () => {
|
|
271
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
272
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('should edit existing message', async () => {
|
|
276
|
+
mockFetch.mockResolvedValueOnce(
|
|
277
|
+
mockJsonResponse(slackMocks.postMessageResponse('1234567890.123456', 'C123'))
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
const result = await provider.edit!('1234567890.123456', 'Updated text')
|
|
281
|
+
|
|
282
|
+
expect(result.success).toBe(true)
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('should call chat.update endpoint', async () => {
|
|
286
|
+
mockFetch.mockResolvedValueOnce(
|
|
287
|
+
mockJsonResponse(slackMocks.postMessageResponse('ts', 'C123'))
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
await provider.edit!('ts', 'New text')
|
|
291
|
+
|
|
292
|
+
const { url } = getLastFetchCall(mockFetch)
|
|
293
|
+
expect(url).toContain('chat.update')
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('should include blocks when editing', async () => {
|
|
297
|
+
mockFetch.mockResolvedValueOnce(
|
|
298
|
+
mockJsonResponse(slackMocks.postMessageResponse('ts', 'C123'))
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
const blocks = [{ type: 'section', text: { type: 'plain_text', text: 'Updated' } }]
|
|
302
|
+
await provider.edit!('ts', 'Text', blocks)
|
|
303
|
+
|
|
304
|
+
const body = parseFetchJsonBody(mockFetch) as { blocks: unknown[] }
|
|
305
|
+
expect(body.blocks).toEqual(blocks)
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
// ===========================================================================
|
|
310
|
+
// Message Deletion Tests
|
|
311
|
+
// ===========================================================================
|
|
312
|
+
|
|
313
|
+
describe('delete', () => {
|
|
314
|
+
beforeEach(async () => {
|
|
315
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
316
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('should delete message', async () => {
|
|
320
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse()))
|
|
321
|
+
|
|
322
|
+
const result = await provider.delete!('1234567890.123456', 'C123')
|
|
323
|
+
|
|
324
|
+
expect(result).toBe(true)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it('should call chat.delete endpoint', async () => {
|
|
328
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse()))
|
|
329
|
+
|
|
330
|
+
await provider.delete!('ts', 'C123')
|
|
331
|
+
|
|
332
|
+
const { url } = getLastFetchCall(mockFetch)
|
|
333
|
+
expect(url).toContain('chat.delete')
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('should return false on deletion failure', async () => {
|
|
337
|
+
mockFetch.mockResolvedValueOnce(
|
|
338
|
+
mockJsonResponse(slackMocks.errorResponse('message_not_found'))
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
const result = await provider.delete!('ts', 'C123')
|
|
342
|
+
|
|
343
|
+
expect(result).toBe(false)
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
// ===========================================================================
|
|
348
|
+
// Reaction Tests
|
|
349
|
+
// ===========================================================================
|
|
350
|
+
|
|
351
|
+
describe('react', () => {
|
|
352
|
+
beforeEach(async () => {
|
|
353
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
354
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('should add reaction to message', async () => {
|
|
358
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse()))
|
|
359
|
+
|
|
360
|
+
const result = await provider.react!('ts', 'C123', 'thumbsup')
|
|
361
|
+
|
|
362
|
+
expect(result).toBe(true)
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('should strip colons from emoji name', async () => {
|
|
366
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse()))
|
|
367
|
+
|
|
368
|
+
await provider.react!('ts', 'C123', ':thumbsup:')
|
|
369
|
+
|
|
370
|
+
const body = parseFetchJsonBody(mockFetch) as { name: string }
|
|
371
|
+
expect(body.name).toBe('thumbsup')
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('should call reactions.add endpoint', async () => {
|
|
375
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse()))
|
|
376
|
+
|
|
377
|
+
await provider.react!('ts', 'C123', 'emoji')
|
|
378
|
+
|
|
379
|
+
const { url } = getLastFetchCall(mockFetch)
|
|
380
|
+
expect(url).toContain('reactions.add')
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
describe('unreact', () => {
|
|
385
|
+
beforeEach(async () => {
|
|
386
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
387
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('should remove reaction from message', async () => {
|
|
391
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse()))
|
|
392
|
+
|
|
393
|
+
const result = await provider.unreact!('ts', 'C123', 'thumbsup')
|
|
394
|
+
|
|
395
|
+
expect(result).toBe(true)
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it('should call reactions.remove endpoint', async () => {
|
|
399
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse()))
|
|
400
|
+
|
|
401
|
+
await provider.unreact!('ts', 'C123', 'emoji')
|
|
402
|
+
|
|
403
|
+
const { url } = getLastFetchCall(mockFetch)
|
|
404
|
+
expect(url).toContain('reactions.remove')
|
|
405
|
+
})
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
// ===========================================================================
|
|
409
|
+
// Message Retrieval Tests
|
|
410
|
+
// ===========================================================================
|
|
411
|
+
|
|
412
|
+
describe('getMessage', () => {
|
|
413
|
+
beforeEach(async () => {
|
|
414
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
415
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
it('should retrieve message by timestamp', async () => {
|
|
419
|
+
mockFetch.mockResolvedValueOnce(
|
|
420
|
+
mockJsonResponse(
|
|
421
|
+
slackMocks.conversationsHistoryResponse([slackMocks.message('ts', 'C123', 'Hello')])
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
const message = await provider.getMessage!('ts', 'C123')
|
|
426
|
+
|
|
427
|
+
expect(message).not.toBeNull()
|
|
428
|
+
expect(message?.id).toBe('ts')
|
|
429
|
+
expect(message?.text).toBe('Hello')
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
it('should return null when message not found', async () => {
|
|
433
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.conversationsHistoryResponse([])))
|
|
434
|
+
|
|
435
|
+
const message = await provider.getMessage!('ts', 'C123')
|
|
436
|
+
|
|
437
|
+
expect(message).toBeNull()
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
describe('listMessages', () => {
|
|
442
|
+
beforeEach(async () => {
|
|
443
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
444
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
it('should return paginated messages', async () => {
|
|
448
|
+
mockFetch.mockResolvedValueOnce(
|
|
449
|
+
mockJsonResponse(
|
|
450
|
+
slackMocks.conversationsHistoryResponse(
|
|
451
|
+
[
|
|
452
|
+
slackMocks.message('ts1', 'C123', 'Message 1'),
|
|
453
|
+
slackMocks.message('ts2', 'C123', 'Message 2'),
|
|
454
|
+
],
|
|
455
|
+
true,
|
|
456
|
+
'cursor123'
|
|
457
|
+
)
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
const result = await provider.listMessages!('C123')
|
|
462
|
+
|
|
463
|
+
expect(result.items).toHaveLength(2)
|
|
464
|
+
expect(result.hasMore).toBe(true)
|
|
465
|
+
expect(result.nextCursor).toBe('cursor123')
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it('should apply pagination options', async () => {
|
|
469
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.conversationsHistoryResponse([])))
|
|
470
|
+
|
|
471
|
+
await provider.listMessages!('C123', { limit: 50, cursor: 'cursor' })
|
|
472
|
+
|
|
473
|
+
const body = parseFetchJsonBody(mockFetch) as { limit: number; cursor: string }
|
|
474
|
+
expect(body.limit).toBe(50)
|
|
475
|
+
expect(body.cursor).toBe('cursor')
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
it('should apply date filters', async () => {
|
|
479
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.conversationsHistoryResponse([])))
|
|
480
|
+
|
|
481
|
+
const since = new Date('2024-01-01')
|
|
482
|
+
const until = new Date('2024-01-31')
|
|
483
|
+
await provider.listMessages!('C123', { since, until })
|
|
484
|
+
|
|
485
|
+
const body = parseFetchJsonBody(mockFetch) as { oldest: string; latest: string }
|
|
486
|
+
expect(body.oldest).toBeDefined()
|
|
487
|
+
expect(body.latest).toBeDefined()
|
|
488
|
+
})
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
describe('searchMessages', () => {
|
|
492
|
+
beforeEach(async () => {
|
|
493
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
494
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
it('should search messages by query', async () => {
|
|
498
|
+
mockFetch.mockResolvedValueOnce(
|
|
499
|
+
mockJsonResponse({
|
|
500
|
+
ok: true,
|
|
501
|
+
messages: {
|
|
502
|
+
matches: [
|
|
503
|
+
slackMocks.message('ts', 'C123', 'Found message', { channel: { id: 'C123' } }),
|
|
504
|
+
],
|
|
505
|
+
paging: { pages: 1, page: 1 },
|
|
506
|
+
total: 1,
|
|
507
|
+
},
|
|
508
|
+
})
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
const result = await provider.searchMessages!('query')
|
|
512
|
+
|
|
513
|
+
expect(result.items).toHaveLength(1)
|
|
514
|
+
expect(result.total).toBe(1)
|
|
515
|
+
})
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
// ===========================================================================
|
|
519
|
+
// Channel Operations Tests
|
|
520
|
+
// ===========================================================================
|
|
521
|
+
|
|
522
|
+
describe('listChannels', () => {
|
|
523
|
+
beforeEach(async () => {
|
|
524
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
525
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
it('should return list of channels', async () => {
|
|
529
|
+
mockFetch.mockResolvedValueOnce(
|
|
530
|
+
mockJsonResponse(
|
|
531
|
+
slackMocks.conversationsListResponse([
|
|
532
|
+
slackMocks.channel('C1', 'general'),
|
|
533
|
+
slackMocks.channel('C2', 'random'),
|
|
534
|
+
])
|
|
535
|
+
)
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
const result = await provider.listChannels!()
|
|
539
|
+
|
|
540
|
+
expect(result.items).toHaveLength(2)
|
|
541
|
+
expect(result.items[0].name).toBe('general')
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
it('should filter by channel types', async () => {
|
|
545
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.conversationsListResponse([])))
|
|
546
|
+
|
|
547
|
+
await provider.listChannels!({ types: ['private'] })
|
|
548
|
+
|
|
549
|
+
const body = parseFetchJsonBody(mockFetch) as { types: string }
|
|
550
|
+
expect(body.types).toContain('private_channel')
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
it('should exclude archived channels by default', async () => {
|
|
554
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.conversationsListResponse([])))
|
|
555
|
+
|
|
556
|
+
await provider.listChannels!()
|
|
557
|
+
|
|
558
|
+
const body = parseFetchJsonBody(mockFetch) as { exclude_archived: boolean }
|
|
559
|
+
expect(body.exclude_archived).toBe(true)
|
|
560
|
+
})
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
describe('getChannel', () => {
|
|
564
|
+
beforeEach(async () => {
|
|
565
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
566
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
it('should retrieve channel by ID', async () => {
|
|
570
|
+
mockFetch.mockResolvedValueOnce(
|
|
571
|
+
mockJsonResponse({
|
|
572
|
+
ok: true,
|
|
573
|
+
channel: slackMocks.channel('C123', 'general'),
|
|
574
|
+
})
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
const channel = await provider.getChannel!('C123')
|
|
578
|
+
|
|
579
|
+
expect(channel).not.toBeNull()
|
|
580
|
+
expect(channel?.id).toBe('C123')
|
|
581
|
+
expect(channel?.name).toBe('general')
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
it('should return null for non-existent channel', async () => {
|
|
585
|
+
mockFetch.mockResolvedValueOnce(
|
|
586
|
+
mockJsonResponse(slackMocks.errorResponse('channel_not_found'))
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
const channel = await provider.getChannel!('C999')
|
|
590
|
+
|
|
591
|
+
expect(channel).toBeNull()
|
|
592
|
+
})
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
describe('createChannel', () => {
|
|
596
|
+
beforeEach(async () => {
|
|
597
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
598
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
it('should create new channel', async () => {
|
|
602
|
+
mockFetch.mockResolvedValueOnce(
|
|
603
|
+
mockJsonResponse({
|
|
604
|
+
ok: true,
|
|
605
|
+
channel: slackMocks.channel('C456', 'new-channel'),
|
|
606
|
+
})
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
const channel = await provider.createChannel!('new-channel')
|
|
610
|
+
|
|
611
|
+
expect(channel.id).toBe('C456')
|
|
612
|
+
expect(channel.name).toBe('new-channel')
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
it('should create private channel when specified', async () => {
|
|
616
|
+
mockFetch.mockResolvedValueOnce(
|
|
617
|
+
mockJsonResponse({
|
|
618
|
+
ok: true,
|
|
619
|
+
channel: slackMocks.channel('C456', 'private', { is_private: true }),
|
|
620
|
+
})
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
await provider.createChannel!('private', { isPrivate: true })
|
|
624
|
+
|
|
625
|
+
const body = parseFetchJsonBody(mockFetch) as { is_private: boolean }
|
|
626
|
+
expect(body.is_private).toBe(true)
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
it('should set topic when provided', async () => {
|
|
630
|
+
mockFetch
|
|
631
|
+
.mockResolvedValueOnce(
|
|
632
|
+
mockJsonResponse({
|
|
633
|
+
ok: true,
|
|
634
|
+
channel: slackMocks.channel('C456', 'channel'),
|
|
635
|
+
})
|
|
636
|
+
)
|
|
637
|
+
.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse()))
|
|
638
|
+
|
|
639
|
+
await provider.createChannel!('channel', { topic: 'Channel topic' })
|
|
640
|
+
|
|
641
|
+
expect(mockFetch).toHaveBeenCalledTimes(2)
|
|
642
|
+
const { url } = getFetchCall(mockFetch, 1)
|
|
643
|
+
expect(url).toContain('conversations.setTopic')
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
it('should throw on creation failure', async () => {
|
|
647
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.errorResponse('name_taken')))
|
|
648
|
+
|
|
649
|
+
await expect(provider.createChannel!('existing')).rejects.toThrow('name_taken')
|
|
650
|
+
})
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
describe('archiveChannel', () => {
|
|
654
|
+
beforeEach(async () => {
|
|
655
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
656
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
it('should archive channel', async () => {
|
|
660
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse()))
|
|
661
|
+
|
|
662
|
+
const result = await provider.archiveChannel!('C123')
|
|
663
|
+
|
|
664
|
+
expect(result).toBe(true)
|
|
665
|
+
})
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
describe('joinChannel', () => {
|
|
669
|
+
beforeEach(async () => {
|
|
670
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
671
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
it('should join channel', async () => {
|
|
675
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse()))
|
|
676
|
+
|
|
677
|
+
const result = await provider.joinChannel!('C123')
|
|
678
|
+
|
|
679
|
+
expect(result).toBe(true)
|
|
680
|
+
})
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
describe('leaveChannel', () => {
|
|
684
|
+
beforeEach(async () => {
|
|
685
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
686
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
it('should leave channel', async () => {
|
|
690
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse()))
|
|
691
|
+
|
|
692
|
+
const result = await provider.leaveChannel!('C123')
|
|
693
|
+
|
|
694
|
+
expect(result).toBe(true)
|
|
695
|
+
})
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
// ===========================================================================
|
|
699
|
+
// Member Operations Tests
|
|
700
|
+
// ===========================================================================
|
|
701
|
+
|
|
702
|
+
describe('listMembers', () => {
|
|
703
|
+
beforeEach(async () => {
|
|
704
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
705
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
it('should list workspace members', async () => {
|
|
709
|
+
mockFetch.mockResolvedValueOnce(
|
|
710
|
+
mockJsonResponse(
|
|
711
|
+
slackMocks.usersListResponse([
|
|
712
|
+
slackMocks.user('U1', 'user1'),
|
|
713
|
+
slackMocks.user('U2', 'user2'),
|
|
714
|
+
])
|
|
715
|
+
)
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
const result = await provider.listMembers!()
|
|
719
|
+
|
|
720
|
+
expect(result.items).toHaveLength(2)
|
|
721
|
+
expect(result.items[0].username).toBe('user1')
|
|
722
|
+
})
|
|
723
|
+
|
|
724
|
+
it('should list channel members when channel specified', async () => {
|
|
725
|
+
mockFetch
|
|
726
|
+
.mockResolvedValueOnce(
|
|
727
|
+
mockJsonResponse({
|
|
728
|
+
ok: true,
|
|
729
|
+
members: ['U1', 'U2'],
|
|
730
|
+
response_metadata: {},
|
|
731
|
+
})
|
|
732
|
+
)
|
|
733
|
+
.mockResolvedValueOnce(
|
|
734
|
+
mockJsonResponse({
|
|
735
|
+
ok: true,
|
|
736
|
+
user: slackMocks.user('U1', 'user1'),
|
|
737
|
+
})
|
|
738
|
+
)
|
|
739
|
+
.mockResolvedValueOnce(
|
|
740
|
+
mockJsonResponse({
|
|
741
|
+
ok: true,
|
|
742
|
+
user: slackMocks.user('U2', 'user2'),
|
|
743
|
+
})
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
const result = await provider.listMembers!({ channel: 'C123' })
|
|
747
|
+
|
|
748
|
+
expect(result.items).toHaveLength(2)
|
|
749
|
+
})
|
|
750
|
+
|
|
751
|
+
it('should filter out deleted users', async () => {
|
|
752
|
+
mockFetch.mockResolvedValueOnce(
|
|
753
|
+
mockJsonResponse(
|
|
754
|
+
slackMocks.usersListResponse([
|
|
755
|
+
slackMocks.user('U1', 'active'),
|
|
756
|
+
slackMocks.user('U2', 'deleted', { deleted: true }),
|
|
757
|
+
])
|
|
758
|
+
)
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
const result = await provider.listMembers!()
|
|
762
|
+
|
|
763
|
+
expect(result.items).toHaveLength(1)
|
|
764
|
+
expect(result.items[0].username).toBe('active')
|
|
765
|
+
})
|
|
766
|
+
})
|
|
767
|
+
|
|
768
|
+
describe('getMember', () => {
|
|
769
|
+
beforeEach(async () => {
|
|
770
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
771
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
772
|
+
})
|
|
773
|
+
|
|
774
|
+
it('should retrieve member by ID', async () => {
|
|
775
|
+
mockFetch.mockResolvedValueOnce(
|
|
776
|
+
mockJsonResponse({
|
|
777
|
+
ok: true,
|
|
778
|
+
user: slackMocks.user('U123', 'testuser'),
|
|
779
|
+
})
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
const member = await provider.getMember!('U123')
|
|
783
|
+
|
|
784
|
+
expect(member).not.toBeNull()
|
|
785
|
+
expect(member?.id).toBe('U123')
|
|
786
|
+
expect(member?.username).toBe('testuser')
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
it('should return null for non-existent user', async () => {
|
|
790
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.errorResponse('user_not_found')))
|
|
791
|
+
|
|
792
|
+
const member = await provider.getMember!('U999')
|
|
793
|
+
|
|
794
|
+
expect(member).toBeNull()
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
it('should map user fields correctly', async () => {
|
|
798
|
+
mockFetch.mockResolvedValueOnce(
|
|
799
|
+
mockJsonResponse({
|
|
800
|
+
ok: true,
|
|
801
|
+
user: slackMocks.user('U123', 'dev', {
|
|
802
|
+
is_admin: true,
|
|
803
|
+
is_bot: false,
|
|
804
|
+
tz: 'America/Los_Angeles',
|
|
805
|
+
}),
|
|
806
|
+
})
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
const member = await provider.getMember!('U123')
|
|
810
|
+
|
|
811
|
+
expect(member?.isAdmin).toBe(true)
|
|
812
|
+
expect(member?.isBot).toBe(false)
|
|
813
|
+
expect(member?.timezone).toBe('America/Los_Angeles')
|
|
814
|
+
})
|
|
815
|
+
})
|
|
816
|
+
|
|
817
|
+
describe('getPresence', () => {
|
|
818
|
+
beforeEach(async () => {
|
|
819
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
820
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
821
|
+
})
|
|
822
|
+
|
|
823
|
+
it('should return online presence', async () => {
|
|
824
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse({ ok: true, presence: 'active' }))
|
|
825
|
+
|
|
826
|
+
const presence = await provider.getPresence!('U123')
|
|
827
|
+
|
|
828
|
+
expect(presence.userId).toBe('U123')
|
|
829
|
+
expect(presence.presence).toBe('online')
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
it('should return away presence', async () => {
|
|
833
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse({ ok: true, presence: 'away' }))
|
|
834
|
+
|
|
835
|
+
const presence = await provider.getPresence!('U123')
|
|
836
|
+
|
|
837
|
+
expect(presence.presence).toBe('away')
|
|
838
|
+
})
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
// ===========================================================================
|
|
842
|
+
// Workspace Tests
|
|
843
|
+
// ===========================================================================
|
|
844
|
+
|
|
845
|
+
describe('getWorkspace', () => {
|
|
846
|
+
beforeEach(async () => {
|
|
847
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
848
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
849
|
+
})
|
|
850
|
+
|
|
851
|
+
it('should retrieve workspace info', async () => {
|
|
852
|
+
mockFetch.mockResolvedValueOnce(
|
|
853
|
+
mockJsonResponse({
|
|
854
|
+
ok: true,
|
|
855
|
+
team: slackMocks.team('T123', 'Test Workspace', 'testworkspace'),
|
|
856
|
+
})
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
const workspace = await provider.getWorkspace!()
|
|
860
|
+
|
|
861
|
+
expect(workspace.id).toBe('T123')
|
|
862
|
+
expect(workspace.name).toBe('Test Workspace')
|
|
863
|
+
expect(workspace.domain).toBe('testworkspace')
|
|
864
|
+
})
|
|
865
|
+
|
|
866
|
+
it('should throw on API failure', async () => {
|
|
867
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.errorResponse('team_not_found')))
|
|
868
|
+
|
|
869
|
+
await expect(provider.getWorkspace!()).rejects.toThrow('team_not_found')
|
|
870
|
+
})
|
|
871
|
+
})
|
|
872
|
+
|
|
873
|
+
// ===========================================================================
|
|
874
|
+
// API Request Formatting Tests
|
|
875
|
+
// ===========================================================================
|
|
876
|
+
|
|
877
|
+
describe('API request formatting', () => {
|
|
878
|
+
beforeEach(async () => {
|
|
879
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
880
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
it('should use POST method for all API calls', async () => {
|
|
884
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse({ user: 'bot' })))
|
|
885
|
+
|
|
886
|
+
await provider.healthCheck()
|
|
887
|
+
|
|
888
|
+
const { options } = getLastFetchCall(mockFetch)
|
|
889
|
+
expect(options?.method).toBe('POST')
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
it('should use JSON content type', async () => {
|
|
893
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.postMessageResponse('ts', 'C')))
|
|
894
|
+
|
|
895
|
+
await provider.send({ channel: 'C', text: 'Test' })
|
|
896
|
+
|
|
897
|
+
const { options } = getLastFetchCall(mockFetch)
|
|
898
|
+
expect(options?.headers).toHaveProperty('Content-Type', 'application/json; charset=utf-8')
|
|
899
|
+
})
|
|
900
|
+
|
|
901
|
+
it('should include Bearer token authorization', async () => {
|
|
902
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.postMessageResponse('ts', 'C')))
|
|
903
|
+
|
|
904
|
+
await provider.send({ channel: 'C', text: 'Test' })
|
|
905
|
+
|
|
906
|
+
const { options } = getLastFetchCall(mockFetch)
|
|
907
|
+
expect(options?.headers).toHaveProperty('Authorization', 'Bearer xoxb-test-token')
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
it('should use correct base URL', async () => {
|
|
911
|
+
mockFetch.mockResolvedValueOnce(mockJsonResponse(slackMocks.okResponse({ user: 'bot' })))
|
|
912
|
+
|
|
913
|
+
await provider.healthCheck()
|
|
914
|
+
|
|
915
|
+
const { url } = getLastFetchCall(mockFetch)
|
|
916
|
+
expect(url).toContain('https://slack.com/api/')
|
|
917
|
+
})
|
|
918
|
+
})
|
|
919
|
+
|
|
920
|
+
// ===========================================================================
|
|
921
|
+
// Dispose Tests
|
|
922
|
+
// ===========================================================================
|
|
923
|
+
|
|
924
|
+
describe('dispose', () => {
|
|
925
|
+
it('should dispose without error', async () => {
|
|
926
|
+
provider = createSlackProvider({ accessToken: 'xoxb-test-token' })
|
|
927
|
+
await provider.initialize({ accessToken: 'xoxb-test-token' })
|
|
928
|
+
|
|
929
|
+
await expect(provider.dispose()).resolves.toBeUndefined()
|
|
930
|
+
})
|
|
931
|
+
})
|
|
932
|
+
})
|