|
4 | 4 |
|
5 | 5 | import { describe, expect, it } from 'vitest' |
6 | 6 | import { |
| 7 | + buildSlackBotDescription, |
7 | 8 | buildSlackBotDisplayName, |
8 | 9 | buildSlackCustomBotSecretBlob, |
9 | 10 | type EnvironmentLookup, |
10 | 11 | extractSlackBotSources, |
| 12 | + groupSlackSourcesByWorkflowCredentials, |
11 | 13 | planLegacySlackTriggerLink, |
12 | 14 | resolveSlackSourceSecrets, |
13 | 15 | type SlackBotSource, |
@@ -195,46 +197,150 @@ describe('extractSlackBotSources', () => { |
195 | 197 | }) |
196 | 198 |
|
197 | 199 | 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') |
206 | 202 | }) |
207 | 203 |
|
208 | 204 | 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()])) |
212 | 208 |
|
213 | 209 | expect(first).toHaveLength(255) |
214 | 210 | expect(second).toHaveLength(255) |
215 | 211 | expect(second.endsWith(' (2)')).toBe(true) |
216 | 212 | }) |
217 | 213 | }) |
218 | 214 |
|
| 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 | + |
219 | 329 | describe('buildSlackCustomBotSecretBlob', () => { |
220 | 330 | 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({ |
224 | 332 | type: 'slack_custom_bot', |
225 | 333 | signingSecret: 'secret', |
226 | 334 | botToken: 'xoxb-token', |
227 | | - metadata: { migrationSourceId: 'workflow-1:block-1:trigger' }, |
| 335 | + metadata: { migrationWorkflowId: 'workflow-1' }, |
228 | 336 | }) |
229 | 337 | }) |
230 | 338 |
|
231 | 339 | 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({ |
235 | 341 | type: 'slack_custom_bot', |
236 | 342 | botToken: 'xoxb-token', |
237 | | - metadata: { migrationSourceId: 'workflow-1:block-1:action' }, |
| 343 | + metadata: { migrationWorkflowId: 'workflow-1' }, |
238 | 344 | }) |
239 | 345 | }) |
240 | 346 | }) |
|
0 commit comments