Skip to content

Commit cd574c4

Browse files
fix(slack): dedupe migrated bots per workflow
1 parent 7971ade commit cd574c4

2 files changed

Lines changed: 421 additions & 136 deletions

File tree

packages/db/scripts/migrate-slack-custom-bots.test.ts

Lines changed: 125 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
import { describe, expect, it } from 'vitest'
66
import {
7+
buildSlackBotDescription,
78
buildSlackBotDisplayName,
89
buildSlackCustomBotSecretBlob,
910
type EnvironmentLookup,
1011
extractSlackBotSources,
12+
groupSlackSourcesByWorkflowCredentials,
1113
planLegacySlackTriggerLink,
1214
resolveSlackSourceSecrets,
1315
type SlackBotSource,
@@ -195,46 +197,150 @@ describe('extractSlackBotSources', () => {
195197
})
196198

197199
describe('buildSlackBotDisplayName', () => {
198-
it('uses workflow, block, and optional tool names', () => {
199-
expect(buildSlackBotDisplayName(source(), new Set())).toBe('Escalations — Notify Support')
200-
expect(
201-
buildSlackBotDisplayName(
202-
source({ kind: 'embedded_tool', toolTitle: 'Send to incidents' }),
203-
new Set()
204-
)
205-
).toBe('Escalations — Notify Support — Send to incidents')
200+
it('uses only the workflow name', () => {
201+
expect(buildSlackBotDisplayName('Escalations', new Set())).toBe('Escalations')
206202
})
207203

208204
it('allocates a normalized suffix while keeping names within 255 characters', () => {
209-
const longSource = source({ workflowName: 'W'.repeat(250), blockName: 'Block' })
210-
const first = buildSlackBotDisplayName(longSource, new Set())
211-
const second = buildSlackBotDisplayName(longSource, new Set([first.toLowerCase()]))
205+
const workflowName = 'W'.repeat(300)
206+
const first = buildSlackBotDisplayName(workflowName, new Set())
207+
const second = buildSlackBotDisplayName(workflowName, new Set([first.toLowerCase()]))
212208

213209
expect(first).toHaveLength(255)
214210
expect(second).toHaveLength(255)
215211
expect(second.endsWith(' (2)')).toBe(true)
216212
})
217213
})
218214

215+
describe('buildSlackBotDescription', () => {
216+
it('identifies blocks without migration terminology', () => {
217+
expect(
218+
buildSlackBotDescription('Escalations', [
219+
source(),
220+
source({
221+
sourceId: 'workflow-1:block-2:tools:0',
222+
blockId: 'block-2',
223+
blockName: 'Incident Agent',
224+
kind: 'embedded_tool',
225+
toolTitle: 'Notify channel',
226+
}),
227+
])
228+
).toBe(
229+
'Used by workflow "Escalations". Blocks: "Incident Agent" (Notify channel), "Notify Support".'
230+
)
231+
})
232+
})
233+
234+
describe('groupSlackSourcesByWorkflowCredentials', () => {
235+
it('groups matching credentials within a workflow and keeps different credentials separate', () => {
236+
const groups = groupSlackSourcesByWorkflowCredentials([
237+
{ source: source(), botToken: 'xoxb-one', signingSecret: 'secret-one' },
238+
{
239+
source: source({
240+
sourceId: 'workflow-1:block-2:trigger',
241+
blockId: 'block-2',
242+
blockName: 'Handle Reply',
243+
kind: 'trigger',
244+
}),
245+
botToken: 'xoxb-one',
246+
signingSecret: 'secret-one',
247+
},
248+
{
249+
source: source({
250+
sourceId: 'workflow-1:block-3:trigger',
251+
blockId: 'block-3',
252+
blockName: 'Handle Mention',
253+
kind: 'trigger',
254+
}),
255+
botToken: 'xoxb-two',
256+
signingSecret: 'secret-two',
257+
},
258+
])
259+
260+
expect(groups).toHaveLength(2)
261+
expect(groups[0].sources.map((candidate) => candidate.blockName)).toEqual([
262+
'Notify Support',
263+
'Handle Reply',
264+
])
265+
expect(groups[1].sources.map((candidate) => candidate.blockName)).toEqual(['Handle Mention'])
266+
})
267+
268+
it('does not combine matching credentials across workflows', () => {
269+
const groups = groupSlackSourcesByWorkflowCredentials([
270+
{ source: source(), botToken: 'xoxb-one', signingSecret: 'secret-one' },
271+
{
272+
source: source({
273+
sourceId: 'workflow-2:block-2:trigger',
274+
workflowId: 'workflow-2',
275+
workflowName: 'Onboarding',
276+
blockId: 'block-2',
277+
kind: 'trigger',
278+
}),
279+
botToken: 'xoxb-one',
280+
signingSecret: 'secret-one',
281+
},
282+
])
283+
284+
expect(groups).toHaveLength(2)
285+
})
286+
287+
it('keeps different signing secrets separate when bot tokens match', () => {
288+
const groups = groupSlackSourcesByWorkflowCredentials([
289+
{ source: source(), botToken: 'xoxb-one', signingSecret: 'secret-one' },
290+
{
291+
source: source({
292+
sourceId: 'workflow-1:block-2:trigger',
293+
blockId: 'block-2',
294+
blockName: 'Slack Trigger',
295+
kind: 'trigger',
296+
}),
297+
botToken: 'xoxb-one',
298+
signingSecret: 'secret-two',
299+
},
300+
])
301+
302+
expect(groups).toHaveLength(2)
303+
})
304+
305+
it('joins an action-only source to the unique matching trigger credential', () => {
306+
const groups = groupSlackSourcesByWorkflowCredentials([
307+
{ source: source(), botToken: 'xoxb-one' },
308+
{
309+
source: source({
310+
sourceId: 'workflow-1:block-2:trigger',
311+
blockId: 'block-2',
312+
blockName: 'Slack Trigger',
313+
kind: 'trigger',
314+
}),
315+
botToken: 'xoxb-one',
316+
signingSecret: 'secret-one',
317+
},
318+
])
319+
320+
expect(groups).toHaveLength(1)
321+
expect(groups[0].signingSecret).toBe('secret-one')
322+
expect(groups[0].sources.map((candidate) => candidate.blockName)).toEqual([
323+
'Slack Trigger',
324+
'Notify Support',
325+
])
326+
})
327+
})
328+
219329
describe('buildSlackCustomBotSecretBlob', () => {
220330
it('builds a trigger-capable credential without calling Slack for identity', () => {
221-
expect(
222-
buildSlackCustomBotSecretBlob('workflow-1:block-1:trigger', 'xoxb-token', 'secret')
223-
).toEqual({
331+
expect(buildSlackCustomBotSecretBlob('workflow-1', 'xoxb-token', 'secret')).toEqual({
224332
type: 'slack_custom_bot',
225333
signingSecret: 'secret',
226334
botToken: 'xoxb-token',
227-
metadata: { migrationSourceId: 'workflow-1:block-1:trigger' },
335+
metadata: { migrationWorkflowId: 'workflow-1' },
228336
})
229337
})
230338

231339
it('builds an action-only credential without inventing a signing secret', () => {
232-
expect(
233-
buildSlackCustomBotSecretBlob('workflow-1:block-1:action', 'xoxb-token', undefined)
234-
).toEqual({
340+
expect(buildSlackCustomBotSecretBlob('workflow-1', 'xoxb-token', undefined)).toEqual({
235341
type: 'slack_custom_bot',
236342
botToken: 'xoxb-token',
237-
metadata: { migrationSourceId: 'workflow-1:block-1:action' },
343+
metadata: { migrationWorkflowId: 'workflow-1' },
238344
})
239345
})
240346
})

0 commit comments

Comments
 (0)