mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-26 21:19:43 -08:00
1197811a1e
* fix: Unify expression error behavior for v1 * fix: Add `package.json` to `tsconfig.build.json` * fix: Make `isFrontend` a constant * fix: Use CommonJS require to read version * fix: Use `JSON.parse()` and `fs.readFileSync()` * feat(editor): Make WF name a link on /executions (#6354) * make wf name a link in exec view * link color * make wf name a link in exec view * link color --------- Co-authored-by: Alex Grozav <alex@grozav.com> * fix: Try restoring inclusions in tsconfig files * fix: Try with copy * refactor: Switch base branch and remove global toggle * chore: Remove unrelated changes * chore: Restore lockfile * fix: Ensure all expression errors fail executions * uncaught ExpressionErrors should not fail e2e tests --------- Co-authored-by: romainminaud <romain.minaud@gmail.com> Co-authored-by: Alex Grozav <alex@grozav.com> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
232 lines
8.1 KiB
TypeScript
232 lines
8.1 KiB
TypeScript
/**
|
|
* @jest-environment jsdom
|
|
*/
|
|
|
|
import { DateTime, Duration, Interval } from 'luxon';
|
|
import { Expression } from '@/Expression';
|
|
import { Workflow } from '@/Workflow';
|
|
import * as Helpers from './Helpers';
|
|
import type { ExpressionTestEvaluation, ExpressionTestTransform } from './ExpressionFixtures/base';
|
|
import { baseFixtures } from './ExpressionFixtures/base';
|
|
import type { INodeExecutionData } from '@/Interfaces';
|
|
import { extendSyntax } from '@/Extensions/ExpressionExtension';
|
|
import { ExpressionError } from '@/ExpressionError';
|
|
|
|
describe('Expression', () => {
|
|
describe('getParameterValue()', () => {
|
|
const nodeTypes = Helpers.NodeTypes();
|
|
const workflow = new Workflow({
|
|
nodes: [
|
|
{
|
|
name: 'node',
|
|
typeVersion: 1,
|
|
type: 'test.set',
|
|
id: 'uuid-1234',
|
|
position: [0, 0],
|
|
parameters: {},
|
|
},
|
|
],
|
|
connections: {},
|
|
active: false,
|
|
nodeTypes,
|
|
});
|
|
const expression = new Expression(workflow);
|
|
|
|
const evaluate = (value: string) =>
|
|
expression.getParameterValue(value, null, 0, 0, 'node', [], 'manual', '', {});
|
|
|
|
it('should not be able to use global built-ins from denylist', () => {
|
|
expect(evaluate('={{document}}')).toEqual({});
|
|
expect(evaluate('={{window}}')).toEqual({});
|
|
|
|
expect(evaluate('={{Window}}')).toEqual({});
|
|
expect(evaluate('={{globalThis}}')).toEqual({});
|
|
expect(evaluate('={{self}}')).toEqual({});
|
|
|
|
expect(evaluate('={{alert}}')).toEqual({});
|
|
expect(evaluate('={{prompt}}')).toEqual({});
|
|
expect(evaluate('={{confirm}}')).toEqual({});
|
|
|
|
expect(evaluate('={{eval}}')).toEqual({});
|
|
expect(evaluate('={{uneval}}')).toEqual({});
|
|
expect(evaluate('={{setTimeout}}')).toEqual({});
|
|
expect(evaluate('={{setInterval}}')).toEqual({});
|
|
expect(evaluate('={{Function}}')).toEqual({});
|
|
|
|
expect(evaluate('={{fetch}}')).toEqual({});
|
|
expect(evaluate('={{XMLHttpRequest}}')).toEqual({});
|
|
|
|
expect(evaluate('={{Promise}}')).toEqual({});
|
|
expect(evaluate('={{Generator}}')).toEqual({});
|
|
expect(evaluate('={{GeneratorFunction}}')).toEqual({});
|
|
expect(evaluate('={{AsyncFunction}}')).toEqual({});
|
|
expect(evaluate('={{AsyncGenerator}}')).toEqual({});
|
|
expect(evaluate('={{AsyncGeneratorFunction}}')).toEqual({});
|
|
|
|
expect(evaluate('={{WebAssembly}}')).toEqual({});
|
|
|
|
expect(evaluate('={{Reflect}}')).toEqual({});
|
|
expect(evaluate('={{Proxy}}')).toEqual({});
|
|
|
|
expect(evaluate('={{constructor}}')).toEqual({});
|
|
|
|
expect(evaluate('={{escape}}')).toEqual({});
|
|
expect(evaluate('={{unescape}}')).toEqual({});
|
|
});
|
|
|
|
it('should be able to use global built-ins from allowlist', () => {
|
|
expect(evaluate('={{new Date()}}')).toBeInstanceOf(Date);
|
|
expect(evaluate('={{DateTime.now().toLocaleString()}}')).toEqual(
|
|
DateTime.now().toLocaleString(),
|
|
);
|
|
expect(evaluate('={{Interval.after(new Date(), 100)}}')).toEqual(
|
|
Interval.after(new Date(), 100),
|
|
);
|
|
expect(evaluate('={{Duration.fromMillis(100)}}')).toEqual(Duration.fromMillis(100));
|
|
|
|
expect(evaluate('={{new Object()}}')).toEqual(new Object());
|
|
|
|
expect(evaluate('={{new Array()}}')).toEqual([]);
|
|
expect(evaluate('={{new Int8Array()}}')).toEqual(new Int8Array());
|
|
expect(evaluate('={{new Uint8Array()}}')).toEqual(new Uint8Array());
|
|
expect(evaluate('={{new Uint8ClampedArray()}}')).toEqual(new Uint8ClampedArray());
|
|
expect(evaluate('={{new Int16Array()}}')).toEqual(new Int16Array());
|
|
expect(evaluate('={{new Uint16Array()}}')).toEqual(new Uint16Array());
|
|
expect(evaluate('={{new Int32Array()}}')).toEqual(new Int32Array());
|
|
expect(evaluate('={{new Uint32Array()}}')).toEqual(new Uint32Array());
|
|
expect(evaluate('={{new Float32Array()}}')).toEqual(new Float32Array());
|
|
expect(evaluate('={{new Float64Array()}}')).toEqual(new Float64Array());
|
|
expect(evaluate('={{new BigInt64Array()}}')).toEqual(new BigInt64Array());
|
|
expect(evaluate('={{new BigUint64Array()}}')).toEqual(new BigUint64Array());
|
|
|
|
expect(evaluate('={{new Map()}}')).toEqual(new Map());
|
|
expect(evaluate('={{new WeakMap()}}')).toEqual(new WeakMap());
|
|
expect(evaluate('={{new Set()}}')).toEqual(new Set());
|
|
expect(evaluate('={{new WeakSet()}}')).toEqual(new WeakSet());
|
|
|
|
expect(evaluate('={{new Error()}}')).toEqual(new Error());
|
|
expect(evaluate('={{new TypeError()}}')).toEqual(new TypeError());
|
|
expect(evaluate('={{new SyntaxError()}}')).toEqual(new SyntaxError());
|
|
expect(evaluate('={{new EvalError()}}')).toEqual(new EvalError());
|
|
expect(evaluate('={{new RangeError()}}')).toEqual(new RangeError());
|
|
expect(evaluate('={{new ReferenceError()}}')).toEqual(new ReferenceError());
|
|
expect(evaluate('={{new URIError()}}')).toEqual(new URIError());
|
|
|
|
expect(evaluate('={{Intl}}')).toEqual(Intl);
|
|
|
|
expect(evaluate('={{new String()}}')).toEqual(new String());
|
|
expect(evaluate("={{new RegExp('')}}")).toEqual(new RegExp(''));
|
|
|
|
expect(evaluate('={{Math}}')).toEqual(Math);
|
|
expect(evaluate('={{new Number()}}')).toEqual(new Number());
|
|
expect(evaluate("={{BigInt('1')}}")).toEqual(BigInt('1'));
|
|
expect(evaluate('={{Infinity}}')).toEqual(Infinity);
|
|
expect(evaluate('={{NaN}}')).toEqual(NaN);
|
|
expect(evaluate('={{isFinite(1)}}')).toEqual(isFinite(1));
|
|
expect(evaluate('={{isNaN(1)}}')).toEqual(isNaN(1));
|
|
expect(evaluate("={{parseFloat('1')}}")).toEqual(parseFloat('1'));
|
|
expect(evaluate("={{parseInt('1', 10)}}")).toEqual(parseInt('1', 10));
|
|
|
|
expect(evaluate('={{JSON.stringify({})}}')).toEqual(JSON.stringify({}));
|
|
expect(evaluate('={{new ArrayBuffer(10)}}')).toEqual(new ArrayBuffer(10));
|
|
expect(evaluate('={{new SharedArrayBuffer(10)}}')).toEqual(new SharedArrayBuffer(10));
|
|
expect(evaluate('={{Atomics}}')).toEqual(Atomics);
|
|
expect(evaluate('={{new DataView(new ArrayBuffer(1))}}')).toEqual(
|
|
new DataView(new ArrayBuffer(1)),
|
|
);
|
|
|
|
expect(evaluate("={{encodeURI('https://google.com')}}")).toEqual(
|
|
encodeURI('https://google.com'),
|
|
);
|
|
expect(evaluate("={{encodeURIComponent('https://google.com')}}")).toEqual(
|
|
encodeURIComponent('https://google.com'),
|
|
);
|
|
expect(evaluate("={{decodeURI('https://google.com')}}")).toEqual(
|
|
decodeURI('https://google.com'),
|
|
);
|
|
expect(evaluate("={{decodeURIComponent('https://google.com')}}")).toEqual(
|
|
decodeURIComponent('https://google.com'),
|
|
);
|
|
|
|
expect(evaluate('={{Boolean(1)}}')).toEqual(Boolean(1));
|
|
expect(evaluate('={{Symbol(1).toString()}}')).toEqual(Symbol(1).toString());
|
|
});
|
|
|
|
it('should not able to do arbitrary code execution', () => {
|
|
const testFn = jest.fn();
|
|
Object.assign(global, { testFn });
|
|
expect(() => evaluate("={{ Date['constructor']('testFn()')()}}")).toThrowError(
|
|
new ExpressionError('Arbitrary code execution detected'),
|
|
);
|
|
expect(testFn).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('Test all expression value fixtures', () => {
|
|
const nodeTypes = Helpers.NodeTypes();
|
|
const workflow = new Workflow({
|
|
nodes: [
|
|
{
|
|
name: 'node',
|
|
typeVersion: 1,
|
|
type: 'test.set',
|
|
id: 'uuid-1234',
|
|
position: [0, 0],
|
|
parameters: {},
|
|
},
|
|
],
|
|
connections: {},
|
|
active: false,
|
|
nodeTypes,
|
|
});
|
|
|
|
const expression = new Expression(workflow);
|
|
|
|
const evaluate = (value: string, data: INodeExecutionData[]) => {
|
|
const itemIndex = data.length === 0 ? -1 : 0;
|
|
return expression.getParameterValue(
|
|
value,
|
|
null,
|
|
0,
|
|
itemIndex,
|
|
'node',
|
|
data,
|
|
'manual',
|
|
'',
|
|
{},
|
|
);
|
|
};
|
|
|
|
for (const t of baseFixtures) {
|
|
if (!t.tests.some((test) => test.type === 'evaluation')) {
|
|
continue;
|
|
}
|
|
test(t.expression, () => {
|
|
for (const test of t.tests.filter(
|
|
(test) => test.type === 'evaluation',
|
|
) as ExpressionTestEvaluation[]) {
|
|
expect(evaluate(t.expression, test.input.map((d) => ({ json: d })) as any)).toStrictEqual(
|
|
test.output,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('Test all expression transform fixtures', () => {
|
|
for (const t of baseFixtures) {
|
|
if (!t.tests.some((test) => test.type === 'transform')) {
|
|
continue;
|
|
}
|
|
test(t.expression, () => {
|
|
for (const test of t.tests.filter(
|
|
(test) => test.type === 'transform',
|
|
) as ExpressionTestTransform[]) {
|
|
const expr = t.expression;
|
|
expect(extendSyntax(expr, test.forceTransform)).toEqual(test.result ?? expr);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|