fix(core): Add ESLint rule to prevent error instances in toThrow assertions (#29889)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Declan Carroll 2026-05-08 06:51:05 +01:00 committed by GitHub
parent 73dae68663
commit 75ed71c001
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 455 additions and 428 deletions

View File

@ -414,6 +414,7 @@ export const baseConfig = tseslint.config(
'n8n-local-rules/no-plain-errors': 'off',
'@typescript-eslint/unbound-method': 'off',
'n8n-local-rules/no-skipped-tests': process.env.NODE_ENV === 'development' ? 'warn' : 'error',
'n8n-local-rules/no-error-instance-in-to-throw': 'error',
},
},
);

View File

@ -17,6 +17,7 @@ import { NoArgumentSpreadRule } from './no-argument-spread.js';
import { NoInternalPackageImportRule } from './no-internal-package-import.js';
import { NoImportEnterpriseEditionRule } from './no-import-enterprise-edition.js';
import { NoTypeOnlyImportInDiRule } from './no-type-only-import-in-di.js';
import { NoErrorInstanceInToThrowRule } from './no-error-instance-in-to-throw.js';
export const rules = {
'no-uncaught-json-parse': NoUncaughtJsonParseRule,
@ -37,4 +38,5 @@ export const rules = {
'no-internal-package-import': NoInternalPackageImportRule,
'no-import-enterprise-edition': NoImportEnterpriseEditionRule,
'no-type-only-import-in-di': NoTypeOnlyImportInDiRule,
'no-error-instance-in-to-throw': NoErrorInstanceInToThrowRule,
} satisfies Record<string, AnyRuleModule>;

View File

@ -0,0 +1,45 @@
import { RuleTester } from '@typescript-eslint/rule-tester';
import { NoErrorInstanceInToThrowRule } from './no-error-instance-in-to-throw.js';
const ruleTester = new RuleTester();
ruleTester.run('no-error-instance-in-to-throw', NoErrorInstanceInToThrowRule, {
valid: [
// Passing a class reference is fine
{ code: 'expect(() => foo()).toThrow(NodeOperationError)' },
// Passing a string is fine
{ code: "expect(() => foo()).toThrow('expected message')" },
// Passing a regex is fine
{ code: 'expect(() => foo()).toThrow(/expected/)' },
// No argument is fine
{ code: 'expect(() => foo()).toThrow()' },
// Async with class reference is fine
{ code: 'await expect(foo()).rejects.toThrow(NodeOperationError)' },
// Async with string is fine
{ code: "await expect(foo()).rejects.toThrow('expected message')" },
// NewExpression not inside toThrow is fine
{ code: 'const err = new Error("test")' },
],
invalid: [
{
code: "expect(() => foo()).toThrow(new Error('bad'))",
errors: [{ messageId: 'noErrorInstance', data: { className: 'Error' } }],
},
{
code: "expect(() => foo()).toThrow(new NodeOperationError(node, 'bad'))",
errors: [{ messageId: 'noErrorInstance', data: { className: 'NodeOperationError' } }],
},
{
code: "await expect(foo()).rejects.toThrow(new Error('bad'))",
errors: [{ messageId: 'noErrorInstance', data: { className: 'Error' } }],
},
{
code: "await expect(foo()).rejects.toThrow(new NodeOperationError(node, 'message'))",
errors: [{ messageId: 'noErrorInstance', data: { className: 'NodeOperationError' } }],
},
{
code: 'expect(() => foo()).toThrow(new TypeError())',
errors: [{ messageId: 'noErrorInstance', data: { className: 'TypeError' } }],
},
],
});

View File

@ -0,0 +1,52 @@
import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
export const NoErrorInstanceInToThrowRule = ESLintUtils.RuleCreator.withoutDocs({
meta: {
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.',
},
messages: {
noErrorInstance:
"Do not pass an error instance to `.toThrow()`. Use `.toThrow({{className}})` for type checking and `.toThrow('message')` for message matching.",
},
schema: [],
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
// Match: expect(...).toThrow(new Foo(...))
// Match: expect(...).rejects.toThrow(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'
) {
return;
}
// Must have exactly one argument that is a NewExpression
if (
node.arguments.length !== 1 ||
node.arguments[0].type !== TSESTree.AST_NODE_TYPES.NewExpression
) {
return;
}
const newExpr = node.arguments[0];
const className =
newExpr.callee.type === TSESTree.AST_NODE_TYPES.Identifier
? newExpr.callee.name
: 'ErrorClass';
context.report({
messageId: 'noErrorInstance',
node: node.arguments[0],
data: { className },
});
},
};
},
});

View File

