n8n/packages/nodes-base/nodes/DynamicCredentialCheck/test/DynamicCredentialCheck.node.test.ts
phyllis-noester acaa0db33a
feat(core): Add node that allows checking dynamic credentials inside node (#27165)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 15:21:42 +00:00

150 lines
4.0 KiB
TypeScript

import type {
CredentialCheckResult,
IExecuteFunctions,
IExecutionContext,
INodeExecutionData,
} from 'n8n-workflow';
import { DynamicCredentialCheck } from '../DynamicCredentialCheck.node';
describe('DynamicCredentialCheck Node', () => {
let node: DynamicCredentialCheck;
beforeEach(() => {
node = new DynamicCredentialCheck();
});
const inputItems: INodeExecutionData[] = [{ json: { data: 'test' } }];
const executionContext: IExecutionContext = {
version: 1,
establishedAt: Date.now(),
source: 'webhook',
credentials: 'encrypted-credentials',
};
const createMockExecuteFunctions = (opts: {
hasExecutionContext?: boolean;
workflowId?: string;
checkCredentialStatus?:
| ((wfId: string, ctx: IExecutionContext) => Promise<CredentialCheckResult>)
| null;
}) => {
const helpers: Record<string, unknown> = {};
if (opts.checkCredentialStatus !== null) {
helpers.checkCredentialStatus = opts.checkCredentialStatus ?? jest.fn();
}
return {
getInputData: () => inputItems,
getExecutionContext: () =>
opts.hasExecutionContext === false ? undefined : executionContext,
getWorkflow: () => ({ id: opts.workflowId }),
getNode: () => ({
name: 'Check Credential Status',
type: 'n8n-nodes-base.dynamicCredentialCheck',
}),
helpers,
} as unknown as IExecuteFunctions;
};
describe('sensitiveOutputFields', () => {
it('should declare authorizationUrl as sensitive', () => {
expect(node.description.sensitiveOutputFields).toContain('credentials[*].authorizationUrl');
});
it('should declare revokeUrl as sensitive', () => {
expect(node.description.sensitiveOutputFields).toContain('credentials[*].revokeUrl');
});
});
describe('execute', () => {
it('should route items to Ready output when all credentials are configured', async () => {
const mockResult: CredentialCheckResult = {
readyToExecute: true,
credentials: [
{
credentialId: 'cred-1',
credentialName: 'OAuth2 API',
credentialType: 'oauth2Api',
resolverId: 'resolver-1',
status: 'configured',
},
],
};
const fns = createMockExecuteFunctions({
workflowId: 'workflow-1',
checkCredentialStatus: jest.fn().mockResolvedValue(mockResult),
});
const output = await node.execute.call(fns);
expect(output[0]).toEqual(inputItems);
expect(output[1]).toEqual([]);
});
it('should route items to Not Ready output with check result when credentials are missing', async () => {
const mockResult: CredentialCheckResult = {
readyToExecute: false,
credentials: [
{
credentialId: 'cred-1',
credentialName: 'OAuth2 API',
credentialType: 'oauth2Api',
resolverId: 'resolver-1',
status: 'missing',
authorizationUrl: 'https://auth.example.com',
},
],
};
const fns = createMockExecuteFunctions({
workflowId: 'workflow-1',
checkCredentialStatus: jest.fn().mockResolvedValue(mockResult),
});
const output = await node.execute.call(fns);
expect(output[0]).toEqual([]);
expect(output[1]).toHaveLength(1);
expect(output[1][0].json).toEqual(mockResult);
});
it('should route to Ready when no execution context is available', async () => {
const fns = createMockExecuteFunctions({
hasExecutionContext: false,
workflowId: 'workflow-1',
});
const output = await node.execute.call(fns);
expect(output[0]).toEqual(inputItems);
expect(output[1]).toEqual([]);
});
it('should route to Ready when workflow ID is not available', async () => {
const fns = createMockExecuteFunctions({
workflowId: undefined,
});
const output = await node.execute.call(fns);
expect(output[0]).toEqual(inputItems);
expect(output[1]).toEqual([]);
});
it('should route to Ready when checkCredentialStatus helper is not available', async () => {
const fns = createMockExecuteFunctions({
workflowId: 'workflow-1',
checkCredentialStatus: null,
});
const output = await node.execute.call(fns);
expect(output[0]).toEqual(inputItems);
expect(output[1]).toEqual([]);
});
});
});