Skip to content

Commit 2f692e9

Browse files
merge static with dynamic data. Update tests
1 parent 764886e commit 2f692e9

File tree

3 files changed

+91
-19
lines changed

3 files changed

+91
-19
lines changed

packages/cli/src/modules/dynamic-credentials.ee/services/__tests__/dynamic-credential.service.test.ts

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ describe('DynamicCredentialService', () => {
5353
const createMockResolver = (
5454
shouldSucceed = true,
5555
shouldThrowDataNotFound = false,
56+
customData?: ICredentialDataDecryptedObject,
5657
): jest.Mocked<ICredentialResolver> => ({
5758
metadata: {
5859
name: 'stub-resolver-1.0',
@@ -65,7 +66,7 @@ describe('DynamicCredentialService', () => {
6566
if (!shouldSucceed) {
6667
throw new Error('Resolution failed');
6768
}
68-
return { token: 'dynamic-token', apiKey: 'dynamic-key' };
69+
return customData ?? { token: 'dynamic-token', apiKey: 'dynamic-key' };
6970
}),
7071
setSecret: jest.fn(),
7172
validateOptions: jest.fn(),
@@ -160,7 +161,7 @@ describe('DynamicCredentialService', () => {
160161
const result = await service.resolveIfNeeded(credentialsEntity, staticData, undefined);
161162

162163
expect(result).toBe(staticData);
163-
expect(mockLogger.warn).toHaveBeenCalledWith(
164+
expect(mockLogger.debug).toHaveBeenCalledWith(
164165
'Resolver not found, falling back to static credentials',
165166
expect.objectContaining({
166167
credentialId: 'cred-123',
@@ -181,7 +182,7 @@ describe('DynamicCredentialService', () => {
181182
const result = await service.resolveIfNeeded(credentialsEntity, staticData, undefined);
182183

183184
expect(result).toBe(staticData);
184-
expect(mockLogger.warn).toHaveBeenCalled();
185+
expect(mockLogger.debug).toHaveBeenCalled();
185186
});
186187

187188
it('execution context is missing and fallback is allowed', async () => {
@@ -197,7 +198,7 @@ describe('DynamicCredentialService', () => {
197198
const result = await service.resolveIfNeeded(credentialsEntity, staticData, undefined);
198199

199200
expect(result).toBe(staticData);
200-
expect(mockLogger.warn).toHaveBeenCalledWith(
201+
expect(mockLogger.debug).toHaveBeenCalledWith(
201202
'No execution context available, falling back to static credentials',
202203
expect.objectContaining({
203204
credentialId: 'cred-123',
@@ -252,7 +253,7 @@ describe('DynamicCredentialService', () => {
252253
);
253254

254255
expect(result).toBe(staticData);
255-
expect(mockLogger.warn).toHaveBeenCalledWith(
256+
expect(mockLogger.debug).toHaveBeenCalledWith(
256257
'Dynamic credential resolution failed, falling back to static',
257258
expect.objectContaining({
258259
credentialId: 'cred-123',
@@ -282,7 +283,7 @@ describe('DynamicCredentialService', () => {
282283
);
283284

284285
expect(result).toBe(staticData);
285-
expect(mockLogger.warn).toHaveBeenCalledWith(
286+
expect(mockLogger.debug).toHaveBeenCalledWith(
286287
'Dynamic credential resolution failed, falling back to static',
287288
expect.objectContaining({
288289
isDataNotFound: true,
@@ -382,34 +383,54 @@ describe('DynamicCredentialService', () => {
382383
service.resolveIfNeeded(credentialsEntity, staticData, executionContext),
383384
).rejects.toThrow('Failed to resolve dynamic credentials for "Test Credential"');
384385

385-
expect(mockLogger.error).toHaveBeenCalledWith(
386+
expect(mockLogger.debug).toHaveBeenCalledWith(
386387
'Dynamic credential resolution failed without fallback',
387388
expect.any(Object),
388389
);
389390
});
390391
});
391392

392393
describe('should successfully resolve dynamic credentials when', () => {
393-
it('all conditions are met and resolver returns data', async () => {
394+
it('all conditions are met and merges static with dynamic data', async () => {
394395
const credentialsEntity = createMockCredentialsEntity();
395396
const resolverEntity = createMockResolverEntity();
396-
const mockResolver = createMockResolver();
397397
const executionContext = createMockExecutionContext('encrypted-credentials');
398398
const credentialContext = createMockCredentialContext();
399399

400+
// Static data includes OAuth client config and old token
401+
const staticOAuthData: ICredentialDataDecryptedObject = {
402+
clientId: 'static-client-id',
403+
clientSecret: 'static-client-secret',
404+
token: 'static-token', // Will be overridden
405+
apiKey: 'static-key', // Will be overridden
406+
};
407+
408+
// Dynamic data includes new tokens (overrides token) and new fields
409+
const dynamicData: ICredentialDataDecryptedObject = {
410+
token: 'dynamic-token',
411+
apiKey: 'dynamic-key',
412+
refreshToken: 'dynamic-refresh-token',
413+
};
414+
415+
const mockResolver = createMockResolver(true, false, dynamicData);
416+
400417
mockResolverRepository.findOneBy.mockResolvedValue(resolverEntity);
401418
mockResolverRegistry.getResolverByName.mockReturnValue(mockResolver);
402419
mockCipher.decrypt.mockReturnValue(JSON.stringify(credentialContext));
403420

404421
const result = await service.resolveIfNeeded(
405422
credentialsEntity,
406-
staticData,
423+
staticOAuthData,
407424
executionContext,
408425
);
409426

427+
// Verify merge: static fields preserved, dynamic fields added/overridden
410428
expect(result).toEqual({
411-
token: 'dynamic-token',
412-
apiKey: 'dynamic-key',
429+
clientId: 'static-client-id', // From static (preserved)
430+
clientSecret: 'static-client-secret', // From static (preserved)
431+
token: 'dynamic-token', // From dynamic (overridden)
432+
apiKey: 'dynamic-key', // From dynamic (overridden)
433+
refreshToken: 'dynamic-refresh-token', // From dynamic (new field)
413434
});
414435
expect(mockResolver.getSecret).toHaveBeenCalledWith('cred-123', credentialContext, {
415436
prefix: 'test',
@@ -525,7 +546,7 @@ describe('DynamicCredentialService', () => {
525546
);
526547

527548
expect(result).toBe(staticData);
528-
expect(mockLogger.warn).toHaveBeenCalled();
549+
expect(mockLogger.debug).toHaveBeenCalled();
529550
});
530551

531552
it('empty resolver config', async () => {
@@ -644,7 +665,7 @@ describe('DynamicCredentialService', () => {
644665
);
645666

646667
expect(result).toBe(staticData);
647-
expect(mockLogger.warn).toHaveBeenCalledWith(
668+
expect(mockLogger.debug).toHaveBeenCalledWith(
648669
'Resolver not found, falling back to static credentials',
649670
expect.objectContaining({
650671
resolverId: 'workflow-resolver-789',

packages/cli/src/modules/dynamic-credentials.ee/services/dynamic-credential.service.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ export class DynamicCredentialService implements ICredentialResolutionProvider {
9494
identity: credentialContext.identity,
9595
});
9696

97-
return dynamicData;
97+
// Adds and override static data with dynamically resolved data
98+
return { ...staticData, ...dynamicData };
9899
} catch (error) {
99100
return this.handleResolutionError(credentialsEntity, staticData, error, resolverId);
100101
}
@@ -132,7 +133,7 @@ export class DynamicCredentialService implements ICredentialResolutionProvider {
132133
const isDataNotFound = error instanceof CredentialResolverDataNotFoundError;
133134

134135
if (credentialsEntity.resolvableAllowFallback) {
135-
this.logger.warn('Dynamic credential resolution failed, falling back to static', {
136+
this.logger.debug('Dynamic credential resolution failed, falling back to static', {
136137
credentialId: credentialsEntity.id,
137138
credentialName: credentialsEntity.name,
138139
resolverId,
@@ -143,7 +144,7 @@ export class DynamicCredentialService implements ICredentialResolutionProvider {
143144
return staticData;
144145
}
145146

146-
this.logger.error('Dynamic credential resolution failed without fallback', {
147+
this.logger.debug('Dynamic credential resolution failed without fallback', {
147148
credentialId: credentialsEntity.id,
148149
credentialName: credentialsEntity.name,
149150
resolverId,
@@ -166,7 +167,7 @@ export class DynamicCredentialService implements ICredentialResolutionProvider {
166167
resolverId: string,
167168
): ICredentialDataDecryptedObject {
168169
if (credentialsEntity.resolvableAllowFallback) {
169-
this.logger.warn('Resolver not found, falling back to static credentials', {
170+
this.logger.debug('Resolver not found, falling back to static credentials', {
170171
credentialId: credentialsEntity.id,
171172
credentialName: credentialsEntity.name,
172173
resolverId,
@@ -188,7 +189,7 @@ export class DynamicCredentialService implements ICredentialResolutionProvider {
188189
staticData: ICredentialDataDecryptedObject,
189190
): ICredentialDataDecryptedObject {
190191
if (credentialsEntity.resolvableAllowFallback) {
191-
this.logger.warn('No execution context available, falling back to static credentials', {
192+
this.logger.debug('No execution context available, falling back to static credentials', {
192193
credentialId: credentialsEntity.id,
193194
credentialName: credentialsEntity.name,
194195
});

packages/core/src/execution-engine/node-execution-context/__tests__/node-execution-context.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,56 @@ describe('NodeExecutionContext', () => {
195195
});
196196
});
197197

198+
describe('_getCredentials', () => {
199+
it('should set executionContext on additionalData before retrieving credentials', async () => {
200+
const credentialDetails = { id: 'cred123', name: 'Test Credential' };
201+
const testNode = mock<INode>({
202+
type: 'n8n-nodes-base.httpRequest',
203+
});
204+
testNode.credentials = { testCredential: credentialDetails };
205+
206+
const runtimeData = {
207+
version: 1 as const,
208+
establishedAt: Date.now(),
209+
source: 'manual' as const,
210+
};
211+
const testRunExecutionData = createRunExecutionData({
212+
resultData: { runData: {} },
213+
executionData: { runtimeData },
214+
});
215+
216+
const mockCredentialsHelper = {
217+
getDecrypted: jest.fn().mockResolvedValue({ token: 'test-token' }),
218+
getCredentialsProperties: jest.fn(),
219+
};
220+
221+
const mockAdditionalData = mock<IWorkflowExecuteAdditionalData>({
222+
credentialsHelper: mockCredentialsHelper,
223+
});
224+
225+
const contextWithCredentials = new TestContext(
226+
workflow,
227+
testNode,
228+
mockAdditionalData,
229+
mode,
230+
testRunExecutionData,
231+
);
232+
233+
await contextWithCredentials['_getCredentials']('testCredential');
234+
235+
expect(mockAdditionalData.executionContext).toEqual(runtimeData);
236+
expect(mockCredentialsHelper.getDecrypted).toHaveBeenCalledWith(
237+
mockAdditionalData,
238+
credentialDetails,
239+
'testCredential',
240+
mode,
241+
undefined,
242+
false,
243+
undefined,
244+
);
245+
});
246+
});
247+
198248
describe('prepareOutputData', () => {
199249
it('should return the input array wrapped in another array', async () => {
200250
const outputData = [mock<INodeExecutionData>(), mock<INodeExecutionData>()];

0 commit comments

Comments
 (0)