@ -2128,11 +2128,9 @@ describe('GoogleGemini Node', () => {
});
executeFunctionsMock.getNode.mockReturnValue({ name: 'Google Gemini' } as INode);
await expect(video.generate.execute.call(executeFunctionsMock, 0)).rejects.toThrow(
new NodeOperationError(executeFunctionsMock.getNode(), 'Failed to generate video', {
description: 'Error generating video',
}),
);
const promise = video.generate.execute.call(executeFunctionsMock, 0);
await expect(promise).rejects.toThrow(NodeOperationError);
await expect(promise).rejects.toThrow('Failed to generate video');
});
it('should throw error for non-Veo model', async () => {
@ -2149,14 +2147,10 @@ describe('GoogleGemini Node', () => {
executeFunctionsMock.getNode.mockReturnValue({ name: 'Google Gemini' } as INode);
await expect(video.generate.execute.call(executeFunctionsMock, 0)).rejects.toThrow(
new NodeOperationError(
executeFunctionsMock.getNode(),
'Model models/gemini-2.0-flash is not supported for video generation. Please use a Veo model',
{
description: 'Video generation is only supported by Veo models',
},
),
const promise = video.generate.execute.call(executeFunctionsMock, 0);
await expect(promise).rejects.toThrow(NodeOperationError);
await expect(promise).rejects.toThrow(
'Model models/gemini-2.0-flash is not supported for video generation. Please use a Veo model',
);
});

View File

@ -282,11 +282,9 @@ describe('GoogleGemini -> utils', () => {
mockExecuteFunctions.getNode.mockReturnValue({ name: 'Google Gemini' } as any);
await expect(uploadFile.call(mockExecuteFunctions, fileContent, mimeType)).rejects.toThrow(
new NodeOperationError(mockExecuteFunctions.getNode(), 'Upload failed', {
description: 'Error uploading file',
}),
);
const promise = uploadFile.call(mockExecuteFunctions, fileContent, mimeType);
await expect(promise).rejects.toThrow(NodeOperationError);
await expect(promise).rejects.toThrow('Upload failed');
});
});
@ -463,17 +461,14 @@ describe('GoogleGemini -> utils', () => {
mockExecuteFunctions.getNodeParameter.mockReturnValue('');
mockExecuteFunctions.getNode.mockReturnValue({ name: 'Google Gemini' } as any);
await expect(
transferFile.call(mockExecuteFunctions, 0, undefined, 'application/octet-stream'),
).rejects.toThrow(
new NodeOperationError(
mockExecuteFunctions.getNode(),
'Binary property name or download URL is required',
{
description: 'Error uploading file',
},
),
const promise = transferFile.call(
mockExecuteFunctions,
0,
undefined,
'application/octet-stream',
);
await expect(promise).rejects.toThrow(NodeOperationError);
await expect(promise).rejects.toThrow('Binary property name or download URL is required');
});
it('should throw error when upload URL is not received', async () => {
@ -495,16 +490,14 @@ describe('GoogleGemini -> utils', () => {
mockExecuteFunctions.getNode.mockReturnValue({ name: 'Google Gemini' } as any);
await expect(
transferFile.call(
mockExecuteFunctions,
0,
'https://example.com/file.pdf',
'application/octet-stream',
),
).rejects.toThrow(
new NodeOperationError(mockExecuteFunctions.getNode(), 'Failed to get upload URL'),
const promise = transferFile.call(
mockExecuteFunctions,
0,
'https://example.com/file.pdf',
'application/octet-stream',
);
await expect(promise).rejects.toThrow(NodeOperationError);
await expect(promise).rejects.toThrow('Failed to get upload URL');
});
it('should poll until file is active and throw error on failure', async () => {
@ -552,18 +545,14 @@ describe('GoogleGemini -> utils', () => {
mockExecuteFunctions.getNode.mockReturnValue({ name: 'Google Gemini' } as any);
await expect(
transferFile.call(
mockExecuteFunctions,
0,
'https://example.com/file.pdf',
'application/octet-stream',
),
).rejects.toThrow(
new NodeOperationError(mockExecuteFunctions.getNode(), 'Processing failed', {
description: 'Error uploading file',
}),
const promise = transferFile.call(
mockExecuteFunctions,
0,
'https://example.com/file.pdf',
'application/octet-stream',
);
await expect(promise).rejects.toThrow(NodeOperationError);
await expect(promise).rejects.toThrow('Processing failed');
});
});
@ -881,19 +870,15 @@ describe('GoogleGemini -> utils', () => {
mockExecuteFunctions.getNode.mockReturnValue({ name: 'Google Gemini' } as any);
await expect(
uploadToFileSearchStore.call(
mockExecuteFunctions,
0,
fileSearchStoreName,
displayName,
'https://example.com/file.pdf',
),
).rejects.toThrow(
new NodeOperationError(mockExecuteFunctions.getNode(), 'Upload failed', {
description: 'Error uploading file to File Search store',
}),
const promise = uploadToFileSearchStore.call(
mockExecuteFunctions,
0,
fileSearchStoreName,
displayName,
'https://example.com/file.pdf',
);
await expect(promise).rejects.toThrow(NodeOperationError);
await expect(promise).rejects.toThrow('Upload failed');
});
it('should throw error when binary property name is missing', async () => {
@ -903,17 +888,14 @@ describe('GoogleGemini -> utils', () => {
mockExecuteFunctions.getNodeParameter.mockReturnValue('');
mockExecuteFunctions.getNode.mockReturnValue({ name: 'Google Gemini' } as any);
await expect(
uploadToFileSearchStore.call(mockExecuteFunctions, 0, fileSearchStoreName, displayName),
).rejects.toThrow(
new NodeOperationError(
mockExecuteFunctions.getNode(),
'Binary property name or download URL is required',
{
description: 'Error uploading file',
},
),
const promise = uploadToFileSearchStore.call(
mockExecuteFunctions,
0,
fileSearchStoreName,
displayName,
);
await expect(promise).rejects.toThrow(NodeOperationError);
await expect(promise).rejects.toThrow('Binary property name or download URL is required');
});
it('should return undefined when response is missing', async () => {

View File

@ -1,6 +1,5 @@
import { ExecutionError } from '@/js-task-runner/errors/execution-error';
import { DisallowedModuleError } from '../errors/disallowed-module.error';
import { createRequireResolver, type RequireResolverOpts } from '../require-resolver';
describe('require resolver', () => {
@ -45,9 +44,8 @@ describe('require resolver', () => {
it('should throw when requiring non-allowlisted external modules', () => {
const resolver = createRequireResolver(defaultOpts);
expect(() => resolver('express')).toThrow(
new ExecutionError(new DisallowedModuleError('express')),
);
expect(() => resolver('express')).toThrow(ExecutionError);
expect(() => resolver('express')).toThrow('express');
});
it('should allow all external modules when allowedExternalModules is "*"', () => {

View File

@ -114,8 +114,10 @@ describe('EmailAuthHandler', () => {
userRepository.findOne.mockResolvedValue(user);
globalConfig.sso.ldap.loginEnabled = false;
await expect(handler.handleLogin(email, password)).rejects.toThrow(
new AuthError('Reset your password to gain access to the instance.'),
const promise = handler.handleLogin(email, password);
await expect(promise).rejects.toThrow(AuthError);
await expect(promise).rejects.toThrow(
'Reset your password to gain access to the instance.',
);
expect(eventService.emit).toHaveBeenCalledWith('login-failed-due-to-ldap-disabled', {

View File

@ -256,10 +256,10 @@ describe('AuthController', () => {
});
const res = mock<Response>();
await expect(authController.resolveSignupToken(req, res, payload)).rejects.toThrow(
new BadRequestError(
'Invite links are not supported on this system, please use single sign on instead.',
),
const promise = authController.resolveSignupToken(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow(
'Invite links are not supported on this system, please use single sign on instead.',
);
});
@ -295,9 +295,9 @@ describe('AuthController', () => {
});
jest.spyOn(license, 'isWithinUsersLimit').mockReturnValue(false);
await expect(authController.resolveSignupToken(req, res, payload)).rejects.toThrow(
new ForbiddenError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED),
);
const promise = authController.resolveSignupToken(req, res, payload);
await expect(promise).rejects.toThrow(ForbiddenError);
await expect(promise).rejects.toThrow(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
});
it('throws a BadRequestError if the users are not found', async () => {
@ -333,9 +333,9 @@ describe('AuthController', () => {
jest.spyOn(license, 'isWithinUsersLimit').mockReturnValue(true);
jest.spyOn(userRepository, 'findManyByIds').mockResolvedValue([]);
await expect(authController.resolveSignupToken(req, res, payload)).rejects.toThrow(
new BadRequestError('Invalid invite URL'),
);
const promise = authController.resolveSignupToken(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Invalid invite URL');
});
it('throws a BadRequestError if the invitee already has a password', async () => {
@ -380,8 +380,10 @@ describe('AuthController', () => {
}),
]);
await expect(authController.resolveSignupToken(req, res, payload)).rejects.toThrow(
new BadRequestError('The invitation was likely either deleted or already claimed'),
const promise = authController.resolveSignupToken(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow(
'The invitation was likely either deleted or already claimed',
);
});
@ -429,9 +431,9 @@ describe('AuthController', () => {
}),
]);
await expect(authController.resolveSignupToken(req, res, payload)).rejects.toThrow(
new BadRequestError('Invalid request'),
);
const promise = authController.resolveSignupToken(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Invalid request');
});
it('returns the inviter if the invitation is valid', async () => {
@ -578,9 +580,9 @@ describe('AuthController', () => {
.spyOn(userService, 'getInvitationIdsFromPayload')
.mockRejectedValue(new BadRequestError('Invalid invite URL'));
await expect(authController.resolveSignupToken(req, res, payload)).rejects.toThrow(
new BadRequestError('Invalid invite URL'),
);
const promise = authController.resolveSignupToken(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Invalid invite URL');
});
it('throws BadRequestError if JWT token payload is missing inviterId or inviteeId', async () => {
@ -612,9 +614,9 @@ describe('AuthController', () => {
.spyOn(userService, 'getInvitationIdsFromPayload')
.mockRejectedValue(new BadRequestError('Invalid invite URL'));
await expect(authController.resolveSignupToken(req, res, payload)).rejects.toThrow(
new BadRequestError('Invalid invite URL'),
);
const promise = authController.resolveSignupToken(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Invalid invite URL');
});
it('throws BadRequestError if token is missing', async () => {
@ -642,9 +644,9 @@ describe('AuthController', () => {
});
const res = mock<Response>();
await expect(authController.resolveSignupToken(req, res, payload)).rejects.toThrow(
new BadRequestError('Token is required'),
);
const promise = authController.resolveSignupToken(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Token is required');
});
});
});

View File

@ -76,10 +76,10 @@ describe('InvitationController', () => {
const req = mock<AuthenticatedRequest>({ user });
const res = mock<Response>();
await expect(invitationController.inviteUser(req, res, payload)).rejects.toThrow(
new BadRequestError(
'SSO is enabled, so users are managed by the Identity Provider and cannot be added through invites',
),
const promise = invitationController.inviteUser(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow(
'SSO is enabled, so users are managed by the Identity Provider and cannot be added through invites',
);
});
@ -103,9 +103,9 @@ describe('InvitationController', () => {
const req = mock<AuthenticatedRequest>({ user });
const res = mock<Response>();
await expect(invitationController.inviteUser(req, res, payload)).rejects.toThrow(
new ForbiddenError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED),
);
const promise = invitationController.inviteUser(req, res, payload);
await expect(promise).rejects.toThrow(ForbiddenError);
await expect(promise).rejects.toThrow(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
});
it('throws a BadRequestError if the owner account is not set up', async () => {
@ -129,8 +129,10 @@ describe('InvitationController', () => {
const req = mock<AuthenticatedRequest>({ user });
const res = mock<Response>();
await expect(invitationController.inviteUser(req, res, payload)).rejects.toThrow(
new BadRequestError('You must set up your own account before inviting others'),
const promise = invitationController.inviteUser(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow(
'You must set up your own account before inviting others',
);
});
@ -163,10 +165,10 @@ describe('InvitationController', () => {
const req = mock<AuthenticatedRequest>({ user });
const res = mock<Response>();
await expect(invitationController.inviteUser(req, res, payload)).rejects.toThrow(
new ForbiddenError(
'Cannot invite admin user without advanced permissions. Please upgrade to a license that includes this feature.',
),
const promise = invitationController.inviteUser(req, res, payload);
await expect(promise).rejects.toThrow(ForbiddenError);
await expect(promise).rejects.toThrow(
'Cannot invite admin user without advanced permissions. Please upgrade to a license that includes this feature.',
);
});
@ -244,12 +246,10 @@ describe('InvitationController', () => {
});
const res = mock<Response>();
await expect(
invitationController.acceptInvitationWithToken(req, res, payload),
).rejects.toThrow(
new BadRequestError(
'Invite links are not supported on this system, please use single sign on instead.',
),
const promise = invitationController.acceptInvitationWithToken(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow(
'Invite links are not supported on this system, please use single sign on instead.',
);
});
@ -269,9 +269,9 @@ describe('InvitationController', () => {
});
const res = mock<Response>();
await expect(
invitationController.acceptInvitationWithToken(req, res, payload),
).rejects.toThrow(new BadRequestError('Token is required'));
const promise = invitationController.acceptInvitationWithToken(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Token is required');
});
it('accepts the invitation successfully with JWT token', async () => {
@ -365,9 +365,9 @@ describe('InvitationController', () => {
});
const res = mock<Response>();
await expect(
invitationController.acceptInvitationWithToken(req, res, payload),
).rejects.toThrow(new BadRequestError('Invalid payload or URL'));
const promise = invitationController.acceptInvitationWithToken(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Invalid payload or URL');
});
it('throws a BadRequestError if invitee already has a password', async () => {
@ -408,9 +408,9 @@ describe('InvitationController', () => {
});
const res = mock<Response>();
await expect(
invitationController.acceptInvitationWithToken(req, res, payload),
).rejects.toThrow(new BadRequestError('This invite has been accepted already'));
const promise = invitationController.acceptInvitationWithToken(req, res, payload);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('This invite has been accepted already');
});
});
});

View File

@ -80,8 +80,10 @@ describe('TelegramIntegration.onBeforeConnect', () => {
makeAgent('agent-other', 'Agent Other', [{ type: 'telegram', credentialId: 'cred-1' }]),
]);
await expect(integration.onBeforeConnect(makeContext())).rejects.toThrow(
new ConflictError('Telegram credential is already connected to agent "Agent Other"'),
const promise = integration.onBeforeConnect(makeContext());
await expect(promise).rejects.toThrow(ConflictError);
await expect(promise).rejects.toThrow(
'Telegram credential is already connected to agent "Agent Other"',
);
// Telegram API must not be called once the DB already indicates a conflict.
expect(fetchSpy).not.toHaveBeenCalled();
@ -119,8 +121,10 @@ describe('TelegramIntegration.onBeforeConnect', () => {
makeAgent('agent-b', 'Beta', [{ type: 'telegram', credentialId: 'cred-1' }]),
]);
await expect(integration.onBeforeConnect(makeContext())).rejects.toThrow(
new ConflictError('Telegram credential is already connected to agent "Alpha"'),
const promise = integration.onBeforeConnect(makeContext());
await expect(promise).rejects.toThrow(ConflictError);
await expect(promise).rejects.toThrow(
'Telegram credential is already connected to agent "Alpha"',
);
});

View File

@ -62,9 +62,9 @@ describe('ChatHubCredentialsService', () => {
it('should throw ForbiddenError when user does not have access to the credential', async () => {
credentialsFinderService.findCredentialForUser.mockResolvedValue(null);
await expect(service.ensureCredentialAccess(mockUser, CREDENTIAL_ID)).rejects.toThrow(
new ForbiddenError("You don't have access to the provided credentials"),
);
const promise = service.ensureCredentialAccess(mockUser, CREDENTIAL_ID);
await expect(promise).rejects.toThrow(ForbiddenError);
await expect(promise).rejects.toThrow("You don't have access to the provided credentials");
});
});
@ -90,9 +90,9 @@ describe('ChatHubCredentialsService', () => {
it('should throw ForbiddenError when no personal project is found', async () => {
projectRepository.getPersonalProjectForUser.mockResolvedValue(null);
await expect(service.findPersonalProject(mockUser, mockTrx)).rejects.toThrow(
new ForbiddenError('Missing personal project'),
);
const promise = service.findPersonalProject(mockUser, mockTrx);
await expect(promise).rejects.toThrow(ForbiddenError);
await expect(promise).rejects.toThrow('Missing personal project');
expect(projectRepository.getPersonalProjectForUser).toHaveBeenCalledWith(
mockUser.id,
@ -138,10 +138,14 @@ describe('ChatHubCredentialsService', () => {
openAiApi: { id: CREDENTIAL_ID, name: 'OpenAI Credentials' },
};
await expect(
service.findWorkflowCredentialAndProject('anthropic', mockCredentials, 'workflow-123'),
).rejects.toThrow(
new BadRequestError('No credentials provided for the selected model provider'),
const promise = service.findWorkflowCredentialAndProject(
'anthropic',
mockCredentials,
'workflow-123',
);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow(
'No credentials provided for the selected model provider',
);
});
@ -160,9 +164,13 @@ describe('ChatHubCredentialsService', () => {
credentialsService.findAllCredentialIdsForWorkflow.mockResolvedValue([]);
credentialsService.findAllGlobalCredentialIds.mockResolvedValue([]);
await expect(
service.findWorkflowCredentialAndProject('openai', mockCredentials, 'workflow-123'),
).rejects.toThrow(new ForbiddenError("You don't have access to the provided credentials"));
const promise = service.findWorkflowCredentialAndProject(
'openai',
mockCredentials,
'workflow-123',
);
await expect(promise).rejects.toThrow(ForbiddenError);
await expect(promise).rejects.toThrow("You don't have access to the provided credentials");
});
});
});

View File

@ -492,14 +492,12 @@ describe('CommunityPackagesService', () => {
license.isCustomNpmRegistryEnabled.mockReturnValue(false);
// ACT & ASSERT
await expect(
communityPackagesService.updatePackage(
installedPackageForUpdateTest.packageName,
installedPackageForUpdateTest,
),
).rejects.toThrow(
new FeatureNotLicensedError(LICENSE_FEATURES.COMMUNITY_NODES_CUSTOM_REGISTRY),
const promise = communityPackagesService.updatePackage(
installedPackageForUpdateTest.packageName,
installedPackageForUpdateTest,
);
await expect(promise).rejects.toThrow(FeatureNotLicensedError);
await expect(promise).rejects.toThrow(LICENSE_FEATURES.COMMUNITY_NODES_CUSTOM_REGISTRY);
});
});

View File

@ -137,9 +137,9 @@ describe('executeNpmCommand', () => {
new Error('npm ERR! 404 Not Found - GET https://registry.npmjs.org/nonexistent-package'),
);
await expect(executeNpmCommand(['install', 'nonexistent-package'])).rejects.toThrow(
new UnexpectedError(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND),
);
const promise = executeNpmCommand(['install', 'nonexistent-package']);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND);
});
it('should throw UnexpectedError for package not found (E404)', async () => {
@ -149,25 +149,25 @@ describe('executeNpmCommand', () => {
),
);
await expect(executeNpmCommand(['view', 'nonexistent-package'])).rejects.toThrow(
new UnexpectedError(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND),
);
const promise = executeNpmCommand(['view', 'nonexistent-package']);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND);
});
it('should throw UnexpectedError for package not found (404 Not Found)', async () => {
mockAsyncExec.mockRejectedValue(new Error('404 Not Found - package does not exist'));
await expect(executeNpmCommand(['install', 'nonexistent-package'])).rejects.toThrow(
new UnexpectedError(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND),
);
const promise = executeNpmCommand(['install', 'nonexistent-package']);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND);
});
it('should throw UnexpectedError for no version available', async () => {
mockAsyncExec.mockRejectedValue(new Error('No valid versions available for package'));
await expect(executeNpmCommand(['install', 'some-package'])).rejects.toThrow(
new UnexpectedError(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND),
);
const promise = executeNpmCommand(['install', 'some-package']);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND);
});
it('should throw UnexpectedError for package version not found', async () => {
@ -175,9 +175,9 @@ describe('executeNpmCommand', () => {
new Error(`${NPM_COMMAND_TOKENS.NPM_PACKAGE_VERSION_NOT_FOUND_ERROR} package@1.2.3`),
);
await expect(executeNpmCommand(['install', 'package@1.2.3'])).rejects.toThrow(
new UnexpectedError(RESPONSE_ERROR_MESSAGES.PACKAGE_VERSION_NOT_FOUND),
);
const promise = executeNpmCommand(['install', 'package@1.2.3']);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(RESPONSE_ERROR_MESSAGES.PACKAGE_VERSION_NOT_FOUND);
});
it('should throw UnexpectedError for disk full (ENOSPC)', async () => {
@ -185,36 +185,36 @@ describe('executeNpmCommand', () => {
new Error(`${NPM_COMMAND_TOKENS.NPM_DISK_NO_SPACE}: no space left on device`),
);
await expect(executeNpmCommand(['install', 'some-package'])).rejects.toThrow(
new UnexpectedError(RESPONSE_ERROR_MESSAGES.DISK_IS_FULL),
);
const promise = executeNpmCommand(['install', 'some-package']);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(RESPONSE_ERROR_MESSAGES.DISK_IS_FULL);
});
it('should throw UnexpectedError for insufficient disk space', async () => {
mockAsyncExec.mockRejectedValue(new Error('Error: insufficient space on device'));
await expect(executeNpmCommand(['install', 'large-package'])).rejects.toThrow(
new UnexpectedError(RESPONSE_ERROR_MESSAGES.DISK_IS_FULL),
);
const promise = executeNpmCommand(['install', 'large-package']);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(RESPONSE_ERROR_MESSAGES.DISK_IS_FULL);
});
it('should throw UnexpectedError for DNS getaddrinfo errors', async () => {
mockAsyncExec.mockRejectedValue(new Error('getaddrinfo ENOTFOUND registry.npmjs.org'));
await expect(executeNpmCommand(['install', 'some-package'])).rejects.toThrow(
new UnexpectedError(
'Network error: Unable to reach npm registry. Please check your internet connection.',
),
const promise = executeNpmCommand(['install', 'some-package']);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'Network error: Unable to reach npm registry. Please check your internet connection.',
);
});
it('should throw UnexpectedError for DNS ENOTFOUND errors', async () => {
mockAsyncExec.mockRejectedValue(new Error('ENOTFOUND registry.npmjs.org'));
await expect(executeNpmCommand(['install', 'some-package'])).rejects.toThrow(
new UnexpectedError(
'Network error: Unable to reach npm registry. Please check your internet connection.',
),
const promise = executeNpmCommand(['install', 'some-package']);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'Network error: Unable to reach npm registry. Please check your internet connection.',
);
});
@ -266,17 +266,17 @@ describe('executeNpmCommand', () => {
it('should handle errors normally when doNotHandleError is false', async () => {
mockAsyncExec.mockRejectedValue(new Error('npm ERR! 404 Not Found'));
await expect(
executeNpmCommand(['install', 'nonexistent'], { doNotHandleError: false }),
).rejects.toThrow(new UnexpectedError(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND));
const promise = executeNpmCommand(['install', 'nonexistent'], { doNotHandleError: false });
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND);
});
it('should handle errors normally when doNotHandleError is undefined (default)', async () => {
mockAsyncExec.mockRejectedValue(new Error('npm ERR! 404 Not Found'));
await expect(executeNpmCommand(['install', 'nonexistent'])).rejects.toThrow(
new UnexpectedError(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND),
);
const promise = executeNpmCommand(['install', 'nonexistent']);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND);
});
});
@ -417,9 +417,9 @@ describe('executeNpmCommand', () => {
it('should handle non-Error objects being thrown', async () => {
mockAsyncExec.mockRejectedValue('string error');
await expect(executeNpmCommand(['install', 'some-package'])).rejects.toThrow(
new UnexpectedError('Failed to execute npm command'),
);
const promise = executeNpmCommand(['install', 'some-package']);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow('Failed to execute npm command');
});
it('should handle errors with no message', async () => {
@ -645,10 +645,10 @@ describe('verifyIntegrity', () => {
mockAsyncExec.mockRejectedValue(new Error('CLI command failed'));
await expect(verifyIntegrity(packageName, version, registryUrl, integrity)).rejects.toThrow(
new UnexpectedError(
'Checksum verification failed. Try restarting n8n and attempting the installation again.',
),
const promise = verifyIntegrity(packageName, version, registryUrl, integrity);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'Checksum verification failed. Try restarting n8n and attempting the installation again.',
);
});
@ -659,10 +659,10 @@ describe('verifyIntegrity', () => {
mockAsyncExec.mockRejectedValue(new Error('getaddrinfo ENOTFOUND registry.npmjs.org'));
await expect(verifyIntegrity(packageName, version, registryUrl, integrity)).rejects.toThrow(
new UnexpectedError(
'Checksum verification failed. Please check your network connection and try again.',
),
const promise = verifyIntegrity(packageName, version, registryUrl, integrity);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'Checksum verification failed. Please check your network connection and try again.',
);
});
@ -673,10 +673,10 @@ describe('verifyIntegrity', () => {
mockAsyncExec.mockRejectedValue(new Error('ENOTFOUND registry.npmjs.org'));
await expect(verifyIntegrity(packageName, version, registryUrl, integrity)).rejects.toThrow(
new UnexpectedError(
'Checksum verification failed. Please check your network connection and try again.',
),
const promise = verifyIntegrity(packageName, version, registryUrl, integrity);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'Checksum verification failed. Please check your network connection and try again.',
);
});
@ -721,10 +721,10 @@ describe('verifyIntegrity', () => {
stderr: '',
});
await expect(verifyIntegrity(packageName, version, registryUrl, integrity)).rejects.toThrow(
new UnexpectedError(
'Checksum verification failed. Try restarting n8n and attempting the installation again.',
),
const promise = verifyIntegrity(packageName, version, registryUrl, integrity);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'Checksum verification failed. Try restarting n8n and attempting the installation again.',
);
expect(mockAsyncExec).toHaveBeenCalledTimes(1);
@ -766,10 +766,10 @@ describe('verifyIntegrity', () => {
mockAsyncExec.mockRejectedValue(new Error('getaddrinfo ENOTFOUND registry.npmjs.org'));
await expect(verifyIntegrity(packageName, version, registryUrl, integrity)).rejects.toThrow(
new UnexpectedError(
'Checksum verification failed. Please check your network connection and try again.',
),
const promise = verifyIntegrity(packageName, version, registryUrl, integrity);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'Checksum verification failed. Please check your network connection and try again.',
);
expect(mockAsyncExec).toHaveBeenCalledTimes(1);
@ -784,10 +784,10 @@ describe('verifyIntegrity', () => {
new Error('npm ERR! 404 Not Found - GET https://registry.npmjs.org/nonexistent-package'),
);
await expect(verifyIntegrity(packageName, version, registryUrl, integrity)).rejects.toThrow(
new UnexpectedError(
'Checksum verification failed. Please check your network connection and try again.',
),
const promise = verifyIntegrity(packageName, version, registryUrl, integrity);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'Checksum verification failed. Please check your network connection and try again.',
);
expect(mockAsyncExec).toHaveBeenCalledTimes(1);
@ -800,10 +800,10 @@ describe('verifyIntegrity', () => {
mockAsyncExec.mockRejectedValue(new Error('Some other error'));
await expect(verifyIntegrity(packageName, version, registryUrl, integrity)).rejects.toThrow(
new UnexpectedError(
'Checksum verification failed. Try restarting n8n and attempting the installation again.',
),
const promise = verifyIntegrity(packageName, version, registryUrl, integrity);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'Checksum verification failed. Try restarting n8n and attempting the installation again.',
);
expect(mockAsyncExec).toHaveBeenCalledTimes(1);
@ -845,9 +845,9 @@ describe('checkIfVersionExistsOrThrow', () => {
mockAsyncExec.mockRejectedValue(new Error('E404 Not Found'));
await expect(checkIfVersionExistsOrThrow(packageName, version, registryUrl)).rejects.toThrow(
new UnexpectedError('Package version does not exist'),
);
const promise1 = checkIfVersionExistsOrThrow(packageName, version, registryUrl);
await expect(promise1).rejects.toThrow(UnexpectedError);
await expect(promise1).rejects.toThrow('Package version does not exist');
});
it('should throw UnexpectedError with proper message on 404 when CLI fallback fails', async () => {
@ -857,9 +857,9 @@ describe('checkIfVersionExistsOrThrow', () => {
mockAsyncExec.mockRejectedValue(new Error('Some error'));
await expect(checkIfVersionExistsOrThrow(packageName, version, registryUrl)).rejects.toThrow(
new UnexpectedError('Failed to check package version existence'),
);
const promise = checkIfVersionExistsOrThrow(packageName, version, registryUrl);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow('Failed to check package version existence');
});
it('should throw UnexpectedError for network failures when CLI fallback fails', async () => {
@ -869,9 +869,9 @@ describe('checkIfVersionExistsOrThrow', () => {
mockAsyncExec.mockRejectedValue(new Error('CLI network failure'));
await expect(checkIfVersionExistsOrThrow(packageName, version, registryUrl)).rejects.toThrow(
new UnexpectedError('Failed to check package version existence'),
);
const promise = checkIfVersionExistsOrThrow(packageName, version, registryUrl);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow('Failed to check package version existence');
});
it('should throw UnexpectedError for server errors (500) when CLI fallback fails', async () => {
@ -893,10 +893,10 @@ describe('checkIfVersionExistsOrThrow', () => {
mockAsyncExec.mockRejectedValue(new Error('getaddrinfo ENOTFOUND registry.npmjs.org'));
await expect(checkIfVersionExistsOrThrow(packageName, version, registryUrl)).rejects.toThrow(
new UnexpectedError(
'The community nodes service is temporarily unreachable. Please try again later.',
),
const promise = checkIfVersionExistsOrThrow(packageName, version, registryUrl);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'The community nodes service is temporarily unreachable. Please try again later.',
);
});
@ -907,10 +907,10 @@ describe('checkIfVersionExistsOrThrow', () => {
mockAsyncExec.mockRejectedValue(new Error('ENOTFOUND registry.npmjs.org'));
await expect(checkIfVersionExistsOrThrow(packageName, version, registryUrl)).rejects.toThrow(
new UnexpectedError(
'The community nodes service is temporarily unreachable. Please try again later.',
),
const promise = checkIfVersionExistsOrThrow(packageName, version, registryUrl);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'The community nodes service is temporarily unreachable. Please try again later.',
);
});
@ -959,9 +959,9 @@ describe('checkIfVersionExistsOrThrow', () => {
mockAsyncExec.mockResolvedValue({ stdout: 'null', stderr: '' });
await expect(checkIfVersionExistsOrThrow(packageName, version, registryUrl)).rejects.toThrow(
new UnexpectedError('Failed to check package version existence'),
);
const promise = checkIfVersionExistsOrThrow(packageName, version, registryUrl);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow('Failed to check package version existence');
});
it('should reject CLI output that is not valid semver', async () => {
@ -991,9 +991,9 @@ describe('checkIfVersionExistsOrThrow', () => {
new Error('E404 Not Found - GET https://registry.npmjs.org/nonexistent-package'),
);
await expect(checkIfVersionExistsOrThrow(packageName, version, registryUrl)).rejects.toThrow(
new UnexpectedError('Package version does not exist'),
);
const promise = checkIfVersionExistsOrThrow(packageName, version, registryUrl);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow('Package version does not exist');
expect(mockAsyncExec).toHaveBeenCalledTimes(1);
});
@ -1005,10 +1005,10 @@ describe('checkIfVersionExistsOrThrow', () => {
mockAsyncExec.mockRejectedValue(new Error('getaddrinfo ENOTFOUND registry.npmjs.org'));
await expect(checkIfVersionExistsOrThrow(packageName, version, registryUrl)).rejects.toThrow(
new UnexpectedError(
'The community nodes service is temporarily unreachable. Please try again later.',
),
const promise = checkIfVersionExistsOrThrow(packageName, version, registryUrl);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'The community nodes service is temporarily unreachable. Please try again later.',
);
expect(mockAsyncExec).toHaveBeenCalledTimes(1);
@ -1021,10 +1021,10 @@ describe('checkIfVersionExistsOrThrow', () => {
mockAsyncExec.mockRejectedValue(new Error('npm ERR! 500 Internal Server Error'));
await expect(checkIfVersionExistsOrThrow(packageName, version, registryUrl)).rejects.toThrow(
new UnexpectedError(
'The community nodes service is temporarily unreachable. Please try again later.',
),
const promise = checkIfVersionExistsOrThrow(packageName, version, registryUrl);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow(
'The community nodes service is temporarily unreachable. Please try again later.',
);
expect(mockAsyncExec).toHaveBeenCalledTimes(1);
@ -1037,9 +1037,9 @@ describe('checkIfVersionExistsOrThrow', () => {
mockAsyncExec.mockRejectedValue(new Error('Some other error'));
await expect(checkIfVersionExistsOrThrow(packageName, version, registryUrl)).rejects.toThrow(
new UnexpectedError('Failed to check package version existence'),
);
const promise = checkIfVersionExistsOrThrow(packageName, version, registryUrl);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow('Failed to check package version existence');
expect(mockAsyncExec).toHaveBeenCalledTimes(1);
});

View File

@ -577,10 +577,9 @@ describe('dataTable filters', () => {
});
// ASSERT
await expect(result).rejects.toThrow(DataTableValidationError);
await expect(result).rejects.toThrow(
new DataTableValidationError(
`${condition.toUpperCase()} filter value cannot be null or undefined`,
),
`${condition.toUpperCase()} filter value cannot be null or undefined`,
);
});
@ -610,10 +609,9 @@ describe('dataTable filters', () => {
});
// ASSERT
await expect(result).rejects.toThrow(DataTableValidationError);
await expect(result).rejects.toThrow(
new DataTableValidationError(
`${condition.toUpperCase()} filter value must be a string`,
),
`${condition.toUpperCase()} filter value must be a string`,
);
});
},
@ -1364,10 +1362,9 @@ describe('dataTable filters', () => {
});
// ASSERT
await expect(result).rejects.toThrow(DataTableValidationError);
await expect(result).rejects.toThrow(
new DataTableValidationError(
`${condition.toUpperCase()} filter value cannot be null or undefined`,
),
`${condition.toUpperCase()} filter value cannot be null or undefined`,
);
});

View File

@ -1292,9 +1292,8 @@ describe('dataTable', () => {
);
// ASSERT
await expect(result).rejects.toThrow(
new DataTableValidationError("unknown column name 'cWrong'"),
);
await expect(result).rejects.toThrow(DataTableValidationError);
await expect(result).rejects.toThrow("unknown column name 'cWrong'");
});
it('inserts rows with partial data (some columns missing)', async () => {
@ -1380,9 +1379,8 @@ describe('dataTable', () => {
);
// ASSERT
await expect(result).rejects.toThrow(
new DataTableValidationError("unknown column name 'cWrong'"),
);
await expect(result).rejects.toThrow(DataTableValidationError);
await expect(result).rejects.toThrow("unknown column name 'cWrong'");
});
it('rejects an invalid date string to date column', async () => {
@ -2372,10 +2370,9 @@ describe('dataTable', () => {
filter: undefined as any,
});
await expect(result).rejects.toThrow(DataTableValidationError);
await expect(result).rejects.toThrow(
new DataTableValidationError(
'Filter is required for delete operations to prevent accidental deletion of all data',
),
'Filter is required for delete operations to prevent accidental deletion of all data',
);
});
@ -2398,10 +2395,9 @@ describe('dataTable', () => {
filter: { type: 'and', filters: [] },
});
await expect(result).rejects.toThrow(DataTableValidationError);
await expect(result).rejects.toThrow(
new DataTableValidationError(
'Filter is required for delete operations to prevent accidental deletion of all data',
),
'Filter is required for delete operations to prevent accidental deletion of all data',
);
});
@ -2950,9 +2946,8 @@ describe('dataTable', () => {
});
// ASSERT
await expect(result).rejects.toThrow(
new DataTableValidationError('Filter must not be empty'),
);
await expect(result).rejects.toThrow(DataTableValidationError);
await expect(result).rejects.toThrow('Filter must not be empty');
const { data } = await dataTableService.getManyRowsAndCount(dataTableId, project1.id, {});
expect(data).toEqual([
@ -2982,9 +2977,8 @@ describe('dataTable', () => {
});
// ASSERT
await expect(result).rejects.toThrow(
new DataTableValidationError('Data columns must not be empty'),
);
await expect(result).rejects.toThrow(DataTableValidationError);
await expect(result).rejects.toThrow('Data columns must not be empty');
const { data } = await dataTableService.getManyRowsAndCount(dataTableId, project1.id, {});
expect(data).toEqual([

View File

@ -356,9 +356,9 @@ describe('OidcService', () => {
const storedState = oidcService.generateState().signed;
const storedNonce = oidcService.generateNonce().signed;
await expect(oidcService.loginUser(callbackUrl, storedState, storedNonce)).rejects.toThrow(
new BadRequestError('Invalid authorization code'),
);
const promise = oidcService.loginUser(callbackUrl, storedState, storedNonce);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Invalid authorization code');
});
it('throws an error if claims() throws an error', async () => {
@ -377,9 +377,9 @@ describe('OidcService', () => {
const storedState = oidcService.generateState().signed;
const storedNonce = oidcService.generateNonce().signed;
await expect(oidcService.loginUser(callbackUrl, storedState, storedNonce)).rejects.toThrow(
new BadRequestError('Invalid token'),
);
const promise = oidcService.loginUser(callbackUrl, storedState, storedNonce);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Invalid token');
});
it('should throw an error if there are no claims', async () => {
@ -398,9 +398,9 @@ describe('OidcService', () => {
const storedState = oidcService.generateState().signed;
const storedNonce = oidcService.generateNonce().signed;
await expect(oidcService.loginUser(callbackUrl, storedState, storedNonce)).rejects.toThrow(
new ForbiddenError('No claims found in the OIDC token'),
);
const promise = oidcService.loginUser(callbackUrl, storedState, storedNonce);
await expect(promise).rejects.toThrow(ForbiddenError);
await expect(promise).rejects.toThrow('No claims found in the OIDC token');
});
it('throws an error if fetchUserInfo throws an error', async () => {
@ -420,9 +420,9 @@ describe('OidcService', () => {
const storedState = oidcService.generateState().signed;
const storedNonce = oidcService.generateNonce().signed;
await expect(oidcService.loginUser(callbackUrl, storedState, storedNonce)).rejects.toThrow(
new BadRequestError('Invalid token'),
);
const promise = oidcService.loginUser(callbackUrl, storedState, storedNonce);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Invalid token');
});
it('throws an error if there is no email', async () => {
@ -442,9 +442,9 @@ describe('OidcService', () => {
const storedState = oidcService.generateState().signed;
const storedNonce = oidcService.generateNonce().signed;
await expect(oidcService.loginUser(callbackUrl, storedState, storedNonce)).rejects.toThrow(
new BadRequestError('An email is required'),
);
const promise = oidcService.loginUser(callbackUrl, storedState, storedNonce);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('An email is required');
});
it('throws an error if the email is invalid', async () => {
@ -466,9 +466,9 @@ describe('OidcService', () => {
const storedState = oidcService.generateState().signed;
const storedNonce = oidcService.generateNonce().signed;
await expect(oidcService.loginUser(callbackUrl, storedState, storedNonce)).rejects.toThrow(
new BadRequestError('Invalid email format'),
);
const promise = oidcService.loginUser(callbackUrl, storedState, storedNonce);
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Invalid email format');
});
it('should return the user if the auth identity already exists', async () => {

View File

@ -178,9 +178,8 @@ describe('Push', () => {
req.headers['x-forwarded-host'] = xForwardedHost;
if (backendName === 'sse') {
expect(() => push.handleRequest(req, res)).toThrow(
new BadRequestError('Invalid origin!'),
);
expect(() => push.handleRequest(req, res)).toThrow(BadRequestError);
expect(() => push.handleRequest(req, res)).toThrow('Invalid origin!');
} else {
push.handleRequest(req, res);
expect(ws.send).toHaveBeenCalledWith('Invalid origin!');
@ -274,9 +273,8 @@ describe('Push', () => {
} else {
// Expected behavior: connection should be rejected
if (backendName === 'sse') {
expect(() => push.handleRequest(req, res)).toThrow(
new BadRequestError('Invalid origin!'),
);
expect(() => push.handleRequest(req, res)).toThrow(BadRequestError);
expect(() => push.handleRequest(req, res)).toThrow('Invalid origin!');
} else {
push.handleRequest(req, res);
expect(ws.send).toHaveBeenCalledWith('Invalid origin!');
@ -291,8 +289,9 @@ describe('Push', () => {
req.query = { pushRef: '' };
if (backendName === 'sse') {
expect(() => push.handleRequest(req, res)).toThrow(BadRequestError);
expect(() => push.handleRequest(req, res)).toThrow(
new BadRequestError('The query parameter "pushRef" is missing!'),
'The query parameter "pushRef" is missing!',
);
} else {
push.handleRequest(req, mock());

View File

@ -776,9 +776,9 @@ describe('SAML email validation', () => {
const mockRequest = {} as express.Request;
await expect(samlService.handleSamlLogin(mockRequest, 'post')).rejects.toThrow(
new BadRequestError('Invalid email format'),
);
const promise = samlService.handleSamlLogin(mockRequest, 'post');
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Invalid email format');
});
test.each([['not-an-email'], ['@missinglocal.com'], ['missing@.com'], ['spaces in@email.com']])(
@ -797,9 +797,9 @@ describe('SAML email validation', () => {
const mockRequest = {} as express.Request;
await expect(samlService.handleSamlLogin(mockRequest, 'post')).rejects.toThrow(
new BadRequestError('Invalid email format'),
);
const promise = samlService.handleSamlLogin(mockRequest, 'post');
await expect(promise).rejects.toThrow(BadRequestError);
await expect(promise).rejects.toThrow('Invalid email format');
},
);

View File

@ -47,9 +47,9 @@ describe('Migration Test Helpers', () => {
describe('initDbUpToMigration', () => {
it('should throw error if migration not found', async () => {
await expect(initDbUpToMigration('NonExistentMigration')).rejects.toThrow(
new UnexpectedError('Migration "NonExistentMigration" not found'),
);
const promise = initDbUpToMigration('NonExistentMigration');
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow('Migration "NonExistentMigration" not found');
});
it('should stop before specified migration', async () => {
@ -72,9 +72,9 @@ describe('Migration Test Helpers', () => {
describe('runSingleMigration', () => {
it('should throw error if migration not found', async () => {
await expect(runSingleMigration('NonExistentMigration')).rejects.toThrow(
new UnexpectedError('Migration "NonExistentMigration" not found'),
);
const promise = runSingleMigration('NonExistentMigration');
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow('Migration "NonExistentMigration" not found');
});
it('should run specific migration', async () => {

View File

@ -27,9 +27,9 @@ describe('BinaryData/utils', () => {
it('should throw on invalid compressed Readable streams', async () => {
const gunzip = createGunzip();
const body = Readable.from(Buffer.from('0001f8b080000000000000000', 'hex')).pipe(gunzip);
await expect(binaryToBuffer(body)).rejects.toThrow(
new UnexpectedError('Failed to decompress response'),
);
const promise = binaryToBuffer(body);
await expect(promise).rejects.toThrow(UnexpectedError);
await expect(promise).rejects.toThrow('Failed to decompress response');
});
});
});

View File

@ -104,7 +104,8 @@ describe('normalizeItems', () => {
},
];
test.each(errorTests)('$description', ({ input }) => {
expect(() => normalizeItems(input)).toThrow(new ApplicationError('Inconsistent item format'));
expect(() => normalizeItems(input)).toThrow(ApplicationError);
expect(() => normalizeItems(input)).toThrow('Inconsistent item format');
});
});
});

View File

@ -458,7 +458,10 @@ describe('validateValueAgainstSchema', () => {
expect(() =>
validateValueAgainstSchema(node, nodeType, value, parameterName, 0, 0),
).toThrow(new ExpressionError("Invalid input for 'count' [item 0]"));
).toThrow(ExpressionError);
expect(() =>
validateValueAgainstSchema(node, nodeType, value, parameterName, 0, 0),
).toThrow("Invalid input for 'count' [item 0]");
});
});
});

View File

@ -73,9 +73,9 @@ describe('AMQP Node', () => {
it('should throw error when sink is empty', async () => {
executeFunctions.getNodeParameter.calledWith('sink', 0).mockReturnValue('');
await expect(new Amqp().execute.call(executeFunctions)).rejects.toThrow(
new NodeOperationError(executeFunctions.getNode(), 'Queue or Topic required!'),
);
const promise = new Amqp().execute.call(executeFunctions);
await expect(promise).rejects.toThrow(NodeOperationError);
await expect(promise).rejects.toThrow('Queue or Topic required!');
});
it('should send message successfully', async () => {

View File

@ -34,12 +34,9 @@ describe('handleError', () => {
Error: { Code: 'EntityAlreadyExists', Message: 'User "existingUserName" already exists' },
} as JsonObject;
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
message: 'User "existingUserName" already exists',
description: ERROR_DESCRIPTIONS.EntityAlreadyExists.User,
}),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, response);
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow('User "existingUserName" already exists');
});
test('should throw NodeApiError for NoSuchEntity with user not found', async () => {
@ -66,12 +63,9 @@ describe('handleError', () => {
response.statusCode = 400;
response.body = { Error: { Code: 'BadRequest', Message: 'Invalid request' } } as JsonObject;
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
message: 'BadRequest',
description: 'Invalid request',
}),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, response);
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow('BadRequest');
});
test('should throw NodeApiError for EntityAlreadyExists with group conflict', async () => {
@ -84,12 +78,9 @@ describe('handleError', () => {
Error: { Code: 'EntityAlreadyExists', Message: 'Group "existingGroupName" already exists' },
} as JsonObject;
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
message: 'Group "existingGroupName" already exists',
description: ERROR_DESCRIPTIONS.EntityAlreadyExists.Group,
}),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, response);
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow('Group "existingGroupName" already exists');
});
test('should throw NodeApiError for NoSuchEntity with group not found', async () => {
@ -102,12 +93,9 @@ describe('handleError', () => {
Error: { Code: 'NoSuchEntity', Message: 'Group "nonExistentGroup" does not exist' },
} as JsonObject;
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
message: 'Group "nonExistentGroup" does not exist',
description: ERROR_DESCRIPTIONS.NoSuchEntity.Group,
}),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, response);
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow('Group "nonExistentGroup" does not exist');
});
test('should throw NodeApiError for DeleteConflict', async () => {
@ -120,11 +108,8 @@ describe('handleError', () => {
Error: { Code: 'DeleteConflict', Message: 'User "userIngroup" is in a group' },
} as JsonObject;
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
message: 'User "userIngroup" is in a group',
description: 'This entity is still in use. Remove users from the group before deleting.',
}),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, response);
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow('User "userIngroup" is in a group');
});
});

View File

@ -252,12 +252,9 @@ describe('metricHandlers', () => {
return undefined;
});
await expect(metricHandlers.toolsUsed.call(mockExecuteFunctions, 0)).rejects.toThrow(
new NodeOperationError(mockNode, 'Intermediate steps missing', {
description:
"Make sure to enable returning intermediate steps in your agent node's options, then map them in here",
}),
);
const promise = metricHandlers.toolsUsed.call(mockExecuteFunctions, 0);
await expect(promise).rejects.toThrow(NodeOperationError);
await expect(promise).rejects.toThrow('Intermediate steps missing');
});
it('should throw error for empty object intermediate steps', async () => {

View File

@ -231,11 +231,9 @@ describe('Google Sheets Search Functions', () => {
(apiRequest.call as jest.Mock).mockResolvedValue(undefined);
await expect(
sheetsSearch.call(mockLoadOptionsFunctions as ILoadOptionsFunctions),
).rejects.toThrow(
new NodeOperationError(mockLoadOptionsFunctions.getNode(), 'No data got returned'),
);
const promise = sheetsSearch.call(mockLoadOptionsFunctions as ILoadOptionsFunctions);
await expect(promise).rejects.toThrow(NodeOperationError);
await expect(promise).rejects.toThrow('No data got returned');
});
it('should filter out non-GRID type sheets', async () => {

View File

@ -28,12 +28,9 @@ describe('handleError', () => {
response.statusCode = 409;
response.body = { code: 'Conflict', message: 'Container already exists' } as JsonObject;
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
message: ErrorMap.Container.Conflict.getMessage('container'),
description: ErrorMap.Container.Conflict.description,
}),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, response);
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow(ErrorMap.Container.Conflict.getMessage('container'));
});
test('should throw NodeApiError for container not found', async () => {
@ -42,12 +39,9 @@ describe('handleError', () => {
response.statusCode = 404;
response.body = { code: 'NotFound', message: 'Container not found' } as JsonObject;
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
message: ErrorMap.Container.NotFound.getMessage('container'),
description: ErrorMap.Container.NotFound.description,
}),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, response);
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow(ErrorMap.Container.NotFound.getMessage('container'));
});
test('should throw NodeApiError for item not found', async () => {
@ -56,12 +50,9 @@ describe('handleError', () => {
response.statusCode = 404;
response.body = { code: 'NotFound', message: 'Item not found' } as JsonObject;
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
message: ErrorMap.Item.NotFound.getMessage('item'),
description: ErrorMap.Item.NotFound.description,
}),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, response);
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow(ErrorMap.Item.NotFound.getMessage('item'));
});
test('should throw generic error if no specific mapping exists', async () => {
@ -70,12 +61,9 @@ describe('handleError', () => {
response.statusCode = 400;
response.body = { code: 'BadRequest', message: 'Invalid request' } as JsonObject;
await expect(handleError.call(mockExecuteSingleFunctions, data, response)).rejects.toThrow(
new NodeApiError(mockExecuteSingleFunctions.getNode(), response.body as JsonObject, {
message: 'BadRequest',
description: 'Invalid request',
}),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, response);
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow('BadRequest');
});
test('should handle error details correctly when match is successful', async () => {
@ -119,25 +107,13 @@ describe('handleError', () => {
}
if (errorDetails && errorDetails.length > 0) {
await expect(
handleError.call(mockExecuteSingleFunctions, data, {
statusCode: 500,
body: { code: 'InternalServerError', message: errorMessage },
headers: {},
}),
).rejects.toThrow(
new NodeApiError(
mockExecuteSingleFunctions.getNode(),
{
code: 'InternalServerError',
message: errorMessage,
} as JsonObject,
{
message: 'InternalServerError',
description: errorDetails.join('\n'),
},
),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, {
statusCode: 500,
body: { code: 'InternalServerError', message: errorMessage },
headers: {},
});
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow('InternalServerError');
}
});
@ -153,25 +129,13 @@ describe('handleError', () => {
}
if (errorDetails && errorDetails.length > 0) {
await expect(
handleError.call(mockExecuteSingleFunctions, data, {
statusCode: 500,
body: { code: 'InternalServerError', message: errorMessage },
headers: {},
}),
).rejects.toThrow(
new NodeApiError(
mockExecuteSingleFunctions.getNode(),
{
code: 'InternalServerError',
message: errorMessage,
} as JsonObject,
{
message: 'InternalServerError',
description: 'Internal Server Error',
},
),
);
const promise = handleError.call(mockExecuteSingleFunctions, data, {
statusCode: 500,
body: { code: 'InternalServerError', message: errorMessage },
headers: {},
});
await expect(promise).rejects.toThrow(NodeApiError);
await expect(promise).rejects.toThrow('InternalServerError');
}
});
});

View File

@ -289,8 +289,9 @@ describe('Data Transformation Functions', () => {
);
vi.useFakeTimers({ now: new Date() });
expect(() => evaluate('={{ "hi".toDateTime() }}')).toThrow(ExpressionExtensionError);
expect(() => evaluate('={{ "hi".toDateTime() }}')).toThrow(
new ExpressionExtensionError('cannot convert to Luxon DateTime'),
'cannot convert to Luxon DateTime',
);
vi.useRealTimers();
});