fix(core): Expand ESLint rule to catch toThrowError error instance assertions (#31187)

This commit is contained in:
Matsu 2026-05-28 12:42:03 +03:00 committed by GitHub
parent f2a2f7060d
commit 39d660e522
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 479 additions and 506 deletions

View File

@ -41,5 +41,25 @@ ruleTester.run('no-error-instance-in-to-throw', NoErrorInstanceInToThrowRule, {
code: 'expect(() => foo()).toThrow(new TypeError())',
errors: [{ messageId: 'noErrorInstance', data: { className: 'TypeError' } }],
},
{
code: "expect(() => foo()).toThrowError(new Error('bad'))",
errors: [{ messageId: 'noErrorInstance', data: { className: 'Error' } }],
},
{
code: "expect(() => foo()).toThrowError(new NodeOperationError(node, 'bad'))",
errors: [{ messageId: 'noErrorInstance', data: { className: 'NodeOperationError' } }],
},
{
code: "await expect(foo()).rejects.toThrowError(new Error('bad'))",
errors: [{ messageId: 'noErrorInstance', data: { className: 'Error' } }],
},
{
code: "await expect(foo()).rejects.toThrowError(new NodeOperationError(node, 'message'))",
errors: [{ messageId: 'noErrorInstance', data: { className: 'NodeOperationError' } }],
},
{
code: 'expect(() => foo()).toThrowError(new TypeError())',
errors: [{ messageId: 'noErrorInstance', data: { className: 'TypeError' } }],
},
],
});

View File

