From b5e2f331cc446b1e90e880148a5f7ca3813c054e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Thu, 20 Feb 2025 13:00:12 +0100 Subject: [PATCH] test: Add unit tests for CredentialsOverwrites (#13372) --- .../__tests__/credentials-overwrites.test.ts | 136 ++++++++++++++++++ packages/cli/src/credentials-overwrites.ts | 2 +- 2 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 packages/cli/src/__tests__/credentials-overwrites.test.ts diff --git a/packages/cli/src/__tests__/credentials-overwrites.test.ts b/packages/cli/src/__tests__/credentials-overwrites.test.ts new file mode 100644 index 0000000000..c9a4f2fb27 --- /dev/null +++ b/packages/cli/src/__tests__/credentials-overwrites.test.ts @@ -0,0 +1,136 @@ +import type { GlobalConfig } from '@n8n/config'; +import { mock } from 'jest-mock-extended'; +import { UnrecognizedCredentialTypeError, type Logger } from 'n8n-core'; +import type { ICredentialType } from 'n8n-workflow'; + +import type { CredentialTypes } from '@/credential-types'; +import { CredentialsOverwrites } from '@/credentials-overwrites'; + +describe('CredentialsOverwrites', () => { + const testCredentialType = mock({ name: 'test', extends: ['parent'] }); + const parentCredentialType = mock({ name: 'parent', extends: undefined }); + + const globalConfig = mock({ credentials: { overwrite: {} } }); + const credentialTypes = mock(); + const logger = mock(); + let credentialsOverwrites: CredentialsOverwrites; + + beforeEach(() => { + jest.resetAllMocks(); + + globalConfig.credentials.overwrite.data = JSON.stringify({ + test: { username: 'user' }, + parent: { password: 'pass' }, + }); + credentialTypes.recognizes.mockReturnValue(true); + credentialTypes.getByName.mockImplementation((credentialType) => { + if (credentialType === testCredentialType.name) return testCredentialType; + if (credentialType === parentCredentialType.name) return parentCredentialType; + throw new UnrecognizedCredentialTypeError(credentialType); + }); + credentialTypes.getParentTypes + .calledWith(testCredentialType.name) + .mockReturnValue([parentCredentialType.name]); + + credentialsOverwrites = new CredentialsOverwrites(globalConfig, credentialTypes, logger); + }); + + describe('constructor', () => { + it('should parse and set overwrite data from config', () => { + expect(credentialsOverwrites.getAll()).toEqual({ + parent: { password: 'pass' }, + test: { + password: 'pass', + username: 'user', + }, + }); + }); + }); + + describe('setData', () => { + it('should reset resolvedTypes when setting new data', () => { + const newData = { test: { token: 'test-token' } }; + credentialsOverwrites.setData(newData); + + expect(credentialsOverwrites.getAll()).toEqual(newData); + }); + }); + + describe('applyOverwrite', () => { + it('should apply overwrites only for empty fields', () => { + const result = credentialsOverwrites.applyOverwrite('test', { + username: 'existingUser', + password: '', + }); + + expect(result).toEqual({ + username: 'existingUser', + password: 'pass', + }); + }); + + it('should return original data if no overwrites exist', () => { + const data = { + username: 'user1', + password: 'pass1', + }; + + credentialTypes.getParentTypes.mockReturnValueOnce([]); + + const result = credentialsOverwrites.applyOverwrite('unknownCredential', data); + expect(result).toEqual(data); + }); + }); + + describe('getOverwrites', () => { + it('should return undefined for unrecognized credential type', () => { + credentialTypes.recognizes.mockReturnValue(false); + + const result = credentialsOverwrites.getOverwrites('unknownType'); + + expect(result).toBeUndefined(); + expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Unknown credential type')); + }); + + it('should cache resolved types', () => { + credentialsOverwrites.getOverwrites('parent'); + const firstCall = credentialsOverwrites.getOverwrites('test'); + const secondCall = credentialsOverwrites.getOverwrites('test'); + + expect(firstCall).toEqual(secondCall); + expect(credentialTypes.getByName).toHaveBeenCalledTimes(2); + // eslint-disable-next-line @typescript-eslint/dot-notation + expect(credentialsOverwrites['resolvedTypes']).toEqual(['parent', 'test']); + }); + + it('should merge overwrites from parent types', () => { + credentialTypes.getByName.mockImplementation((credentialType) => { + if (credentialType === 'childType') + return mock({ extends: ['parentType1', 'parentType2'] }); + if (credentialType === 'parentType1') return mock({ extends: undefined }); + if (credentialType === 'parentType2') return mock({ extends: undefined }); + throw new UnrecognizedCredentialTypeError(credentialType); + }); + + globalConfig.credentials.overwrite.data = JSON.stringify({ + childType: { specificField: 'childValue' }, + parentType1: { parentField1: 'value1' }, + parentType2: { parentField2: 'value2' }, + }); + + const credentialsOverwrites = new CredentialsOverwrites( + globalConfig, + credentialTypes, + logger, + ); + + const result = credentialsOverwrites.getOverwrites('childType'); + + expect(result).toEqual({ + parentField1: 'value1', + parentField2: 'value2', + specificField: 'childValue', + }); + }); + }); +}); diff --git a/packages/cli/src/credentials-overwrites.ts b/packages/cli/src/credentials-overwrites.ts index 6689649b0f..3ba1b8043c 100644 --- a/packages/cli/src/credentials-overwrites.ts +++ b/packages/cli/src/credentials-overwrites.ts @@ -60,7 +60,7 @@ export class CredentialsOverwrites { return returnData; } - private getOverwrites(type: string): ICredentialDataDecryptedObject | undefined { + getOverwrites(type: string): ICredentialDataDecryptedObject | undefined { if (this.resolvedTypes.includes(type)) { // Type got already resolved and can so returned directly return this.overwriteData[type];