Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into pay-923-node-input-…
Browse files Browse the repository at this point in the history
…output-search

# Conflicts:
#	packages/editor-ui/src/utils/objectUtils.ts
  • Loading branch information
cstuncsik committed Oct 31, 2023
2 parents dbc716d + b94b8b2 commit b9d24a6
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 20 deletions.
14 changes: 8 additions & 6 deletions packages/cli/src/commands/import/credentials.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { flags } from '@oclif/command';
import { Credentials } from 'n8n-core';
import { Cipher } from 'n8n-core';
import fs from 'fs';
import glob from 'fast-glob';
import { Container } from 'typedi';
Expand Down Expand Up @@ -69,6 +69,7 @@ export class ImportCredentialsCommand extends BaseCommand {

let totalImported = 0;

const cipher = Container.get(Cipher);
await this.initOwnerCredentialRole();
const user = flags.userId ? await this.getAssignee(flags.userId) : await this.getOwner();

Expand All @@ -92,12 +93,10 @@ export class ImportCredentialsCommand extends BaseCommand {
const credential = jsonParse<ICredentialsEncrypted>(
fs.readFileSync(file, { encoding: 'utf8' }),
);

if (typeof credential.data === 'object') {
// plain data / decrypted input. Should be encrypted first.
Credentials.prototype.setData.call(credential, credential.data);
credential.data = cipher.encrypt(credential.data);
}

await this.storeCredential(credential, user);
}
});
Expand All @@ -123,7 +122,7 @@ export class ImportCredentialsCommand extends BaseCommand {
for (const credential of credentials) {
if (typeof credential.data === 'object') {
// plain data / decrypted input. Should be encrypted first.
Credentials.prototype.setData.call(credential, credential.data);
credential.data = cipher.encrypt(credential.data);
}
await this.storeCredential(credential, user);
}
Expand Down Expand Up @@ -155,7 +154,10 @@ export class ImportCredentialsCommand extends BaseCommand {
this.ownerCredentialRole = ownerCredentialRole;
}

private async storeCredential(credential: object, user: User) {
private async storeCredential(credential: Partial<CredentialsEntity>, user: User) {
if (!credential.nodesAccess) {
credential.nodesAccess = [];
}
const result = await this.transactionManager.upsert(CredentialsEntity, credential, ['id']);
await this.transactionManager.upsert(
SharedCredentials,
Expand Down
47 changes: 47 additions & 0 deletions packages/cli/test/integration/commands/credentials.cmd.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as Config from '@oclif/config';

import { InternalHooks } from '@/InternalHooks';
import { ImportCredentialsCommand } from '@/commands/import/credentials';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import * as testDb from '../shared/testDb';
import { mockInstance } from '../shared/utils';

beforeAll(async () => {
mockInstance(InternalHooks);
mockInstance(LoadNodesAndCredentials);
await testDb.init();
});

beforeEach(async () => {
await testDb.truncate(['Credentials']);
});

afterAll(async () => {
await testDb.terminate();
});

test('import:credentials should import a credential', async () => {
const config: Config.IConfig = new Config.Config({ root: __dirname });
const before = await testDb.getAllCredentials();
expect(before.length).toBe(0);
const importer = new ImportCredentialsCommand(
['--input=./test/integration/commands/importCredentials/credentials.json'],
config,
);
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit');
});

await importer.init();
try {
await importer.run();
} catch (error) {
expect(error.message).toBe('process.exit');
}
const after = await testDb.getAllCredentials();
expect(after.length).toBe(1);
expect(after[0].name).toBe('cred-aws-test');
expect(after[0].id).toBe('123');
expect(after[0].nodesAccess).toStrictEqual([]);
mockExit.mockRestore();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[
{
"createdAt": "2023-07-10T14:50:49.193Z",
"updatedAt": "2023-10-27T13:34:42.917Z",
"id": "123",
"name": "cred-aws-test",
"data": {
"region": "eu-west-1",
"accessKeyId": "999999999999",
"secretAccessKey": "aaaaaaaaaaaaa"
},
"type": "aws",
"nodesAccess": ""
}
]
4 changes: 4 additions & 0 deletions packages/cli/test/integration/shared/testDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ export function affixRoleToSaveCredential(role: Role) {
saveCredential(credentialPayload, { user, role });
}

export async function getAllCredentials() {
return Db.collections.Credentials.find();
}

// ----------------------------------
// user creation
// ----------------------------------
Expand Down
98 changes: 98 additions & 0 deletions packages/editor-ui/src/utils/__tests__/objectUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { isObjectOrArray, isObject, searchInObject } from '@/utils';

const testData = [1, '', true, null, undefined, new Date(), () => {}].map((value) => [
value,
typeof value,
]);

describe('objectUtils', () => {
describe('isObjectOrArray', () => {
it('should return true for objects', () => {
assert(isObjectOrArray({}));
});

it('should return true for arrays', () => {
assert(isObjectOrArray([]));
});

test.each(testData)('should return false for %j (type %s)', (value) => {
assert(!isObjectOrArray(value));
});
});

describe('isObject', () => {
it('should return true for objects', () => {
assert(isObject({}));
});

it('should return false for arrays', () => {
assert(!isObject([]));
});

test.each(testData)('should return false for %j (type %s)', (value) => {
assert(!isObject(value));
});
});

describe('searchInObject', () => {
it('should return true if the search string is found in the object', () => {
assert(searchInObject({ a: 'b' }, 'b'));
});

it('should return false if the search string is not found in the object', () => {
assert(!searchInObject({ a: 'b' }, 'c'));
});

it('should return true if the search string is not found in the object as a key', () => {
assert(searchInObject({ a: 'b' }, 'a'));
});

it('should return true if the search string is found in a nested object', () => {
assert(searchInObject({ a: { b: 'c' } }, 'c'));
});

it('should return true if the search string is found in a nested object as a key', () => {
assert(searchInObject({ a: { b: 'c' } }, 'b'));
});

it('should return true if the search string is found in an array', () => {
assert(searchInObject(['a', 'b'], 'a'));
});

it('should return true if the search string is found in a nested array', () => {
assert(searchInObject(['a', ['b', 'c']], 'c'));
});

it('should return false if the search string is not found in an array', () => {
assert(!searchInObject(['a', 'b'], 'c'));
});

it('should return false if the search string is not found in a nested array', () => {
assert(!searchInObject(['a', ['b', 'c']], 'd'));
});

it('should return true if the search string is found in a nested object in an array', () => {
assert(searchInObject([{ a: 'b' }], 'b'));
});

it('should return true if the search string is found in a nested array in an object', () => {
assert(searchInObject({ a: ['b', 'c'] }, 'c'));
});

it('should return false if the search string is not found in a nested object in an array', () => {
assert(!searchInObject([{ a: 'b' }], 'c'));
});

it('should return false if the search string is not found in a nested array in an object', () => {
assert(!searchInObject({ a: ['b', 'c'] }, 'd'));
});

it('should return true if the search string is found in an object as a key in a nested array', () => {
assert(searchInObject({ a: ['b', { c: 'd' }] }, 'c'));
});

it('should return true if the search string is found in an object in a nested array', () => {
assert(searchInObject({ a: ['b', { c: 'd' }] }, 'd'));
});
});
});
14 changes: 10 additions & 4 deletions packages/editor-ui/src/utils/objectUtils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
export function isObjectOrArray(maybeObject: unknown): maybeObject is { [key: string]: string } {
return typeof maybeObject === 'object' && maybeObject !== null;
type ObjectOrArray = Record<string, unknown> | unknown[];

export function isDateObject(maybeDate: unknown): maybeDate is Date {
return maybeDate instanceof Date;
}

export function isObjectOrArray(maybeObject: unknown): maybeObject is ObjectOrArray {
return typeof maybeObject === 'object' && maybeObject !== null && !isDateObject(maybeObject);
}

export function isObject(maybeObject: unknown): maybeObject is { [key: string]: string } {
export function isObject(maybeObject: unknown): maybeObject is Record<string, unknown> {
return isObjectOrArray(maybeObject) && !Array.isArray(maybeObject);
}

export const searchInObject = (obj: unknown, searchString: string): boolean =>
export const searchInObject = (obj: ObjectOrArray, searchString: string): boolean =>
(Array.isArray(obj) ? obj : Object.entries(obj)).some((entry) =>
isObjectOrArray(entry)
? searchInObject(entry, searchString)
Expand Down
29 changes: 22 additions & 7 deletions packages/workflow/src/RoutingNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ import type {
INodePropertyCollection,
NodeParameterValueType,
PostReceiveAction,
JsonObject,
} from './Interfaces';
import type { NodeError } from './NodeErrors';
import { NodeApiError, NodeOperationError } from './NodeErrors';
import * as NodeHelpers from './NodeHelpers';

Expand Down Expand Up @@ -211,16 +213,29 @@ export class RoutingNode {
returnData.push(...responseData);
} catch (error) {
if (thisArgs !== undefined && thisArgs.continueOnFail()) {
returnData.push({ json: {}, error: error.message });
returnData.push({ json: {}, error: error as NodeError });
continue;
}
if (error instanceof NodeApiError) error = error.cause;
throw new NodeApiError(this.node, error, {

interface AxiosError extends NodeError {
isAxiosError: boolean;
description: string | undefined;
response?: { status: number };
}

let routingError = error as AxiosError;

if (error instanceof NodeApiError) routingError = error.cause as AxiosError;

throw new NodeApiError(this.node, error as JsonObject, {
runIndex,
itemIndex: i,
message: error?.message,
description: error?.description,
httpCode: error.isAxiosError && error.response && String(error.response?.status),
message: routingError?.message,
description: routingError?.description,
httpCode:
routingError.isAxiosError && routingError.response
? String(routingError.response?.status)
: 'none',
});
}
}
Expand Down Expand Up @@ -276,7 +291,7 @@ export class RoutingNode {
});
});
} catch (error) {
throw new NodeOperationError(this.node, error, {
throw new NodeOperationError(this.node, error as Error, {
runIndex,
itemIndex,
description: `The rootProperty "${action.properties.property}" could not be found on item.`,
Expand Down
4 changes: 1 addition & 3 deletions packages/workflow/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
"paths": {
"@/*": ["./*"]
},
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo",
// TODO: remove all options below this line
"useUnknownInCatchVariables": false
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo"
},
"include": ["src/**/*.ts", "test/**/*.ts"]
}

0 comments on commit b9d24a6

Please sign in to comment.