@ -5,11 +5,11 @@ export const NoErrorInstanceInToThrowRule = ESLintUtils.RuleCreator.withoutDocs(
type: 'problem',
docs: {
description:
'Disallow passing error instances to `.toThrow()`. Jest compares by reference, not message, making assertions flaky. Pass the error class and message string separately instead.',
'Disallow passing error instances to `.toThrow()` and `.toThrowError`. Jest compares by reference, not message, making assertions flaky. Pass the error class and message string separately instead.',
},
messages: {
noErrorInstance:
"Do not pass an error instance to `.toThrow()`. Use `.toThrow({{className}})` for type checking and `.toThrow('message')` for message matching.",
"Do not pass an error instance to `.toThrow()` or `.toThrowError`. Use `.toThrow({{className}})` for type checking and `.toThrow('message')` for message matching.",
},
schema: [],
},
@ -19,10 +19,11 @@ export const NoErrorInstanceInToThrowRule = ESLintUtils.RuleCreator.withoutDocs(
CallExpression(node) {
// Match: expect(...).toThrow(new Foo(...))
// Match: expect(...).rejects.toThrow(new Foo(...))
// Match: expect(...).rejects.toThrowError(new Foo(...))
if (
node.callee.type !== TSESTree.AST_NODE_TYPES.MemberExpression ||
node.callee.property.type !== TSESTree.AST_NODE_TYPES.Identifier ||
node.callee.property.name !== 'toThrow'
(node.callee.property.name !== 'toThrow' && node.callee.property.name !== 'toThrowError')
) {
return;
}

View File

@ -142,9 +142,9 @@ describe('AuthController', () => {
// Act
await expect(controller.login(req, res, body)).rejects.toThrowError(
new AuthError('SSO is enabled, please log in with SSO'),
);
const execution = controller.login(req, res, body);
await expect(execution).rejects.toThrow(AuthError);
await expect(execution).rejects.toThrow('SSO is enabled, please log in with SSO');
// Assert
@ -174,9 +174,9 @@ describe('AuthController', () => {
emailAuthHandler.handleLogin.mockResolvedValue(undefined);
config.set('userManagement.authenticationMethod', 'oidc');
await expect(controller.login(req, res, body)).rejects.toThrowError(
new AuthError('SSO is enabled, please log in with SSO'),
);
const execution = controller.login(req, res, body);
await expect(execution).rejects.toThrow(AuthError);
await expect(execution).rejects.toThrow('SSO is enabled, please log in with SSO');
expect(eventsService.emit).toHaveBeenCalledWith('user-login-failed', {
authenticationMethod: 'email',

View File

@ -115,13 +115,13 @@ describe('MeController', () => {
}
});
await expect(
controller.updateCurrentUser(
req,
mock(),
mock({ email: user.email, firstName: 'John', lastName: 'Potato' }),
),
).rejects.toThrowError(new BadRequestError('Invalid email address'));
const execution = controller.updateCurrentUser(
req,
mock(),
mock({ email: user.email, firstName: 'John', lastName: 'Potato' }),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Invalid email address');
});
describe('when user is authenticated via LDAP or OIDC', () => {
@ -143,18 +143,19 @@ describe('MeController', () => {
} as unknown as AuthIdentity);
getCurrentAuthenticationMethodMock.mockReturnValue('ldap');
await expect(
controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'ldap@email.com',
firstName: 'Jane',
lastName: 'Doe',
}),
),
).rejects.toThrowError(
new BadRequestError('LDAP user may not change their profile information'),
const execution = controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'ldap@email.com',
firstName: 'Jane',
lastName: 'Doe',
}),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow(
'LDAP user may not change their profile information',
);
});
@ -176,18 +177,19 @@ describe('MeController', () => {
} as unknown as AuthIdentity);
getCurrentAuthenticationMethodMock.mockReturnValue('oidc');
await expect(
controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'new-oidc@email.com',
firstName: 'John',
lastName: 'Doe',
}),
),
).rejects.toThrowError(
new BadRequestError('OIDC user may not change their profile information'),
const execution = controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'new-oidc@email.com',
firstName: 'John',
lastName: 'Doe',
}),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow(
'OIDC user may not change their profile information',
);
});
@ -237,18 +239,19 @@ describe('MeController', () => {
} as unknown as AuthIdentity);
getCurrentAuthenticationMethodMock.mockReturnValue('ldap');
await expect(
controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'multi@email.com',
firstName: 'Jane',
lastName: 'Doe',
}),
),
).rejects.toThrowError(
new BadRequestError('LDAP user may not change their profile information'),
const execution = controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'multi@email.com',
firstName: 'Jane',
lastName: 'Doe',
}),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow(
'LDAP user may not change their profile information',
);
});
});
@ -281,18 +284,19 @@ describe('MeController', () => {
} as unknown as AuthIdentity);
getCurrentAuthenticationMethodMock.mockReturnValue('saml');
await expect(
controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'saml@email.com',
firstName: 'Jane',
lastName: 'Doe',
}),
),
).rejects.toThrowError(
new BadRequestError('SAML user may not change their profile information'),
const execution = controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'saml@email.com',
firstName: 'Jane',
lastName: 'Doe',
}),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow(
'SAML user may not change their profile information',
);
});
@ -504,17 +508,18 @@ describe('MeController', () => {
} as unknown as User;
const req = { user, browserId } as unknown as AuthenticatedRequest;
await expect(
controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'new@email.com',
firstName: 'John',
lastName: 'Potato',
}),
),
).rejects.toThrowError(new BadRequestError('Two-factor code is required to change email'));
const execution = controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'new@email.com',
firstName: 'John',
lastName: 'Potato',
}),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Two-factor code is required to change email');
});
it('should throw InvalidMfaCodeError if mfa code is invalid', async () => {
@ -589,17 +594,18 @@ describe('MeController', () => {
} as unknown as User;
const req = { user, browserId } as unknown as AuthenticatedRequest;
await expect(
controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'michel-new@email.com',
firstName: 'Michel',
lastName: 'n8n',
}),
),
).rejects.toThrowError(new BadRequestError('Current password is required to change email'));
const execution = controller.updateCurrentUser(
req,
mock(),
new UserUpdateRequestDto({
email: 'michel-new@email.com',
firstName: 'Michel',
lastName: 'n8n',
}),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Current password is required to change email');
});
it('should throw BadRequestError if currentPassword is not a string', async () => {
@ -611,14 +617,15 @@ describe('MeController', () => {
} as unknown as User;
const req = { user, browserId } as unknown as AuthenticatedRequest;
await expect(
controller.updateCurrentUser(req, mock(), {
email: 'michel-new@email.com',
firstName: 'Michel',
lastName: 'n8n',
currentPassword: 123 as any,
} as any),
).rejects.toThrowError(new BadRequestError('Current password is required to change email'));
const execution = controller.updateCurrentUser(req, mock(), {
email: 'michel-new@email.com',
firstName: 'Michel',
lastName: 'n8n',
currentPassword: 123 as any,
} as any);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Current password is required to change email');
});
it('should throw BadRequestError if currentPassword is incorrect', async () => {
@ -629,21 +636,20 @@ describe('MeController', () => {
} as unknown as User;
const req = { user, browserId } as unknown as AuthenticatedRequest;
await expect(
controller.updateCurrentUser(
req,
mock(),
mock({
email: 'michel-new@email.com',
firstName: 'Michel',
lastName: 'n8n',
currentPassword: 'wrong-password',
}),
),
).rejects.toThrowError(
new BadRequestError(
'Unable to update profile. Please check your credentials and try again.',
),
const execution = controller.updateCurrentUser(
req,
mock(),
mock({
email: 'michel-new@email.com',
firstName: 'Michel',
lastName: 'n8n',
currentPassword: 'wrong-password',
}),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow(
'Unable to update profile. Please check your credentials and try again.',
);
});
@ -728,16 +734,15 @@ describe('MeController', () => {
} as unknown as User;
const req = { user, browserId } as unknown as AuthenticatedRequest;
await expect(
controller.updateCurrentUser(
req,
mock(),
mock({ email: user.email, firstName: 'John', lastName: 'Doe' }),
),
).rejects.toThrowError(
new ForbiddenError(
'This account is managed via environment variables and cannot be modified through the API',
),
const execution = controller.updateCurrentUser(
req,
mock(),
mock({ email: user.email, firstName: 'John', lastName: 'Doe' }),
);
await expect(execution).rejects.toThrow(ForbiddenError);
await expect(execution).rejects.toThrow(
'This account is managed via environment variables and cannot be modified through the API',
);
});
@ -749,16 +754,15 @@ describe('MeController', () => {
} as unknown as User,
} as unknown as AuthenticatedRequest;
await expect(
controller.updatePassword(
req,
mock(),
mock({ currentPassword: 'old_password', newPassword: 'NewPassword123' }),
),
).rejects.toThrowError(
new ForbiddenError(
'This account is managed via environment variables and cannot be modified through the API',
),
const execution = controller.updatePassword(
req,
mock(),
mock({ currentPassword: 'old_password', newPassword: 'NewPassword123' }),
);
await expect(execution).rejects.toThrow(ForbiddenError);
await expect(execution).rejects.toThrow(
'This account is managed via environment variables and cannot be modified through the API',
);
});
@ -795,22 +799,30 @@ describe('MeController', () => {
const req = {
user: mock({ password: undefined }),
} as unknown as AuthenticatedRequest;
await expect(
controller.updatePassword(req, mock(), mock({ currentPassword: '', newPassword: '' })),
).rejects.toThrowError(new BadRequestError('Requesting user not set up.'));
const execution = controller.updatePassword(
req,
mock(),
mock({ currentPassword: '', newPassword: '' }),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Requesting user not set up.');
});
it("should throw if currentPassword does not match the user's password", async () => {
const req = {
user: mock({ password: passwordHash }),
} as unknown as AuthenticatedRequest;
await expect(
controller.updatePassword(
req,
mock(),
mock({ currentPassword: 'not_old_password', newPassword: '' }),
),
).rejects.toThrowError(new BadRequestError('Provided current password is incorrect.'));
const execution = controller.updatePassword(
req,
mock(),
mock({ currentPassword: 'not_old_password', newPassword: '' }),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Provided current password is incorrect.');
});
describe('should throw if newPassword is not valid', () => {
@ -820,13 +832,15 @@ describe('MeController', () => {
user: mock({ password: passwordHash }),
browserId,
} as unknown as AuthenticatedRequest;
await expect(
controller.updatePassword(
req,
mock(),
mock({ currentPassword: 'old_password', newPassword }),
),
).rejects.toThrowError(new BadRequestError(errorMessage));
const execution = controller.updatePassword(
req,
mock(),
mock({ currentPassword: 'old_password', newPassword }),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow(errorMessage);
});
});
});
@ -876,15 +890,14 @@ describe('MeController', () => {
user: mock({ password: passwordHash, mfaEnabled: true }),
} as unknown as AuthenticatedRequest;
await expect(
controller.updatePassword(
req,
mock(),
mock({ currentPassword: 'old_password', newPassword: 'NewPassword123' }),
),
).rejects.toThrowError(
new BadRequestError('Two-factor code is required to change password.'),
const execution = controller.updatePassword(
req,
mock(),
mock({ currentPassword: 'old_password', newPassword: 'NewPassword123' }),
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Two-factor code is required to change password.');
});
it('should throw InvalidMfaCodeError if invalid mfa code is given', async () => {
@ -937,9 +950,11 @@ describe('MeController', () => {
const req = mock<MeRequest.SurveyAnswers>({
body: undefined,
});
await expect(controller.storeSurveyAnswers(req)).rejects.toThrowError(
new BadRequestError('Personalization answers are mandatory'),
);
const execution = controller.storeSurveyAnswers(req);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Personalization answers are mandatory');
});
it('should not flag XSS attempt for `<` sign in company size', async () => {
@ -977,7 +992,7 @@ describe('MeController', () => {
[fieldName]: ['<script>alert("XSS")</script>'],
};
await expect(controller.storeSurveyAnswers(req)).rejects.toThrowError(BadRequestError);
await expect(controller.storeSurveyAnswers(req)).rejects.toThrow(BadRequestError);
});
test.each([
@ -997,7 +1012,7 @@ describe('MeController', () => {
[fieldName]: '<script>alert("XSS")</script>',
};
await expect(controller.storeSurveyAnswers(req)).rejects.toThrowError(BadRequestError);
await expect(controller.storeSurveyAnswers(req)).rejects.toThrow(BadRequestError);
});
});
});

View File

@ -30,9 +30,10 @@ describe('OwnerController', () => {
.spyOn(ownershipService, 'setupOwner')
.mockRejectedValueOnce(new BadRequestError('Instance owner already setup'));
await expect(controller.setupOwner(mock(), mock(), mock())).rejects.toThrowError(
new BadRequestError('Instance owner already setup'),
);
const execution = controller.setupOwner(mock(), mock(), mock());
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Instance owner already setup');
expect(authService.issueCookie).not.toHaveBeenCalled();
});

View File

@ -22,9 +22,10 @@ describe('TranslationController', () => {
const req = mock<TranslationRequest.Credential>({ query: { credentialType } });
credentialTypes.recognizes.calledWith(credentialType).mockReturnValue(false);
await expect(controller.getCredentialTranslation(req)).rejects.toThrowError(
new BadRequestError(`Invalid Credential type: "${credentialType}"`),
);
const execution = controller.getCredentialTranslation(req);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow(`Invalid Credential type: "${credentialType}"`);
});
it('should return translation json on valid credential types', async () => {

View File

@ -98,9 +98,10 @@ describe('LicenseService', () => {
Object.entries(LicenseErrors).forEach(([errorId, message]) =>
it(`should handle ${errorId} error`, async () => {
license.activate.mockRejectedValueOnce(new LicenseError(errorId));
await expect(licenseService.activateLicense('')).rejects.toThrowError(
new BadRequestError(message),
);
const execution = licenseService.activateLicense('');
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow(message);
}),
);
});
@ -126,9 +127,10 @@ describe('LicenseService', () => {
test('on failure', async () => {
license.renew.mockRejectedValueOnce(new LicenseError('RESERVATION_EXPIRED'));
await expect(licenseService.renewLicense()).rejects.toThrowError(
new BadRequestError('Activation key has expired'),
);
const execution = licenseService.renewLicense();
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Activation key has expired');
expect(eventService.emit).toHaveBeenCalledWith('license-renewal-attempted', {
success: false,

View File

@ -242,15 +242,18 @@ describe('InsightsController', () => {
});
it('should throw a BadRequestError when endDate is before startDate', async () => {
await expect(
controller.getInsightsSummary(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getInsightsSummary(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate: new Date('2025-06-10'),
endDate: new Date('2025-06-01'),
projectId: 'test-project',
}),
).rejects.toThrowError(
new BadRequestError('endDate must be the same as or after startDate'),
},
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('endDate must be the same as or after startDate');
});
});
@ -266,14 +269,19 @@ describe('InsightsController', () => {
]);
// ACT & ASSERT
await expect(
controller.getInsightsSummary(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getInsightsSummary(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate: new Date('2025-06-01T00:00:00Z'),
// same day as startDate to force 'hour' granularity
endDate: new Date('2025-06-01T00:00:00Z'),
}),
).rejects.toThrowError(
new ForbiddenError('Hourly data is not available with your current license'),
},
);
await expect(execution).rejects.toThrow(ForbiddenError);
await expect(execution).rejects.toThrow(
'Hourly data is not available with your current license',
);
});
@ -291,15 +299,18 @@ describe('InsightsController', () => {
]);
// ACT & ASSERT
await expect(
controller.getInsightsSummary(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getInsightsSummary(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate: outOfRangeStart,
endDate,
}),
).rejects.toThrowError(
new ForbiddenError(
'The selected date range exceeds the maximum history allowed by your license',
),
},
);
await expect(execution).rejects.toThrow(ForbiddenError);
await expect(execution).rejects.toThrow(
'The selected date range exceeds the maximum history allowed by your license',
);
});
});
@ -489,17 +500,20 @@ describe('InsightsController', () => {
const startDate = DateTime.now().startOf('day').minus({ days: 10 }).toJSDate();
const endDate = DateTime.now().startOf('day').minus({ days: 12 }).toJSDate();
await expect(
controller.getInsightsByWorkflow(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getInsightsByWorkflow(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate,
endDate,
skip: 0,
take: 5,
sortBy: 'total:desc',
}),
).rejects.toThrowError(
new BadRequestError('endDate must be the same as or after startDate'),
},
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('endDate must be the same as or after startDate');
});
});
@ -515,16 +529,21 @@ describe('InsightsController', () => {
});
// ACT & ASSERT
await expect(
controller.getInsightsByWorkflow(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getInsightsByWorkflow(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate: new Date('2025-06-01T00:00:00Z'),
endDate: new Date('2025-06-01T00:00:00Z'),
skip: 0,
take: 5,
sortBy: 'total:desc',
}),
).rejects.toThrowError(
new ForbiddenError('Hourly data is not available with your current license'),
},
);
await expect(execution).rejects.toThrow(ForbiddenError);
await expect(execution).rejects.toThrow(
'Hourly data is not available with your current license',
);
});
@ -542,18 +561,21 @@ describe('InsightsController', () => {
});
// ACT & ASSERT
await expect(
controller.getInsightsByWorkflow(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getInsightsByWorkflow(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate: outOfRangeStart,
endDate,
skip: 0,
take: 5,
sortBy: 'total:desc',
}),
).rejects.toThrowError(
new ForbiddenError(
'The selected date range exceeds the maximum history allowed by your license',
),
},
);
await expect(execution).rejects.toThrow(ForbiddenError);
await expect(execution).rejects.toThrow(
'The selected date range exceeds the maximum history allowed by your license',
);
});
});
@ -718,14 +740,17 @@ describe('InsightsController', () => {
});
it('should throw a BadRequestError when endDate is before startDate', async () => {
await expect(
controller.getInsightsByTime(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getInsightsByTime(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate: new Date('2025-06-10'),
endDate: new Date('2025-06-01'),
}),
).rejects.toThrowError(
new BadRequestError('endDate must be the same as or after startDate'),
},
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('endDate must be the same as or after startDate');
});
});
@ -736,13 +761,18 @@ describe('InsightsController', () => {
insightsByPeriodRepository.getInsightsByTime.mockResolvedValue([]);
await expect(
controller.getInsightsByTime(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getInsightsByTime(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate: new Date('2025-06-01T00:00:00Z'),
endDate: new Date('2025-06-01T00:00:00Z'),
}),
).rejects.toThrowError(
new ForbiddenError('Hourly data is not available with your current license'),
},
);
await expect(execution).rejects.toThrow(ForbiddenError);
await expect(execution).rejects.toThrow(
'Hourly data is not available with your current license',
);
});
@ -755,15 +785,18 @@ describe('InsightsController', () => {
insightsByPeriodRepository.getInsightsByTime.mockResolvedValue([]);
await expect(
controller.getInsightsByTime(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getInsightsByTime(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate: outOfRangeStart,
endDate,
}),
).rejects.toThrowError(
new ForbiddenError(
'The selected date range exceeds the maximum history allowed by your license',
),
},
);
await expect(execution).rejects.toThrow(ForbiddenError);
await expect(execution).rejects.toThrow(
'The selected date range exceeds the maximum history allowed by your license',
);
});
});
@ -866,14 +899,17 @@ describe('InsightsController', () => {
});
it('should throw a BadRequestError when endDate is before startDate', async () => {
await expect(
controller.getTimeSavedInsightsByTime(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getTimeSavedInsightsByTime(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate: new Date('2025-06-10'),
endDate: new Date('2025-06-01'),
}),
).rejects.toThrowError(
new BadRequestError('endDate must be the same as or after startDate'),
},
);
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('endDate must be the same as or after startDate');
});
});
@ -884,13 +920,18 @@ describe('InsightsController', () => {
insightsByPeriodRepository.getInsightsByTime.mockResolvedValue([]);
await expect(
controller.getTimeSavedInsightsByTime(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getTimeSavedInsightsByTime(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate: new Date('2025-06-01T00:00:00Z'),
endDate: new Date('2025-06-01T00:00:00Z'),
}),
).rejects.toThrowError(
new ForbiddenError('Hourly data is not available with your current license'),
},
);
await expect(execution).rejects.toThrow(ForbiddenError);
await expect(execution).rejects.toThrow(
'Hourly data is not available with your current license',
);
});
@ -903,15 +944,18 @@ describe('InsightsController', () => {
insightsByPeriodRepository.getInsightsByTime.mockResolvedValue([]);
await expect(
controller.getTimeSavedInsightsByTime(mock<AuthenticatedRequest>(), mock<Response>(), {
const execution = controller.getTimeSavedInsightsByTime(
mock<AuthenticatedRequest>(),
mock<Response>(),
{
startDate: outOfRangeStart,
endDate,
}),
).rejects.toThrowError(
new ForbiddenError(
'The selected date range exceeds the maximum history allowed by your license',
),
},
);
await expect(execution).rejects.toThrow(ForbiddenError);
await expect(execution).rejects.toThrow(
'The selected date range exceeds the maximum history allowed by your license',
);
});
});

