test: Migrate nodes-base from Jest to Vitest (#31564)

This commit is contained in:
Matsu
2026-06-16 11:57:00 +03:00
committed by GitHub
parent 189cbfb7ba
commit df189711ee
493 changed files with 6941 additions and 6836 deletions
@@ -20,6 +20,7 @@ import nock from 'nock';
import { readFileSync, mkdtempSync, existsSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { expect } from 'vitest';
import { ExecutionLifecycleHooks } from '../dist/execution-engine/execution-lifecycle-hooks';
import { WorkflowExecute } from '../dist/execution-engine/workflow-execute';
+1 -1
View File
@@ -56,7 +56,7 @@ Nodes can test credentials via `methods.credentialTest`.
## Testing
### Unit Tests
- Use `jest-mock-extended` for mocking interfaces
- Use `vitest-mock-extended` for mocking interfaces
- Use `nock` for HTTP mocking
- Mock all external dependencies
- Test happy paths, error handling, edge cases, and binary data
+22 -22
View File
@@ -12,7 +12,7 @@ You are an expert AI agent specialized in writing comprehensive, reliable unit t
### 3. Testing guidelines
- **Don't add useless comments** such as "Arrange, Assert, Act" or "Mock something".
- **Don't add useless comments** such as "Arrange, Assert, Act" or "Mock something".
- **Always work from within the package directory** when running tests. E.g. for a node in nodes-base enter `packages/nodes-base` or for langchain node enter `packages/@n8n/nodes-langchain`
- **Use `pnpm test <file_name>`** for running tests
- **Mock all external dependencies** in unit tests
@@ -33,7 +33,7 @@ Always include tests for:
### 1. Core n8n Interfaces Mocking
```typescript
import { mock, mockDeep } from 'jest-mock-extended';
import { mock, mockDeep } from 'vitest-mock-extended';
import type { IExecuteFunctions, IWebhookFunctions, INode } from 'n8n-workflow';
// Standard execute functions mock
@@ -88,8 +88,8 @@ mockExecuteFunctions.helpers.prepareBinaryData.mockResolvedValue({
### 3. External API Mocking
```typescript
// Using jest.spyOn for API functions
const apiRequestSpy = jest.spyOn(GenericFunctions, 'apiRequest');
// Using vi.spyOn for API functions
const apiRequestSpy = vi.spyOn(GenericFunctions, 'apiRequest');
apiRequestSpy.mockResolvedValue({
id: '123',
name: 'Test Item',
@@ -114,15 +114,15 @@ afterEach(() => {
```typescript
// Database mocking
const mockDataTable = mock<IDataStoreProjectService>({
getColumns: jest.fn(),
addColumn: jest.fn(),
updateRow: jest.fn(),
getColumns: vi.fn(),
addColumn: vi.fn(),
updateRow: vi.fn(),
});
// Redis client mocking
const mockClient = mock<RedisClient>();
const createClient = jest.fn().mockReturnValue(mockClient);
jest.mock('redis', () => ({ createClient }));
const createClient = vi.fn().mockReturnValue(mockClient);
vi.mock('redis', () => ({ createClient }));
```
## Test Implementation Patterns
@@ -131,7 +131,7 @@ jest.mock('redis', () => ({ createClient }));
```typescript
describe('Node Execution', () => {
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]);
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
});
@@ -142,7 +142,7 @@ describe('Node Execution', () => {
const params = { operation: 'create', name: 'Test' };
return params[param];
});
apiRequestSpy.mockResolvedValue({ id: '123', name: 'Test' });
// Execute
@@ -209,10 +209,10 @@ describe('Binary Data Handling', () => {
it('should handle file upload operations', async () => {
const fileBuffer = Buffer.from('test file content');
mockExecuteFunctions.helpers.getBinaryStream.mockResolvedValue(fileBuffer);
// Test file upload logic
const result = await node.execute.call(mockExecuteFunctions);
expect(result[0][0].json).toHaveProperty('fileId');
});
});
@@ -223,7 +223,7 @@ describe('Binary Data Handling', () => {
describe('Webhook Operations', () => {
it('should handle GET requests', async () => {
const mockRequest = { method: 'GET', query: { id: '123' } };
const mockResponse = { render: jest.fn(), send: jest.fn() };
const mockResponse = { render: vi.fn(), send: vi.fn() };
mockWebhookFunctions.getRequestObject.mockReturnValue(mockRequest);
mockWebhookFunctions.getResponseObject.mockReturnValue(mockResponse);
@@ -234,9 +234,9 @@ describe('Webhook Operations', () => {
});
it('should process POST data', async () => {
const mockRequest = {
method: 'POST',
body: { name: 'Test', email: 'test@example.com' }
const mockRequest = {
method: 'POST',
body: { name: 'Test', email: 'test@example.com' }
};
mockWebhookFunctions.getRequestObject.mockReturnValue(mockRequest);
@@ -404,7 +404,7 @@ await expect(asyncFunction()).rejects.toThrow(Error);
## Example Complete Test Suite
```typescript
import { mock, mockDeep } from 'jest-mock-extended';
import { mock, mockDeep } from 'vitest-mock-extended';
import type { IExecuteFunctions, INode } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { TestNode } from '../TestNode';
@@ -412,17 +412,17 @@ import * as GenericFunctions from '../GenericFunctions';
describe('TestNode', () => {
let node: TestNode;
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
const apiRequestSpy = jest.spyOn(GenericFunctions, 'apiRequest');
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
const apiRequestSpy = vi.spyOn(GenericFunctions, 'apiRequest');
beforeEach(() => {
node = new TestNode();
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
});
afterEach(() => {
jest.resetAllMocks();
vi.resetAllMocks();
});
describe('execute', () => {
@@ -1,54 +1,67 @@
import { UserError } from 'n8n-workflow';
global.fetch = jest.fn();
global.fetch = vi.fn();
class MockSecurityConfig {
awsSystemCredentialsAccess = true;
}
const { mockContainer, mockReadFile, MockSecurityConfig } = vi.hoisted(() => {
class MockSecurityConfig {
awsSystemCredentialsAccess = true;
}
return {
mockContainer: { get: vi.fn() },
mockReadFile: vi.fn(),
MockSecurityConfig,
};
});
const mockContainer = {
get: jest.fn(),
};
const mockReadFile = jest.fn();
jest.mock('@n8n/di', () => ({
vi.mock('@n8n/di', () => ({
Container: mockContainer,
}));
jest.mock('@n8n/config', () => ({
vi.mock('@n8n/config', () => ({
SecurityConfig: MockSecurityConfig,
}));
jest.mock('fs/promises', () => ({
vi.mock('fs/promises', () => ({
readFile: mockReadFile,
}));
import * as systemCredentialsUtils from './system-credentials-utils';
import type { Mock } from 'vitest';
const mockEnvGetter = jest.fn();
const mockEnvGetter = vi.fn();
const { getSystemCredentials, credentialsResolver } = systemCredentialsUtils;
const envGetter = (...args: Parameters<typeof systemCredentialsUtils.envGetter>) =>
systemCredentialsUtils.envGetter(...args);
describe('system-credentials-utils', () => {
let mockSecurityConfigInstance: MockSecurityConfig;
let mockSecurityConfigInstance: InstanceType<typeof MockSecurityConfig>;
const realProcessEnv = process.env;
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
jest.spyOn(systemCredentialsUtils, 'envGetter').mockImplementation(mockEnvGetter);
// `envGetter` reads `process.env` and is called as a module-internal binding, which
// Vitest's spies cannot intercept. Route env reads through `mockEnvGetter` instead so
// the existing per-test setups and `toHaveBeenCalledWith` assertions keep working.
process.env = new Proxy({} as NodeJS.ProcessEnv, {
get: (_target, key) => (typeof key === 'string' ? mockEnvGetter(key) : undefined),
});
mockSecurityConfigInstance = new MockSecurityConfig();
mockContainer.get.mockReturnValue(mockSecurityConfigInstance);
mockEnvGetter.mockReturnValue(undefined);
(global.fetch as jest.Mock).mockReset();
(global.fetch as Mock).mockReset();
mockReadFile.mockReset();
});
afterEach(() => {
process.env = realProcessEnv;
});
describe('envGetter', () => {
it('should be called with correct environment variable names', () => {
mockEnvGetter.mockReturnValue('test-value');
@@ -94,7 +107,7 @@ describe('system-credentials-utils', () => {
it('should return null when no credentials are found', async () => {
mockEnvGetter.mockReturnValue(undefined);
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
(global.fetch as Mock).mockRejectedValue(new Error('Network error'));
const result = await getSystemCredentials();
expect(result).toBeNull();
@@ -235,9 +248,9 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
const result = await credentialsResolver.containerMetadata();
@@ -278,9 +291,9 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
await credentialsResolver.containerMetadata();
@@ -304,7 +317,7 @@ describe('system-credentials-utils', () => {
return undefined;
});
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: false,
});
@@ -320,7 +333,7 @@ describe('system-credentials-utils', () => {
return undefined;
});
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
(global.fetch as Mock).mockRejectedValue(new Error('Network error'));
const result = await credentialsResolver.containerMetadata();
expect(result).toBeNull();
@@ -359,9 +372,9 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
const result = await credentialsResolver.podIdentity();
@@ -402,9 +415,9 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
await credentialsResolver.podIdentity();
@@ -428,7 +441,7 @@ describe('system-credentials-utils', () => {
return undefined;
});
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: false,
});
@@ -444,7 +457,7 @@ describe('system-credentials-utils', () => {
return undefined;
});
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
(global.fetch as Mock).mockRejectedValue(new Error('Network error'));
const result = await credentialsResolver.podIdentity();
expect(result).toBeNull();
@@ -472,9 +485,9 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
const result = await credentialsResolver.podIdentity();
@@ -521,9 +534,9 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
await credentialsResolver.podIdentity();
@@ -561,9 +574,9 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
const result = await credentialsResolver.podIdentity();
@@ -607,9 +620,9 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
await credentialsResolver.podIdentity();
@@ -646,9 +659,9 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
await credentialsResolver.podIdentity();
@@ -685,9 +698,9 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
const result = await credentialsResolver.podIdentity();
@@ -707,7 +720,7 @@ describe('system-credentials-utils', () => {
}),
);
// Ensure Authorization header is not included
expect((global.fetch as jest.Mock).mock.calls[0][1].headers.Authorization).toBeUndefined();
expect((global.fetch as Mock).mock.calls[0][1].headers.Authorization).toBeUndefined();
});
});
@@ -719,18 +732,18 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock)
(global.fetch as Mock)
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValue('test-token'),
text: vi.fn().mockResolvedValue('test-token'),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValue('test-role'),
text: vi.fn().mockResolvedValue('test-role'),
})
.mockResolvedValueOnce({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
const result = await credentialsResolver.instanceMetadata();
@@ -750,15 +763,15 @@ describe('system-credentials-utils', () => {
Token: 'test-token',
};
(global.fetch as jest.Mock)
(global.fetch as Mock)
.mockRejectedValueOnce(new Error('Token request failed'))
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValue('test-role'),
text: vi.fn().mockResolvedValue('test-role'),
})
.mockResolvedValueOnce({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
const result = await credentialsResolver.instanceMetadata();
@@ -770,10 +783,10 @@ describe('system-credentials-utils', () => {
});
it('should return null when role name request fails', async () => {
(global.fetch as jest.Mock)
(global.fetch as Mock)
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValue('test-token'),
text: vi.fn().mockResolvedValue('test-token'),
})
.mockResolvedValueOnce({
ok: false,
@@ -788,18 +801,18 @@ describe('system-credentials-utils', () => {
AccessKeyId: 'test-access-key',
};
(global.fetch as jest.Mock)
(global.fetch as Mock)
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValue('test-token'),
text: vi.fn().mockResolvedValue('test-token'),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValue('test-role'),
text: vi.fn().mockResolvedValue('test-role'),
})
.mockResolvedValueOnce({
ok: true,
json: jest.fn().mockResolvedValue(incompleteCredentials),
json: vi.fn().mockResolvedValue(incompleteCredentials),
});
const result = await credentialsResolver.instanceMetadata();
@@ -807,7 +820,7 @@ describe('system-credentials-utils', () => {
});
it('should return null when fetch throws an error', async () => {
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
(global.fetch as Mock).mockRejectedValue(new Error('Network error'));
const result = await credentialsResolver.instanceMetadata();
expect(result).toBeNull();
@@ -850,9 +863,9 @@ describe('system-credentials-utils', () => {
},
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockCredentials),
json: vi.fn().mockResolvedValue(mockCredentials),
});
const result = await credentialsResolver.roleForServiceAccount();
@@ -875,7 +888,7 @@ describe('system-credentials-utils', () => {
body: expect.stringContaining('Action=AssumeRoleWithWebIdentity'),
}),
);
expect((global.fetch as jest.Mock).mock.calls[0][1].body).toContain(
expect((global.fetch as Mock).mock.calls[0][1].body).toContain(
'RoleArn=arn%3Aaws%3Aiam%3A%3A123456789012%3Arole%2Ftest-role',
);
});
@@ -892,7 +905,7 @@ describe('system-credentials-utils', () => {
}
});
mockReadFile.mockResolvedValue('test-web-identity-token');
(global.fetch as jest.Mock).mockResolvedValue({ ok: false });
(global.fetch as Mock).mockResolvedValue({ ok: false });
const result = await credentialsResolver.roleForServiceAccount();
expect(result).toBeNull();
@@ -919,9 +932,9 @@ describe('system-credentials-utils', () => {
},
},
};
(global.fetch as jest.Mock).mockResolvedValue({
(global.fetch as Mock).mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(incomplete),
json: vi.fn().mockResolvedValue(incomplete),
});
const result = await credentialsResolver.roleForServiceAccount();
@@ -940,7 +953,7 @@ describe('system-credentials-utils', () => {
}
});
mockReadFile.mockResolvedValue('test-web-identity-token');
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
(global.fetch as Mock).mockRejectedValue(new Error('Network error'));
const result = await credentialsResolver.roleForServiceAccount();
expect(result).toBeNull();
@@ -1,33 +1,34 @@
import { OperationalError, UserError } from 'n8n-workflow';
import type { AwsAssumeRoleCredentialsType, AwsIamCredentialsType, AWSRegion } from './types';
global.fetch = jest.fn();
global.fetch = vi.fn();
jest.mock('aws4', () => ({
sign: jest.fn(),
vi.mock('aws4', () => ({
sign: vi.fn(),
}));
jest.mock('xml2js', () => ({
parseString: jest.fn(),
vi.mock('xml2js', () => ({
parseString: vi.fn(),
}));
import { sign } from 'aws4';
import { parseString } from 'xml2js';
import { assertSupportedAwsRegion, assumeRole, awsGetSignInOptionsAndUpdateRequest } from './utils';
import * as systemCredentialsUtils from './system-credentials-utils';
import type { MockedFunction, MockInstance } from 'vitest';
describe('assumeRole', () => {
let mockFetch: jest.MockedFunction<typeof fetch>;
let mockSign: jest.MockedFunction<typeof sign>;
let mockParseString: jest.MockedFunction<typeof parseString>;
let consoleErrorSpy: jest.SpyInstance;
let mockFetch: MockedFunction<typeof fetch>;
let mockSign: MockedFunction<typeof sign>;
let mockParseString: MockedFunction<typeof parseString>;
let consoleErrorSpy: MockInstance;
beforeEach(() => {
jest.clearAllMocks();
mockFetch = global.fetch as jest.MockedFunction<typeof fetch>;
mockSign = sign as jest.MockedFunction<typeof sign>;
mockParseString = parseString as jest.MockedFunction<typeof parseString>;
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
vi.clearAllMocks();
mockFetch = global.fetch as MockedFunction<typeof fetch>;
mockSign = sign as MockedFunction<typeof sign>;
mockParseString = parseString as MockedFunction<typeof parseString>;
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
mockSign.mockImplementation((request: any) => request as any);
});
@@ -54,13 +55,13 @@ describe('assumeRole', () => {
source: 'environment' as const,
};
jest
.spyOn(systemCredentialsUtils, 'getSystemCredentials')
.mockResolvedValue(mockSystemCredentials);
vi.spyOn(systemCredentialsUtils, 'getSystemCredentials').mockResolvedValue(
mockSystemCredentials,
);
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue(`<?xml version="1.0" encoding="UTF-8"?>
text: vi.fn().mockResolvedValue(`<?xml version="1.0" encoding="UTF-8"?>
<AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
<AssumeRoleResult>
<Credentials>
@@ -131,13 +132,13 @@ describe('assumeRole', () => {
source: 'instanceMetadata' as const,
};
jest
.spyOn(systemCredentialsUtils, 'getSystemCredentials')
.mockResolvedValue(mockSystemCredentials);
vi.spyOn(systemCredentialsUtils, 'getSystemCredentials').mockResolvedValue(
mockSystemCredentials,
);
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue(`<?xml version="1.0" encoding="UTF-8"?>
text: vi.fn().mockResolvedValue(`<?xml version="1.0" encoding="UTF-8"?>
<AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
<AssumeRoleResult>
<Credentials>
@@ -201,7 +202,7 @@ describe('assumeRole', () => {
roleSessionName: 'test-session',
};
jest.spyOn(systemCredentialsUtils, 'getSystemCredentials').mockResolvedValue(null);
vi.spyOn(systemCredentialsUtils, 'getSystemCredentials').mockResolvedValue(null);
await expect(assumeRole(credentials, 'us-east-1')).rejects.toThrow(UserError);
await expect(assumeRole(credentials, 'us-east-1')).rejects.toThrow(
@@ -225,13 +226,13 @@ describe('assumeRole', () => {
source: 'environment' as const,
};
jest
.spyOn(systemCredentialsUtils, 'getSystemCredentials')
.mockResolvedValue(mockSystemCredentials);
vi.spyOn(systemCredentialsUtils, 'getSystemCredentials').mockResolvedValue(
mockSystemCredentials,
);
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
text: vi.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
};
mockFetch.mockResolvedValue(mockResponse as any);
@@ -277,7 +278,7 @@ describe('assumeRole', () => {
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
text: vi.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
};
mockFetch.mockResolvedValue(mockResponse as any);
@@ -332,7 +333,7 @@ describe('assumeRole', () => {
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
text: vi.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
};
mockFetch.mockResolvedValue(mockResponse as any);
@@ -445,7 +446,7 @@ describe('assumeRole', () => {
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
text: vi.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
};
mockFetch.mockResolvedValue(mockResponse as any);
@@ -489,7 +490,7 @@ describe('assumeRole', () => {
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
text: vi.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
};
mockFetch.mockResolvedValue(mockResponse as any);
@@ -530,7 +531,7 @@ describe('assumeRole', () => {
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
text: vi.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
};
mockFetch.mockResolvedValue(mockResponse as any);
@@ -597,7 +598,7 @@ describe('assumeRole', () => {
ok: false,
status: 403,
statusText: 'Forbidden',
text: jest.fn().mockResolvedValue('Access denied'),
text: vi.fn().mockResolvedValue('Access denied'),
};
mockFetch.mockResolvedValue(mockResponse as any);
@@ -622,7 +623,7 @@ describe('assumeRole', () => {
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue('invalid xml'),
text: vi.fn().mockResolvedValue('invalid xml'),
};
mockFetch.mockResolvedValue(mockResponse as any);
@@ -648,7 +649,7 @@ describe('assumeRole', () => {
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
text: vi.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
};
mockFetch.mockResolvedValue(mockResponse as any);
@@ -681,7 +682,7 @@ describe('assumeRole', () => {
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
text: vi.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
};
mockFetch.mockResolvedValue(mockResponse as any);
@@ -713,7 +714,7 @@ describe('assumeRole', () => {
const mockResponse = {
ok: true,
text: jest.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
text: vi.fn().mockResolvedValue('<?xml version="1.0" encoding="UTF-8"?>'),
};
mockFetch.mockResolvedValue(mockResponse as any);
@@ -844,15 +845,15 @@ describe('awsGetSignInOptionsAndUpdateRequest', () => {
});
describe('assumeRole region validation', () => {
let mockFetch: jest.MockedFunction<typeof fetch>;
let mockSign: jest.MockedFunction<typeof sign>;
let consoleErrorSpy: jest.SpyInstance;
let mockFetch: MockedFunction<typeof fetch>;
let mockSign: MockedFunction<typeof sign>;
let consoleErrorSpy: MockInstance;
beforeEach(() => {
jest.clearAllMocks();
mockFetch = global.fetch as jest.MockedFunction<typeof fetch>;
mockSign = sign as jest.MockedFunction<typeof sign>;
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
vi.clearAllMocks();
mockFetch = global.fetch as MockedFunction<typeof fetch>;
mockSign = sign as MockedFunction<typeof sign>;
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
mockSign.mockImplementation((request: any) => request as any);
});
@@ -3,21 +3,22 @@ import type { IHttpRequestOptions } from 'n8n-workflow';
import { Aws } from '../Aws.credentials';
import type { AwsIamCredentialsType } from '../common/aws/types';
import type { Mock } from 'vitest';
jest.mock('aws4', () => ({
sign: jest.fn(),
vi.mock('aws4', () => ({
sign: vi.fn(),
}));
describe('Aws Credential', () => {
const aws = new Aws();
let mockSign: jest.Mock;
let mockSign: Mock;
beforeEach(() => {
mockSign = sign as unknown as jest.Mock;
mockSign = sign as unknown as Mock;
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should have correct properties', () => {
@@ -1,11 +1,12 @@
import { sign } from 'aws4';
import type { IHttpRequestOptions } from 'n8n-workflow';
import type { Mock, MockInstance } from 'vitest';
import { AwsAssumeRole } from '../AwsAssumeRole.credentials';
import type { AwsAssumeRoleCredentialsType } from '../common/aws/types';
jest.mock('aws4', () => ({
sign: jest.fn(),
vi.mock('aws4', () => ({
sign: vi.fn(),
}));
const stsAssumeRoleResponseXml = `<?xml version="1.0"?>
@@ -22,8 +23,8 @@ const stsAssumeRoleResponseXml = `<?xml version="1.0"?>
describe('AwsAssumeRole Credential', () => {
const aws = new AwsAssumeRole();
let mockSign: jest.Mock;
let mockFetch: jest.SpyInstance;
let mockSign: Mock;
let mockFetch: MockInstance;
const credentials: AwsAssumeRoleCredentialsType = {
region: 'us-east-1',
@@ -37,14 +38,14 @@ describe('AwsAssumeRole Credential', () => {
};
beforeEach(() => {
mockSign = sign as unknown as jest.Mock;
mockFetch = jest
mockSign = sign as unknown as Mock;
mockFetch = vi
.spyOn(global, 'fetch')
.mockResolvedValue(new Response(stsAssumeRoleResponseXml, { status: 200 }));
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
mockFetch.mockRestore();
});
@@ -3,19 +3,20 @@ import jwt from 'jsonwebtoken';
import type { IHttpRequestOptions } from 'n8n-workflow';
import { SalesforceJwtApi, resolveAuthUrl } from '../SalesforceJwtApi.credentials';
import type { Mock } from 'vitest';
jest.mock('axios');
jest.mock('jsonwebtoken', () => ({
sign: jest.fn(),
vi.mock('axios');
vi.mock('jsonwebtoken', () => ({
default: { sign: vi.fn() },
}));
jest.mock('@utils/utilities', () => ({
vi.mock('@utils/utilities', () => ({
formatPrivateKey: (key: string) => key,
}));
describe('SalesforceJwtApi Credential', () => {
const credential = new SalesforceJwtApi();
const mockedAxios = axios as unknown as jest.Mock;
const mockedSign = jwt.sign as unknown as jest.Mock;
const mockedAxios = axios as unknown as Mock;
const mockedSign = jwt.sign as unknown as Mock;
const baseCredentials = {
clientId: 'connected-app-client-id',
@@ -57,7 +57,7 @@ describe('assumeRole() — centralized validation', () => {
});
// Mock global fetch so we can inspect dispatcher behavior without hitting the network.
const fetchMock = jest.fn();
const fetchMock = vi.fn();
beforeEach(() => {
fetchMock.mockReset();
// Return a minimal STS-success XML so assumeRole() completes.
-16
View File
@@ -1,16 +0,0 @@
// Avoid tests failing because of difference between local and GitHub actions timezone
process.env.TZ = 'UTC';
/** @type {import('jest').Config} */
module.exports = {
...require('../../jest.config'),
testPathIgnorePatterns: ['/dist/', '/node_modules/', '\\.integration\\.test\\.ts$'],
collectCoverageFrom: [
'credentials/**/*.ts',
'nodes/**/*.ts',
'utils/**/*.ts',
...require('../../jest.coverage-excludes'),
],
globalSetup: '<rootDir>/test/globalSetup.ts',
setupFilesAfterEnv: ['jest-expect-message', '<rootDir>/test/setup.ts'],
};
@@ -3,42 +3,43 @@ import type { IDataObject, IWebhookFunctions } from 'n8n-workflow';
import { AcuitySchedulingTrigger } from '../AcuitySchedulingTrigger.node';
import { verifySignature } from '../AcuitySchedulingTriggerHelpers';
import { acuitySchedulingApiRequest } from '../GenericFunctions';
import type { Mock } from 'vitest';
jest.mock('../AcuitySchedulingTriggerHelpers', () => ({
verifySignature: jest.fn(),
vi.mock('../AcuitySchedulingTriggerHelpers', () => ({
verifySignature: vi.fn(),
}));
jest.mock('../GenericFunctions', () => ({
acuitySchedulingApiRequest: jest.fn(),
vi.mock('../GenericFunctions', () => ({
acuitySchedulingApiRequest: vi.fn(),
}));
const mockedVerifySignature = jest.mocked(verifySignature);
const mockedAcuitySchedulingApiRequest = jest.mocked(acuitySchedulingApiRequest);
const mockedVerifySignature = vi.mocked(verifySignature);
const mockedAcuitySchedulingApiRequest = vi.mocked(acuitySchedulingApiRequest);
describe('AcuitySchedulingTrigger', () => {
let trigger: AcuitySchedulingTrigger;
let response: { status: jest.Mock; send: jest.Mock; end: jest.Mock };
let response: { status: Mock; send: Mock; end: Mock };
let ctx: IWebhookFunctions;
const requestBody: IDataObject = { action: 'appointment.scheduled', id: 123 };
const buildContext = (body: IDataObject = requestBody): IWebhookFunctions => {
response = {
status: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
end: jest.fn().mockReturnThis(),
status: vi.fn().mockReturnThis(),
send: vi.fn().mockReturnThis(),
end: vi.fn().mockReturnThis(),
};
return {
getRequestObject: jest.fn().mockReturnValue({ body }),
getResponseObject: jest.fn().mockReturnValue(response),
getNodeParameter: jest.fn(),
getRequestObject: vi.fn().mockReturnValue({ body }),
getResponseObject: vi.fn().mockReturnValue(response),
getNodeParameter: vi.fn(),
helpers: {
returnJsonArray: jest.fn().mockImplementation((data: IDataObject) => [data]),
returnJsonArray: vi.fn().mockImplementation((data: IDataObject) => [data]),
},
} as unknown as IWebhookFunctions;
};
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
trigger = new AcuitySchedulingTrigger();
ctx = buildContext();
mockedVerifySignature.mockResolvedValue(true);
@@ -46,7 +47,7 @@ describe('AcuitySchedulingTrigger', () => {
describe('webhook', () => {
it('triggers workflow when signature is valid and resolveData is false', async () => {
(ctx.getNodeParameter as jest.Mock).mockImplementation((name: string) =>
(ctx.getNodeParameter as Mock).mockImplementation((name: string) =>
name === 'resolveData' ? false : undefined,
);
@@ -70,7 +71,7 @@ describe('AcuitySchedulingTrigger', () => {
it('triggers workflow when no signing secret is configured (backward compat)', async () => {
mockedVerifySignature.mockResolvedValue(true);
(ctx.getNodeParameter as jest.Mock).mockImplementation((name: string) =>
(ctx.getNodeParameter as Mock).mockImplementation((name: string) =>
name === 'resolveData' ? false : undefined,
);
@@ -81,7 +82,7 @@ describe('AcuitySchedulingTrigger', () => {
it('resolves data via API when resolveData is true', async () => {
ctx = buildContext({ id: 123 });
(ctx.getNodeParameter as jest.Mock).mockImplementation((name: string) => {
(ctx.getNodeParameter as Mock).mockImplementation((name: string) => {
if (name === 'resolveData') return true;
if (name === 'event') return 'appointment.scheduled';
return undefined;
@@ -1,5 +1,5 @@
import { createHmac } from 'crypto';
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { IWebhookFunctions } from 'n8n-workflow';
import { verifySignature } from '../AcuitySchedulingTriggerHelpers';
@@ -31,7 +31,7 @@ describe('AcuitySchedulingTriggerHelpers', () => {
ctx.getCredentials.mockResolvedValue(opts.credentials ?? { apiKey });
}
ctx.getRequestObject.mockReturnValue({
header: jest.fn().mockImplementation((name: string) => {
header: vi.fn().mockImplementation((name: string) => {
if (name === 'x-acuity-signature') return signatureHeader;
return null;
}),
@@ -1,6 +1,7 @@
import * as getMany from '../../../../v2/actions/base/getMany.operation';
import * as transport from '../../../../v2/transport';
import { createMockExecuteFunction } from '../helpers';
import type * as _importType0 from '../../../../v2/transport';
const bases = [
{
@@ -20,11 +21,11 @@ const bases = [
},
];
jest.mock('../../../../v2/transport', () => {
const originalModule = jest.requireActual('../../../../v2/transport');
vi.mock('../../../../v2/transport', async () => {
const originalModule = await vi.importActual<typeof _importType0>('../../../../v2/transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return { bases };
}),
};
@@ -1,15 +1,16 @@
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import get from 'lodash/get';
import type { IExecuteFunctions } from 'n8n-workflow';
import * as getSchema from '../../../../v2/actions/base/getSchema.operation';
import * as transport from '../../../../v2/transport';
import type * as _importType0 from '../../../../v2/transport';
jest.mock('../../../../v2/transport', () => {
const originalModule = jest.requireActual('../../../../v2/transport');
vi.mock('../../../../v2/transport', async () => {
const originalModule = await vi.importActual<typeof _importType0>('../../../../v2/transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return { tables: [] };
}),
};
@@ -36,8 +37,8 @@ describe('Test AirtableV2, base => getSchema', () => {
await getSchema.execute.call(
mockDeep<IExecuteFunctions>({
getInputData: jest.fn(() => items),
getNodeParameter: jest.fn((param: string, itemIndex: number) => {
getInputData: vi.fn(() => items),
getNodeParameter: vi.fn((param: string, itemIndex: number) => {
if (param === 'base') {
return items[itemIndex].json.id;
}
@@ -19,10 +19,10 @@ export const createMockExecuteFunction = (
) => {
const mockNode = typeVersion === node.typeVersion ? node : { ...node, typeVersion };
const fakeExecuteFunction = {
getInputData: jest.fn(() => {
getInputData: vi.fn(() => {
return [{ json: {} }];
}),
getNodeParameter: jest.fn(
getNodeParameter: vi.fn(
(
parameterName: string,
_itemIndex: number,
@@ -33,11 +33,11 @@ export const createMockExecuteFunction = (
return get(nodeParameters, parameter, fallbackValue);
},
),
getNode: jest.fn(() => {
getNode: vi.fn(() => {
return mockNode;
}),
helpers: { constructExecutionMetaData: jest.fn(constructExecutionMetaData) },
continueOnFail: jest.fn(() => false),
helpers: { constructExecutionMetaData: vi.fn(constructExecutionMetaData) },
continueOnFail: vi.fn(() => false),
} as unknown as IExecuteFunctions;
return fakeExecuteFunction;
};
@@ -1,19 +1,20 @@
import * as create from '../../../../v2/actions/record/create.operation';
import * as transport from '../../../../v2/transport';
import { createMockExecuteFunction } from '../helpers';
import type * as _importType0 from '../../../../v2/transport';
jest.mock('../../../../v2/transport', () => {
const originalModule = jest.requireActual('../../../../v2/transport');
vi.mock('../../../../v2/transport', async () => {
const originalModule = await vi.importActual<typeof _importType0>('../../../../v2/transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {};
}),
};
});
describe('Test AirtableV2, create operation', () => {
beforeEach(() => jest.clearAllMocks());
beforeEach(() => vi.clearAllMocks());
it('should create a record, autoMapInputData', async () => {
const nodeParameters = {
@@ -1,12 +1,13 @@
import * as deleteRecord from '../../../../v2/actions/record/deleteRecord.operation';
import * as transport from '../../../../v2/transport';
import { createMockExecuteFunction } from '../helpers';
import type * as _importType0 from '../../../../v2/transport';
jest.mock('../../../../v2/transport', () => {
const originalModule = jest.requireActual('../../../../v2/transport');
vi.mock('../../../../v2/transport', async () => {
const originalModule = await vi.importActual<typeof _importType0>('../../../../v2/transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {};
}),
};
@@ -1,12 +1,13 @@
import * as get from '../../../../v2/actions/record/get.operation';
import * as transport from '../../../../v2/transport';
import { createMockExecuteFunction } from '../helpers';
import type * as _importType0 from '../../../../v2/transport';
jest.mock('../../../../v2/transport', () => {
const originalModule = jest.requireActual('../../../../v2/transport');
vi.mock('../../../../v2/transport', async () => {
const originalModule = await vi.importActual<typeof _importType0>('../../../../v2/transport');
return {
...originalModule,
apiRequest: jest.fn(async function (method: string) {
apiRequest: vi.fn(async function (method: string) {
if (method === 'GET') {
return {
id: 'recXXX',
@@ -17,7 +18,7 @@ jest.mock('../../../../v2/transport', () => {
};
}
}),
downloadRecordAttachments: jest.fn(async function () {
downloadRecordAttachments: vi.fn(async function () {
return [
{
json: {
@@ -75,7 +76,7 @@ describe('Test AirtableV2, get operation', () => {
]);
});
afterEach(() => jest.clearAllMocks());
afterEach(() => vi.clearAllMocks());
it('should get a record with attachments and nested fields structure for v2.2', async () => {
const nodeParameters = {
@@ -1,12 +1,13 @@
import * as search from '../../../../v2/actions/record/search.operation';
import * as transport from '../../../../v2/transport';
import { createMockExecuteFunction } from '../helpers';
import type * as _importType0 from '../../../../v2/transport';
jest.mock('../../../../v2/transport', () => {
const originalModule = jest.requireActual('../../../../v2/transport');
vi.mock('../../../../v2/transport', async () => {
const originalModule = await vi.importActual<typeof _importType0>('../../../../v2/transport');
return {
...originalModule,
apiRequest: jest.fn(async function (method: string) {
apiRequest: vi.fn(async function (method: string) {
if (method === 'GET') {
return {
records: [
@@ -21,7 +22,7 @@ jest.mock('../../../../v2/transport', () => {
};
}
}),
apiRequestAllItems: jest.fn(async function (method: string) {
apiRequestAllItems: vi.fn(async function (method: string) {
if (method === 'GET') {
return {
records: [
@@ -43,7 +44,7 @@ jest.mock('../../../../v2/transport', () => {
};
}
}),
downloadRecordAttachments: jest.fn(async function () {
downloadRecordAttachments: vi.fn(async function () {
return [
{
json: {
@@ -166,7 +167,7 @@ describe('Test AirtableV2, search operation', () => {
});
});
afterEach(() => jest.clearAllMocks());
afterEach(() => vi.clearAllMocks());
it('should search records with attachments and nested fields structure for v2.2', async () => {
const nodeParameters = {
@@ -1,22 +1,23 @@
import type { MockProxy } from 'jest-mock-extended';
import { mock } from 'jest-mock-extended';
import type { MockProxy } from 'vitest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { IExecuteFunctions } from 'n8n-workflow';
import * as update from '../../../../v2/actions/record/update.operation';
import * as transport from '../../../../v2/transport';
import { createMockExecuteFunction } from '../helpers';
import type * as _importType0 from '../../../../v2/transport';
jest.mock('../../../../v2/transport', () => {
const originalModule = jest.requireActual('../../../../v2/transport');
vi.mock('../../../../v2/transport', async () => {
const originalModule = await vi.importActual<typeof _importType0>('../../../../v2/transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {};
}),
batchUpdate: jest.fn(async function () {
batchUpdate: vi.fn(async function () {
return {};
}),
apiRequestAllItems: jest.fn(async function (method: string) {
apiRequestAllItems: vi.fn(async function (method: string) {
if (method === 'GET') {
return {
records: [
@@ -45,12 +46,12 @@ describe('Test AirtableV2, update operation', () => {
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should skip validation if typecast option is true', async () => {
mockExecuteFunctions = mock<IExecuteFunctions>();
mockExecuteFunctions.helpers.constructExecutionMetaData = jest.fn(() => []);
mockExecuteFunctions.helpers.constructExecutionMetaData = vi.fn(() => []);
mockExecuteFunctions.getNode.mockReturnValue({
parameters: { columns: { schema: [] } },
} as any);
@@ -85,7 +86,7 @@ describe('Test AirtableV2, update operation', () => {
it('should coerce attachment JSON string to array and allow new single-select option when typecast is true', async () => {
mockExecuteFunctions = mock<IExecuteFunctions>();
mockExecuteFunctions.helpers.constructExecutionMetaData = jest.fn(() => []);
mockExecuteFunctions.helpers.constructExecutionMetaData = vi.fn(() => []);
mockExecuteFunctions.getNode.mockReturnValue({
parameters: {
columns: {
@@ -5,6 +5,7 @@ import { ERROR_MESSAGES, BASE_URL_V2, AIRTOP_HOOKS_BASE_URL } from '../../../con
import * as methods from '../../../methods';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
import type { Mock } from 'vitest';
const AGENTS_ENDPOINT = `${BASE_URL_V2}/agents`;
const AGENTS_HOOKS_ENDPOINT = `${AIRTOP_HOOKS_BASE_URL}/agents`;
@@ -67,7 +68,7 @@ const createMockLoadOptionsFunction = (
getCurrentNodeParameter(parameterName: string) {
return nodeParameters[parameterName];
},
getCredentials: jest.fn(),
getCredentials: vi.fn(),
getNode: () => ({
id: '1',
name: 'Airtop node',
@@ -79,25 +80,21 @@ const createMockLoadOptionsFunction = (
} as unknown as ILoadOptionsFunctions;
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(),
apiRequest: vi.fn(),
};
});
describe('Test Airtop, agent run operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
});
afterEach(() => {
jest.resetAllMocks();
vi.resetAllMocks();
});
it('should list available agents', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce(mockAgentsListResponse);
const mockLoadOptions = createMockLoadOptionsFunction();
@@ -121,7 +118,7 @@ describe('Test Airtop, agent run operation', () => {
});
it('should get agent input parameters schema for selected agent ID', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce(mockAgentDetailsResponse);
const mockLoadOptions = createMockLoadOptionsFunction({
@@ -164,7 +161,7 @@ describe('Test Airtop, agent run operation', () => {
});
it('should validate required agent parameters', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce(mockAgentDetailsResponse);
const nodeParameters = {
@@ -191,7 +188,7 @@ describe('Test Airtop, agent run operation', () => {
});
it('should return invocationId without waiting for agent completion', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
// First call: getAgentDetails
apiRequestMock.mockResolvedValueOnce(mockAgentDetailsResponse);
// Second call: invoke agent webhook
@@ -236,7 +233,7 @@ describe('Test Airtop, agent run operation', () => {
});
it('should wait for agent until response contains an output', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
// Mock getAgentDetails
apiRequestMock.mockResolvedValueOnce(mockAgentDetailsResponse);
@@ -320,7 +317,7 @@ describe('Test Airtop, agent run operation', () => {
});
it('should return empty results when no agents are available', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce({ agents: [] });
const mockLoadOptions = createMockLoadOptionsFunction();
@@ -330,7 +327,7 @@ describe('Test Airtop, agent run operation', () => {
});
it('should return empty fields when agent has no parameters schema', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce({
id: 'test-agent-123',
name: 'Test Agent',
@@ -350,7 +347,7 @@ describe('Test Airtop, agent run operation', () => {
});
it('should filter agents by name when search filter is provided', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce(mockAgentsListResponse);
const mockLoadOptions = createMockLoadOptionsFunction();
@@ -365,7 +362,7 @@ describe('Test Airtop, agent run operation', () => {
});
it('should wrap agent parameters in configVars when executing', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
// First call: getAgentDetails
apiRequestMock.mockResolvedValueOnce(mockAgentDetailsResponse);
// Second call: invoke agent webhook
@@ -413,7 +410,7 @@ describe('Test Airtop, agent run operation', () => {
});
it('should pass all required parameters successfully', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce(mockAgentDetailsResponse);
apiRequestMock.mockResolvedValueOnce(mockInvocationResponse);
apiRequestMock.mockResolvedValueOnce(mockAgentStatusResponseWithOutput);
@@ -26,11 +26,11 @@ const mockResponse = {
const mockJsonSchema =
'{"type":"object","properties":{"title":{"type":"string"},"price":{"type":"string"}}}';
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async (method: string, endpoint: string) => {
apiRequest: vi.fn(async (method: string, endpoint: string) => {
// For paginated extraction requests
if (endpoint.includes('/paginated-extraction')) {
return mockResponse;
@@ -46,24 +46,26 @@ jest.mock('../../../transport', () => {
};
});
jest.mock('../../../GenericFunctions', () => {
const originalModule = jest.requireActual<typeof GenericFunctions>('../../../GenericFunctions');
vi.mock('../../../GenericFunctions', async () => {
const originalModule = await vi.importActual<typeof GenericFunctions>(
'../../../GenericFunctions',
);
return {
...originalModule,
createSessionAndWindow: jest.fn().mockImplementation(async () => {
createSessionAndWindow: vi.fn().mockImplementation(async () => {
return {
sessionId: 'new-session-123',
windowId: 'new-window-123',
};
}),
shouldCreateNewSession: jest.fn().mockImplementation(function (
shouldCreateNewSession: vi.fn().mockImplementation(function (
this: IExecuteFunctions,
index: number,
) {
const sessionMode = this.getNodeParameter('sessionMode', index) as string;
return sessionMode === 'new';
}),
validateAirtopApiResponse: jest.fn(),
validateAirtopApiResponse: vi.fn(),
};
});
@@ -74,12 +76,10 @@ describe('Test Airtop, getPaginated operation', () => {
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
jest.unmock('../../../GenericFunctions');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should extract data with minimal parameters', async () => {
@@ -25,11 +25,11 @@ const mockResponse = {
const mockJsonSchema =
'{"type":"object","properties":{"productCount":{"type":"number"},"priceRange":{"type":"object"}}}';
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function (method: string, endpoint: string) {
apiRequest: vi.fn(async function (method: string, endpoint: string) {
if (method === 'DELETE' && endpoint.includes('/sessions/')) {
return { status: 'success' };
}
@@ -38,17 +38,19 @@ jest.mock('../../../transport', () => {
};
});
jest.mock('../../../GenericFunctions', () => {
const originalModule = jest.requireActual<typeof GenericFunctions>('../../../GenericFunctions');
vi.mock('../../../GenericFunctions', async () => {
const originalModule = await vi.importActual<typeof GenericFunctions>(
'../../../GenericFunctions',
);
return {
...originalModule,
createSessionAndWindow: jest.fn().mockImplementation(async () => {
createSessionAndWindow: vi.fn().mockImplementation(async () => {
return {
sessionId: 'new-session-456',
windowId: 'new-win-456',
};
}),
shouldCreateNewSession: jest.fn().mockImplementation(function (this: any) {
shouldCreateNewSession: vi.fn().mockImplementation(function (this: any) {
const sessionMode = this.getNodeParameter('sessionMode', 0);
return sessionMode === 'new';
}),
@@ -62,12 +64,10 @@ describe('Test Airtop, query page operation', () => {
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
jest.unmock('../../../GenericFunctions');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should query the page with minimal parameters using existing session', async () => {
@@ -20,11 +20,11 @@ const mockResponse = {
},
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function (method: string, endpoint: string) {
apiRequest: vi.fn(async function (method: string, endpoint: string) {
if (method === 'DELETE' && endpoint.includes('/sessions/')) {
return { status: 'success' };
}
@@ -33,17 +33,19 @@ jest.mock('../../../transport', () => {
};
});
jest.mock('../../../GenericFunctions', () => {
const originalModule = jest.requireActual<typeof GenericFunctions>('../../../GenericFunctions');
vi.mock('../../../GenericFunctions', async () => {
const originalModule = await vi.importActual<typeof GenericFunctions>(
'../../../GenericFunctions',
);
return {
...originalModule,
createSessionAndWindow: jest.fn().mockImplementation(async () => {
createSessionAndWindow: vi.fn().mockImplementation(async () => {
return {
sessionId: 'new-session-456',
windowId: 'new-win-456',
};
}),
shouldCreateNewSession: jest.fn().mockImplementation(function (this: any) {
shouldCreateNewSession: vi.fn().mockImplementation(function (this: any) {
const sessionMode = this.getNodeParameter('sessionMode', 0);
return sessionMode === 'new';
}),
@@ -57,12 +59,10 @@ describe('Test Airtop, scrape operation', () => {
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
jest.unmock('../../../GenericFunctions');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should scrape content with minimal parameters using existing session', async () => {
@@ -10,21 +10,17 @@ const baseNodeParameters = {
fileId: 'file-123',
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn().mockResolvedValue({}),
apiRequest: vi.fn().mockResolvedValue({}),
};
});
describe('Test Airtop, delete file operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should delete file successfully', async () => {
@@ -2,6 +2,7 @@ import * as get from '../../../actions/file/get.operation';
import { ERROR_MESSAGES } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
import type { Mock } from 'vitest';
const baseNodeParameters = {
resource: 'file',
@@ -28,25 +29,21 @@ const mockPreparedBinaryData = {
data: 'mock-base64-data',
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(),
apiRequest: vi.fn(),
};
});
describe('Test Airtop, get file operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should get file details successfully', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce(mockFileResponse);
const result = await get.execute.call(createMockExecuteFunction(baseNodeParameters), 0);
@@ -63,7 +60,7 @@ describe('Test Airtop, get file operation', () => {
});
it('should output file with binary data when specified', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce(mockFileResponse);
const nodeParameters = {
@@ -73,8 +70,8 @@ describe('Test Airtop, get file operation', () => {
const mockExecuteFunction = createMockExecuteFunction(nodeParameters);
mockExecuteFunction.helpers.httpRequest = jest.fn().mockResolvedValue(mockBinaryBuffer);
mockExecuteFunction.helpers.prepareBinaryData = jest
mockExecuteFunction.helpers.httpRequest = vi.fn().mockResolvedValue(mockBinaryBuffer);
mockExecuteFunction.helpers.prepareBinaryData = vi
.fn()
.mockResolvedValue(mockPreparedBinaryData);
@@ -1,6 +1,7 @@
import * as getMany from '../../../actions/file/getMany.operation';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
import type { Mock } from 'vitest';
const baseNodeParameters = {
resource: 'file',
@@ -43,25 +44,21 @@ const mockPaginatedResponse = {
},
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(),
apiRequest: vi.fn(),
};
});
describe('Test Airtop, get many files operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should get all files successfully', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce(mockFilesResponse);
const result = await getMany.execute.call(createMockExecuteFunction(baseNodeParameters), 0);
@@ -87,7 +84,7 @@ describe('Test Airtop, get many files operation', () => {
});
it('should handle limited results', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce(mockPaginatedResponse);
const nodeParameters = {
@@ -2,6 +2,7 @@ import * as helpers from '../../../actions/file/helpers';
import * as GenericFunctions from '../../../GenericFunctions';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
import type { Mock } from 'vitest';
const mockFileCreateResponse = {
data: {
@@ -11,37 +12,34 @@ const mockFileCreateResponse = {
};
// Mock the transport and other dependencies
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async () => {}),
apiRequest: vi.fn(async () => {}),
};
});
jest.mock('../../../GenericFunctions', () => {
const originalModule = jest.requireActual<typeof GenericFunctions>('../../../GenericFunctions');
vi.mock('../../../GenericFunctions', async () => {
const originalModule = await vi.importActual<typeof GenericFunctions>(
'../../../GenericFunctions',
);
return {
...originalModule,
waitForSessionEvent: jest.fn(),
waitForSessionEvent: vi.fn(),
};
});
describe('Test Airtop file helpers', () => {
afterAll(() => {
jest.unmock('../../../transport');
jest.unmock('../../../GenericFunctions');
});
afterEach(() => {
jest.clearAllMocks();
(transport.apiRequest as jest.Mock).mockReset();
(GenericFunctions.waitForSessionEvent as jest.Mock).mockReset();
vi.clearAllMocks();
(transport.apiRequest as Mock).mockReset();
(GenericFunctions.waitForSessionEvent as Mock).mockReset();
});
describe('requestAllFiles', () => {
it('should request all files with pagination', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
const mockFilesResponse1 = {
data: {
files: [{ id: 'file-1' }, { id: 'file-2' }],
@@ -90,7 +88,7 @@ describe('Test Airtop file helpers', () => {
});
it('should handle empty response', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
const mockEmptyResponse = {
data: {
files: [],
@@ -117,7 +115,7 @@ describe('Test Airtop file helpers', () => {
describe('pollFileUntilAvailable', () => {
it('should poll until file is available', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock
.mockResolvedValueOnce({ data: { status: 'uploading' } })
.mockResolvedValueOnce({ data: { status: 'available' } });
@@ -137,7 +135,7 @@ describe('Test Airtop file helpers', () => {
});
it('should throw timeout error if file never becomes available', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValue({ data: { status: 'processing' } });
const promise = helpers.pollFileUntilAvailable.call(
@@ -152,15 +150,15 @@ describe('Test Airtop file helpers', () => {
describe('createAndUploadFile', () => {
it('should create file entry, upload file, and poll until available', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock
.mockResolvedValueOnce(mockFileCreateResponse)
.mockResolvedValueOnce({ data: { status: 'available' } });
const mockExecuteFunction = createMockExecuteFunction({});
const mockHttpRequest = jest.fn().mockResolvedValueOnce({});
const mockHttpRequest = vi.fn().mockResolvedValueOnce({});
mockExecuteFunction.helpers.httpRequest = mockHttpRequest;
const pollingFunctionMock = jest.fn().mockResolvedValueOnce(mockFileCreateResponse.data.id);
const pollingFunctionMock = vi.fn().mockResolvedValueOnce(mockFileCreateResponse.data.id);
const result = await helpers.createAndUploadFile.call(
mockExecuteFunction,
@@ -190,7 +188,7 @@ describe('Test Airtop file helpers', () => {
});
it('should throw error if file creation response is missing id or upload URL', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce({});
await expect(
@@ -206,7 +204,7 @@ describe('Test Airtop file helpers', () => {
describe('waitForFileInSession', () => {
it('should resolve when file_upload_status event with available status is received', async () => {
const waitForSessionEventMock = GenericFunctions.waitForSessionEvent as jest.Mock;
const waitForSessionEventMock = GenericFunctions.waitForSessionEvent as Mock;
const mockEvent = {
event: 'file_upload_status',
status: 'available',
@@ -227,7 +225,7 @@ describe('Test Airtop file helpers', () => {
});
it('should throw error when uploading a file with invalid file format', async () => {
const waitForSessionEventMock = GenericFunctions.waitForSessionEvent as jest.Mock;
const waitForSessionEventMock = GenericFunctions.waitForSessionEvent as Mock;
const mockEvent = {
event: 'file_upload_status',
status: 'upload_failed',
@@ -246,7 +244,7 @@ describe('Test Airtop file helpers', () => {
});
it('should throw error when upload_failed status is received', async () => {
const waitForSessionEventMock = GenericFunctions.waitForSessionEvent as jest.Mock;
const waitForSessionEventMock = GenericFunctions.waitForSessionEvent as Mock;
const mockEvent = {
fileId: 'file-123',
event: 'file_upload_status',
@@ -266,7 +264,7 @@ describe('Test Airtop file helpers', () => {
});
it('should timeout if no matching event is received', async () => {
const waitForSessionEventMock = GenericFunctions.waitForSessionEvent as jest.Mock;
const waitForSessionEventMock = GenericFunctions.waitForSessionEvent as Mock;
waitForSessionEventMock.mockRejectedValueOnce(new Error('Timeout reached'));
const mockExecuteFunction = createMockExecuteFunction({});
@@ -279,13 +277,13 @@ describe('Test Airtop file helpers', () => {
describe('pushFileToSession', () => {
it('should push file to session and wait', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
const mockFileId = 'file-123';
const mockSessionId = 'session-123';
apiRequestMock.mockResolvedValueOnce({});
// Mock waitForFileInSession
const waitForFileInSessionMock = jest.fn().mockResolvedValueOnce({});
const waitForFileInSessionMock = vi.fn().mockResolvedValueOnce({});
// Call the function
await helpers.pushFileToSession.call(
@@ -305,7 +303,7 @@ describe('Test Airtop file helpers', () => {
describe('triggerFileInput', () => {
it('should trigger file input in window', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce({});
const mockFileId = 'file-123';
const mockWindowId = 'window-123';
@@ -337,7 +335,7 @@ describe('Test Airtop file helpers', () => {
const mockBuffer = [1, 2, 3];
// Mock http request
const mockHttpRequest = jest.fn().mockResolvedValueOnce(mockBuffer);
const mockHttpRequest = vi.fn().mockResolvedValueOnce(mockBuffer);
// Create mock execute function with http request helper
const mockExecuteFunction = createMockExecuteFunction({});
@@ -358,7 +356,7 @@ describe('Test Airtop file helpers', () => {
const mockBuffer = [1, 2, 3];
// Mock getBinaryDataBuffer
const mockGetBinaryDataBuffer = jest.fn().mockResolvedValue(mockBuffer);
const mockGetBinaryDataBuffer = vi.fn().mockResolvedValue(mockBuffer);
// Create mock execute function with getBinaryDataBuffer helper
const mockExecuteFunction = createMockExecuteFunction({});
@@ -20,11 +20,11 @@ const mockResponse = {
message: 'Click executed successfully',
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {
status: 'success',
data: mockResponse,
@@ -40,11 +40,10 @@ describe('Test Airtop, click operation', () => {
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should execute click with minimal parameters', async () => {
@@ -2,6 +2,7 @@ import * as fill from '../../../actions/interaction/fill.operation';
import { ERROR_MESSAGES } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
import type { Mock } from 'vitest';
const baseNodeParameters = {
resource: 'interaction',
@@ -24,25 +25,21 @@ const mockCompletedResponse = {
},
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(),
apiRequest: vi.fn(),
};
});
describe('Test Airtop, fill form operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should execute fill operation successfully', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
// Mock the initial async request
apiRequestMock.mockResolvedValueOnce(mockAsyncResponse);
@@ -94,7 +91,7 @@ describe('Test Airtop, fill form operation', () => {
});
it('should throw error when operation times out after 2 sec', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
const nodeParameters = {
...baseNodeParameters,
};
@@ -113,7 +110,7 @@ describe('Test Airtop, fill form operation', () => {
});
it('should handle error status in response', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
const errorResponse = {
status: 'error',
error: {
@@ -19,11 +19,11 @@ const mockResponse = {
message: 'Hover interaction executed successfully',
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {
status: 'success',
data: mockResponse,
@@ -39,11 +39,10 @@ describe('Test Airtop, hover operation', () => {
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should execute hover with minimal parameters', async () => {
@@ -2,6 +2,7 @@ import * as scroll from '../../../actions/interaction/scroll.operation';
import { ERROR_MESSAGES } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
import type { Mock } from 'vitest';
const baseNodeParameters = {
resource: 'interaction',
@@ -40,25 +41,21 @@ const mockResponse = {
message: 'Scrolled successfully',
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(),
apiRequest: vi.fn(),
};
});
describe('Test Airtop, scroll operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should execute automatic scroll operation successfully', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce(mockResponse);
const result = await scroll.execute.call(
@@ -88,7 +85,7 @@ describe('Test Airtop, scroll operation', () => {
});
it('should execute manual scroll operation successfully', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
apiRequestMock.mockResolvedValueOnce(mockResponse);
const result = await scroll.execute.call(
@@ -152,7 +149,7 @@ describe('Test Airtop, scroll operation', () => {
});
it('should throw an error when the API returns an error response', async () => {
const apiRequestMock = transport.apiRequest as jest.Mock;
const apiRequestMock = transport.apiRequest as Mock;
const errorResponse = {
errors: [
{
@@ -21,11 +21,11 @@ const mockResponse = {
message: 'Text typed successfully',
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {
status: 'success',
data: mockResponse,
@@ -41,11 +41,10 @@ describe('Test Airtop, type operation', () => {
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should execute type with minimal parameters', async () => {
@@ -16,11 +16,11 @@ const baseNodeParameters = {
saveProfileOnTermination: false,
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {
...mockCreatedSession,
};
@@ -29,12 +29,8 @@ jest.mock('../../../transport', () => {
});
describe('Test Airtop, session create operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
/**
* Minimal parameters
@@ -3,11 +3,11 @@ import { ERROR_MESSAGES } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {
status: 'success',
message: 'Profile will be saved on session termination',
@@ -24,12 +24,8 @@ const baseParameters = {
};
describe('Test Airtop, session save operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should save a profile on session termination successfully', async () => {
@@ -3,11 +3,11 @@ import { ERROR_MESSAGES } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {
status: 'success',
};
@@ -16,12 +16,8 @@ jest.mock('../../../transport', () => {
});
describe('Test Airtop, session terminate operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should terminate a session successfully', async () => {
@@ -2,22 +2,21 @@ import * as waitForDownload from '../../../actions/session/waitForDownload.opera
import { ERROR_MESSAGES } from '../../../constants';
import * as GenericFunctions from '../../../GenericFunctions';
import { createMockExecuteFunction } from '../helpers';
import type { Mock } from 'vitest';
jest.mock('../../../GenericFunctions', () => {
const originalModule = jest.requireActual<typeof GenericFunctions>('../../../GenericFunctions');
vi.mock('../../../GenericFunctions', async () => {
const originalModule = await vi.importActual<typeof GenericFunctions>(
'../../../GenericFunctions',
);
return {
...originalModule,
waitForSessionEvent: jest.fn(),
waitForSessionEvent: vi.fn(),
};
});
describe('Test Airtop, session waitForDownload operation', () => {
afterAll(() => {
jest.unmock('../../../GenericFunctions');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should wait for download successfully', async () => {
@@ -28,7 +27,7 @@ describe('Test Airtop, session waitForDownload operation', () => {
downloadUrl: 'https://example.com/download/test-file-123',
};
(GenericFunctions.waitForSessionEvent as jest.Mock).mockResolvedValue(mockEvent);
(GenericFunctions.waitForSessionEvent as Mock).mockResolvedValue(mockEvent);
const nodeParameters = {
resource: 'session',
@@ -5,11 +5,11 @@ import { ERROR_MESSAGES } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {
status: 'success',
data: {
@@ -27,11 +27,10 @@ describe('Test Airtop, window close operation', () => {
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should close a window successfully', async () => {
@@ -15,11 +15,11 @@ const baseNodeParameters = {
additionalFields: {},
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function (method: string) {
apiRequest: vi.fn(async function (method: string) {
if (method === 'GET') {
return {
status: 'success',
@@ -45,11 +45,10 @@ describe('Test Airtop, window create operation', () => {
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should create a window with minimal parameters', async () => {
@@ -19,11 +19,11 @@ const mockResponse = {
message: 'Page loaded successfully',
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {
status: 'success',
data: mockResponse,
@@ -39,11 +39,10 @@ describe('Test Airtop, window load operation', () => {
afterAll(() => {
nock.restore();
jest.unmock('../../../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should load URL with minimal parameters', async () => {
@@ -38,11 +38,11 @@ const expectedBinaryResult = {
},
};
jest.mock('../../../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../../../transport');
vi.mock('../../../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../../../transport');
return {
...originalModule,
apiRequest: jest.fn(async function () {
apiRequest: vi.fn(async function () {
return {
status: 'success',
...mockResponse,
@@ -51,22 +51,19 @@ jest.mock('../../../transport', () => {
};
});
jest.mock('../../../GenericFunctions', () => {
const originalModule = jest.requireActual<typeof GenericFunctions>('../../../GenericFunctions');
vi.mock('../../../GenericFunctions', async () => {
const originalModule = await vi.importActual<typeof GenericFunctions>(
'../../../GenericFunctions',
);
return {
...originalModule,
convertScreenshotToBinary: jest.fn(() => mockBinaryBuffer),
convertScreenshotToBinary: vi.fn(() => mockBinaryBuffer),
};
});
describe('Test Airtop, take screenshot operation', () => {
afterAll(() => {
jest.unmock('../../../transport');
jest.unmock('../../../GenericFunctions');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should take screenshot in base64 format', async () => {
@@ -5,11 +5,11 @@ import { SESSION_MODE } from '../actions/common/fields';
import { executeRequestWithSessionManagement } from '../actions/common/session.utils';
import * as transport from '../transport';
jest.mock('../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../transport');
vi.mock('../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../transport');
return {
...originalModule,
apiRequest: jest.fn(async () => {
apiRequest: vi.fn(async () => {
return {
success: true,
};
@@ -17,29 +17,25 @@ jest.mock('../transport', () => {
};
});
jest.mock('../GenericFunctions', () => ({
shouldCreateNewSession: jest.fn(function (this: IExecuteFunctions, index: number) {
vi.mock('../GenericFunctions', () => ({
shouldCreateNewSession: vi.fn(function (this: IExecuteFunctions, index: number) {
const sessionMode = this.getNodeParameter('sessionMode', index);
return sessionMode === SESSION_MODE.NEW;
}),
createSessionAndWindow: jest.fn(async () => ({
createSessionAndWindow: vi.fn(async () => ({
sessionId: 'new-session-123',
windowId: 'new-window-123',
})),
validateSessionAndWindowId: jest.fn(() => ({
validateSessionAndWindowId: vi.fn(() => ({
sessionId: 'existing-session-123',
windowId: 'existing-window-123',
})),
validateAirtopApiResponse: jest.fn(),
validateAirtopApiResponse: vi.fn(),
}));
describe('executeRequestWithSessionManagement', () => {
afterAll(() => {
jest.unmock('../transport');
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
describe("When 'sessionMode' is 'new'", () => {
@@ -23,11 +23,11 @@ const mockCreatedSession = {
data: { id: 'new-session-123', status: SESSION_STATUS.RUNNING },
};
jest.mock('../transport', () => {
const originalModule = jest.requireActual<typeof transport>('../transport');
vi.mock('../transport', async () => {
const originalModule = await vi.importActual<typeof transport>('../transport');
return {
...originalModule,
apiRequest: jest.fn(async (method: string, endpoint: string, params: { fail?: boolean }) => {
apiRequest: vi.fn(async (method: string, endpoint: string, params: { fail?: boolean }) => {
// return failed request
if (endpoint.endsWith('/sessions') && params.fail) {
return {};
@@ -441,8 +441,8 @@ describe('Test Airtop utils', () => {
warnings: [],
};
const expectedError = new NodeApiError(mockNode, { message: 'Error 1\nError 2' });
expect(() => validateAirtopApiResponse(mockNode, response)).toThrow(expectedError);
expect(() => validateAirtopApiResponse(mockNode, response)).toThrow(NodeApiError);
expect(() => validateAirtopApiResponse(mockNode, response)).toThrow('Error 1\nError 2');
});
});
@@ -1,4 +1,4 @@
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type {
ICredentialDataDecryptedObject,
IExecuteFunctions,
@@ -11,24 +11,24 @@ import { Amqp } from './Amqp.node';
// Mock the entire rhea module
const mockSender = {
close: jest.fn(),
send: jest.fn().mockReturnValue({ id: 'test-message-id' }),
close: vi.fn(),
send: vi.fn().mockReturnValue({ id: 'test-message-id' }),
};
const mockConnection = {
close: jest.fn(),
open_sender: jest.fn().mockReturnValue(mockSender),
close: vi.fn(),
open_sender: vi.fn().mockReturnValue(mockSender),
options: { reconnect: true },
};
const mockContainer = {
connect: jest.fn().mockReturnValue(mockConnection),
on: jest.fn(),
once: jest.fn(),
connect: vi.fn().mockReturnValue(mockConnection),
on: vi.fn(),
once: vi.fn(),
};
jest.mock('rhea', () => ({
create_container: jest.fn(() => mockContainer),
vi.mock('rhea', () => ({
create_container: vi.fn(() => mockContainer),
}));
describe('AMQP Node', () => {
@@ -41,12 +41,12 @@ describe('AMQP Node', () => {
});
const executeFunctions = mock<IExecuteFunctions>({
getNode: jest.fn().mockReturnValue({ name: 'AMQP Test Node' }),
continueOnFail: jest.fn().mockReturnValue(false),
getNode: vi.fn().mockReturnValue({ name: 'AMQP Test Node' }),
continueOnFail: vi.fn().mockReturnValue(false),
});
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
executeFunctions.getCredentials.calledWith('amqp').mockResolvedValue(credentials);
executeFunctions.getInputData.mockReturnValue([{ json: { testing: true } }]);
@@ -1,36 +1,36 @@
import { testTriggerNode } from '@test/nodes/TriggerHelpers';
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import { NodeOperationError } from 'n8n-workflow';
import type { ITriggerFunctions } from 'n8n-workflow';
import { AmqpTrigger } from './AmqpTrigger.node';
let eventHandlers: Record<string, (...args: unknown[]) => void> = {};
const mockAddCredit = jest.fn();
const mockClose = jest.fn();
const mockOpenReceiver = jest.fn();
const mockEmitExecutionError = jest.fn();
const mockAddCredit = vi.fn();
const mockClose = vi.fn();
const mockOpenReceiver = vi.fn();
const mockEmitExecutionError = vi.fn();
const mockConnection = {
open_receiver: mockOpenReceiver,
close: mockClose,
};
jest.mock('rhea', () => ({
create_container: jest.fn(() => ({
vi.mock('rhea', () => ({
create_container: vi.fn(() => ({
on: (event: string, handler: (...args: unknown[]) => void) => {
eventHandlers[event] = handler;
},
removeAllListeners: jest.fn((event: string) => {
removeAllListeners: vi.fn((event: string) => {
delete eventHandlers[event];
}),
connect: jest.fn(() => mockConnection),
connect: vi.fn(() => mockConnection),
})),
}));
describe('AMQP Trigger Node', () => {
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
eventHandlers = {};
mockEmitExecutionError.mockClear();
});
@@ -94,7 +94,7 @@ describe('AMQP Trigger Node', () => {
});
it('should reject in manual mode after 15s with no message', async () => {
const timeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation((fn) => {
const timeoutSpy = vi.spyOn(global, 'setTimeout').mockImplementation((fn) => {
fn(); // fire immediately
return 1 as unknown as NodeJS.Timeout;
});
@@ -131,8 +131,8 @@ describe('AMQP Trigger Node', () => {
it('should call saveFailedExecution when handleMessage throws an error in trigger mode', async () => {
const trigger = new AmqpTrigger();
const emit = jest.fn();
const saveFailedExecution = jest.fn();
const emit = vi.fn();
const saveFailedExecution = vi.fn();
const triggerFunctions = mockDeep<ITriggerFunctions>();
Object.assign(triggerFunctions, { emit, saveFailedExecution });
@@ -160,7 +160,7 @@ describe('AMQP Trigger Node', () => {
eventHandlers['message']({
message: { body: 'invalid json {', message_id: 1 },
receiver: {
has_credit: jest.fn().mockReturnValue(true),
has_credit: vi.fn().mockReturnValue(true),
},
});
@@ -171,7 +171,7 @@ describe('AMQP Trigger Node', () => {
it('should handle errors in manual mode and reject the promise', async () => {
const trigger = new AmqpTrigger();
const emit = jest.fn();
const emit = vi.fn();
const triggerFunctions = mockDeep<ITriggerFunctions>();
Object.assign(triggerFunctions, { emit });
@@ -271,7 +271,7 @@ describe('AMQP Trigger Node', () => {
eventHandlers['message']({
message,
receiver: {
has_credit: jest.fn().mockReturnValue(true),
has_credit: vi.fn().mockReturnValue(true),
},
});
@@ -279,7 +279,7 @@ describe('AMQP Trigger Node', () => {
});
it('should add credit when receiver has no credit', async () => {
const addCreditSpy = jest.fn();
const addCreditSpy = vi.fn();
await testTriggerNode(AmqpTrigger, {
mode: 'trigger',
node: {
@@ -291,18 +291,18 @@ describe('AMQP Trigger Node', () => {
credential: { hostname: 'localhost', port: 5672 },
});
jest.useFakeTimers();
vi.useFakeTimers();
const message = { body: 'hello', message_id: 1 };
eventHandlers['message']({
message,
receiver: {
has_credit: jest.fn().mockReturnValue(false),
has_credit: vi.fn().mockReturnValue(false),
add_credit: addCreditSpy,
},
});
jest.advanceTimersByTime(10);
jest.useRealTimers();
vi.advanceTimersByTime(10);
vi.useRealTimers();
expect(addCreditSpy).toHaveBeenCalledWith(100);
});
@@ -1,33 +1,34 @@
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import type { ITriggerFunctions, IDeferredPromise, IRun } from 'n8n-workflow';
import type { EventContext } from 'rhea';
import { handleMessage } from './handleMessage';
import type { Mock, Mocked } from 'vitest';
interface MockReceiver {
has_credit: jest.Mock<boolean>;
add_credit: jest.Mock;
has_credit: Mock<() => boolean>;
add_credit: Mock;
}
describe('handleMessage', () => {
let mockTriggerFunctions: jest.Mocked<ITriggerFunctions>;
let mockTriggerFunctions: Mocked<ITriggerFunctions>;
let mockContext: EventContext;
let mockReceiver: MockReceiver;
let mockDeferredPromise: jest.Mocked<IDeferredPromise<IRun>>;
let mockDeferredPromise: Mocked<IDeferredPromise<IRun>>;
beforeEach(() => {
jest.clearAllMocks();
jest.useFakeTimers();
vi.clearAllMocks();
vi.useFakeTimers();
mockDeferredPromise = {
promise: Promise.resolve({} as IRun),
resolve: jest.fn(),
reject: jest.fn(),
} as jest.Mocked<IDeferredPromise<IRun>>;
resolve: vi.fn(),
reject: vi.fn(),
} as Mocked<IDeferredPromise<IRun>>;
mockReceiver = {
has_credit: jest.fn<boolean, []>().mockReturnValue(true),
add_credit: jest.fn(),
has_credit: vi.fn<() => boolean>().mockReturnValue(true),
add_credit: vi.fn(),
};
mockContext = {
@@ -40,15 +41,15 @@ describe('handleMessage', () => {
mockTriggerFunctions = mockDeep<ITriggerFunctions>({
helpers: {
createDeferredPromise: jest.fn().mockReturnValue(mockDeferredPromise),
returnJsonArray: jest.fn((data) => data),
createDeferredPromise: vi.fn().mockReturnValue(mockDeferredPromise),
returnJsonArray: vi.fn((data) => data),
},
emit: jest.fn(),
emit: vi.fn(),
});
});
afterEach(() => {
jest.useRealTimers();
vi.useRealTimers();
});
describe('message handling', () => {
@@ -287,7 +288,7 @@ describe('handleMessage', () => {
parallelProcessing: false,
});
jest.advanceTimersByTime(100);
vi.advanceTimersByTime(100);
await handlePromise;
expect(promiseResolved).toBe(true);
@@ -304,7 +305,7 @@ describe('handleMessage', () => {
sleepTime: 20,
});
jest.advanceTimersByTime(25);
vi.advanceTimersByTime(25);
expect(mockReceiver.add_credit).toHaveBeenCalledWith(50);
});
@@ -317,7 +318,7 @@ describe('handleMessage', () => {
pullMessagesNumber: 100,
});
jest.advanceTimersByTime(20);
vi.advanceTimersByTime(20);
expect(mockReceiver.add_credit).not.toHaveBeenCalled();
});
@@ -330,7 +331,7 @@ describe('handleMessage', () => {
pullMessagesNumber: 100,
});
jest.advanceTimersByTime(15);
vi.advanceTimersByTime(15);
expect(mockReceiver.add_credit).toHaveBeenCalledWith(100);
});
@@ -344,10 +345,10 @@ describe('handleMessage', () => {
sleepTime: 50,
});
jest.advanceTimersByTime(30);
vi.advanceTimersByTime(30);
expect(mockReceiver.add_credit).not.toHaveBeenCalled();
jest.advanceTimersByTime(25);
vi.advanceTimersByTime(25);
expect(mockReceiver.add_credit).toHaveBeenCalledWith(100);
});
@@ -2,14 +2,15 @@ import type { IWebhookFunctions } from 'n8n-workflow';
import { AsanaTrigger } from '../AsanaTrigger.node';
import { verifySignature } from '../AsanaTriggerHelpers';
import type { Mock, Mocked } from 'vitest';
jest.mock('../AsanaTriggerHelpers');
jest.mock('../GenericFunctions');
vi.mock('../AsanaTriggerHelpers');
vi.mock('../GenericFunctions');
describe('AsanaTrigger', () => {
let trigger: AsanaTrigger;
let mockWebhookFunctions: Pick<
jest.Mocked<IWebhookFunctions>,
Mocked<IWebhookFunctions>,
| 'getBodyData'
| 'getHeaderData'
| 'getRequestObject'
@@ -19,17 +20,17 @@ describe('AsanaTrigger', () => {
>;
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
trigger = new AsanaTrigger();
mockWebhookFunctions = {
getBodyData: jest.fn(),
getHeaderData: jest.fn(),
getRequestObject: jest.fn(),
getResponseObject: jest.fn(),
getWorkflowStaticData: jest.fn(),
getBodyData: vi.fn(),
getHeaderData: vi.fn(),
getRequestObject: vi.fn(),
getResponseObject: vi.fn(),
getWorkflowStaticData: vi.fn(),
helpers: {
returnJsonArray: jest.fn((data) => data),
returnJsonArray: vi.fn((data) => data),
} as any,
};
});
@@ -38,9 +39,9 @@ describe('AsanaTrigger', () => {
it('should complete the handshake when X-Hook-Secret header is present', async () => {
const handshakeSecret = 'asana-handshake-secret';
const mockResponse = {
set: jest.fn().mockReturnThis(),
status: jest.fn().mockReturnThis(),
end: jest.fn(),
set: vi.fn().mockReturnThis(),
status: vi.fn().mockReturnThis(),
end: vi.fn(),
};
const webhookData: any = {};
@@ -65,12 +66,12 @@ describe('AsanaTrigger', () => {
it('should return 401 when signature verification fails', async () => {
const mockResponse = {
status: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
end: jest.fn(),
status: vi.fn().mockReturnThis(),
send: vi.fn().mockReturnThis(),
end: vi.fn(),
};
(verifySignature as jest.Mock).mockReturnValue(false);
(verifySignature as Mock).mockReturnValue(false);
mockWebhookFunctions.getBodyData.mockReturnValue({});
mockWebhookFunctions.getHeaderData.mockReturnValue({});
mockWebhookFunctions.getRequestObject.mockReturnValue({} as any);
@@ -90,7 +91,7 @@ describe('AsanaTrigger', () => {
it('should process events when signature verification passes', async () => {
const events = [{ action: 'changed', resource: { gid: '1' } }];
(verifySignature as jest.Mock).mockReturnValue(true);
(verifySignature as Mock).mockReturnValue(true);
mockWebhookFunctions.getBodyData.mockReturnValue({ events });
mockWebhookFunctions.getHeaderData.mockReturnValue({});
mockWebhookFunctions.getRequestObject.mockReturnValue({
@@ -112,7 +113,7 @@ describe('AsanaTrigger', () => {
it('should process events when no secret is configured (backward compatibility)', async () => {
const events = [{ action: 'added' }];
(verifySignature as jest.Mock).mockReturnValue(true);
(verifySignature as Mock).mockReturnValue(true);
mockWebhookFunctions.getBodyData.mockReturnValue({ events });
mockWebhookFunctions.getHeaderData.mockReturnValue({});
mockWebhookFunctions.getRequestObject.mockReturnValue({
@@ -130,7 +131,7 @@ describe('AsanaTrigger', () => {
});
it('should return empty result when events array is empty after verification', async () => {
(verifySignature as jest.Mock).mockReturnValue(true);
(verifySignature as Mock).mockReturnValue(true);
mockWebhookFunctions.getBodyData.mockReturnValue({ events: [] });
mockWebhookFunctions.getHeaderData.mockReturnValue({});
mockWebhookFunctions.getRequestObject.mockReturnValue({
@@ -8,11 +8,11 @@ describe('AsanaTriggerHelpers', () => {
const testPayload = Buffer.from('{"events":[{"action":"changed"}]}');
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
mockWebhookFunctions = {
getRequestObject: jest.fn(),
getWorkflowStaticData: jest.fn(),
getRequestObject: vi.fn(),
getWorkflowStaticData: vi.fn(),
};
});
@@ -20,7 +20,7 @@ describe('AsanaTriggerHelpers', () => {
it('should return true if no secret is configured (backward compatibility)', () => {
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockReturnValue(null),
header: vi.fn().mockReturnValue(null),
rawBody: testPayload,
});
@@ -38,7 +38,7 @@ describe('AsanaTriggerHelpers', () => {
hookSecret: testSecret,
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockImplementation((header) => {
header: vi.fn().mockImplementation((header) => {
if (header === 'x-hook-signature') return expectedSignature;
return null;
}),
@@ -57,7 +57,7 @@ describe('AsanaTriggerHelpers', () => {
hookSecret: testSecret,
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockImplementation((header) => {
header: vi.fn().mockImplementation((header) => {
if (header === 'x-hook-signature') return wrongSignature;
return null;
}),
@@ -74,7 +74,7 @@ describe('AsanaTriggerHelpers', () => {
hookSecret: testSecret,
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockReturnValue(null),
header: vi.fn().mockReturnValue(null),
rawBody: testPayload,
});
@@ -93,7 +93,7 @@ describe('AsanaTriggerHelpers', () => {
hookSecret: testSecret,
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockImplementation((header: string) => {
header: vi.fn().mockImplementation((header: string) => {
if (header === 'x-hook-signature') return expectedSignature;
return null;
}),
@@ -110,7 +110,7 @@ describe('AsanaTriggerHelpers', () => {
hookSecret: testSecret,
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockReturnValue('any'),
header: vi.fn().mockReturnValue('any'),
rawBody: undefined,
});
@@ -1,5 +1,5 @@
import { mock } from 'jest-mock-extended';
import type { MockProxy } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { MockProxy } from 'vitest-mock-extended';
import type { IExecuteSingleFunctions, IHttpRequestOptions } from 'n8n-workflow';
import { NodeOperationError, NodeApiError } from 'n8n-workflow';
@@ -16,26 +16,27 @@ import {
} from '../../helpers/utils';
import { searchUsers } from '../../methods/listSearch';
import { awsApiRequest, awsApiRequestAllItems } from '../../transport/index';
import type { Mock } from 'vitest';
jest.mock('../../transport/index', () => ({
awsApiRequest: jest.fn(),
awsApiRequestAllItems: jest.fn(),
vi.mock('../../transport/index', () => ({
awsApiRequest: vi.fn(),
awsApiRequestAllItems: vi.fn(),
}));
jest.mock('../../methods/listSearch', () => ({
searchUsers: jest.fn(),
vi.mock('../../methods/listSearch', () => ({
searchUsers: vi.fn(),
}));
describe('AWS Cognito - Helpers functions', () => {
let loadOptionsFunctions: MockProxy<IExecuteSingleFunctions>;
let mockRequestWithAuthentication: jest.Mock;
let mockReturnJsonArray: jest.Mock;
let mockRequestWithAuthentication: Mock;
let mockReturnJsonArray: Mock;
let requestOptions: IHttpRequestOptions;
beforeEach(() => {
loadOptionsFunctions = mock<IExecuteSingleFunctions>();
mockRequestWithAuthentication = jest.fn();
mockReturnJsonArray = jest.fn();
mockRequestWithAuthentication = vi.fn();
mockReturnJsonArray = vi.fn();
loadOptionsFunctions.helpers.httpRequestWithAuthentication = mockRequestWithAuthentication;
loadOptionsFunctions.helpers.returnJsonArray = mockReturnJsonArray;
loadOptionsFunctions.getCredentials.mockResolvedValue({
@@ -46,7 +47,7 @@ describe('AWS Cognito - Helpers functions', () => {
});
afterEach(() => {
jest.resetAllMocks();
vi.resetAllMocks();
});
describe('getUserPool', () => {
@@ -60,7 +61,7 @@ describe('AWS Cognito - Helpers functions', () => {
},
};
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
(awsApiRequest as Mock).mockResolvedValue(mockResponse);
const userPool = await getUserPool.call(loadOptionsFunctions, userPoolId);
@@ -76,7 +77,7 @@ describe('AWS Cognito - Helpers functions', () => {
it('should throw an error if user pool is not found', async () => {
const userPoolId = 'invalid-user-pool-id';
(awsApiRequest as jest.Mock).mockResolvedValue({});
(awsApiRequest as Mock).mockResolvedValue({});
await expect(getUserPool.call(loadOptionsFunctions, userPoolId)).rejects.toThrowError(
NodeOperationError,
@@ -94,8 +95,8 @@ describe('AWS Cognito - Helpers functions', () => {
});
it('should return an empty list if no users are found', async () => {
(loadOptionsFunctions.getNodeParameter as jest.Mock).mockReturnValue('userPoolId');
(awsApiRequestAllItems as jest.Mock).mockResolvedValue([]);
(loadOptionsFunctions.getNodeParameter as Mock).mockReturnValue('userPoolId');
(awsApiRequestAllItems as Mock).mockResolvedValue([]);
const result = await getUsersInGroup.call(loadOptionsFunctions, 'groupName', 'userPoolId');
expect(result).toEqual([]);
@@ -115,7 +116,7 @@ describe('AWS Cognito - Helpers functions', () => {
},
];
(awsApiRequestAllItems as jest.Mock).mockResolvedValue(mockUsers);
(awsApiRequestAllItems as Mock).mockResolvedValue(mockUsers);
const result = await getUsersInGroup.call(loadOptionsFunctions, 'groupName', 'userPoolId');
expect(result).toEqual([
@@ -151,7 +152,7 @@ describe('AWS Cognito - Helpers functions', () => {
},
];
(awsApiRequestAllItems as jest.Mock).mockResolvedValue(mockUsers);
(awsApiRequestAllItems as Mock).mockResolvedValue(mockUsers);
const result = await getUsersInGroup.call(loadOptionsFunctions, 'groupName', 'userPoolId');
expect(result).toEqual([
@@ -315,7 +316,7 @@ describe('AWS Cognito - Helpers functions', () => {
Users: [{ Username: 'existing-user' }],
};
(awsApiRequest as jest.Mock).mockResolvedValue(mockApiResponse);
(awsApiRequest as Mock).mockResolvedValue(mockApiResponse);
const result = await getUserNameFromExistingUsers.call(
loadOptionsFunctions,
@@ -333,7 +334,7 @@ describe('AWS Cognito - Helpers functions', () => {
Users: [],
};
(awsApiRequest as jest.Mock).mockResolvedValue(mockApiResponse);
(awsApiRequest as Mock).mockResolvedValue(mockApiResponse);
const result = await getUserNameFromExistingUsers.call(
loadOptionsFunctions,
@@ -364,7 +365,7 @@ describe('AWS Cognito - Helpers functions', () => {
headers: {},
};
(awsApiRequest as jest.Mock).mockResolvedValue({
(awsApiRequest as Mock).mockResolvedValue({
UserPool: { UsernameAttributes: ['email'] },
});
@@ -393,11 +394,11 @@ describe('AWS Cognito - Helpers functions', () => {
return undefined;
});
(awsApiRequest as jest.Mock).mockResolvedValue({
(awsApiRequest as Mock).mockResolvedValue({
UserPool: { UsernameAttributes: ['email'] },
});
(searchUsers as jest.Mock).mockResolvedValue({
(searchUsers as Mock).mockResolvedValue({
results: [{ name: 'existing-user', value: 'existing-user' }],
});
@@ -425,11 +426,11 @@ describe('AWS Cognito - Helpers functions', () => {
return undefined;
});
(awsApiRequest as jest.Mock).mockResolvedValue({
(awsApiRequest as Mock).mockResolvedValue({
UserPool: { UsernameAttributes: ['email'] },
});
(searchUsers as jest.Mock).mockResolvedValue({
(searchUsers as Mock).mockResolvedValue({
results: [],
});
@@ -12,10 +12,11 @@ import {
searchGroupsForUser,
} from '../../methods/listSearch';
import { awsApiRequest, awsApiRequestAllItems } from '../../transport/index';
import type { Mock } from 'vitest';
jest.mock('../../transport/index', () => ({
awsApiRequest: jest.fn(),
awsApiRequestAllItems: jest.fn(),
vi.mock('../../transport/index', () => ({
awsApiRequest: vi.fn(),
awsApiRequestAllItems: vi.fn(),
}));
describe('AWS Cognito Functions', () => {
@@ -49,12 +50,12 @@ describe('AWS Cognito Functions', () => {
NextToken: 'next-token',
};
(awsApiRequest as jest.Mock)
(awsApiRequest as Mock)
.mockResolvedValueOnce(mockDescribeUserPoolResponse)
.mockResolvedValueOnce(mockResponse);
const mockContext = {
getNodeParameter: jest.fn((param) => {
getNodeParameter: vi.fn((param) => {
if (param === 'userPool') {
return 'user-pool-id';
}
@@ -82,8 +83,8 @@ describe('AWS Cognito Functions', () => {
it('should throw an error if UserPoolId is missing', async () => {
const mockContext = {
getNodeParameter: jest.fn().mockReturnValue(''),
getNode: jest.fn(),
getNodeParameter: vi.fn().mockReturnValue(''),
getNode: vi.fn(),
} as unknown as ILoadOptionsFunctions;
await expect(searchUsers.call(mockContext)).rejects.toThrow(
@@ -102,10 +103,10 @@ describe('AWS Cognito Functions', () => {
NextToken: 'next-token',
};
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
(awsApiRequest as Mock).mockResolvedValue(mockResponse);
const mockContext = {
getNodeParameter: jest.fn((param) => {
getNodeParameter: vi.fn((param) => {
if (param === 'userPool') {
return { value: 'user-pool-id' };
}
@@ -133,8 +134,8 @@ describe('AWS Cognito Functions', () => {
it('should throw an error if UserPoolId is missing', async () => {
const mockContext = {
getNodeParameter: jest.fn().mockReturnValue(null),
getNode: jest.fn(),
getNodeParameter: vi.fn().mockReturnValue(null),
getNode: vi.fn(),
} as unknown as ILoadOptionsFunctions;
await expect(searchGroups.call(mockContext)).rejects.toThrow(ApplicationError);
@@ -151,10 +152,10 @@ describe('AWS Cognito Functions', () => {
NextToken: 'next-token',
};
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
(awsApiRequest as Mock).mockResolvedValue(mockResponse);
const mockContext = {
getNodeParameter: jest.fn((param) => {
getNodeParameter: vi.fn((param) => {
if (param === 'userPool') {
return { value: 'user-pool-id' };
}
@@ -186,18 +187,18 @@ describe('AWS Cognito Functions', () => {
const userPoolId = 'eu-central-1_KkXQgdCJv';
const mockContext = {
getNodeParameter: jest.fn((param) => {
getNodeParameter: vi.fn((param) => {
if (param === 'user') return userName;
if (param === 'userPool') return userPoolId;
return null;
}),
getNode: jest.fn(() => ({
getNode: vi.fn(() => ({
name: 'mockNode',
})),
} as unknown as ILoadOptionsFunctions;
beforeEach(() => {
(awsApiRequest as jest.Mock).mockResolvedValueOnce({
(awsApiRequest as Mock).mockResolvedValueOnce({
UserPool: {
Id: userPoolId,
UsernameAttributes: ['email', 'phone_number'],
@@ -206,7 +207,7 @@ describe('AWS Cognito Functions', () => {
});
it('should handle empty groups response', async () => {
(awsApiRequestAllItems as jest.Mock).mockResolvedValueOnce([]);
(awsApiRequestAllItems as Mock).mockResolvedValueOnce([]);
const result = await searchGroupsForUser.call(mockContext, '');
@@ -224,7 +225,7 @@ describe('AWS Cognito Functions', () => {
{ GroupName: 'Guests' },
];
(awsApiRequestAllItems as jest.Mock).mockResolvedValueOnce(mockGroups);
(awsApiRequestAllItems as Mock).mockResolvedValueOnce(mockGroups);
const result = await searchGroupsForUser.call(mockContext, 'dev');
@@ -236,7 +237,7 @@ describe('AWS Cognito Functions', () => {
it('should return all groups when no filter is passed', async () => {
const mockGroups = [{ GroupName: 'Zeta' }, { GroupName: 'Alpha' }, { GroupName: 'Beta' }];
(awsApiRequestAllItems as jest.Mock).mockResolvedValueOnce(mockGroups);
(awsApiRequestAllItems as Mock).mockResolvedValueOnce(mockGroups);
const result = await searchGroupsForUser.call(mockContext);
@@ -21,7 +21,7 @@ describe('Test AWS Comprehend Node', () => {
};
beforeAll(async () => {
jest.useFakeTimers({ doNotFake: ['nextTick'], now });
vi.useFakeTimers({ now });
const baseUrl = 'https://comprehend.eu-central-1.amazonaws.com';
@@ -1,4 +1,4 @@
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import type { IExecuteFunctions } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
@@ -9,19 +9,20 @@ import {
awsApiRequestSOAPAllItems,
} from '../GenericFunctions';
jest.mock('xml2js', () => ({
parseString: jest.fn(),
vi.mock('xml2js', () => ({
parseString: vi.fn(),
}));
import { parseString as parseXml } from 'xml2js';
import type { Mock, Mocked, MockedFunction } from 'vitest';
describe('ELB GenericFunctions', () => {
describe('awsApiRequest', () => {
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
beforeEach(() => {
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
mockExecuteFunctions.getNode.mockReturnValue({
id: 'test-node',
@@ -35,8 +36,7 @@ describe('ELB GenericFunctions', () => {
it('should make successful API request with basic parameters', async () => {
const mockResponse = { success: true };
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(mockResponse);
const result = await awsApiRequest.call(
@@ -61,8 +61,7 @@ describe('ELB GenericFunctions', () => {
it('should handle API errors', async () => {
const apiError = new Error('API Error');
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockRejectedValue(apiError);
await expect(
@@ -72,18 +71,17 @@ describe('ELB GenericFunctions', () => {
});
describe('awsApiRequestREST', () => {
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
beforeEach(() => {
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should parse valid JSON response', async () => {
const jsonResponse = '{"result": "success", "data": [1,2,3]}';
const expectedResult = { result: 'success', data: [1, 2, 3] };
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(jsonResponse);
const result = await awsApiRequestREST.call(
@@ -98,8 +96,7 @@ describe('ELB GenericFunctions', () => {
it('should return raw response when JSON parsing fails', async () => {
const rawResponse = 'not json data';
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(rawResponse);
const result = await awsApiRequestREST.call(
@@ -114,12 +111,12 @@ describe('ELB GenericFunctions', () => {
});
describe('awsApiRequestSOAP', () => {
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
const mockParseXml = parseXml as jest.MockedFunction<typeof parseXml>;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
const mockParseXml = parseXml as MockedFunction<typeof parseXml>;
beforeEach(() => {
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should parse XML response correctly', async () => {
@@ -135,7 +132,7 @@ describe('ELB GenericFunctions', () => {
},
};
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
xmlResponse,
);
mockParseXml.mockImplementation((_xml, _options, callback) => {
@@ -156,7 +153,7 @@ describe('ELB GenericFunctions', () => {
const xmlResponse = 'invalid xml';
const xmlError = new Error('Invalid XML');
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
xmlResponse,
);
mockParseXml.mockImplementation((_xml, _options, callback) => {
@@ -175,12 +172,12 @@ describe('ELB GenericFunctions', () => {
});
describe('awsApiRequestSOAPAllItems', () => {
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
const mockParseXml = parseXml as jest.MockedFunction<typeof parseXml>;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
const mockParseXml = parseXml as MockedFunction<typeof parseXml>;
beforeEach(() => {
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
});
describe('pagination patterns', () => {
@@ -198,7 +195,7 @@ describe('ELB GenericFunctions', () => {
};
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
'<xml>response</xml>',
);
mockParseXml.mockImplementation((_xml, _options, callback) => {
@@ -238,7 +235,7 @@ describe('ELB GenericFunctions', () => {
};
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock)
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock)
.mockResolvedValueOnce('<xml>first-response</xml>')
.mockResolvedValueOnce('<xml>second-response</xml>');
@@ -270,7 +267,7 @@ describe('ELB GenericFunctions', () => {
);
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockRejectedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockRejectedValue(
elbError,
);
@@ -4,8 +4,8 @@ import type { INodeExecutionData, IN8nHttpFullResponse, JsonObject } from 'n8n-w
import { handleError } from '../../helpers/errorHandler';
const mockExecuteSingleFunctions = {
getNode: jest.fn(() => ({ name: 'MockNode' })),
getNodeParameter: jest.fn(),
getNode: vi.fn(() => ({ name: 'MockNode' })),
getNodeParameter: vi.fn(),
} as any;
describe('handleError', () => {
@@ -23,7 +23,7 @@ describe('handleError', () => {
});
test('should throw NodeApiError for EntityAlreadyExists with user conflict', async () => {
mockExecuteSingleFunctions.getNodeParameter = jest
mockExecuteSingleFunctions.getNodeParameter = vi
.fn()
.mockReturnValueOnce('user')
.mockReturnValueOnce('existingUserName');
@@ -16,9 +16,10 @@ import {
encodeBodyAsFormUrlEncoded,
} from '../../helpers/utils';
import { awsApiRequest } from '../../transport';
import type { Mock } from 'vitest';
jest.mock('../../transport', () => ({
awsApiRequest: jest.fn(),
vi.mock('../../transport', () => ({
awsApiRequest: vi.fn(),
}));
describe('AWS IAM - Helper Functions', () => {
@@ -26,10 +27,10 @@ describe('AWS IAM - Helper Functions', () => {
beforeEach(() => {
mockNode = {
getNodeParameter: jest.fn(),
getNode: jest.fn(),
getNodeParameter: vi.fn(),
getNode: vi.fn(),
helpers: {
returnJsonArray: jest.fn((input: unknown[]) => input.map((i) => ({ json: i }))),
returnJsonArray: vi.fn((input: unknown[]) => input.map((i) => ({ json: i }))),
},
};
});
@@ -67,7 +68,7 @@ describe('AWS IAM - Helper Functions', () => {
const mockResponse = {
GetGroupResponse: { GetGroupResult: { Users: [{ UserName: 'user1' }] } },
};
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
(awsApiRequest as Mock).mockResolvedValue(mockResponse);
const result = await findUsersForGroup.call(mockNode, 'groupName');
expect(result).toEqual([{ UserName: 'user1' }]);
@@ -134,7 +135,7 @@ describe('AWS IAM - Helper Functions', () => {
});
const mockUsers = [{ UserName: 'user1' }];
(awsApiRequest as jest.Mock).mockResolvedValue(mockUsers);
(awsApiRequest as Mock).mockResolvedValue(mockUsers);
const requestOptions = { headers: {}, url: '' };
const result = await deleteGroupMembers.call(mockNode, requestOptions);
@@ -184,7 +185,7 @@ describe('AWS IAM - Helper Functions', () => {
},
};
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
(awsApiRequest as Mock).mockResolvedValue(mockResponse);
await expect(validateUserPath.call(mockNode, { headers: {}, url: '' })).rejects.toThrowError(
NodeOperationError,
@@ -203,7 +204,7 @@ describe('AWS IAM - Helper Functions', () => {
},
};
(awsApiRequest as jest.Mock).mockResolvedValue(mockResponse);
(awsApiRequest as Mock).mockResolvedValue(mockResponse);
const result = await validateUserPath.call(mockNode, requestOptions);
expect(result.body).toHaveProperty('PathPrefix', '/validPrefix/');
@@ -381,7 +382,7 @@ describe('AWS IAM - Helper Functions', () => {
};
const mockUsers = [{ UserName: 'user1' }];
(awsApiRequest as jest.Mock).mockResolvedValueOnce({
(awsApiRequest as Mock).mockResolvedValueOnce({
GetGroupResponse: { GetGroupResult: { Users: mockUsers } },
});
@@ -408,7 +409,7 @@ describe('AWS IAM - Helper Functions', () => {
it('should remove a user from all groups', async () => {
mockNode.getNodeParameter.mockReturnValue('user1');
const mockUserGroups = { results: [{ value: 'group1' }, { value: 'group2' }] };
(awsApiRequest as jest.Mock).mockResolvedValue(mockUserGroups);
(awsApiRequest as Mock).mockResolvedValue(mockUserGroups);
const requestOptions = { headers: {}, url: '' };
const result = await removeUserFromGroups.call(mockNode, requestOptions);
@@ -2,28 +2,29 @@ import type { ILoadOptionsFunctions } from 'n8n-workflow';
import { searchUsers, searchGroups, searchGroupsForUser } from '../../methods/listSearch';
import { awsApiRequest } from '../../transport';
import type { Mock } from 'vitest';
jest.mock('../../transport', () => ({
awsApiRequest: jest.fn(),
vi.mock('../../transport', () => ({
awsApiRequest: vi.fn(),
}));
describe('AWS IAM - List search', () => {
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
const mockContext = {
helpers: {
requestWithAuthentication: jest.fn(),
requestWithAuthentication: vi.fn(),
},
getNodeParameter: jest.fn(),
getCredentials: jest.fn(),
getNodeParameter: vi.fn(),
getCredentials: vi.fn(),
} as unknown as ILoadOptionsFunctions;
describe('searchUsers', () => {
it('should return an empty result if no users are found', async () => {
const responseData = { ListUsersResponse: { ListUsersResult: { Users: [] } } };
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
(awsApiRequest as Mock).mockResolvedValue(responseData);
const result = await searchUsers.call(mockContext);
expect(result.results).toEqual([]);
@@ -37,7 +38,7 @@ describe('AWS IAM - List search', () => {
},
},
};
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
(awsApiRequest as Mock).mockResolvedValue(responseData);
const result = await searchUsers.call(mockContext);
expect(result.results).toEqual([
@@ -54,7 +55,7 @@ describe('AWS IAM - List search', () => {
},
},
};
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
(awsApiRequest as Mock).mockResolvedValue(responseData);
const result = await searchUsers.call(mockContext, 'User1');
expect(result.results).toEqual([{ name: 'User1', value: 'User1' }]);
@@ -64,7 +65,7 @@ describe('AWS IAM - List search', () => {
describe('searchGroups', () => {
it('should return an empty result if no groups are found', async () => {
const responseData = { ListGroupsResponse: { ListGroupsResult: { Groups: [] } } };
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
(awsApiRequest as Mock).mockResolvedValue(responseData);
const result = await searchGroups.call(mockContext);
expect(result.results).toEqual([]);
@@ -78,7 +79,7 @@ describe('AWS IAM - List search', () => {
},
},
};
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
(awsApiRequest as Mock).mockResolvedValue(responseData);
const result = await searchGroups.call(mockContext);
expect(result.results).toEqual([
@@ -95,7 +96,7 @@ describe('AWS IAM - List search', () => {
},
},
};
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
(awsApiRequest as Mock).mockResolvedValue(responseData);
const result = await searchGroups.call(mockContext, 'Group1');
expect(result.results).toEqual([{ name: 'Group1', value: 'Group1' }]);
@@ -104,12 +105,12 @@ describe('AWS IAM - List search', () => {
describe('searchGroupsForUser', () => {
it('should return empty if no user groups are found', async () => {
mockContext.getNodeParameter = jest.fn().mockReturnValue('user1');
mockContext.getNodeParameter = vi.fn().mockReturnValue('user1');
const responseData = {
ListGroupsResponse: { ListGroupsResult: { Groups: [] } },
};
(awsApiRequest as jest.Mock).mockResolvedValue(responseData);
(awsApiRequest as Mock).mockResolvedValue(responseData);
const result = await searchGroupsForUser.call(mockContext);
@@ -1,7 +1,8 @@
import { mockDeep } from 'jest-mock-extended';
import type { IExecuteFunctions, INode } from 'n8n-workflow';
import { jsonParse } from 'n8n-workflow';
import nock from 'nock';
import type { Mock } from 'vitest';
import { mockDeep } from 'vitest-mock-extended';
import { AwsRekognition } from '../AwsRekognition.node';
import * as GenericFunctions from '../GenericFunctions';
@@ -17,12 +18,12 @@ const mockRekognitionResponse = {
describe('AWS Rekognition Node - binary data input', () => {
const executeFunctionsMock = mockDeep<IExecuteFunctions>();
let awsApiRequestSpy: jest.SpyInstance;
let awsApiRequestSpy: Mock;
const node = new AwsRekognition();
beforeEach(() => {
jest.resetAllMocks();
awsApiRequestSpy = jest.spyOn(GenericFunctions, 'awsApiRequestREST');
vi.resetAllMocks();
awsApiRequestSpy = vi.spyOn(GenericFunctions, 'awsApiRequestREST');
executeFunctionsMock.getCredentials.mockResolvedValue({
accessKeyId: 'test-key',
secretAccessKey: 'test-secret',
@@ -9,7 +9,7 @@ describe('Test S3 V1 Node', () => {
const now = 1683028800000;
beforeAll(async () => {
jest.useFakeTimers({ doNotFake: ['nextTick'], now });
vi.useFakeTimers({ now });
mock = nock('https://bucket.s3.eu-central-1.amazonaws.com');
});
@@ -1,4 +1,4 @@
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import type { IExecuteFunctions, ILoadOptionsFunctions } from 'n8n-workflow';
import {
@@ -7,20 +7,20 @@ import {
awsApiRequestSOAP,
awsApiRequestSOAPAllItems,
} from '../../V1/GenericFunctions';
import type { Mock, Mocked } from 'vitest';
describe('AWS S3 V1 GenericFunctions', () => {
describe('awsApiRequest', () => {
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
beforeEach(() => {
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should make AWS API request with basic parameters', async () => {
const mockResponse = { success: true };
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(mockResponse);
const result = await awsApiRequest.call(
@@ -53,8 +53,7 @@ describe('AWS S3 V1 GenericFunctions', () => {
it('should handle query parameters correctly', async () => {
const mockResponse = { data: 'test' };
const queryParams = { 'list-type': '2', 'max-keys': '10' };
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(mockResponse);
await awsApiRequest.call(
@@ -83,17 +82,16 @@ describe('AWS S3 V1 GenericFunctions', () => {
});
describe('awsApiRequestREST', () => {
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
beforeEach(() => {
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should parse valid JSON response', async () => {
const jsonString = JSON.stringify({ id: '123', name: 'test' });
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(jsonString);
const result = await awsApiRequestREST.call(mockExecuteFunctions, 's3', 'GET', '/bucket');
@@ -103,8 +101,7 @@ describe('AWS S3 V1 GenericFunctions', () => {
it('should return raw response when JSON parsing fails', async () => {
const rawResponse = 'not valid json';
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(rawResponse);
const result = await awsApiRequestREST.call(mockExecuteFunctions, 's3', 'GET', '/bucket');
@@ -114,19 +111,18 @@ describe('AWS S3 V1 GenericFunctions', () => {
});
describe('awsApiRequestSOAP', () => {
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
beforeEach(() => {
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should parse valid XML response', async () => {
const xmlResponse =
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Name>test-bucket</Name></ListBucketResult>';
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(xmlResponse);
const result = await awsApiRequestSOAP.call(mockExecuteFunctions, 's3', 'GET', '/bucket');
@@ -137,8 +133,7 @@ describe('AWS S3 V1 GenericFunctions', () => {
it('should return error when XML parsing fails', async () => {
const invalidXml = 'not valid xml';
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(invalidXml);
const result = await awsApiRequestSOAP.call(mockExecuteFunctions, 's3', 'GET', '/bucket');
@@ -148,19 +143,18 @@ describe('AWS S3 V1 GenericFunctions', () => {
});
describe('awsApiRequestSOAPAllItems', () => {
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
beforeEach(() => {
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should collect all items from single page response', async () => {
const xmlResponse =
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>file1.txt</Key><Size>1024</Size></Contents><Contents><Key>file2.txt</Key><Size>2048</Size></Contents><IsTruncated>false</IsTruncated></ListBucketResult>';
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(xmlResponse);
const result = await awsApiRequestSOAPAllItems.call(
@@ -183,8 +177,7 @@ describe('AWS S3 V1 GenericFunctions', () => {
const xmlResponse =
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><IsTruncated>false</IsTruncated></ListBucketResult>';
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(xmlResponse);
const result = await awsApiRequestSOAPAllItems.call(
@@ -206,8 +199,7 @@ describe('AWS S3 V1 GenericFunctions', () => {
const secondPageResponse =
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>file2.txt</Key><Size>2048</Size></Contents><IsTruncated>false</IsTruncated></ListBucketResult>';
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth
.mockResolvedValueOnce(firstPageResponse)
.mockResolvedValueOnce(secondPageResponse);
@@ -248,8 +240,7 @@ describe('AWS S3 V1 GenericFunctions', () => {
const secondPageResponse =
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>file3.txt</Key><Size>3072</Size></Contents><IsTruncated>false</IsTruncated></ListBucketResult>';
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth
.mockResolvedValueOnce(firstPageResponse)
.mockResolvedValueOnce(secondPageResponse);
@@ -277,8 +268,7 @@ describe('AWS S3 V1 GenericFunctions', () => {
const xmlResponse =
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>single-file.txt</Key><Size>512</Size></Contents><IsTruncated>false</IsTruncated></ListBucketResult>';
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(xmlResponse);
const result = await awsApiRequestSOAPAllItems.call(
@@ -297,8 +287,7 @@ describe('AWS S3 V1 GenericFunctions', () => {
it('should handle error responses from SOAP parsing', async () => {
const invalidXml = 'invalid xml response';
const mockRequestWithAuth = mockExecuteFunctions.helpers
.requestWithAuthentication as jest.Mock;
const mockRequestWithAuth = mockExecuteFunctions.helpers.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(invalidXml);
const result = await awsApiRequestSOAPAllItems.call(
@@ -321,7 +310,7 @@ describe('AWS S3 V1 GenericFunctions', () => {
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>file1.txt</Key></Contents><IsTruncated>false</IsTruncated></ListBucketResult>';
const mockRequestWithAuth = mockLoadOptionsFunctions.helpers
.requestWithAuthentication as jest.Mock;
.requestWithAuthentication as Mock;
mockRequestWithAuth.mockResolvedValue(xmlResponse);
const result = await awsApiRequestSOAPAllItems.call(
@@ -1,9 +1,10 @@
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import type { IExecuteFunctions, INode } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { AwsS3V2 } from '../../V2/AwsS3V2.node';
import * as GenericFunctions from '../../V2/GenericFunctions';
import type { MockInstance } from 'vitest';
const mockLocationResponse = {
LocationConstraint: {
@@ -20,12 +21,12 @@ const mockFileResponse = {
describe('AWS S3 V2 Node - File Download', () => {
const executeFunctionsMock = mockDeep<IExecuteFunctions>();
let awsApiRequestRESTSpy: jest.SpyInstance;
let awsApiRequestRESTSpy: MockInstance;
let node: AwsS3V2;
beforeEach(() => {
jest.resetAllMocks();
awsApiRequestRESTSpy = jest.spyOn(GenericFunctions, 'awsApiRequestREST');
vi.resetAllMocks();
awsApiRequestRESTSpy = vi.spyOn(GenericFunctions, 'awsApiRequestREST');
node = new AwsS3V2({
displayName: 'AWS S3',
name: 'awsS3',
@@ -164,7 +165,7 @@ describe('AWS S3 V2 Node - File Download', () => {
];
for (const testCase of testCases) {
jest.clearAllMocks();
vi.clearAllMocks();
awsApiRequestRESTSpy
.mockResolvedValueOnce(mockLocationResponse)
.mockResolvedValueOnce(mockFileResponse);
@@ -356,8 +357,8 @@ describe('AWS S3 V2 Node - File Download', () => {
describe('AWS S3 V2 Node - Bucket Search', () => {
const executeFunctionsMock = mockDeep<IExecuteFunctions>();
let awsApiRequestRESTSpy: jest.SpyInstance;
let awsApiRequestRESTAllItemsSpy: jest.SpyInstance;
let awsApiRequestRESTSpy: MockInstance;
let awsApiRequestRESTAllItemsSpy: MockInstance;
let node: AwsS3V2;
const mockContents = [
@@ -373,9 +374,9 @@ describe('AWS S3 V2 Node - Bucket Search', () => {
};
beforeEach(() => {
jest.resetAllMocks();
awsApiRequestRESTSpy = jest.spyOn(GenericFunctions, 'awsApiRequestREST');
awsApiRequestRESTAllItemsSpy = jest.spyOn(GenericFunctions, 'awsApiRequestRESTAllItems');
vi.resetAllMocks();
awsApiRequestRESTSpy = vi.spyOn(GenericFunctions, 'awsApiRequestREST');
awsApiRequestRESTAllItemsSpy = vi.spyOn(GenericFunctions, 'awsApiRequestRESTAllItems');
node = new AwsS3V2({
displayName: 'AWS S3',
name: 'awsS3',
@@ -9,7 +9,7 @@ describe('Test S3 V2 Node', () => {
const now = 1683028800000;
beforeAll(async () => {
jest.useFakeTimers({ doNotFake: ['nextTick'], now });
vi.useFakeTimers({ now });
mock = nock('https://s3.eu-central-1.amazonaws.com/buc.ket');
});
@@ -1,9 +1,10 @@
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import type { IExecuteFunctions, INode } from 'n8n-workflow';
import nock from 'nock';
import { AwsTextract } from '../AwsTextract.node';
import * as GenericFunctions from '../GenericFunctions';
import type { MockInstance } from 'vitest';
const mockTextractResponse = {
ExpenseDocuments: [
@@ -28,14 +29,14 @@ const mockSimplifiedResponse = {
describe('AWS Textract Node', () => {
const executeFunctionsMock = mockDeep<IExecuteFunctions>();
let awsApiRequestSpy: jest.SpyInstance;
let simplifySpy: jest.SpyInstance;
let awsApiRequestSpy: MockInstance;
let simplifySpy: MockInstance;
const node = new AwsTextract();
beforeEach(() => {
jest.resetAllMocks();
awsApiRequestSpy = jest.spyOn(GenericFunctions, 'awsApiRequestREST');
simplifySpy = jest.spyOn(GenericFunctions, 'simplify');
vi.resetAllMocks();
awsApiRequestSpy = vi.spyOn(GenericFunctions, 'awsApiRequestREST');
simplifySpy = vi.spyOn(GenericFunctions, 'simplify');
executeFunctionsMock.getCredentials.mockResolvedValue({
accessKeyId: 'test-key',
secretAccessKey: 'test-secret',
@@ -1,24 +1,24 @@
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { ICredentialTestFunctions } from 'n8n-workflow';
import { UserError } from 'n8n-workflow';
jest.mock('aws4', () => ({
sign: jest.fn(),
vi.mock('aws4', () => ({
sign: vi.fn(),
}));
import { sign } from 'aws4';
import { simplify, validateCredentials, type IExpenseDocument } from '../GenericFunctions';
describe('AWS Textract Generic Functions', () => {
const mockSign = sign as jest.MockedFunction<typeof sign>;
const mockSign = vi.mocked(sign);
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
describe('validateCredentials region validation', () => {
const buildContext = () => {
const helpers = { request: jest.fn() };
const helpers = { request: vi.fn() };
const context = mock<ICredentialTestFunctions>({
helpers: helpers as never,
});
@@ -1,26 +1,26 @@
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { IExecuteFunctions } from 'n8n-workflow';
import { UserError } from 'n8n-workflow';
jest.mock('aws4', () => ({
sign: jest.fn(),
vi.mock('aws4', () => ({
sign: vi.fn(),
}));
import { sign } from 'aws4';
import { awsApiRequest } from '../GenericFunctions';
describe('AWS Transcribe Generic Functions', () => {
const mockSign = sign as jest.MockedFunction<typeof sign>;
const mockSign = vi.mocked(sign);
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
describe('awsApiRequest region validation', () => {
const buildContext = (region: unknown) => {
const helpers = { request: jest.fn() };
const helpers = { request: vi.fn() };
const context = mock<IExecuteFunctions>({
getCredentials: jest.fn().mockResolvedValue({
getCredentials: vi.fn().mockResolvedValue({
region,
accessKeyId: 'AKIA-test',
secretAccessKey: 'secret-test',
@@ -1,22 +1,23 @@
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import type { IExecuteFunctions, ILoadOptionsFunctions } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
import { AwsLambda } from '../AwsLambda.node';
import * as GenericFunctions from '../GenericFunctions';
import type { Mock, Mocked, MockInstance } from 'vitest';
describe('AwsLambda', () => {
let node: AwsLambda;
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
let mockLoadOptionsFunctions: jest.Mocked<ILoadOptionsFunctions>;
let awsApiRequestRESTSpy: jest.SpyInstance;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
let mockLoadOptionsFunctions: Mocked<ILoadOptionsFunctions>;
let awsApiRequestRESTSpy: MockInstance;
beforeEach(() => {
node = new AwsLambda();
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
mockLoadOptionsFunctions = mockDeep<ILoadOptionsFunctions>();
jest.clearAllMocks();
awsApiRequestRESTSpy = jest.spyOn(GenericFunctions, 'awsApiRequestREST');
vi.clearAllMocks();
awsApiRequestRESTSpy = vi.spyOn(GenericFunctions, 'awsApiRequestREST');
mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]);
mockExecuteFunctions.getNode.mockReturnValue({
@@ -27,7 +28,7 @@ describe('AwsLambda', () => {
position: [0, 0],
parameters: {},
});
(mockExecuteFunctions.helpers.constructExecutionMetaData as jest.Mock).mockImplementation(
(mockExecuteFunctions.helpers.constructExecutionMetaData as Mock).mockImplementation(
(data: any, meta: any) => {
return [
{
@@ -37,13 +38,13 @@ describe('AwsLambda', () => {
];
},
);
(mockExecuteFunctions.helpers.returnJsonArray as jest.Mock).mockImplementation((data: any) => [
(mockExecuteFunctions.helpers.returnJsonArray as Mock).mockImplementation((data: any) => [
{ json: data },
]);
});
afterEach(() => {
jest.resetAllMocks();
vi.resetAllMocks();
});
describe('Load Options Methods', () => {
@@ -1,19 +1,20 @@
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import type { IExecuteFunctions, ILoadOptionsFunctions, IWebhookFunctions } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
import { awsApiRequest, awsApiRequestREST, awsApiRequestSOAP } from '../GenericFunctions';
import type { Mock, Mocked } from 'vitest';
describe('AWS GenericFunctions', () => {
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
let mockLoadOptionsFunctions: jest.Mocked<ILoadOptionsFunctions>;
let mockWebhookFunctions: jest.Mocked<IWebhookFunctions>;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
let mockLoadOptionsFunctions: Mocked<ILoadOptionsFunctions>;
let mockWebhookFunctions: Mocked<IWebhookFunctions>;
beforeEach(() => {
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
mockLoadOptionsFunctions = mockDeep<ILoadOptionsFunctions>();
mockWebhookFunctions = mockDeep<IWebhookFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
mockExecuteFunctions.getNode.mockReturnValue({
id: 'test-node',
@@ -26,7 +27,7 @@ describe('AWS GenericFunctions', () => {
});
afterEach(() => {
jest.resetAllMocks();
vi.resetAllMocks();
});
describe('awsApiRequest', () => {
@@ -40,7 +41,7 @@ describe('AWS GenericFunctions', () => {
const mockResponse = { success: true, data: 'test response' };
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
mockResponse,
);
@@ -76,7 +77,7 @@ describe('AWS GenericFunctions', () => {
const mockResponse = { result: 'lambda response' };
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
mockResponse,
);
@@ -108,7 +109,7 @@ describe('AWS GenericFunctions', () => {
const mockResponse = { success: true };
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
mockResponse,
);
@@ -133,7 +134,7 @@ describe('AWS GenericFunctions', () => {
const mockResponse = ['option1', 'option2'];
mockLoadOptionsFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockLoadOptionsFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
mockResponse,
);
mockLoadOptionsFunctions.getNode.mockReturnValue({
@@ -160,7 +161,7 @@ describe('AWS GenericFunctions', () => {
const mockResponse = { webhook: 'processed' };
mockWebhookFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockWebhookFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockWebhookFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
mockResponse,
);
mockWebhookFunctions.getNode.mockReturnValue({
@@ -187,7 +188,7 @@ describe('AWS GenericFunctions', () => {
const mockResponse = { success: true };
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
mockResponse,
);
@@ -206,7 +207,7 @@ describe('AWS GenericFunctions', () => {
const mockResponse = { success: true };
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
mockResponse,
);
@@ -238,7 +239,7 @@ describe('AWS GenericFunctions', () => {
const apiError = new Error('AWS API Error');
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockRejectedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockRejectedValue(
apiError,
);
@@ -258,7 +259,7 @@ describe('AWS GenericFunctions', () => {
};
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockRejectedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockRejectedValue(
authError,
);
@@ -274,7 +275,7 @@ describe('AWS GenericFunctions', () => {
const mockResponse = { success: true };
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
mockResponse,
);
@@ -296,7 +297,7 @@ describe('AWS GenericFunctions', () => {
const mockResponse = { success: true };
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
mockResponse,
);
@@ -315,7 +316,7 @@ describe('AWS GenericFunctions', () => {
const mockResponse = { success: true };
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
mockResponse,
);
@@ -345,7 +346,7 @@ describe('AWS GenericFunctions', () => {
const expectedParsed = { result: 'success', data: [1, 2, 3] };
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
jsonResponse,
);
@@ -366,7 +367,7 @@ describe('AWS GenericFunctions', () => {
const invalidJsonResponse = 'Not valid JSON content';
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
invalidJsonResponse,
);
@@ -385,7 +386,7 @@ describe('AWS GenericFunctions', () => {
const emptyResponse = '';
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
emptyResponse,
);
@@ -404,7 +405,7 @@ describe('AWS GenericFunctions', () => {
const nullResponse = null;
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
nullResponse,
);
@@ -423,7 +424,7 @@ describe('AWS GenericFunctions', () => {
const numberResponse = 42;
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
numberResponse,
);
@@ -442,7 +443,7 @@ describe('AWS GenericFunctions', () => {
const booleanResponse = true;
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
booleanResponse,
);
@@ -461,7 +462,7 @@ describe('AWS GenericFunctions', () => {
const jsonResponse = '["option1", "option2", "option3"]';
mockLoadOptionsFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockLoadOptionsFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
jsonResponse,
);
mockLoadOptionsFunctions.getNode.mockReturnValue({
@@ -499,7 +500,7 @@ describe('AWS GenericFunctions', () => {
const malformedJson = '{"incomplete": json';
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
malformedJson,
);
@@ -522,7 +523,7 @@ describe('AWS GenericFunctions', () => {
const xmlResponse = '<response><status>success</status><data>test</data></response>';
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
xmlResponse,
);
@@ -547,7 +548,7 @@ describe('AWS GenericFunctions', () => {
const invalidXmlResponse = 'Not valid XML content';
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
invalidXmlResponse,
);
@@ -566,7 +567,7 @@ describe('AWS GenericFunctions', () => {
const emptyResponse = '';
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
emptyResponse,
);
@@ -581,7 +582,7 @@ describe('AWS GenericFunctions', () => {
'<ListQueuesResult><QueueUrl>url1</QueueUrl><QueueUrl>url2</QueueUrl></ListQueuesResult>';
mockExecuteFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockExecuteFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockExecuteFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
complexXml,
);
@@ -604,7 +605,7 @@ describe('AWS GenericFunctions', () => {
const xmlResponse = '<options><item>opt1</item><item>opt2</item></options>';
mockLoadOptionsFunctions.getCredentials.mockResolvedValue(mockCredentials);
(mockLoadOptionsFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValue(
(mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock).mockResolvedValue(
xmlResponse,
);
mockLoadOptionsFunctions.getNode.mockReturnValue({
@@ -3,13 +3,14 @@ import type { IWebhookFunctions } from 'n8n-workflow';
import { AwsSnsTrigger } from '../AwsSnsTrigger.node';
import { verifySignature } from '../AwsSnsTriggerHelpers';
import { awsApiRequestSOAP } from '../GenericFunctions';
import type { Mock } from 'vitest';
jest.mock('../AwsSnsTriggerHelpers', () => ({
verifySignature: jest.fn(),
vi.mock('../AwsSnsTriggerHelpers', () => ({
verifySignature: vi.fn(),
}));
jest.mock('../GenericFunctions', () => ({
awsApiRequestSOAP: jest.fn(),
vi.mock('../GenericFunctions', () => ({
awsApiRequestSOAP: vi.fn(),
}));
const topicArn = 'arn:aws:sns:us-east-1:123456789012:MyTopic';
@@ -27,11 +28,11 @@ describe('AwsSnsTrigger', () => {
'https://sns.us-east-1.amazonaws.com/SimpleNotificationService-1234567890abcdef1234567890abcdef.pem',
};
const verifySignatureMock = verifySignature as jest.Mock;
const awsApiRequestSOAPMock = awsApiRequestSOAP as jest.Mock;
const verifySignatureMock = verifySignature as Mock;
const awsApiRequestSOAPMock = awsApiRequestSOAP as Mock;
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
verifySignatureMock.mockResolvedValue(true);
awsApiRequestSOAPMock.mockResolvedValue({});
});
@@ -102,17 +103,17 @@ describe('AwsSnsTrigger', () => {
function createWebhookFunctions(body: object) {
const response = {
status: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
end: jest.fn(),
status: vi.fn().mockReturnThis(),
send: vi.fn().mockReturnThis(),
end: vi.fn(),
};
const returnJsonArray = jest.fn((data) => [{ json: data }]);
const returnJsonArray = vi.fn((data) => [{ json: data }]);
const webhookFunctions = {
getRequestObject: jest.fn().mockReturnValue({
getRequestObject: vi.fn().mockReturnValue({
rawBody: Buffer.from(JSON.stringify(body)),
}),
getNodeParameter: jest.fn().mockReturnValue(topicArn),
getResponseObject: jest.fn().mockReturnValue(response),
getNodeParameter: vi.fn().mockReturnValue(topicArn),
getResponseObject: vi.fn().mockReturnValue(response),
helpers: {
returnJsonArray,
},
@@ -10,15 +10,19 @@ import {
clearCertificateCache,
verifySignature,
} from '../AwsSnsTriggerHelpers';
import type { Mock } from 'vitest';
import type * as _importType0 from 'crypto';
jest.mock('crypto', () => ({
...jest.requireActual('crypto'),
createVerify: jest.fn(),
X509Certificate: jest.fn().mockImplementation(() => ({
publicKey: 'public-key',
validFrom: 'Jan 1 2020 GMT',
validTo: 'Jan 1 2100 GMT',
})),
vi.mock('crypto', async () => ({
...(await vi.importActual<typeof _importType0>('crypto')),
createVerify: vi.fn(),
X509Certificate: vi.fn(function () {
return {
publicKey: 'public-key',
validFrom: 'Jan 1 2020 GMT',
validTo: 'Jan 1 2100 GMT',
};
}),
}));
describe('AwsSnsTriggerHelpers', () => {
@@ -40,22 +44,22 @@ describe('AwsSnsTriggerHelpers', () => {
};
let mockWebhookFunctions: IWebhookFunctions;
let update: jest.Mock;
let end: jest.Mock;
let verify: jest.Mock;
let update: Mock;
let end: Mock;
let verify: Mock;
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
clearCertificateCache();
update = jest.fn().mockReturnThis();
end = jest.fn();
verify = jest.fn().mockReturnValue(true);
(createVerify as jest.Mock).mockReturnValue({ update, end, verify });
update = vi.fn().mockReturnThis();
end = vi.fn();
verify = vi.fn().mockReturnValue(true);
(createVerify as Mock).mockReturnValue({ update, end, verify });
mockWebhookFunctions = {
helpers: {
httpRequest: jest.fn().mockResolvedValue(certificate),
httpRequest: vi.fn().mockResolvedValue(certificate),
},
} as unknown as IWebhookFunctions;
});
@@ -260,20 +264,20 @@ describe('AwsSnsTriggerHelpers - integration with real crypto', () => {
function buildMockWebhookFunctions(certPem: string): IWebhookFunctions {
return {
helpers: {
httpRequest: jest.fn().mockResolvedValue(certPem),
httpRequest: vi.fn().mockResolvedValue(certPem),
},
} as unknown as IWebhookFunctions;
}
beforeEach(() => {
jest.restoreAllMocks();
beforeEach(async () => {
vi.restoreAllMocks();
clearCertificateCache();
const realCrypto = jest.requireActual('crypto');
(createVerify as jest.Mock).mockImplementation(realCrypto.createVerify);
(X509Certificate as unknown as jest.Mock).mockImplementation(
(pem: string) => new realCrypto.X509Certificate(pem),
);
const realCrypto = await vi.importActual<typeof _importType0>('crypto');
(createVerify as Mock).mockImplementation(realCrypto.createVerify);
(X509Certificate as unknown as Mock).mockImplementation(function (pem: string) {
return new realCrypto.X509Certificate(pem);
});
});
it('verifies a correctly signed Notification using real RSA', async () => {
@@ -12,17 +12,17 @@ import {
describe('Baserow > GenericFunctions', () => {
const mockExecuteFunctions: any = {
helpers: {
requestWithAuthentication: jest.fn(),
requestWithAuthentication: vi.fn(),
},
getCredentials: jest.fn().mockResolvedValue({
getCredentials: vi.fn().mockResolvedValue({
host: 'https://api.baserow.io',
}),
getNodeParameter: jest.fn(),
getNode: jest.fn(),
getNodeParameter: vi.fn(),
getNode: vi.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
mockExecuteFunctions.getCredentials.mockResolvedValue({
host: 'https://api.baserow.io',
});
@@ -1,4 +1,4 @@
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { IExecuteFunctions } from 'n8n-workflow';
import {
@@ -25,15 +25,15 @@ import {
import * as GenericFunctions from '../GenericFunctions';
// Mock the GenericFunctions
jest.mock('../GenericFunctions');
const mockedGenericFunctions = jest.mocked(GenericFunctions);
vi.mock('../GenericFunctions');
const mockedGenericFunctions = vi.mocked(GenericFunctions);
describe('Beeminder Node Functions', () => {
let mockContext: IExecuteFunctions;
beforeEach(() => {
mockContext = mock<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
});
describe('Datapoint Operations', () => {
@@ -1,4 +1,4 @@
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import type {
ICredentialsDecrypted,
ICredentialTestFunctions,
@@ -10,11 +10,12 @@ import type {
import { BitbucketTrigger } from '../BitbucketTrigger.node';
import * as GenericFunctions from '../GenericFunctions';
import type { MockInstance } from 'vitest';
describe('BitbucketTrigger', () => {
let bitbucketTrigger: BitbucketTrigger;
let bitbucketApiRequestSpy: jest.SpyInstance;
let bitbucketApiRequestAllItemsSpy: jest.SpyInstance;
let bitbucketApiRequestSpy: MockInstance;
let bitbucketApiRequestAllItemsSpy: MockInstance;
const mockNode: INode = {
id: 'test-node-id',
@@ -26,9 +27,9 @@ describe('BitbucketTrigger', () => {
};
beforeEach(() => {
jest.resetAllMocks();
bitbucketApiRequestSpy = jest.spyOn(GenericFunctions, 'bitbucketApiRequest');
bitbucketApiRequestAllItemsSpy = jest.spyOn(GenericFunctions, 'bitbucketApiRequestAllItems');
vi.resetAllMocks();
bitbucketApiRequestSpy = vi.spyOn(GenericFunctions, 'bitbucketApiRequest');
bitbucketApiRequestAllItemsSpy = vi.spyOn(GenericFunctions, 'bitbucketApiRequestAllItems');
bitbucketTrigger = new BitbucketTrigger();
});
@@ -1,4 +1,4 @@
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import type {
IExecuteFunctions,
IHookFunctions,
@@ -27,7 +27,7 @@ describe('Bitbucket GenericFunctions', () => {
};
beforeEach(() => {
jest.resetAllMocks();
vi.resetAllMocks();
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
mockHookFunctions.getNode.mockReturnValue(mockNode);
mockLoadOptionsFunctions.getNode.mockReturnValue(mockNode);
@@ -294,7 +294,7 @@ describe('Bitbucket GenericFunctions', () => {
};
beforeEach(() => {
jest.resetAllMocks();
vi.resetAllMocks();
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
mockExecuteFunctions.getNodeParameter.mockReturnValue('accessToken');
});
@@ -1,24 +1,25 @@
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { IHookFunctions, IWebhookFunctions } from 'n8n-workflow';
import { BoxTrigger } from '../BoxTrigger.node';
jest.mock('../BoxTriggerHelpers', () => ({
verifySignature: jest.fn(),
vi.mock('../BoxTriggerHelpers', () => ({
verifySignature: vi.fn(),
}));
import { verifySignature } from '../BoxTriggerHelpers';
import type { Mock, MockedFunction } from 'vitest';
const mockedVerifySignature = verifySignature as jest.MockedFunction<typeof verifySignature>;
const mockedVerifySignature = verifySignature as MockedFunction<typeof verifySignature>;
describe('Box Trigger Webhook Lifecycle', () => {
const mockHookFunctions = mock<IHookFunctions>();
const mockStaticData: Record<string, string> = {};
const mockRequestOAuth2 = jest.fn();
const mockRequestOAuth2 = vi.fn();
beforeEach(() => {
// resetAllMocks clears Once queues and implementations — prevents bleed between tests
jest.resetAllMocks();
vi.resetAllMocks();
Object.keys(mockStaticData).forEach((key) => delete mockStaticData[key]);
mockHookFunctions.getWorkflowStaticData.mockReturnValue(mockStaticData);
mockHookFunctions.getNodeWebhookUrl.mockReturnValue('https://n8n.io/webhook/box-test');
@@ -299,16 +300,16 @@ describe('Box Trigger Webhook Lifecycle', () => {
describe('Box Trigger webhook()', () => {
let mockWebhookFunctions: ReturnType<typeof mock<IWebhookFunctions>>;
let mockResponseObject: { status: jest.Mock; send: jest.Mock; end: jest.Mock };
let mockResponseObject: { status: Mock; send: Mock; end: Mock };
beforeEach(() => {
jest.resetAllMocks();
vi.resetAllMocks();
mockWebhookFunctions = mock<IWebhookFunctions>();
mockResponseObject = {
status: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
end: jest.fn(),
status: vi.fn().mockReturnThis(),
send: vi.fn().mockReturnThis(),
end: vi.fn(),
};
mockWebhookFunctions.getResponseObject.mockReturnValue(mockResponseObject as never);
mockWebhookFunctions.getBodyData.mockReturnValue({
@@ -317,7 +318,7 @@ describe('Box Trigger webhook()', () => {
});
mockWebhookFunctions.helpers = {
...mockWebhookFunctions.helpers,
returnJsonArray: jest.fn().mockImplementation((data) => [{ json: data }]),
returnJsonArray: vi.fn().mockImplementation((data) => [{ json: data }]),
};
});
@@ -26,23 +26,23 @@ describe('BoxTriggerHelpers', () => {
headers: Record<string, string | undefined>,
body: Buffer | string | undefined | null = rawBody,
) => ({
header: jest.fn((name: string) => headers[name.toLowerCase()] ?? null),
header: vi.fn((name: string) => headers[name.toLowerCase()] ?? null),
rawBody: body,
});
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(Date, 'now').mockImplementation(() => fixedNow);
vi.clearAllMocks();
vi.spyOn(Date, 'now').mockImplementation(() => fixedNow);
mockWebhookFunctions = {
getCredentials: jest.fn(),
getRequestObject: jest.fn(),
getNode: jest.fn().mockReturnValue({ name: 'Box Trigger' }),
getCredentials: vi.fn(),
getRequestObject: vi.fn(),
getNode: vi.fn().mockReturnValue({ name: 'Box Trigger' }),
};
});
afterEach(() => {
jest.restoreAllMocks();
vi.restoreAllMocks();
});
it('should return true when no signing keys are configured (backward compatibility)', async () => {
@@ -185,7 +185,7 @@ describe('BoxTriggerHelpers', () => {
it('should return false when delivery timestamp is older than 10 minutes', async () => {
// 11 minutes after the delivery timestamp
jest.spyOn(Date, 'now').mockImplementation(() => Date.parse('2024-01-01T00:11:00Z'));
vi.spyOn(Date, 'now').mockImplementation(() => Date.parse('2024-01-01T00:11:00Z'));
mockWebhookFunctions.getCredentials.mockResolvedValue({
signingKeyPrimary: primaryKey,
@@ -24,12 +24,12 @@ describe('Brandfetch', () => {
describe('brandfetchApiRequest', () => {
const mockThis = {
helpers: {
requestWithAuthentication: jest.fn().mockResolvedValue({ statusCode: 200 }),
requestWithAuthentication: vi.fn().mockResolvedValue({ statusCode: 200 }),
},
getNode() {
return node;
},
getNodeParameter: jest.fn(),
getNodeParameter: vi.fn(),
} as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
it('should make an authenticated API request to Brandfetch', async () => {
@@ -9,18 +9,18 @@ function makeContext(overrides: {
binaries: Record<string, { metadata: IBinaryData; buffer: Buffer }>;
itemIndex?: number;
}): IExecuteSingleFunctions {
const getNodeParameter = jest.fn().mockReturnValue({
const getNodeParameter = vi.fn().mockReturnValue({
binaryPropertyName: overrides.binaryPropertyName,
});
const assertBinaryData = jest.fn((propertyName: string) => {
const assertBinaryData = vi.fn((propertyName: string) => {
const entry = overrides.binaries[propertyName];
if (!entry) throw new Error(`No binary named ${propertyName}`);
return entry.metadata;
});
// eslint-disable-next-line @typescript-eslint/require-await
const getBinaryDataBuffer = jest.fn(async (propertyName: string) => {
const getBinaryDataBuffer = vi.fn(async (propertyName: string) => {
const entry = overrides.binaries[propertyName];
if (!entry) throw new Error(`No binary named ${propertyName}`);
return entry.buffer;
@@ -28,8 +28,8 @@ function makeContext(overrides: {
return {
getNodeParameter,
getItemIndex: jest.fn().mockReturnValue(overrides.itemIndex ?? 0),
getNode: jest.fn().mockReturnValue({ name: 'Brevo' }),
getItemIndex: vi.fn().mockReturnValue(overrides.itemIndex ?? 0),
getNode: vi.fn().mockReturnValue({ name: 'Brevo' }),
helpers: {
assertBinaryData,
getBinaryDataBuffer,
@@ -4,41 +4,43 @@ import type { IHookFunctions, IWebhookFunctions } from 'n8n-workflow';
import { CalTrigger } from '../CalTrigger.node';
import { verifySignature } from '../CalTriggerHelpers';
import { calApiRequest } from '../GenericFunctions';
import type { Mock, Mocked } from 'vitest';
import type * as _importType0 from 'crypto';
jest.mock('../GenericFunctions');
jest.mock('../CalTriggerHelpers');
jest.mock('crypto', () => ({
...jest.requireActual('crypto'),
randomBytes: jest.fn(),
vi.mock('../GenericFunctions');
vi.mock('../CalTriggerHelpers');
vi.mock('crypto', async () => ({
...(await vi.importActual<typeof _importType0>('crypto')),
randomBytes: vi.fn(),
}));
describe('CalTrigger', () => {
let trigger: CalTrigger;
let mockHookFunctions: Pick<
jest.Mocked<IHookFunctions>,
Mocked<IHookFunctions>,
'getNodeWebhookUrl' | 'getNodeParameter' | 'getWorkflowStaticData' | 'helpers'
>;
let mockWebhookFunctions: Pick<
jest.Mocked<IWebhookFunctions>,
Mocked<IWebhookFunctions>,
'getRequestObject' | 'getResponseObject' | 'helpers'
>;
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
trigger = new CalTrigger();
mockHookFunctions = {
getNodeWebhookUrl: jest.fn(),
getNodeParameter: jest.fn(),
getWorkflowStaticData: jest.fn(),
getNodeWebhookUrl: vi.fn(),
getNodeParameter: vi.fn(),
getWorkflowStaticData: vi.fn(),
helpers: {} as any,
};
mockWebhookFunctions = {
getRequestObject: jest.fn(),
getResponseObject: jest.fn(),
getRequestObject: vi.fn(),
getResponseObject: vi.fn(),
helpers: {
returnJsonArray: jest.fn((data) => data),
returnJsonArray: vi.fn((data) => data),
} as any,
};
});
@@ -61,10 +63,10 @@ describe('CalTrigger', () => {
const webhookData: any = {};
mockHookFunctions.getWorkflowStaticData.mockReturnValue(webhookData);
(randomBytes as jest.Mock).mockReturnValue({
toString: jest.fn().mockReturnValue(webhookSecret),
(randomBytes as Mock).mockReturnValue({
toString: vi.fn().mockReturnValue(webhookSecret),
});
(calApiRequest as jest.Mock).mockResolvedValue({
(calApiRequest as Mock).mockResolvedValue({
webhook: { id: webhookId },
});
@@ -101,10 +103,10 @@ describe('CalTrigger', () => {
const webhookData: any = {};
mockHookFunctions.getWorkflowStaticData.mockReturnValue(webhookData);
(randomBytes as jest.Mock).mockReturnValue({
toString: jest.fn().mockReturnValue(webhookSecret),
(randomBytes as Mock).mockReturnValue({
toString: vi.fn().mockReturnValue(webhookSecret),
});
(calApiRequest as jest.Mock).mockResolvedValue({
(calApiRequest as Mock).mockResolvedValue({
webhook: { id: webhookId },
});
@@ -135,10 +137,10 @@ describe('CalTrigger', () => {
const webhookData: any = {};
mockHookFunctions.getWorkflowStaticData.mockReturnValue(webhookData);
(randomBytes as jest.Mock).mockReturnValue({
toString: jest.fn().mockReturnValue('secret'),
(randomBytes as Mock).mockReturnValue({
toString: vi.fn().mockReturnValue('secret'),
});
(calApiRequest as jest.Mock).mockResolvedValue({ webhook: {} });
(calApiRequest as Mock).mockResolvedValue({ webhook: {} });
const result = await trigger.webhookMethods!.default.create.call(
mockHookFunctions as unknown as IHookFunctions,
@@ -162,7 +164,7 @@ describe('CalTrigger', () => {
};
mockHookFunctions.getWorkflowStaticData.mockReturnValue(webhookData);
(calApiRequest as jest.Mock).mockResolvedValue({});
(calApiRequest as Mock).mockResolvedValue({});
const result = await trigger.webhookMethods!.default.delete.call(
mockHookFunctions as unknown as IHookFunctions,
@@ -190,12 +192,12 @@ describe('CalTrigger', () => {
describe('webhook', () => {
it('should return 401 when signature verification fails', async () => {
const mockResponse = {
status: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
end: jest.fn(),
status: vi.fn().mockReturnThis(),
send: vi.fn().mockReturnThis(),
end: vi.fn(),
};
(verifySignature as jest.Mock).mockReturnValue(false);
(verifySignature as Mock).mockReturnValue(false);
mockWebhookFunctions.getResponseObject.mockReturnValue(mockResponse as any);
const result = await trigger.webhook.call(
@@ -217,7 +219,7 @@ describe('CalTrigger', () => {
payload: { bookingId: 'abc-123' },
};
(verifySignature as jest.Mock).mockReturnValue(true);
(verifySignature as Mock).mockReturnValue(true);
mockWebhookFunctions.getRequestObject.mockReturnValue({
body: requestBody,
} as any);
@@ -8,11 +8,11 @@ describe('CalTriggerHelpers', () => {
const testPayload = Buffer.from('{"triggerEvent":"BOOKING_CREATED"}');
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
mockWebhookFunctions = {
getRequestObject: jest.fn(),
getWorkflowStaticData: jest.fn(),
getRequestObject: vi.fn(),
getWorkflowStaticData: vi.fn(),
};
});
@@ -20,7 +20,7 @@ describe('CalTriggerHelpers', () => {
it('should return true if no secret is configured', () => {
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockReturnValue(null),
header: vi.fn().mockReturnValue(null),
rawBody: testPayload,
});
@@ -38,7 +38,7 @@ describe('CalTriggerHelpers', () => {
webhookSecret: testSecret,
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockImplementation((header) => {
header: vi.fn().mockImplementation((header) => {
if (header === 'x-cal-signature-256') return expectedSignature;
return null;
}),
@@ -57,7 +57,7 @@ describe('CalTriggerHelpers', () => {
webhookSecret: testSecret,
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockImplementation((header) => {
header: vi.fn().mockImplementation((header) => {
if (header === 'x-cal-signature-256') return wrongSignature;
return null;
}),
@@ -74,7 +74,7 @@ describe('CalTriggerHelpers', () => {
webhookSecret: testSecret,
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockReturnValue(null),
header: vi.fn().mockReturnValue(null),
rawBody: testPayload,
});
@@ -88,7 +88,7 @@ describe('CalTriggerHelpers', () => {
webhookSecret: testSecret,
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockReturnValue('any-signature'),
header: vi.fn().mockReturnValue('any-signature'),
rawBody: undefined,
});
@@ -107,7 +107,7 @@ describe('CalTriggerHelpers', () => {
webhookSecret: testSecret,
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockImplementation((header) => {
header: vi.fn().mockImplementation((header) => {
if (header === 'x-cal-signature-256') return expectedSignature;
return null;
}),
@@ -1,14 +1,15 @@
import { randomBytes } from 'crypto';
import type * as _importType0 from 'crypto';
import type { IHookFunctions, IDataObject, IWebhookFunctions } from 'n8n-workflow';
import type { Mock, Mocked } from 'vitest';
import { verifySignature } from '../CalendlyTriggerHelpers';
import { CalendlyTriggerV1 } from '../v1/CalendlyTriggerV1.node';
jest.mock('../CalendlyTriggerHelpers');
jest.mock('crypto', () => ({
...jest.requireActual('crypto'),
randomBytes: jest.fn(),
vi.mock('../CalendlyTriggerHelpers');
vi.mock('crypto', async () => ({
...(await vi.importActual<typeof _importType0>('crypto')),
randomBytes: vi.fn(),
}));
describe('CalendlyTrigger', () => {
@@ -19,12 +20,12 @@ describe('CalendlyTrigger', () => {
const webhookSecret = 'a'.repeat(64);
let trigger: CalendlyTriggerV1;
let requestWithAuthentication: jest.Mock;
let requestWithAuthentication: Mock;
let webhookData: IDataObject;
let mockHookFunctions: jest.Mocked<IHookFunctions>;
let mockHookFunctions: Mocked<IHookFunctions>;
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
trigger = new CalendlyTriggerV1({
displayName: 'Calendly Trigger',
@@ -33,27 +34,27 @@ describe('CalendlyTrigger', () => {
group: ['trigger'],
description: 'Starts the workflow when Calendly events occur',
});
requestWithAuthentication = jest.fn();
requestWithAuthentication = vi.fn();
webhookData = {};
(randomBytes as jest.Mock).mockReturnValue({
toString: jest.fn().mockReturnValue(webhookSecret),
(randomBytes as Mock).mockReturnValue({
toString: vi.fn().mockReturnValue(webhookSecret),
});
mockHookFunctions = {
getNode: jest.fn().mockReturnValue({ name: 'Calendly Trigger', type: 'calendlyTrigger' }),
getNodeWebhookUrl: jest.fn().mockReturnValue(webhookUrl),
getNodeParameter: jest.fn((name: string) => {
getNode: vi.fn().mockReturnValue({ name: 'Calendly Trigger', type: 'calendlyTrigger' }),
getNodeWebhookUrl: vi.fn().mockReturnValue(webhookUrl),
getNodeParameter: vi.fn((name: string) => {
if (name === 'authentication') return 'apiKey';
if (name === 'events') return ['invitee.created'];
if (name === 'scope') return 'user';
return undefined;
}),
getWorkflowStaticData: jest.fn().mockReturnValue(webhookData),
getWorkflowStaticData: vi.fn().mockReturnValue(webhookData),
helpers: {
requestWithAuthentication,
},
} as unknown as jest.Mocked<IHookFunctions>;
} as unknown as Mocked<IHookFunctions>;
requestWithAuthentication.mockImplementation(async (_credentialsType, requestOptions) => {
if (requestOptions.uri === 'https://api.calendly.com/users/me') {
@@ -249,14 +250,14 @@ describe('CalendlyTrigger', () => {
describe('webhook', () => {
it('should return 401 when signature verification fails', async () => {
const mockRes = {
status: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
end: jest.fn(),
status: vi.fn().mockReturnThis(),
send: vi.fn().mockReturnThis(),
end: vi.fn(),
};
(verifySignature as jest.Mock).mockReturnValue(false);
(verifySignature as Mock).mockReturnValue(false);
const mockFn = {
getResponseObject: jest.fn().mockReturnValue(mockRes),
getResponseObject: vi.fn().mockReturnValue(mockRes),
} as unknown as IWebhookFunctions;
const result = await trigger.webhook.call(mockFn);
@@ -267,11 +268,11 @@ describe('CalendlyTrigger', () => {
it('should process the webhook when signature is valid', async () => {
const bodyData = { event: 'invitee.created', payload: { foo: 'bar' } };
(verifySignature as jest.Mock).mockReturnValue(true);
(verifySignature as Mock).mockReturnValue(true);
const mockFn = {
getBodyData: jest.fn().mockReturnValue(bodyData),
helpers: { returnJsonArray: jest.fn((data) => data) },
getBodyData: vi.fn().mockReturnValue(bodyData),
helpers: { returnJsonArray: vi.fn((data) => data) },
} as unknown as IWebhookFunctions;
const result = await trigger.webhook.call(mockFn);
@@ -21,13 +21,13 @@ describe('CalendlyTriggerHelpers', () => {
rawBody: Buffer | string | undefined;
}) {
return {
getWorkflowStaticData: jest
getWorkflowStaticData: vi
.fn()
.mockReturnValue(
opts.webhookSecret !== undefined ? { webhookSecret: opts.webhookSecret } : {},
),
getRequestObject: jest.fn().mockReturnValue({
header: jest
getRequestObject: vi.fn().mockReturnValue({
header: vi
.fn()
.mockImplementation((name: string) =>
name === 'calendly-webhook-signature' ? opts.headerValue : null,
@@ -1,19 +1,20 @@
import type { IHookFunctions } from 'n8n-workflow';
import { calendlyApiRequest } from '../GenericFunctions';
import type { Mocked } from 'vitest';
describe('Calendly GenericFunctions', () => {
const requestWithAuthentication = jest.fn();
const requestWithAuthentication = vi.fn();
const mockHookFunctions = {
getNodeParameter: jest.fn(),
getNodeParameter: vi.fn(),
helpers: {
requestWithAuthentication,
},
} as unknown as jest.Mocked<IHookFunctions>;
} as unknown as Mocked<IHookFunctions>;
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
requestWithAuthentication.mockResolvedValue({});
mockHookFunctions.getNodeParameter.mockReturnValue('apiKey');
});
@@ -1,5 +1,5 @@
import type { MockProxy } from 'jest-mock-extended';
import { mock } from 'jest-mock-extended';
import type { MockProxy } from 'vitest-mock-extended';
import { mock } from 'vitest-mock-extended';
import { normalizeItems } from 'n8n-core';
import type { IExecuteFunctions, INode, IWorkflowDataProxyData } from 'n8n-workflow';
@@ -22,7 +22,7 @@ describe('Code Node unit test', () => {
pythonThisArg.getNodeParameter.calledWith('pythonCode', 0).mockReturnValue('return []');
pythonThisArg.getInputData.mockReturnValue([{ json: {} }]);
const runSpy = jest
const runSpy = vi
.spyOn(PythonTaskRunnerSandbox.prototype, 'runUsingIncomingItems')
.mockResolvedValue([]);
@@ -1,4 +1,4 @@
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { IExecuteFunctions } from 'n8n-workflow';
import { createResultOk, createResultError } from 'n8n-workflow';
@@ -12,7 +12,7 @@ describe('JsTaskRunnerSandbox', () => {
const executeFunctions = mock<IExecuteFunctions>();
executeFunctions.helpers = {
...executeFunctions.helpers,
normalizeItems: jest
normalizeItems: vi
.fn()
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return
.mockImplementation((items: any) => (Array.isArray(items) ? items : [items])),
@@ -77,7 +77,7 @@ describe('JsTaskRunnerSandbox', () => {
const executeFunctions = mock<IExecuteFunctions>();
executeFunctions.helpers = {
...executeFunctions.helpers,
normalizeItems: jest
normalizeItems: vi
.fn()
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return
.mockImplementation((items: any) => (Array.isArray(items) ? items : [items])),
@@ -113,7 +113,7 @@ describe('JsTaskRunnerSandbox', () => {
const executeFunctions = mock<IExecuteFunctions>();
executeFunctions.helpers = {
...executeFunctions.helpers,
normalizeItems: jest
normalizeItems: vi
.fn()
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return
.mockImplementation((items: any) => (Array.isArray(items) ? items : [items])),
@@ -126,7 +126,7 @@ describe('JsTaskRunnerSandbox', () => {
// Mock throwExecutionError to throw an error for testing
const throwExecutionErrorModule = await import('../throw-execution-error');
const throwExecutionErrorSpy = jest
const throwExecutionErrorSpy = vi
.spyOn(throwExecutionErrorModule, 'throwExecutionError')
.mockImplementation(() => {
throw new Error('Execution failed');
@@ -210,7 +210,7 @@ describe('JsTaskRunnerSandbox', () => {
// Mock throwExecutionError to throw an error for testing
const throwExecutionErrorModule = await import('../throw-execution-error');
const throwExecutionErrorSpy = jest
const throwExecutionErrorSpy = vi
.spyOn(throwExecutionErrorModule, 'throwExecutionError')
.mockImplementation(() => {
throw new Error('Execution failed');
@@ -234,7 +234,7 @@ describe('JsTaskRunnerSandbox', () => {
// Mock throwExecutionError to throw an error for testing
const throwExecutionErrorModule = await import('../throw-execution-error');
const throwExecutionErrorSpy = jest
const throwExecutionErrorSpy = vi
.spyOn(throwExecutionErrorModule, 'throwExecutionError')
.mockImplementation(() => {
throw new Error('Execution failed');
@@ -1,11 +1,11 @@
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { IExecuteFunctions } from 'n8n-workflow';
import { createResultOk, createResultError, NodeOperationError } from 'n8n-workflow';
import { PythonTaskRunnerSandbox } from '../PythonTaskRunnerSandbox';
const createNormalizeItemsMock = () =>
jest.fn().mockImplementation((items: any) => {
vi.fn().mockImplementation((items: any) => {
const itemsArray = Array.isArray(items) ? items : [items];
return itemsArray.map((item: any) => {
if (item.json !== undefined) {
@@ -126,7 +126,7 @@ describe('PythonTaskRunnerSandbox', () => {
executeFunctions.startJob.mockResolvedValue(createResultError(executionError));
const throwExecutionErrorModule = await import('../throw-execution-error');
const throwExecutionErrorSpy = jest
const throwExecutionErrorSpy = vi
.spyOn(throwExecutionErrorModule, 'throwExecutionError')
.mockImplementation(() => {
throw new Error('Execution failed');
@@ -264,7 +264,7 @@ describe('PythonTaskRunnerSandbox', () => {
executeFunctions.startJob.mockResolvedValue(createResultError(executionError));
const throwExecutionErrorModule = await import('../throw-execution-error');
const throwExecutionErrorSpy = jest
const throwExecutionErrorSpy = vi
.spyOn(throwExecutionErrorModule, 'throwExecutionError')
.mockImplementation(() => {
throw new Error('Tool execution failed');
@@ -1,4 +1,4 @@
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { IExecuteFunctions, IWorkflowDataProxyData } from 'n8n-workflow';
import { getSandboxContext } from '../Sandbox';
@@ -36,9 +36,9 @@ describe('getSandboxContext', () => {
it('demonstrates that helpers IS spread into the sandbox (regression-guard contrast)', () => {
const executeFns = buildExecuteFunctionsMock();
const helpersWithLeak = {
httpRequestWithAuthentication: jest.fn(),
requestWithAuthenticationPaginated: jest.fn(),
getInboundArtifact: jest.fn(),
httpRequestWithAuthentication: vi.fn(),
requestWithAuthenticationPaginated: vi.fn(),
getInboundArtifact: vi.fn(),
} as unknown as IExecuteFunctions['helpers'];
executeFns.helpers = helpersWithLeak;
@@ -1,4 +1,4 @@
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { addPostExecutionWarning } from '../utils';
@@ -7,7 +7,7 @@ describe('addPostExecutionWarning', () => {
const context = mock<IExecuteFunctions>();
const inputItemsLength = 2;
beforeEach(() => jest.resetAllMocks());
beforeEach(() => vi.resetAllMocks());
it('should add execution hints when returnData length differs from inputItemsLength', () => {
const returnData: INodeExecutionData[] = [{ json: {}, pairedItem: 0 }];
@@ -1,33 +1,34 @@
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import type { IExecuteFunctions, INode, IBinaryData } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { Compression } from '../../Compression.node';
import { boundedGunzip } from '../../decompress/BoundedGunzip';
import { boundedUnzip } from '../../decompress/BoundedUnzip';
import type { Mocked } from 'vitest';
jest.mock('../../decompress/BoundedGunzip');
jest.mock('../../decompress/BoundedUnzip');
vi.mock('../../decompress/BoundedGunzip');
vi.mock('../../decompress/BoundedUnzip');
const mockBoundedUnzip = (data: Record<string, Buffer>, error?: Error) => {
if (error) {
jest.mocked(boundedUnzip).mockRejectedValue(error);
vi.mocked(boundedUnzip).mockRejectedValue(error);
} else {
jest.mocked(boundedUnzip).mockResolvedValue(data);
vi.mocked(boundedUnzip).mockResolvedValue(data);
}
};
const mockBoundedGunzip = (data: Buffer, error?: Error) => {
if (error) {
jest.mocked(boundedGunzip).mockRejectedValue(error);
vi.mocked(boundedGunzip).mockRejectedValue(error);
} else {
jest.mocked(boundedGunzip).mockResolvedValue(data);
vi.mocked(boundedGunzip).mockResolvedValue(data);
}
};
describe('Compression Node - Decompress Operation', () => {
let compression: Compression;
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
const mockNode: INode = {
id: 'test-node',
name: 'Compression',
@@ -40,7 +41,7 @@ describe('Compression Node - Decompress Operation', () => {
beforeEach(() => {
compression = new Compression();
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
mockExecuteFunctions.getInputData.mockReturnValue([{ json: { test: 'data' } }]);
@@ -56,7 +57,7 @@ describe('Compression Node - Decompress Operation', () => {
});
afterEach(() => {
jest.resetAllMocks();
vi.resetAllMocks();
});
describe('Zip Decompression', () => {
@@ -73,13 +74,13 @@ describe('Compression Node - Decompress Operation', () => {
file2_txt: Buffer.from([87, 111, 114, 108, 100]),
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock zip data'),
);
mockBoundedUnzip(mockZipContents);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation(
vi.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation(
async (buffer, fileName) =>
({
data: buffer.toString('base64'),
@@ -115,13 +116,13 @@ describe('Compression Node - Decompress Operation', () => {
file2_txt: Buffer.from([87, 111, 114, 108, 100]),
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock zip data'),
);
mockBoundedUnzip(mockZipContents);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation(
vi.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation(
async (buffer, fileName) =>
({
data: buffer.toString('base64'),
@@ -135,7 +136,7 @@ describe('Compression Node - Decompress Operation', () => {
expect(result[0][0].binary?.file_0).toBeDefined();
expect(result[0][0].binary?.file_1).toBeDefined();
expect(jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData)).toHaveBeenCalledTimes(2);
expect(vi.mocked(mockExecuteFunctions.helpers.prepareBinaryData)).toHaveBeenCalledTimes(2);
});
it('should process multiple zip files from comma-separated binary properties', async () => {
@@ -159,13 +160,13 @@ describe('Compression Node - Decompress Operation', () => {
file_txt: Buffer.from([72, 101, 108, 108, 111]),
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock zip data'),
);
mockBoundedUnzip(mockZipContents);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation(
vi.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation(
async (buffer, fileName) =>
({
data: buffer.toString('base64'),
@@ -178,11 +179,11 @@ describe('Compression Node - Decompress Operation', () => {
await compression.execute.call(mockExecuteFunctions);
expect(boundedUnzip).toHaveBeenCalledTimes(2);
expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
expect(vi.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
0,
'data1',
);
expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
expect(vi.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
0,
'data2',
);
@@ -200,13 +201,13 @@ describe('Compression Node - Decompress Operation', () => {
const mockGunzipData = Buffer.from([72, 101, 108, 108, 111]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock gzip data'),
);
mockBoundedGunzip(mockGunzipData);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
vi.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'test.txt',
@@ -234,13 +235,13 @@ describe('Compression Node - Decompress Operation', () => {
const mockGunzipData = Buffer.from([72, 101, 108, 108, 111]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock gzip data'),
);
mockBoundedGunzip(mockGunzipData);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
vi.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'test.txt',
@@ -263,16 +264,15 @@ describe('Compression Node - Decompress Operation', () => {
const mockGunzipData = Buffer.from([123, 125]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock gzip data'),
);
mockBoundedGunzip(mockGunzipData);
let callCount = 0;
jest
.mocked(mockExecuteFunctions.helpers.prepareBinaryData)
.mockImplementation(async (buffer, fileName, mimeType) => {
vi.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockImplementation(
async (buffer, fileName, mimeType) => {
callCount++;
if (callCount === 1) {
return {
@@ -288,7 +288,8 @@ describe('Compression Node - Decompress Operation', () => {
fileName: fileName ?? 'data',
fileExtension: 'json',
} as IBinaryData;
});
},
);
const result = await compression.execute.call(mockExecuteFunctions);
@@ -316,13 +317,13 @@ describe('Compression Node - Decompress Operation', () => {
const mockGunzipData = Buffer.from([72, 101, 108, 108, 111]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock gzip data'),
);
mockBoundedGunzip(mockGunzipData);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
vi.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'test.txt',
@@ -346,7 +347,7 @@ describe('Compression Node - Decompress Operation', () => {
fileExtension: undefined,
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
await expect(compression.execute.call(mockExecuteFunctions)).rejects.toThrow(
NodeOperationError,
@@ -361,10 +362,10 @@ describe('Compression Node - Decompress Operation', () => {
fileExtension: 'zip',
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('invalid zip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('invalid zip data'),
);
mockBoundedUnzip({}, new Error('Invalid zip file'));
mockExecuteFunctions.continueOnFail.mockReturnValue(true);
@@ -383,10 +384,10 @@ describe('Compression Node - Decompress Operation', () => {
fileExtension: 'zip',
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('invalid zip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('invalid zip data'),
);
mockBoundedUnzip({}, new Error('Invalid zip file'));
mockExecuteFunctions.continueOnFail.mockReturnValue(false);
@@ -403,10 +404,10 @@ describe('Compression Node - Decompress Operation', () => {
fileExtension: 'gz',
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('invalid gzip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('invalid gzip data'),
);
mockBoundedGunzip(Buffer.alloc(0), new Error('Invalid gzip file'));
mockExecuteFunctions.continueOnFail.mockReturnValue(true);
@@ -422,10 +423,10 @@ describe('Compression Node - Decompress Operation', () => {
fileName: 'test.zip',
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock zip data'),
);
await expect(compression.execute.call(mockExecuteFunctions)).rejects.toThrow(
NodeOperationError,
@@ -449,13 +450,13 @@ describe('Compression Node - Decompress Operation', () => {
const mockGunzipData = Buffer.from([72, 101, 108, 108, 111]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock gzip data'),
);
mockBoundedGunzip(mockGunzipData);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
vi.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'test.txt',
@@ -494,13 +495,13 @@ describe('Compression Node - Decompress Operation', () => {
file1_txt: Buffer.from([72, 101, 108, 108, 111]),
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock zip data'),
);
mockBoundedUnzip(mockZipContents);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
vi.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'file1.txt',
@@ -520,10 +521,10 @@ describe('Compression Node - Decompress Operation', () => {
fileExtension: 'zip',
};
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock zip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock zip data'),
);
mockBoundedUnzip({});
const result = await compression.execute.call(mockExecuteFunctions);
@@ -551,13 +552,13 @@ describe('Compression Node - Decompress Operation', () => {
const mockGunzipData = Buffer.from([72, 101, 108, 108, 111]);
jest.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
jest
.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer)
.mockResolvedValue(Buffer.from('mock gzip data'));
vi.mocked(mockExecuteFunctions.helpers.assertBinaryData).mockReturnValue(mockBinaryData);
vi.mocked(mockExecuteFunctions.helpers.getBinaryDataBuffer).mockResolvedValue(
Buffer.from('mock gzip data'),
);
mockBoundedGunzip(mockGunzipData);
jest.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
vi.mocked(mockExecuteFunctions.helpers.prepareBinaryData).mockResolvedValue({
data: 'SGVsbG8=',
mimeType: 'text/plain',
fileName: 'test.txt',
@@ -566,11 +567,11 @@ describe('Compression Node - Decompress Operation', () => {
const result = await compression.execute.call(mockExecuteFunctions);
expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
expect(vi.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
0,
'data1',
);
expect(jest.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
expect(vi.mocked(mockExecuteFunctions.helpers.assertBinaryData)).toHaveBeenCalledWith(
0,
'data2',
);
@@ -1,46 +1,8 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import type fs from 'fs';
import fsPromises, { type FileHandle } from 'fs/promises';
import { Readable } from 'stream';
// Reads a real fixture file (nodes/Crypto/v1/test/fixtures/binary.data, content "test") rather
// than mocking fast-glob/fs: NodeTestHarness loads the node from dist via require(), where
// vi.mock can't intercept those modules.
describe('Test Crypto Node', () => {
jest.mock('fast-glob', () => async () => ['/test/binary.data']);
jest.mock('fs/promises');
fsPromises.access = async () => {};
fsPromises.stat = jest.fn(async (path: fs.PathLike) => {
if (path === '/test/binary.data') {
return {
isFile: () => true,
dev: 123456,
ino: 654321,
} as fs.Stats;
}
throw Object.assign(new Error('File not found'), { code: 'ENOENT' });
}) as unknown as typeof fsPromises.stat;
fsPromises.open = jest.fn(async (path: fs.PathLike) => {
if (path === '/test/binary.data') {
return {
close: async () => {},
// eslint-disable-next-line @typescript-eslint/require-await
stat: async () =>
({
isFile: () => true,
dev: 123456,
ino: 654321,
}) as fs.Stats,
createReadStream: () => {
const stream = Readable.from(Buffer.from('test')) as fs.ReadStream;
// Emit 'open' event asynchronously to match real fs.ReadStream behavior
setImmediate(() => stream.emit('open'));
return stream;
},
} as FileHandle;
}
throw Object.assign(new Error('File not found'), { code: 'ENOENT' });
}) as unknown as typeof fsPromises.open;
beforeEach(() => {
jest.spyOn(fsPromises, 'realpath').mockImplementation(async (path) => path as string);
});
new NodeTestHarness().setupTests();
});
@@ -144,7 +144,7 @@
},
{
"parameters": {
"fileSelector": "/test/binary.data"
"fileSelector": "nodes/Crypto/v1/test/fixtures/binary.data"
},
"id": "09bbc611-c2ca-4750-94a7-d1bb4fc53a57",
"name": "Read Binary Files",
@@ -0,0 +1 @@
test
@@ -1,13 +1,14 @@
import { generateKeyPairSync } from 'crypto';
import { mockDeep } from 'jest-mock-extended';
import { mockDeep } from 'vitest-mock-extended';
import type { IExecuteFunctions, INodeTypeBaseDescription } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { CryptoV2 } from '../CryptoV2.node';
import type { Mock, Mocked } from 'vitest';
describe('CryptoV2 Node', () => {
let cryptoNode: CryptoV2;
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
let mockExecuteFunctions: Mocked<IExecuteFunctions>;
const baseDescription: INodeTypeBaseDescription = {
displayName: 'Crypto',
@@ -23,7 +24,7 @@ describe('CryptoV2 Node', () => {
beforeEach(() => {
cryptoNode = new CryptoV2(baseDescription);
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
jest.clearAllMocks();
vi.clearAllMocks();
mockExecuteFunctions.getNode.mockReturnValue({
id: 'crypto-node',
@@ -129,7 +130,7 @@ describe('CryptoV2 Node', () => {
hmacSecret: 'key',
signPrivateKey: '',
});
(mockExecuteFunctions.helpers.assertBinaryData as jest.Mock).mockReturnValue({
(mockExecuteFunctions.helpers.assertBinaryData as Mock).mockReturnValue({
data: 'dGVzdA==',
mimeType: 'text/plain',
});
@@ -3,40 +3,41 @@ import type { IDataObject, IWebhookFunctions } from 'n8n-workflow';
import { CurrentsTrigger } from '../CurrentsTrigger.node';
// Mock the helper module
jest.mock('../CurrentsTriggerHelpers', () => ({
verifyWebhook: jest.fn(),
vi.mock('../CurrentsTriggerHelpers', () => ({
verifyWebhook: vi.fn(),
}));
import { verifyWebhook } from '../CurrentsTriggerHelpers';
import type { Mock } from 'vitest';
describe('CurrentsTrigger', () => {
let trigger: CurrentsTrigger;
let mockWebhookFunctions: Partial<IWebhookFunctions>;
let mockResponse: { status: jest.Mock; send: jest.Mock; end: jest.Mock };
let mockResponse: { status: Mock; send: Mock; end: Mock };
beforeEach(() => {
trigger = new CurrentsTrigger();
mockResponse = {
status: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
end: jest.fn().mockReturnThis(),
status: vi.fn().mockReturnThis(),
send: vi.fn().mockReturnThis(),
end: vi.fn().mockReturnThis(),
};
mockWebhookFunctions = {
getBodyData: jest.fn(),
getNodeParameter: jest.fn(),
getResponseObject: jest.fn().mockReturnValue(mockResponse),
getBodyData: vi.fn(),
getNodeParameter: vi.fn(),
getResponseObject: vi.fn().mockReturnValue(mockResponse),
helpers: {
returnJsonArray: jest.fn((data) => data),
returnJsonArray: vi.fn((data) => data),
} as unknown as IWebhookFunctions['helpers'],
};
(verifyWebhook as jest.Mock).mockReturnValue(true);
(verifyWebhook as Mock).mockReturnValue(true);
});
describe('webhook', () => {
it('should return 401 when verification fails', async () => {
(verifyWebhook as jest.Mock).mockReturnValue(false);
(verifyWebhook as Mock).mockReturnValue(false);
const result = await trigger.webhook.call(mockWebhookFunctions as IWebhookFunctions);
@@ -52,11 +53,8 @@ describe('CurrentsTrigger', () => {
buildId: 'build-456',
};
(mockWebhookFunctions.getBodyData as jest.Mock).mockReturnValue(bodyData);
(mockWebhookFunctions.getNodeParameter as jest.Mock).mockReturnValue([
'RUN_FINISH',
'RUN_START',
]);
(mockWebhookFunctions.getBodyData as Mock).mockReturnValue(bodyData);
(mockWebhookFunctions.getNodeParameter as Mock).mockReturnValue(['RUN_FINISH', 'RUN_START']);
const result = await trigger.webhook.call(mockWebhookFunctions as IWebhookFunctions);
@@ -70,11 +68,8 @@ describe('CurrentsTrigger', () => {
runUrl: 'https://app.currents.dev/run/123',
};
(mockWebhookFunctions.getBodyData as jest.Mock).mockReturnValue(bodyData);
(mockWebhookFunctions.getNodeParameter as jest.Mock).mockReturnValue([
'RUN_FINISH',
'RUN_START',
]);
(mockWebhookFunctions.getBodyData as Mock).mockReturnValue(bodyData);
(mockWebhookFunctions.getNodeParameter as Mock).mockReturnValue(['RUN_FINISH', 'RUN_START']);
const result = await trigger.webhook.call(mockWebhookFunctions as IWebhookFunctions);
@@ -88,8 +83,8 @@ describe('CurrentsTrigger', () => {
runUrl: 'https://app.currents.dev/run/123',
};
(mockWebhookFunctions.getBodyData as jest.Mock).mockReturnValue(bodyData);
(mockWebhookFunctions.getNodeParameter as jest.Mock).mockReturnValue([]);
(mockWebhookFunctions.getBodyData as Mock).mockReturnValue(bodyData);
(mockWebhookFunctions.getNodeParameter as Mock).mockReturnValue([]);
const result = await trigger.webhook.call(mockWebhookFunctions as IWebhookFunctions);
@@ -113,8 +108,8 @@ describe('CurrentsTrigger', () => {
flaky: 2,
};
(mockWebhookFunctions.getBodyData as jest.Mock).mockReturnValue(bodyData);
(mockWebhookFunctions.getNodeParameter as jest.Mock).mockReturnValue(['RUN_FINISH']);
(mockWebhookFunctions.getBodyData as Mock).mockReturnValue(bodyData);
(mockWebhookFunctions.getNodeParameter as Mock).mockReturnValue(['RUN_FINISH']);
const result = await trigger.webhook.call(mockWebhookFunctions as IWebhookFunctions);
@@ -9,6 +9,7 @@ import {
updateWebhook,
verifyWebhook,
} from '../CurrentsTriggerHelpers';
import type { Mock } from 'vitest';
describe('CurrentsTriggerHelpers', () => {
describe('generateWebhookSecret', () => {
@@ -30,20 +31,20 @@ describe('CurrentsTriggerHelpers', () => {
beforeEach(() => {
mockWebhookFunctions = {
getRequestObject: jest.fn(),
getHeaderData: jest.fn(),
getWorkflowStaticData: jest.fn(),
getRequestObject: vi.fn(),
getHeaderData: vi.fn(),
getWorkflowStaticData: vi.fn(),
};
});
it('should return true when no secret in static data (no verification)', () => {
const nowMs = Date.now();
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getRequestObject as Mock).mockReturnValue({
headers: { 'x-timestamp': String(nowMs) },
});
(mockWebhookFunctions.getHeaderData as jest.Mock).mockReturnValue({});
(mockWebhookFunctions.getWorkflowStaticData as jest.Mock).mockReturnValue({});
(mockWebhookFunctions.getHeaderData as Mock).mockReturnValue({});
(mockWebhookFunctions.getWorkflowStaticData as Mock).mockReturnValue({});
const result = verifyWebhook.call(mockWebhookFunctions as IWebhookFunctions);
expect(result).toBe(true);
@@ -52,22 +53,22 @@ describe('CurrentsTriggerHelpers', () => {
it('should return false when timestamp is stale', () => {
const tenMinutesAgoMs = Date.now() - 600 * 1000;
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getRequestObject as Mock).mockReturnValue({
headers: { 'x-timestamp': String(tenMinutesAgoMs) },
});
(mockWebhookFunctions.getHeaderData as jest.Mock).mockReturnValue({});
(mockWebhookFunctions.getWorkflowStaticData as jest.Mock).mockReturnValue({});
(mockWebhookFunctions.getHeaderData as Mock).mockReturnValue({});
(mockWebhookFunctions.getWorkflowStaticData as Mock).mockReturnValue({});
const result = verifyWebhook.call(mockWebhookFunctions as IWebhookFunctions);
expect(result).toBe(false);
});
it('should return false when timestamp is invalid/non-numeric', () => {
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getRequestObject as Mock).mockReturnValue({
headers: { 'x-timestamp': 'not-a-number' },
});
(mockWebhookFunctions.getHeaderData as jest.Mock).mockReturnValue({});
(mockWebhookFunctions.getWorkflowStaticData as jest.Mock).mockReturnValue({});
(mockWebhookFunctions.getHeaderData as Mock).mockReturnValue({});
(mockWebhookFunctions.getWorkflowStaticData as Mock).mockReturnValue({});
const result = verifyWebhook.call(mockWebhookFunctions as IWebhookFunctions);
expect(result).toBe(false);
@@ -77,13 +78,13 @@ describe('CurrentsTriggerHelpers', () => {
const nowMs = Date.now();
const secret = 'auto-generated-secret';
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getRequestObject as Mock).mockReturnValue({
headers: { 'x-timestamp': String(nowMs) },
});
(mockWebhookFunctions.getHeaderData as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getHeaderData as Mock).mockReturnValue({
'x-webhook-secret': secret,
});
(mockWebhookFunctions.getWorkflowStaticData as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getWorkflowStaticData as Mock).mockReturnValue({
webhookSecret: secret,
} as IDataObject);
@@ -94,13 +95,13 @@ describe('CurrentsTriggerHelpers', () => {
it('should return false when secret does not match (different length)', () => {
const nowMs = Date.now();
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getRequestObject as Mock).mockReturnValue({
headers: { 'x-timestamp': String(nowMs) },
});
(mockWebhookFunctions.getHeaderData as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getHeaderData as Mock).mockReturnValue({
'x-webhook-secret': 'wrong-secret',
});
(mockWebhookFunctions.getWorkflowStaticData as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getWorkflowStaticData as Mock).mockReturnValue({
webhookSecret: 'correct-secret',
} as IDataObject);
@@ -111,13 +112,13 @@ describe('CurrentsTriggerHelpers', () => {
it('should return false when secret does not match (same length)', () => {
const nowMs = Date.now();
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getRequestObject as Mock).mockReturnValue({
headers: { 'x-timestamp': String(nowMs) },
});
(mockWebhookFunctions.getHeaderData as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getHeaderData as Mock).mockReturnValue({
'x-webhook-secret': 'wrong-secret-aa',
});
(mockWebhookFunctions.getWorkflowStaticData as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getWorkflowStaticData as Mock).mockReturnValue({
webhookSecret: 'correct-secret',
} as IDataObject);
@@ -128,11 +129,11 @@ describe('CurrentsTriggerHelpers', () => {
it('should return false when secret header is missing but expected', () => {
const nowMs = Date.now();
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getRequestObject as Mock).mockReturnValue({
headers: { 'x-timestamp': String(nowMs) },
});
(mockWebhookFunctions.getHeaderData as jest.Mock).mockReturnValue({});
(mockWebhookFunctions.getWorkflowStaticData as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getHeaderData as Mock).mockReturnValue({});
(mockWebhookFunctions.getWorkflowStaticData as Mock).mockReturnValue({
webhookSecret: 'expected-secret',
} as IDataObject);
@@ -141,11 +142,11 @@ describe('CurrentsTriggerHelpers', () => {
});
it('should handle missing timestamp header gracefully', () => {
(mockWebhookFunctions.getRequestObject as jest.Mock).mockReturnValue({
(mockWebhookFunctions.getRequestObject as Mock).mockReturnValue({
headers: {},
});
(mockWebhookFunctions.getHeaderData as jest.Mock).mockReturnValue({});
(mockWebhookFunctions.getWorkflowStaticData as jest.Mock).mockReturnValue({});
(mockWebhookFunctions.getHeaderData as Mock).mockReturnValue({});
(mockWebhookFunctions.getWorkflowStaticData as Mock).mockReturnValue({});
const result = verifyWebhook.call(mockWebhookFunctions as IWebhookFunctions);
expect(result).toBe(true);
@@ -158,7 +159,7 @@ describe('CurrentsTriggerHelpers', () => {
beforeEach(() => {
mockHookFunctions = {
helpers: {
httpRequestWithAuthentication: jest.fn(),
httpRequestWithAuthentication: vi.fn(),
} as unknown as IHookFunctions['helpers'],
};
});
@@ -179,7 +180,7 @@ describe('CurrentsTriggerHelpers', () => {
},
];
(mockHookFunctions.helpers!.httpRequestWithAuthentication as jest.Mock).mockResolvedValue({
(mockHookFunctions.helpers!.httpRequestWithAuthentication as Mock).mockResolvedValue({
data: mockWebhooks,
});
@@ -197,7 +198,7 @@ describe('CurrentsTriggerHelpers', () => {
});
it('should return empty array when no webhooks exist', async () => {
(mockHookFunctions.helpers!.httpRequestWithAuthentication as jest.Mock).mockResolvedValue({
(mockHookFunctions.helpers!.httpRequestWithAuthentication as Mock).mockResolvedValue({
data: null,
});
@@ -213,7 +214,7 @@ describe('CurrentsTriggerHelpers', () => {
beforeEach(() => {
mockHookFunctions = {
helpers: {
httpRequestWithAuthentication: jest.fn(),
httpRequestWithAuthentication: vi.fn(),
} as unknown as IHookFunctions['helpers'],
};
});
@@ -235,7 +236,7 @@ describe('CurrentsTriggerHelpers', () => {
},
];
(mockHookFunctions.helpers!.httpRequestWithAuthentication as jest.Mock).mockResolvedValue({
(mockHookFunctions.helpers!.httpRequestWithAuthentication as Mock).mockResolvedValue({
data: mockWebhooks,
});
@@ -258,7 +259,7 @@ describe('CurrentsTriggerHelpers', () => {
},
];
(mockHookFunctions.helpers!.httpRequestWithAuthentication as jest.Mock).mockResolvedValue({
(mockHookFunctions.helpers!.httpRequestWithAuthentication as Mock).mockResolvedValue({
data: mockWebhooks,
});
@@ -278,7 +279,7 @@ describe('CurrentsTriggerHelpers', () => {
beforeEach(() => {
mockHookFunctions = {
helpers: {
httpRequestWithAuthentication: jest.fn(),
httpRequestWithAuthentication: vi.fn(),
} as unknown as IHookFunctions['helpers'],
};
});
@@ -293,7 +294,7 @@ describe('CurrentsTriggerHelpers', () => {
label: 'n8n workflow 456',
};
(mockHookFunctions.helpers!.httpRequestWithAuthentication as jest.Mock).mockResolvedValue({
(mockHookFunctions.helpers!.httpRequestWithAuthentication as Mock).mockResolvedValue({
data: createdWebhook,
});
@@ -329,7 +330,7 @@ describe('CurrentsTriggerHelpers', () => {
hookEvents: [],
};
(mockHookFunctions.helpers!.httpRequestWithAuthentication as jest.Mock).mockResolvedValue({
(mockHookFunctions.helpers!.httpRequestWithAuthentication as Mock).mockResolvedValue({
data: createdWebhook,
});
@@ -361,7 +362,7 @@ describe('CurrentsTriggerHelpers', () => {
beforeEach(() => {
mockHookFunctions = {
helpers: {
httpRequestWithAuthentication: jest.fn(),
httpRequestWithAuthentication: vi.fn(),
} as unknown as IHookFunctions['helpers'],
};
});
@@ -374,7 +375,7 @@ describe('CurrentsTriggerHelpers', () => {
hookEvents: ['RUN_FINISH', 'RUN_TIMEOUT'],
};
(mockHookFunctions.helpers!.httpRequestWithAuthentication as jest.Mock).mockResolvedValue({
(mockHookFunctions.helpers!.httpRequestWithAuthentication as Mock).mockResolvedValue({
data: updatedWebhook,
});
@@ -405,7 +406,7 @@ describe('CurrentsTriggerHelpers', () => {
label: 'updated label',
};
(mockHookFunctions.helpers!.httpRequestWithAuthentication as jest.Mock).mockResolvedValue({
(mockHookFunctions.helpers!.httpRequestWithAuthentication as Mock).mockResolvedValue({
data: updatedWebhook,
});
@@ -439,13 +440,13 @@ describe('CurrentsTriggerHelpers', () => {
beforeEach(() => {
mockHookFunctions = {
helpers: {
httpRequestWithAuthentication: jest.fn(),
httpRequestWithAuthentication: vi.fn(),
} as unknown as IHookFunctions['helpers'],
};
});
it('should delete webhook by hookId', async () => {
(mockHookFunctions.helpers!.httpRequestWithAuthentication as jest.Mock).mockResolvedValue({});
(mockHookFunctions.helpers!.httpRequestWithAuthentication as Mock).mockResolvedValue({});
await deleteWebhook.call(mockHookFunctions as IHookFunctions, 'hook-123');
@@ -459,7 +460,7 @@ describe('CurrentsTriggerHelpers', () => {
});
it('should not throw on successful deletion', async () => {
(mockHookFunctions.helpers!.httpRequestWithAuthentication as jest.Mock).mockResolvedValue({});
(mockHookFunctions.helpers!.httpRequestWithAuthentication as Mock).mockResolvedValue({});
await expect(
deleteWebhook.call(mockHookFunctions as IHookFunctions, 'hook-123'),
@@ -1,14 +1,15 @@
import type { IDataObject, ILoadOptionsFunctions } from 'n8n-workflow';
import { getProjects } from '../methods/listSearch';
import type { Mock } from 'vitest';
describe('Currents listSearch', () => {
describe('getProjects', () => {
let mockContext: Partial<ILoadOptionsFunctions>;
let mockHttpRequest: jest.Mock;
let mockHttpRequest: Mock;
beforeEach(() => {
mockHttpRequest = jest.fn();
mockHttpRequest = vi.fn();
mockContext = {
helpers: {
httpRequestWithAuthentication: mockHttpRequest,
@@ -1,13 +1,14 @@
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { IWebhookFunctions, INodeType } from 'n8n-workflow';
import { CustomerIoTrigger } from '../CustomerIoTrigger.node';
jest.mock('../CustomerIoTriggerHelpers', () => ({
verifySignature: jest.fn(),
vi.mock('../CustomerIoTriggerHelpers', () => ({
verifySignature: vi.fn(),
}));
import { verifySignature } from '../CustomerIoTriggerHelpers';
import type { Mock } from 'vitest';
describe('CustomerIoTrigger Node', () => {
let customerIoTrigger: INodeType;
@@ -19,26 +20,26 @@ describe('CustomerIoTrigger Node', () => {
};
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
customerIoTrigger = new CustomerIoTrigger();
mockWebhookFunctions = mock<IWebhookFunctions>();
mockWebhookFunctions.helpers = {
returnJsonArray: jest.fn().mockImplementation((data) => [{ json: data }]),
returnJsonArray: vi.fn().mockImplementation((data) => [{ json: data }]),
} as any;
mockWebhookFunctions.getBodyData.mockReturnValue(mockBody);
mockWebhookFunctions.getResponseObject.mockReturnValue({
status: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
end: jest.fn(),
status: vi.fn().mockReturnThis(),
send: vi.fn().mockReturnThis(),
end: vi.fn(),
} as any);
});
describe('webhook method', () => {
it('should trigger workflow when signature is valid', async () => {
(verifySignature as jest.Mock).mockResolvedValue(true);
(verifySignature as Mock).mockResolvedValue(true);
const result = await customerIoTrigger.webhook!.call(mockWebhookFunctions);
@@ -47,12 +48,12 @@ describe('CustomerIoTrigger Node', () => {
});
it('should respond with 401 when signature is invalid', async () => {
(verifySignature as jest.Mock).mockResolvedValue(false);
(verifySignature as Mock).mockResolvedValue(false);
const mockResponse = {
status: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
end: jest.fn(),
status: vi.fn().mockReturnThis(),
send: vi.fn().mockReturnThis(),
end: vi.fn(),
};
mockWebhookFunctions.getResponseObject.mockReturnValue(mockResponse as any);
@@ -1,16 +1,18 @@
import { createHmac, timingSafeEqual } from 'crypto';
import { verifySignature } from '../CustomerIoTriggerHelpers';
import type { Mock } from 'vitest';
import type * as _importType0 from 'crypto';
jest.mock('crypto', () => ({
...jest.requireActual('crypto'),
createHmac: jest.fn().mockReturnValue({
update: jest.fn().mockReturnThis(),
digest: jest
vi.mock('crypto', async () => ({
...(await vi.importActual<typeof _importType0>('crypto')),
createHmac: vi.fn().mockReturnValue({
update: vi.fn().mockReturnThis(),
digest: vi
.fn()
.mockReturnValue('a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503'),
}),
timingSafeEqual: jest.fn(),
timingSafeEqual: vi.fn(),
}));
describe('CustomerIoTriggerHelpers', () => {
@@ -21,20 +23,20 @@ describe('CustomerIoTriggerHelpers', () => {
const testSignature = 'a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503';
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
// Mock Date.now() to return a fixed timestamp within the replay window
const fixedDate = new Date(parseInt(testTimestamp, 10) * 1000);
jest.spyOn(Date, 'now').mockImplementation(() => fixedDate.getTime());
vi.spyOn(Date, 'now').mockImplementation(() => fixedDate.getTime());
mockWebhookFunctions = {
getCredentials: jest.fn(),
getRequestObject: jest.fn(),
getNode: jest.fn().mockReturnValue({ name: 'Customer.io Trigger' }),
getCredentials: vi.fn(),
getRequestObject: vi.fn(),
getNode: vi.fn().mockReturnValue({ name: 'Customer.io Trigger' }),
};
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockImplementation((header) => {
header: vi.fn().mockImplementation((header) => {
if (header === 'x-cio-signature') return testSignature;
if (header === 'x-cio-timestamp') return testTimestamp;
return null;
@@ -68,7 +70,7 @@ describe('CustomerIoTriggerHelpers', () => {
webhookSigningKey: testSigningKey,
});
(timingSafeEqual as jest.Mock).mockReturnValue(true);
(timingSafeEqual as Mock).mockReturnValue(true);
const result = await verifySignature.call(mockWebhookFunctions);
@@ -82,7 +84,7 @@ describe('CustomerIoTriggerHelpers', () => {
webhookSigningKey: testSigningKey,
});
(timingSafeEqual as jest.Mock).mockReturnValue(false);
(timingSafeEqual as Mock).mockReturnValue(false);
const result = await verifySignature.call(mockWebhookFunctions);
@@ -97,7 +99,7 @@ describe('CustomerIoTriggerHelpers', () => {
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockImplementation((header) => {
header: vi.fn().mockImplementation((header) => {
if (header === 'x-cio-timestamp') return testTimestamp;
return null;
}),
@@ -115,7 +117,7 @@ describe('CustomerIoTriggerHelpers', () => {
});
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockImplementation((header) => {
header: vi.fn().mockImplementation((header) => {
if (header === 'x-cio-signature') return testSignature;
return null;
}),
@@ -133,7 +135,7 @@ describe('CustomerIoTriggerHelpers', () => {
});
const futureDate = new Date((parseInt(testTimestamp, 10) + 301) * 1000);
jest.spyOn(Date, 'now').mockImplementation(() => futureDate.getTime());
vi.spyOn(Date, 'now').mockImplementation(() => futureDate.getTime());
const result = await verifySignature.call(mockWebhookFunctions);
@@ -145,7 +147,7 @@ describe('CustomerIoTriggerHelpers', () => {
webhookSigningKey: testSigningKey,
});
(timingSafeEqual as jest.Mock).mockReturnValue(true);
(timingSafeEqual as Mock).mockReturnValue(true);
await verifySignature.call(mockWebhookFunctions);
@@ -161,7 +163,7 @@ describe('CustomerIoTriggerHelpers', () => {
const rawBuffer = Buffer.from(testBody);
mockWebhookFunctions.getRequestObject.mockReturnValue({
header: jest.fn().mockImplementation((header) => {
header: vi.fn().mockImplementation((header) => {
if (header === 'x-cio-signature') return testSignature;
if (header === 'x-cio-timestamp') return testTimestamp;
return null;
@@ -169,7 +171,7 @@ describe('CustomerIoTriggerHelpers', () => {
rawBody: rawBuffer,
});
(timingSafeEqual as jest.Mock).mockReturnValue(true);
(timingSafeEqual as Mock).mockReturnValue(true);
await verifySignature.call(mockWebhookFunctions);
@@ -1,6 +1,6 @@
import type { IExecuteFunctions, INode } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import { resolveDataTableId } from '../../common/utils';
@@ -80,14 +80,14 @@ describe('resolveDataTableId', () => {
ctx.getNode.mockReturnValue(mockNode);
const mockAggregateProxy = {
getManyAndCount: jest.fn().mockResolvedValue({
getManyAndCount: vi.fn().mockResolvedValue({
data: [{ id: 'resolved-table-id', name: 'my table' }],
count: 1,
}),
};
ctx.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const resourceLocator = {
@@ -109,14 +109,14 @@ describe('resolveDataTableId', () => {
ctx.getNode.mockReturnValue(mockNode);
const mockAggregateProxy = {
getManyAndCount: jest.fn().mockResolvedValue({
getManyAndCount: vi.fn().mockResolvedValue({
data: [{ id: 'table-id', name: 'customers' }],
count: 1,
}),
};
ctx.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const resourceLocator = {
@@ -137,14 +137,14 @@ describe('resolveDataTableId', () => {
ctx.getNode.mockReturnValue(mockNode);
const mockAggregateProxy = {
getManyAndCount: jest.fn().mockResolvedValue({
getManyAndCount: vi.fn().mockResolvedValue({
data: [],
count: 0,
}),
};
ctx.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const resourceLocator = {
@@ -163,14 +163,14 @@ describe('resolveDataTableId', () => {
ctx.getNode.mockReturnValue(mockNode);
const mockAggregateProxy = {
getManyAndCount: jest.fn().mockResolvedValue({
getManyAndCount: vi.fn().mockResolvedValue({
data: [{ id: 'table-id', name: 'users & customers' }],
count: 1,
}),
};
ctx.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const resourceLocator = {
@@ -12,8 +12,8 @@ import { executeSelectMany, getSelectFilter } from '../../common/selectMany';
describe('selectMany utils', () => {
let mockExecuteFunctions: IExecuteFunctions;
const getManyRowsAndCount = jest.fn();
const dataTableProxy = jest.mocked<IDataTableProjectService>({
const getManyRowsAndCount = vi.fn();
const dataTableProxy = vi.mocked<IDataTableProjectService>({
getManyRowsAndCount,
} as unknown as IDataTableProjectService);
const dataTableId = 2345;
@@ -30,7 +30,7 @@ describe('selectMany utils', () => {
];
const mockDataTableProxy = {
getColumns: jest.fn().mockResolvedValue([
getColumns: vi.fn().mockResolvedValue([
{ name: 'name', type: 'string' },
{ name: 'age', type: 'number' },
{ name: 'status', type: 'string' },
@@ -38,8 +38,8 @@ describe('selectMany utils', () => {
};
mockExecuteFunctions = {
getNode: jest.fn().mockReturnValue(node),
getNodeParameter: jest.fn().mockImplementation((field) => {
getNode: vi.fn().mockReturnValue(node),
getNodeParameter: vi.fn().mockImplementation((field) => {
switch (field) {
case DATA_TABLE_ID_FIELD:
return dataTableId;
@@ -50,11 +50,11 @@ describe('selectMany utils', () => {
}
}),
helpers: {
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTableProxy),
getDataTableProxy: vi.fn().mockResolvedValue(mockDataTableProxy),
},
} as unknown as IExecuteFunctions;
jest.clearAllMocks();
vi.clearAllMocks();
});
describe('executeSelectMany', () => {
@@ -121,11 +121,10 @@ describe('selectMany utils', () => {
filters = [];
// ACT ASSERT
await expect(executeSelectMany(mockExecuteFunctions, 0, dataTableProxy)).rejects.toEqual(
new NodeOperationError(
node,
'synchronization error: result count changed during pagination',
),
const execution = executeSelectMany(mockExecuteFunctions, 0, dataTableProxy);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow(
'synchronization error: result count changed during pagination',
);
});
@@ -262,7 +261,7 @@ describe('selectMany utils', () => {
{ condition: 'eq', keyName: 'status', keyValue: 'active' },
{ condition: 'gte', keyName: 'age', keyValue: 21 },
];
mockExecuteFunctions.getNodeParameter = jest.fn().mockImplementation((field) => {
mockExecuteFunctions.getNodeParameter = vi.fn().mockImplementation((field) => {
switch (field) {
case DATA_TABLE_ID_FIELD:
return dataTableId;
@@ -290,7 +289,7 @@ describe('selectMany utils', () => {
{ condition: 'eq', keyName: 'status', keyValue: 'inactive' },
{ condition: 'gte', keyName: 'age', keyValue: 21 },
];
mockExecuteFunctions.getNodeParameter = jest.fn().mockImplementation((field) => {
mockExecuteFunctions.getNodeParameter = vi.fn().mockImplementation((field) => {
switch (field) {
case DATA_TABLE_ID_FIELD:
return dataTableId;
@@ -318,7 +317,7 @@ describe('selectMany utils', () => {
{ condition: 'eq', keyName: 'status', keyValue: 'inactive' },
{ condition: 'gte', keyName: 'age', keyValue: 21 },
];
mockExecuteFunctions.getNodeParameter = jest.fn().mockImplementation((field) => {
mockExecuteFunctions.getNodeParameter = vi.fn().mockImplementation((field) => {
switch (field) {
case DATA_TABLE_ID_FIELD:
return dataTableId;
@@ -345,7 +344,7 @@ describe('selectMany utils', () => {
const testDate = new Date('2025-12-11T10:30:59.000Z');
const testUpdatedDate = new Date('2025-12-12T11:16:53.385Z');
filters = [];
mockExecuteFunctions.getNodeParameter = jest.fn().mockImplementation((field) => {
mockExecuteFunctions.getNodeParameter = vi.fn().mockImplementation((field) => {
switch (field) {
case DATA_TABLE_ID_FIELD:
return dataTableId;
@@ -357,7 +356,7 @@ describe('selectMany utils', () => {
return true;
}
});
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ ...node, typeVersion: 1.1 });
mockExecuteFunctions.getNode = vi.fn().mockReturnValue({ ...node, typeVersion: 1.1 });
getManyRowsAndCount.mockReturnValue({
data: [
{
@@ -394,7 +393,7 @@ describe('selectMany utils', () => {
const testDate = new Date('2025-12-11T10:30:59.000Z');
const testUpdatedDate = new Date('2025-12-12T11:16:53.385Z');
filters = [];
mockExecuteFunctions.getNodeParameter = jest.fn().mockImplementation((field) => {
mockExecuteFunctions.getNodeParameter = vi.fn().mockImplementation((field) => {
switch (field) {
case DATA_TABLE_ID_FIELD:
return dataTableId;
@@ -406,7 +405,7 @@ describe('selectMany utils', () => {
return true;
}
});
mockExecuteFunctions.getNode = jest.fn().mockReturnValue({ ...node, typeVersion: 1 });
mockExecuteFunctions.getNode = vi.fn().mockReturnValue({ ...node, typeVersion: 1 });
getManyRowsAndCount.mockReturnValue({
data: [
{
@@ -587,13 +586,12 @@ describe('selectMany utils', () => {
];
// ACT & ASSERT
await expect(getSelectFilter(mockExecuteFunctions, 0)).rejects.toEqual(
new NodeOperationError(
node,
'Filter validation failed: Column(s) "invalid_column" do not exist in the selected table. ' +
'This often happens when switching between tables with different schemas. ' +
'Please update your filter conditions.',
),
const execution = getSelectFilter(mockExecuteFunctions, 0);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow(
'Filter validation failed: Column(s) "invalid_column" do not exist in the selected table. ' +
'This often happens when switching between tables with different schemas. ' +
'Please update your filter conditions.',
);
});
@@ -647,13 +645,12 @@ describe('selectMany utils', () => {
];
// ACT & ASSERT
await expect(getSelectFilter(mockExecuteFunctions, 0)).rejects.toEqual(
new NodeOperationError(
node,
'Filter validation failed: Column(s) "invalid1, invalid2" do not exist in the selected table. ' +
'This often happens when switching between tables with different schemas. ' +
'Please update your filter conditions.',
),
const execution = getSelectFilter(mockExecuteFunctions, 0);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow(
'Filter validation failed: Column(s) "invalid1, invalid2" do not exist in the selected table. ' +
'This often happens when switching between tables with different schemas. ' +
'Please update your filter conditions.',
);
});
});
@@ -4,6 +4,7 @@ import type { IDataTableProjectService, IExecuteFunctions, INode } from 'n8n-wor
import { ANY_CONDITION } from '../../common/constants';
import { DATA_TABLE_ID_FIELD } from '../../common/fields';
import * as getOperation from '../../actions/row/get.operation';
import type { Mock } from 'vitest';
describe('DataTable Get Operation - Sort Feature', () => {
let mockExecuteFunctions: IExecuteFunctions;
@@ -11,8 +12,8 @@ describe('DataTable Get Operation - Sort Feature', () => {
const node = { id: 'test', typeVersion: 1.1 } as INode;
beforeEach(() => {
const getManyRowsAndCount = jest.fn();
const getColumns = jest.fn();
const getManyRowsAndCount = vi.fn();
const getColumns = vi.fn();
mockDataTableProxy = {
getManyRowsAndCount,
@@ -27,20 +28,20 @@ describe('DataTable Get Operation - Sort Feature', () => {
]);
mockExecuteFunctions = {
getNode: jest.fn().mockReturnValue(node),
getNodeParameter: jest.fn(),
getNode: vi.fn().mockReturnValue(node),
getNodeParameter: vi.fn(),
helpers: {
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTableProxy),
getDataTableProxy: vi.fn().mockResolvedValue(mockDataTableProxy),
},
} as unknown as IExecuteFunctions;
jest.clearAllMocks();
vi.clearAllMocks();
});
describe('Single Column Sort', () => {
it('should sort by column ascending', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
(mockExecuteFunctions.getNodeParameter as Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'name';
@@ -52,7 +53,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
(mockDataTableProxy.getManyRowsAndCount as Mock).mockResolvedValue({
data: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
@@ -73,7 +74,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
it('should sort by column descending', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
(mockExecuteFunctions.getNodeParameter as Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'age';
@@ -85,7 +86,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
(mockDataTableProxy.getManyRowsAndCount as Mock).mockResolvedValue({
data: [
{ id: 2, age: 30 },
{ id: 1, age: 25 },
@@ -107,7 +108,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
it.each(['createdAt', 'updatedAt'])(
'should allow sorting by system column %s even though getColumns does not include it',
async (column: string) => {
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
(mockExecuteFunctions.getNodeParameter as Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return column;
@@ -119,7 +120,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
(mockDataTableProxy.getManyRowsAndCount as Mock).mockResolvedValue({
data: [{ id: 1 }],
count: 1,
});
@@ -136,7 +137,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
it('should sort by id column', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
(mockExecuteFunctions.getNodeParameter as Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'id';
@@ -147,7 +148,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
(mockDataTableProxy.getManyRowsAndCount as Mock).mockResolvedValue({
data: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
@@ -171,7 +172,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
describe('No Sort Rule', () => {
it('should work without sort rule (orderBy false)', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
(mockExecuteFunctions.getNodeParameter as Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return false;
if (param === 'returnAll') return false;
@@ -181,7 +182,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
(mockDataTableProxy.getManyRowsAndCount as Mock).mockResolvedValue({
data: [{ id: 1 }],
count: 1,
});
@@ -200,9 +201,9 @@ describe('DataTable Get Operation - Sort Feature', () => {
it('should work with v1.0 (legacy version)', async () => {
// ARRANGE
const v10Node = { id: 'test', typeVersion: 1.0 } as INode;
(mockExecuteFunctions.getNode as jest.Mock).mockReturnValue(v10Node);
(mockExecuteFunctions.getNode as Mock).mockReturnValue(v10Node);
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
(mockExecuteFunctions.getNodeParameter as Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return false;
if (param === 'returnAll') return false;
@@ -212,7 +213,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
(mockDataTableProxy.getManyRowsAndCount as Mock).mockResolvedValue({
data: [{ id: 1 }],
count: 1,
});
@@ -232,7 +233,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
describe('Sort with Filters', () => {
it('should combine sort and filters correctly', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
(mockExecuteFunctions.getNodeParameter as Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'name';
@@ -246,7 +247,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
(mockDataTableProxy.getManyRowsAndCount as Mock).mockResolvedValue({
data: [
{ id: 1, name: 'Alice', status: 'active' },
{ id: 2, name: 'Bob', status: 'active' },
@@ -278,7 +279,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
describe('Column Validation', () => {
it('should throw when orderBy column does not exist in the table', async () => {
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
(mockExecuteFunctions.getNodeParameter as Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'nonExistentColumn';
@@ -296,7 +297,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
});
it('should not throw when orderBy column exists in the table', async () => {
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
(mockExecuteFunctions.getNodeParameter as Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'name';
@@ -308,7 +309,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
(mockDataTableProxy.getManyRowsAndCount as Mock).mockResolvedValue({
data: [{ id: 1, name: 'Alice' }],
count: 1,
});
@@ -321,7 +322,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
describe('Sort with Pagination', () => {
it('should maintain sort order with returnAll=true', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
(mockExecuteFunctions.getNodeParameter as Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'id';
@@ -332,7 +333,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
(mockDataTableProxy.getManyRowsAndCount as Mock).mockResolvedValue({
data: [{ id: 5 }, { id: 4 }, { id: 3 }],
count: 3,
});
@@ -350,7 +351,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
it('should maintain sort order with limit', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
(mockExecuteFunctions.getNodeParameter as Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'name';
@@ -362,7 +363,7 @@ describe('DataTable Get Operation - Sort Feature', () => {
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
(mockDataTableProxy.getManyRowsAndCount as Mock).mockResolvedValue({
data: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
@@ -1,4 +1,4 @@
import { mock } from 'jest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type {
IExecuteFunctions,
IDataTableProjectAggregateService,
@@ -41,7 +41,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const mockResult = {
@@ -83,7 +83,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const mockResult = {
@@ -119,7 +119,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const existingTable = {
@@ -159,7 +159,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
mockAggregateProxy.getManyAndCount.mockResolvedValue({
@@ -210,7 +210,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const mockResult = {
@@ -254,7 +254,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTableProxy),
getDataTableProxy: vi.fn().mockResolvedValue(mockDataTableProxy),
} as any;
mockDataTableProxy.deleteDataTable.mockResolvedValue(true);
@@ -276,7 +276,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTableProxy),
getDataTableProxy: vi.fn().mockResolvedValue(mockDataTableProxy),
} as any;
mockDataTableProxy.deleteDataTable.mockResolvedValue(false);
@@ -302,7 +302,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const mockTables = [
@@ -338,7 +338,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const mockTables = [
@@ -374,7 +374,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const mockTables = [{ id: 'table-1', name: 'Test Table', columns: [] }];
@@ -408,7 +408,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
const mockTables = [
@@ -445,7 +445,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
// First page
@@ -500,7 +500,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableAggregateProxy: jest.fn().mockResolvedValue(mockAggregateProxy),
getDataTableAggregateProxy: vi.fn().mockResolvedValue(mockAggregateProxy),
} as any;
mockAggregateProxy.getManyAndCount.mockResolvedValue({
@@ -527,7 +527,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTableProxy),
getDataTableProxy: vi.fn().mockResolvedValue(mockDataTableProxy),
} as any;
mockDataTableProxy.updateDataTable.mockResolvedValue(true);
@@ -552,7 +552,7 @@ describe('Table Operations', () => {
});
mockExecuteFunctions.helpers = {
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTableProxy),
getDataTableProxy: vi.fn().mockResolvedValue(mockDataTableProxy),
} as any;
mockDataTableProxy.updateDataTable.mockResolvedValue(false);
@@ -3,11 +3,11 @@ import type * as nWorkflow from 'n8n-workflow';
import nock from 'nock';
// Mock sleep from n8n-workflow so polling tests run without real delays
jest.mock('n8n-workflow', () => {
const actual = jest.requireActual<typeof nWorkflow>('n8n-workflow');
vi.mock('n8n-workflow', async () => {
const actual = await vi.importActual<typeof nWorkflow>('n8n-workflow');
return {
...actual,
sleep: jest.fn().mockResolvedValue(undefined),
sleep: vi.fn().mockResolvedValue(undefined),
};
});

Some files were not shown because too many files have changed in this diff Show More