Skip to content

Commit 39acc9d

Browse files
authored
fix: action module fetching logic to resolve correct module IDs (#1340)
* fix: action module fetching logic and add tests - src/tools/auth0/handlers/actions.ts: simplify module fetching logic by removing redundant checks for actionModules - test/tools/auth0/handlers/actions.tests.js: add test to ensure modules are fetched from API even when actionModules are provided * add error handling for missing module version id * add error handling for missing module version id * test: add error handling for missing module version id in actions tests * lint fix
1 parent 07e8731 commit 39acc9d

2 files changed

Lines changed: 174 additions & 17 deletions

File tree

src/tools/auth0/handlers/actions.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ export default class ActionHandler extends DefaultAPIHandler {
247247
}
248248

249249
async calcChanges(assets: Assets): Promise<CalculatedChanges> {
250-
let { actions, actionModules } = assets;
250+
let { actions } = assets;
251251

252252
// Do nothing if not set
253253
if (!actions)
@@ -259,19 +259,15 @@ export default class ActionHandler extends DefaultAPIHandler {
259259
};
260260

261261
let modules: ActionModule[] | null = null;
262-
if (actionModules && actionModules.length > 0) {
263-
modules = actionModules;
264-
} else {
265-
try {
266-
modules = await paginate<ActionModule>(this.client.actions.modules.list, {
267-
paginate: true,
268-
});
269-
} catch {
270-
log.debug(
271-
'Skipping actions modules enrichment because action modules could not be retrieved.'
272-
);
273-
modules = null;
274-
}
262+
try {
263+
modules = await paginate<ActionModule>(this.client.actions.modules.list, {
264+
paginate: true,
265+
});
266+
} catch {
267+
log.debug(
268+
'Skipping actions modules enrichment because action modules could not be retrieved.'
269+
);
270+
modules = null;
275271
}
276272

277273
if (modules != null) {
@@ -313,13 +309,20 @@ export default class ActionHandler extends DefaultAPIHandler {
313309
allModuleVersions.push(...moduleVersions.data);
314310
}
315311

312+
const moduleVersionId = allModuleVersions?.find(
313+
(v) => v.version_number === module.module_version_number
314+
)?.id;
315+
if (!moduleVersionId) {
316+
throw new Error(
317+
`Could not find action module version id for module '${module.module_name}' version '${module.module_version_number}'`
318+
);
319+
}
320+
316321
return {
317322
module_name: module.module_name,
318323
module_id: foundModule.id,
319324
module_version_number: module.module_version_number,
320-
module_version_id:
321-
allModuleVersions?.find((v) => v.version_number === module.module_version_number)
322-
?.id || '',
325+
module_version_id: moduleVersionId,
323326
};
324327
}
325328
return module;

test/tools/auth0/handlers/actions.tests.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,160 @@ describe('#actions handler', () => {
551551
await stageFn.apply(handler, [{ actions: [action] }]);
552552
});
553553

554+
it('should fetch modules from API even when actionModules are provided in assets', async () => {
555+
const actionId = 'action-with-modules-id';
556+
const moduleId = 'module-id-from-api';
557+
const moduleVersionId = 'version-uuid-from-api';
558+
559+
const action = {
560+
name: 'action-with-modules',
561+
supported_triggers: [{ id: 'post-login', version: 'v1' }],
562+
modules: [{ module_name: 'test-module', module_version_number: 1 }],
563+
};
564+
565+
// Local config module — has no `id` field (desired state only)
566+
const localConfigModule = { name: 'test-module', code: 'module.exports = {};' };
567+
568+
let modulesListCalled = false;
569+
let createCalledWith = null;
570+
571+
const auth0 = {
572+
actions: {
573+
get: () => Promise.resolve({ data: { ...action, id: actionId } }),
574+
create: (data) => {
575+
createCalledWith = data;
576+
return Promise.resolve({ data: { ...data, id: actionId } });
577+
},
578+
update: () => Promise.resolve({ data: [] }),
579+
delete: () => Promise.resolve({ data: [] }),
580+
list: () => {
581+
if (!auth0.listCalled) {
582+
auth0.listCalled = true;
583+
return mockPagedData({ include_totals: true }, 'actions', []);
584+
}
585+
return mockPagedData({ include_totals: true }, 'actions', [
586+
{ name: action.name, supported_triggers: action.supported_triggers, id: actionId },
587+
]);
588+
},
589+
createVersion: () =>
590+
Promise.resolve({
591+
data: {
592+
code: 'action-code',
593+
dependencies: [],
594+
id: 'version-id',
595+
runtime: 'node12',
596+
secrets: [],
597+
},
598+
}),
599+
modules: {
600+
list: () => {
601+
modulesListCalled = true;
602+
return mockPagedData({ paginate: true }, 'modules', [
603+
{ id: moduleId, name: 'test-module', code: 'module.exports = {};' },
604+
]);
605+
},
606+
versions: {
607+
list: () =>
608+
Promise.resolve(
609+
mockPagedData({ paginate: true }, 'versions', [
610+
{ id: moduleVersionId, version_number: 1 },
611+
])
612+
),
613+
},
614+
},
615+
},
616+
pool: {
617+
addEachTask: (data) => {
618+
const results = data.data.map(data.generator);
619+
return { promise: () => Promise.all(results) };
620+
},
621+
},
622+
listCalled: false,
623+
};
624+
625+
const handler = new actions.default({ client: pageClient(auth0), config });
626+
const stageFn = Object.getPrototypeOf(handler).processChanges;
627+
628+
// Pass both actions and actionModules in assets (the bug scenario)
629+
await stageFn.apply(handler, [{ actions: [action], actionModules: [localConfigModule] }]);
630+
631+
// API must have been called to resolve module IDs — not the local config shortcut
632+
expect(modulesListCalled).to.equal(true);
633+
634+
// The created action must have a valid module_version_id, not undefined or ''
635+
expect(createCalledWith.modules[0].module_version_id).to.equal(moduleVersionId);
636+
expect(createCalledWith.modules[0].module_id).to.equal(moduleId);
637+
});
638+
639+
it('should throw an error when the requested module version number is not found in API', async () => {
640+
const actionId = 'action-missing-version-id';
641+
const moduleId = 'module-id-from-api';
642+
643+
const action = {
644+
name: 'action-missing-version',
645+
supported_triggers: [{ id: 'post-login', version: 'v1' }],
646+
// references version 99 which does not exist in the API
647+
modules: [{ module_name: 'test-module', module_version_number: 99 }],
648+
};
649+
650+
const auth0 = {
651+
actions: {
652+
get: () => Promise.resolve({ data: { ...action, id: actionId } }),
653+
create: (data) => Promise.resolve({ data: { ...data, id: actionId } }),
654+
update: () => Promise.resolve({ data: [] }),
655+
delete: () => Promise.resolve({ data: [] }),
656+
list: () => {
657+
if (!auth0.listCalled) {
658+
auth0.listCalled = true;
659+
return mockPagedData({ include_totals: true }, 'actions', []);
660+
}
661+
return mockPagedData({ include_totals: true }, 'actions', [
662+
{ name: action.name, supported_triggers: action.supported_triggers, id: actionId },
663+
]);
664+
},
665+
createVersion: () =>
666+
Promise.resolve({
667+
data: {
668+
code: 'action-code',
669+
dependencies: [],
670+
id: 'version-id',
671+
runtime: 'node12',
672+
secrets: [],
673+
},
674+
}),
675+
modules: {
676+
list: () =>
677+
mockPagedData({ paginate: true }, 'modules', [
678+
{ id: moduleId, name: 'test-module', code: 'module.exports = {};' },
679+
]),
680+
versions: {
681+
list: () =>
682+
Promise.resolve(
683+
// Only version 1 exists — version 99 is absent
684+
mockPagedData({ paginate: true }, 'versions', [
685+
{ id: 'v1-uuid', version_number: 1 },
686+
])
687+
),
688+
},
689+
},
690+
},
691+
pool: {
692+
addEachTask: (data) => {
693+
const results = data.data.map(data.generator);
694+
return { promise: () => Promise.all(results) };
695+
},
696+
},
697+
listCalled: false,
698+
};
699+
700+
const handler = new actions.default({ client: pageClient(auth0), config });
701+
const stageFn = Object.getPrototypeOf(handler).processChanges;
702+
703+
await expect(stageFn.apply(handler, [{ actions: [action] }])).to.be.rejectedWith(
704+
/Could not find action module version id for module 'test-module' version '99'/
705+
);
706+
});
707+
554708
it('should handle actions without modules', async () => {
555709
const actionId = 'action-no-modules-id';
556710
const action = {

0 commit comments

Comments
 (0)