diff --git a/packages/@n8n/instance-ai/src/tools/workflows/__tests__/setup-workflow.service.test.ts b/packages/@n8n/instance-ai/src/tools/workflows/__tests__/setup-workflow.service.test.ts index 567f13a71133f..126f39894a206 100644 --- a/packages/@n8n/instance-ai/src/tools/workflows/__tests__/setup-workflow.service.test.ts +++ b/packages/@n8n/instance-ai/src/tools/workflows/__tests__/setup-workflow.service.test.ts @@ -539,7 +539,7 @@ describe('analyzeWorkflow', () => { expect(result[0].credentialType).toBe('slackApi'); }); - it('marks needsAction correctly after credentials are applied', async () => { + it('hides credential-only requests whose credential is already set and tests OK', async () => { const node = makeNode({ credentials: { slackApi: { id: 'cred-1', name: 'My Slack' } }, }); @@ -557,10 +557,91 @@ describe('analyzeWorkflow', () => { const result = await analyzeWorkflow(context, 'wf-1'); + expect(result).toHaveLength(0); + }); + + it('keeps credential-only requests whose credential test fails', async () => { + const node = makeNode({ + credentials: { slackApi: { id: 'cred-1', name: 'My Slack' } }, + }); + (context.workflowService.getAsWorkflowJSON as jest.Mock).mockResolvedValue( + makeWorkflowJSON([node]), + ); + (context.nodeService.getDescription as jest.Mock).mockResolvedValue({ + group: [], + credentials: [{ name: 'slackApi' }], + }); + (context.credentialService.list as jest.Mock).mockResolvedValue([ + { id: 'cred-1', name: 'My Slack', updatedAt: '2025-01-01T00:00:00.000Z' }, + ]); + (context.credentialService.test as jest.Mock).mockResolvedValue({ + success: false, + message: 'Invalid token', + }); + + const result = await analyzeWorkflow(context, 'wf-1'); + + expect(result).toHaveLength(1); + expect(result[0].needsAction).toBe(true); + }); + + it('keeps testable trigger requests even when their credential is already valid', async () => { + const trigger = makeNode({ + name: 'Webhook', + type: 'n8n-nodes-base.webhook', + id: 'n-trigger', + credentials: { httpHeaderAuth: { id: 'cred-1', name: 'My Auth' } }, + }); + (context.workflowService.getAsWorkflowJSON as jest.Mock).mockResolvedValue( + makeWorkflowJSON([trigger]), + ); + (context.nodeService.getDescription as jest.Mock).mockResolvedValue({ + group: ['trigger'], + credentials: [{ name: 'httpHeaderAuth' }], + webhooks: [{}], + }); + (context.credentialService.list as jest.Mock).mockResolvedValue([ + { id: 'cred-1', name: 'My Auth', updatedAt: '2025-01-01T00:00:00.000Z' }, + ]); + (context.credentialService.test as jest.Mock).mockResolvedValue({ success: true }); + + const result = await analyzeWorkflow(context, 'wf-1'); + expect(result).toHaveLength(1); + expect(result[0].isTrigger).toBe(true); + expect(result[0].isTestable).toBe(true); expect(result[0].needsAction).toBe(false); }); + it('keeps requests with parameter issues regardless of credential validity', async () => { + const node = makeNode({ + credentials: { slackApi: { id: 'cred-1', name: 'My Slack' } }, + }); + (context.workflowService.getAsWorkflowJSON as jest.Mock).mockResolvedValue( + makeWorkflowJSON([node]), + ); + (context.nodeService.getDescription as jest.Mock).mockResolvedValue({ + group: [], + credentials: [{ name: 'slackApi' }], + properties: [{ name: 'resource', displayName: 'Resource', type: 'string' }], + }); + (context.nodeService as unknown as Record).getParameterIssues = jest + .fn() + .mockResolvedValue({ + resource: ['Parameter "resource" is required'], + }); + (context.credentialService.list as jest.Mock).mockResolvedValue([ + { id: 'cred-1', name: 'My Slack', updatedAt: '2025-01-01T00:00:00.000Z' }, + ]); + (context.credentialService.test as jest.Mock).mockResolvedValue({ success: true }); + + const result = await analyzeWorkflow(context, 'wf-1'); + + expect(result).toHaveLength(1); + expect(result[0].needsAction).toBe(true); + expect(result[0].parameterIssues).toBeDefined(); + }); + it('sorts by execution order with triggers first', async () => { const trigger = makeNode({ name: 'Webhook', diff --git a/packages/@n8n/instance-ai/src/tools/workflows/setup-workflow.service.ts b/packages/@n8n/instance-ai/src/tools/workflows/setup-workflow.service.ts index af061c27f4af4..816a0ac984046 100644 --- a/packages/@n8n/instance-ai/src/tools/workflows/setup-workflow.service.ts +++ b/packages/@n8n/instance-ai/src/tools/workflows/setup-workflow.service.ts @@ -770,7 +770,12 @@ export async function analyzeWorkflow( req.credentialType !== undefined || req.isTrigger || (req.parameterIssues && Object.keys(req.parameterIssues).length > 0), - ); + ) + // Hide cards the user has nothing to do on: credentials already set and + // tested, no parameter issues, not a trigger awaiting testing. Trigger + // steps are always kept — triggers require user testing regardless of + // credential state. + .filter((req) => !!req.needsAction || (req.isTrigger && !!req.isTestable)); sortByExecutionOrder( setupRequests,