View File

@ -994,12 +994,13 @@ describe('InsightsService (Integration)', () => {
const startDate = DateTime.now().minus({ days: 3 }).startOf('day');
const endDate = startDate.plus({ hours: 10 });
expect(() =>
const execution = () =>
insightsService.validateDateFiltersLicense({
startDate: startDate.toJSDate(),
endDate: endDate.toJSDate(),
}),
).toThrowError(new UserError('Hourly data is not available with your current license'));
});
expect(execution).toThrow(UserError);
expect(execution).toThrow('Hourly data is not available with your current license');
});
test('does not throw if granularity is hour and hourly data is licensed', () => {
@ -1025,10 +1026,10 @@ describe('InsightsService (Integration)', () => {
const startDate = today.minus({ days: 8 }).toJSDate();
const endDate = today.toJSDate();
expect(() => insightsService.validateDateFiltersLicense({ startDate, endDate })).toThrowError(
new UserError(
'The selected date range exceeds the maximum history allowed by your license',
),
const execution = () => insightsService.validateDateFiltersLicense({ startDate, endDate });
expect(execution).toThrow(UserError);
expect(execution).toThrow(
'The selected date range exceeds the maximum history allowed by your license',
);
});

View File

@ -429,9 +429,10 @@ describe('SamlService', () => {
raw: {},
});
await expect(
samlService.handleSamlLogin(mock<express.Request>(), 'post'),
).rejects.toThrowError(new BadRequestError('Invalid email format'));
const execution = samlService.handleSamlLogin(mock<express.Request>(), 'post');
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Invalid email format');
});
it('logs in user that has already completed onboarding', async () => {

View File

@ -272,9 +272,9 @@ describe('OwnershipService', () => {
it('should throw a BadRequestError if the instance owner is already setup', async () => {
jest.spyOn(userRepository, 'exists').mockResolvedValueOnce(true);
await expect(ownershipService.setupOwner(mock())).rejects.toThrowError(
new BadRequestError('Instance owner already setup'),
);
const execution = ownershipService.setupOwner(mock());
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Instance owner already setup');
expect(userRepository.save).not.toHaveBeenCalled();
expect(eventService.emit).not.toHaveBeenCalled();
@ -287,9 +287,9 @@ describe('OwnershipService', () => {
userRepository.exists.mockResolvedValueOnce(false);
userRepository.findOne.mockResolvedValueOnce(null);
await expect(ownershipService.setupOwner(mock())).rejects.toThrowError(
new BadRequestError('Instance owner shell user not found'),
);
const execution = ownershipService.setupOwner(mock());
await expect(execution).rejects.toThrow(BadRequestError);
await expect(execution).rejects.toThrow('Instance owner shell user not found');
expect(userRepository.save).not.toHaveBeenCalled();
});

View File

@ -8,7 +8,12 @@ import type {
INodeExecutionData,
INodeType,
} from 'n8n-workflow';
import { NodeConnectionTypes, createRunExecutionData } from 'n8n-workflow';
import {
ApplicationError,
NodeConnectionTypes,
UnexpectedError,
createRunExecutionData,
} from 'n8n-workflow';
import { mock } from 'vitest-mock-extended';
import { NodeTypes } from '@test/helpers';
@ -62,9 +67,10 @@ describe('processRunExecutionData', () => {
// ACT & ASSERT
// The function returns a Promise, but throws synchronously, so we can't await it.
// eslint-disable-next-line @typescript-eslint/promise-function-async
expect(() => workflowExecute.processRunExecutionData(workflow)).toThrowError(
'Failed to run workflow due to missing execution data',
);
const execution = () => workflowExecute.processRunExecutionData(workflow);
expect(execution).toThrow(UnexpectedError);
expect(execution).toThrow('Failed to run workflow due to missing execution data');
});
test('returns input data verbatim', async () => {
@ -246,7 +252,10 @@ describe('processRunExecutionData', () => {
// ACT & ASSERT
// The function returns a Promise, but throws synchronously, so we can't await it.
// eslint-disable-next-line @typescript-eslint/promise-function-async
expect(() => workflowExecute.processRunExecutionData(workflow)).toThrowError(
const execution = () => workflowExecute.processRunExecutionData(workflow);
expect(execution).toThrow(ApplicationError);
expect(execution).toThrow(
'The workflow has issues and cannot be executed for that reason. Please fix them first.',
);
});

View File

@ -54,9 +54,10 @@ describe('ensureType', () => {
it('throws error for invalid conversion to number', () => {
const value = 'invalid';
expect(() => ensureType('number', value, 'myParam')).toThrowError(
new ExpressionError("Parameter 'myParam' must be a number, but we got 'invalid'"),
);
const execution = () => ensureType('number', value, 'myParam');
expect(execution).toThrow(ExpressionError);
expect(execution).toThrow("Parameter 'myParam' must be a number, but we got 'invalid'");
});
it('parses valid JSON string to object if toType is object', () => {
@ -68,9 +69,10 @@ describe('ensureType', () => {
it('throws error for invalid JSON string to object conversion', () => {
const value = 'invalid_json';
expect(() => ensureType('object', value, 'myParam')).toThrowError(
"Parameter 'myParam' could not be parsed",
);
const execution = () => ensureType('object', value, 'myParam');
expect(execution).toThrow(ExpressionError);
expect(execution).toThrow("Parameter 'myParam' could not be parsed");
});
it('throws error for non-array value if toType is array', () => {

View File

@ -460,13 +460,9 @@ describe('AWS Cognito - Helpers functions', () => {
return undefined;
});
await expect(
preSendAttributes.call(loadOptionsFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(loadOptionsFunctions.getNode(), 'No user attributes provided', {
description: 'At least one user attribute must be provided for the update operation.',
}),
);
const execution = preSendAttributes.call(loadOptionsFunctions, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('No user attributes provided');
});
it('should throw an error if a user attribute is invalid (empty value) (create operation)', async () => {
@ -478,13 +474,9 @@ describe('AWS Cognito - Helpers functions', () => {
return undefined;
});
await expect(
preSendAttributes.call(loadOptionsFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(loadOptionsFunctions.getNode(), 'Invalid User Attribute', {
description: 'Each attribute must have a valid name and value.',
}),
);
const execution = preSendAttributes.call(loadOptionsFunctions, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('Invalid User Attribute');
});
it('should throw an error if email_verified is true but email is missing (create operation)', async () => {
@ -496,18 +488,9 @@ describe('AWS Cognito - Helpers functions', () => {
return undefined;
});
await expect(
preSendAttributes.call(loadOptionsFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(
loadOptionsFunctions.getNode(),
'Missing required "email" attribute',
{
description:
'"email_verified" is set to true, but the corresponding "email" attribute is not provided.',
},
),
);
const execution = preSendAttributes.call(loadOptionsFunctions, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('Missing required "email" attribute');
});
it('should throw an error if phone_number_verified is true but phone_number is missing (create operation)', async () => {
@ -521,18 +504,9 @@ describe('AWS Cognito - Helpers functions', () => {
return undefined;
});
await expect(
preSendAttributes.call(loadOptionsFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(
loadOptionsFunctions.getNode(),
'Missing required "phone_number" attribute',
{
description:
'"phone_number_verified" is set to true, but the corresponding "phone_number" attribute is not provided.',
},
),
);
const execution = preSendAttributes.call(loadOptionsFunctions, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('Missing required "phone_number" attribute');
});
it('should add the user attribute to the body when valid (create operation)', async () => {
@ -594,13 +568,9 @@ describe('AWS Cognito - Helpers functions', () => {
return undefined;
});
await expect(
preSendAttributes.call(loadOptionsFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(loadOptionsFunctions.getNode(), 'Invalid User Attribute', {
description: 'Each attribute must have a valid name and value.',
}),
);
const execution = preSendAttributes.call(loadOptionsFunctions, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('Invalid User Attribute');
});
});

View File

@ -1,7 +1,6 @@
import { NodeApiError } from 'n8n-workflow';
import type { INodeExecutionData, IN8nHttpFullResponse, JsonObject } from 'n8n-workflow';
import { ERROR_DESCRIPTIONS } from '../../helpers/constants';
import { handleError } from '../../helpers/errorHandler';
const mockExecuteSingleFunctions = {
@ -49,12 +48,9 @@ describe('handleError', () => {
Error: { Code: 'NoSuchEntity', Message: 'User "nonExistentUser" does not exist' },
} as JsonObject;
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrowError(
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
message: 'User "nonExistentUser" does not exist',
description: ERROR_DESCRIPTIONS.NoSuchEntity.User,
}),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, response);
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow('User "nonExistentUser" does not exist');
});
test('should throw generic error if no specific mapping exists', async () => {

View File

@ -220,9 +220,9 @@ describe('AWS IAM - Helper Functions', () => {
return '';
});
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
new NodeOperationError(mockNode.getNode(), 'User name should not contain spaces.'),
);
const execution = validateName.call(mockNode, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('User name should not contain spaces.');
});
it('should throw an error if userName contains invalid characters', async () => {
@ -232,11 +232,10 @@ describe('AWS IAM - Helper Functions', () => {
return '';
});
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
new NodeOperationError(
mockNode.getNode(),
'User name can have up to 64 characters. Valid characters: letters, numbers, hyphens (-), and underscores (_).',
),
const execution = validateName.call(mockNode, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow(
'User name can have up to 64 characters. Valid characters: letters, numbers, hyphens (-), and underscores (_).',
);
});
@ -257,9 +256,9 @@ describe('AWS IAM - Helper Functions', () => {
return '';
});
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
new NodeOperationError(mockNode.getNode(), 'Group name should not contain spaces.'),
);
const execution = validateName.call(mockNode, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('Group name should not contain spaces.');
});
it('should throw an error if groupName contains invalid characters', async () => {
@ -269,11 +268,10 @@ describe('AWS IAM - Helper Functions', () => {
return '';
});
await expect(validateName.call(mockNode, requestOptions)).rejects.toThrowError(
new NodeOperationError(
mockNode.getNode(),
'Group name can have up to 128 characters. Valid characters: letters, numbers, hyphens (-), and underscores (_).',
),
const execution = validateName.call(mockNode, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow(
'Group name can have up to 128 characters. Valid characters: letters, numbers, hyphens (-), and underscores (_).',
);
});

View File

@ -63,11 +63,13 @@ describe('Azure Cosmos DB', () => {
expect(foundResource).toBeUndefined();
expect(() => {
const throwFn = () => {
if (!foundResource) {
throw new OperationalError('Unable to determine the resource type from the URL');
}
}).toThrowError(new OperationalError('Unable to determine the resource type from the URL'));
};
expect(throwFn).toThrow(OperationalError);
expect(throwFn).toThrow('Unable to determine the resource type from the URL');
});
it('should throw OperationalError if no resource type found in URL path', async () => {
@ -100,11 +102,13 @@ describe('Azure Cosmos DB', () => {
expect(foundResource).toBeUndefined();
expect(() => {
const throwFn = () => {
if (!foundResource) {
throw new OperationalError('Unable to determine the resource type from the URL');
}
}).toThrowError(new OperationalError('Unable to determine the resource type from the URL'));
};
expect(throwFn).toThrow(OperationalError);
expect(throwFn).toThrow('Unable to determine the resource type from the URL');
});
it('should properly construct the resourceId and payload', async () => {

View File

@ -52,11 +52,9 @@ describe('getPartitionKey', () => {
const mockApiResponse = {};
azureCosmosDbApiRequest.mockResolvedValue(mockApiResponse);
await expect(getPartitionKey.call(mockExecuteSingleFunctions)).rejects.toThrowError(
new NodeOperationError(mockExecuteSingleFunctions.getNode(), 'Partition key not found', {
description: 'Failed to determine the partition key for this collection',
}),
);
const execution = getPartitionKey.call(mockExecuteSingleFunctions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('Partition key not found');
});
test('should throw NodeApiError for 404 error', async () => {
@ -77,16 +75,9 @@ describe('getPartitionKey', () => {
azureCosmosDbApiRequest.mockRejectedValue(mockError);
await expect(getPartitionKey.call(mockExecuteSingleFunctions)).rejects.toThrowError(
new NodeApiError(
mockExecuteSingleFunctions.getNode(),
{},
{
message: errorMessage,
description: ErrorMap.Container.NotFound.description,
},
),
);
const execution = getPartitionKey.call(mockExecuteSingleFunctions);
await expect(execution).rejects.toThrow(NodeApiError);
await expect(execution).rejects.toThrow(errorMessage);
});
});
@ -110,18 +101,9 @@ describe('validatePartitionKey', () => {
};
azureCosmosDbApiRequest.mockResolvedValue(mockApiResponse);
await expect(
validatePartitionKey.call(mockExecuteSingleFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(
mockExecuteSingleFunctions.getNode(),
"Partition key not found in 'Item Contents'",
{
description:
"Partition key 'partitionKey' must be present and have a valid, non-empty value in 'Item Contents'.",
},
),
);
const execution = validatePartitionKey.call(mockExecuteSingleFunctions, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow("Partition key not found in 'Item Contents'");
});
test('should throw NodeOperationError when partition key is missing for "update" operation', async () => {
@ -135,17 +117,9 @@ describe('validatePartitionKey', () => {
};
azureCosmosDbApiRequest.mockResolvedValue(mockApiResponse);
await expect(
validatePartitionKey.call(mockExecuteSingleFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(
mockExecuteSingleFunctions.getNode(),
'Partition key is missing or empty',
{
description: 'Ensure the "Partition Key" field has a valid, non-empty value.',
},
),
);
const execution = validatePartitionKey.call(mockExecuteSingleFunctions, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('Partition key is missing or empty');
});
test('should throw NodeOperationError when partition key is missing for "get" operation', async () => {
@ -159,17 +133,9 @@ describe('validatePartitionKey', () => {
};
azureCosmosDbApiRequest.mockResolvedValue(mockApiResponse);
await expect(
validatePartitionKey.call(mockExecuteSingleFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(
mockExecuteSingleFunctions.getNode(),
'Partition key is missing or empty',
{
description: 'Ensure the "Partition Key" field exists and has a valid, non-empty value.',
},
),
);
const execution = validatePartitionKey.call(mockExecuteSingleFunctions, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('Partition key is missing or empty');
});
test('should throw NodeOperationError when invalid JSON is provided for customProperties', async () => {
@ -183,17 +149,9 @@ describe('validatePartitionKey', () => {
};
azureCosmosDbApiRequest.mockResolvedValue(mockApiResponse);
await expect(
validatePartitionKey.call(mockExecuteSingleFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(
mockExecuteSingleFunctions.getNode(),
'Invalid JSON format in "Item Contents"',
{
description: 'Ensure the "Item Contents" field contains a valid JSON object',
},
),
);
const execution = validatePartitionKey.call(mockExecuteSingleFunctions, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('Invalid JSON format in "Item Contents"');
});
});
@ -229,17 +187,9 @@ describe('validateQueryParameters', () => {
.mockReturnValueOnce('$1')
.mockReturnValueOnce({ queryParameters: 'param1, param2' });
await expect(
validateQueryParameters.call(mockExecuteSingleFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(
mockExecuteSingleFunctions.getNode(),
'Empty parameter value provided',
{
description: 'Please provide non-empty values for the query parameters',
},
),
);
const execution = validateQueryParameters.call(mockExecuteSingleFunctions, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('Empty parameter value provided');
});
test('should successfully map parameters when they match', async () => {
@ -394,18 +344,9 @@ describe('validateQueryParameters', () => {
.mockReturnValueOnce('$1')
.mockReturnValueOnce({ queryParametersJson: '{"a": 1}' });
await expect(
validateQueryParameters.call(mockExecuteSingleFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(
mockExecuteSingleFunctions.getNode(),
'Query Parameters (JSON) must be a JSON array',
{
description:
'Provide values as a JSON array, e.g. [1737062400000, "01234", true, null]',
},
),
);
const execution = validateQueryParameters.call(mockExecuteSingleFunctions, requestOptions);
await expect(execution).rejects.toThrow(NodeOperationError);
await expect(execution).rejects.toThrow('Query Parameters (JSON) must be a JSON array');
});
});
@ -437,16 +378,16 @@ describe('processJsonInput', () => {
test('should throw OperationalError for invalid JSON string', () => {
const invalidJson = '{key: value}';
expect(() => processJsonInput(invalidJson)).toThrowError(
new OperationalError('Input must contain a valid JSON', { level: 'warning' }),
);
const throwFn = () => processJsonInput(invalidJson);
expect(throwFn).toThrow(OperationalError);
expect(throwFn).toThrow('Input must contain a valid JSON');
});
test('should throw OperationalError for invalid non-string and non-object input', () => {
const invalidInput = 123;
expect(() => processJsonInput(invalidInput, 'testInput')).toThrowError(
new OperationalError("Input 'testInput' must contain a valid JSON", { level: 'warning' }),
);
const throwFn = () => processJsonInput(invalidInput, 'testInput');
expect(throwFn).toThrow(OperationalError);
expect(throwFn).toThrow("Input 'testInput' must contain a valid JSON");
});
});
@ -470,23 +411,21 @@ describe('validateCustomProperties', () => {
const emptyCustomProperties = {};
mockExecuteSingleFunctions.getNodeParameter.mockReturnValue(emptyCustomProperties);
await expect(
validateCustomProperties.call(mockExecuteSingleFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(mockExecuteSingleFunctions.getNode(), 'Item contents are empty', {
description: 'Ensure the "Item Contents" field contains at least one valid property.',
}),
const emptyExecution = validateCustomProperties.call(
mockExecuteSingleFunctions,
requestOptions,
);
await expect(emptyExecution).rejects.toThrow(NodeOperationError);
await expect(emptyExecution).rejects.toThrow('Item contents are empty');
const invalidValues = { property1: null, property2: '' };
mockExecuteSingleFunctions.getNodeParameter.mockReturnValue(invalidValues);
await expect(
validateCustomProperties.call(mockExecuteSingleFunctions, requestOptions),
).rejects.toThrowError(
new NodeOperationError(mockExecuteSingleFunctions.getNode(), 'Item contents are empty', {
description: 'Ensure the "Item Contents" field contains at least one valid property.',
}),
const invalidExecution = validateCustomProperties.call(
mockExecuteSingleFunctions,
requestOptions,
);
await expect(invalidExecution).rejects.toThrow(NodeOperationError);
await expect(invalidExecution).rejects.toThrow('Item contents are empty');
});
});

View File

@ -65,19 +65,13 @@ describe('Generic Functions', () => {
},
};
await expect(
sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
),
).rejects.toThrowError(
new NodeApiError(mockExecuteSingleFunctions.getNode(), errorResponse.body, {
message: 'Invalid model',
description:
'The model is not valid. Permitted models can be found in the documentation at https://docs.perplexity.ai/guides/model-cards.',
}),
const execution = sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
);
await expect(execution).rejects.toThrow(NodeApiError);
await expect(execution).rejects.toThrow('Invalid model');
});
it('should throw NodeApiError with "Invalid parameter" message if error type is invalid_parameter', async () => {
@ -91,19 +85,13 @@ describe('Generic Functions', () => {
},
};
await expect(
sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
),
).rejects.toThrowError(
new NodeApiError(mockExecuteSingleFunctions.getNode(), errorResponse.body, {
message: 'Invalid parameter provided.',
description:
'Please check all input parameters and ensure they are correctly formatted. Valid values can be found in the documentation at https://docs.perplexity.ai/api-reference/chat-completions.',
}),
const execution = sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
);
await expect(execution).rejects.toThrow(NodeApiError);
await expect(execution).rejects.toThrow('Invalid parameter provided.');
});
it('should handle "invalid_model" error with itemIndex', async () => {
@ -118,18 +106,13 @@ describe('Generic Functions', () => {
},
};
await expect(
sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
),
).rejects.toThrowError(
new NodeApiError(mockExecuteSingleFunctions.getNode(), errorResponse.body, {
message: 'Invalid model',
description: 'Permitted models documentation...',
}),
const execution = sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
);
await expect(execution).rejects.toThrow(NodeApiError);
await expect(execution).rejects.toThrow('Invalid model');
});
it('should handle "invalid_parameter" error with non-string message', async () => {
@ -143,18 +126,13 @@ describe('Generic Functions', () => {
},
};
await expect(
sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
),
).rejects.toThrowError(
new NodeApiError(mockExecuteSingleFunctions.getNode(), errorResponse.body, {
message: 'An unexpected issue occurred.',
description: 'Please check parameters...',
}),
const execution = sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
);
await expect(execution).rejects.toThrow(NodeApiError);
await expect(execution).rejects.toThrow('An unexpected issue occurred.');
});
it('should throw generic error for unknown error type', async () => {
@ -168,18 +146,13 @@ describe('Generic Functions', () => {
},
};
await expect(
sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
),
).rejects.toThrowError(
new NodeApiError(mockExecuteSingleFunctions.getNode(), errorResponse.body, {
message: 'Internal server error.',
description: 'Refer to API documentation...',
}),
const execution = sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
);
await expect(execution).rejects.toThrow(NodeApiError);
await expect(execution).rejects.toThrow('Internal server error.');
});
it('should include itemIndex in error message when present', async () => {
@ -194,17 +167,13 @@ describe('Generic Functions', () => {
},
};
await expect(
sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
),
).rejects.toThrowError(
new NodeApiError(mockExecuteSingleFunctions.getNode(), errorResponse.body, {
message: 'Error with item [Item 2].',
}),
const execution = sendErrorPostReceive.call(
mockExecuteSingleFunctions,
testData,
errorResponse as unknown as IN8nHttpFullResponse,
);
await expect(execution).rejects.toThrow(NodeApiError);
await expect(execution).rejects.toThrow('Error with item [Item 2].');
});
});