mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-31 08:46:58 +02:00
fix(core): Expand ESLint rule to catch toThrowError error instance assertions (#31187)
This commit is contained in:
parent
f2a2f7060d
commit
39d660e522
|
|
@ -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' } }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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 (_).',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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].');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user