> ## Documentation Index
> Fetch the complete documentation index at: https://docs-staging-automated-sdk-version-update.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Test unitaire d'une action

> Tests unitaires pour une action Post Login qui définit les rôles d'un utilisateur comme demande personnalisée du jeton d'ID et qui refuse l'accès lorsqu'aucun rôle n'est présent, avec Jest, Mocha et Node.js Test Runner en JavaScript et TypeScript.

<h2 id="project">
  Projet
</h2>

Dans chaque exemple, le code source de l'action se trouve sous `src/` et ses tests unitaires sous `test/`, selon la même structure pour les projets JavaScript et TypeScript.

<Tabs>
  <Tab title="JavaScript">
    <Tree>
      <Tree.Folder name="/" defaultOpen>
        <Tree.Folder name="src" />

        <Tree.Folder name="test" />
      </Tree.Folder>
    </Tree>
  </Tab>

  <Tab title="TypeScript">
    <Tree>
      <Tree.Folder name="/" defaultOpen>
        <Tree.Folder name="src" />

        <Tree.Folder name="test">
          <Tree.Folder name="test-utils" />
        </Tree.Folder>
      </Tree.Folder>
    </Tree>
  </Tab>
</Tabs>

<h2 id="action">
  Action
</h2>

L'action Post Login suivante vérifie les rôles de l'utilisateur à partir de `event.authorization` et les ajoute comme demande personnalisée au jeton d'ID; l'accès est refusé si aucun rôle n'est présent.

<Tabs>
  <Tab title="JavaScript">
    ```js title="test-an-action.js" theme={null}
    /** @import {Event, PostLoginAPI} from "@auth0/actions/post-login/v3" */

    const CUSTOM_CLAIM_NAMESPACE = 'https://example.com';

    /**
    * Gestionnaire appelé lors de l'exécution d'un flux PostLogin.
    *
    * @param {Event} event - Détails sur l'utilisateur et le contexte dans lequel il se connecte.
    * @param {PostLoginAPI} api - Interface dont les méthodes permettent de modifier le comportement de la connexion.
    */
    exports.onExecutePostLogin = async (event, api) => {
      const roles = event.authorization?.roles;

      if (roles === undefined || roles.length === 0) {
        api.access.deny('Restricted');
        return;
      }

      api.idToken.setCustomClaim(`${CUSTOM_CLAIM_NAMESPACE}/roles`, roles);
    }
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts title="test-an-action.ts" theme={null}
    import type { PostLoginAPI, Event } from '@auth0/actions/post-login/v3';

    const CUSTOM_CLAIM_NAMESPACE = 'https://example.com';

    /**
    * Gestionnaire appelé lors de l’exécution d’un flux PostLogin.
    *
    * @param {Event} event - Renseignements sur l’utilisateur et le contexte dans lequel il se connecte.
    * @param {PostLoginAPI} api - Interface dont les méthodes permettent de modifier le comportement de la connexion.
    */
    exports.onExecutePostLogin = async (event: Event, api: PostLoginAPI) => {
      const roles = event.authorization?.roles;

      if (roles === undefined || roles.length === 0) {
        api.access.deny('Restricted');
        return;
      }

      api.idToken.setCustomClaim(`${CUSTOM_CLAIM_NAMESPACE}/roles`, roles);
    };

    ```
  </Tab>
</Tabs>

<h2 id="unit-test">
  Test unitaire
</h2>

Les tests unitaires simulent les objets `event` et `api` afin de vérifier que les utilisateurs administrateurs obtiennent bien la demande personnalisée, tandis que les utilisateurs sans rôle ou dont l'objet `authorization` est absent se voient refuser l'accès.

<AccordionGroup>
  <Accordion title="Jest">
    <Tabs>
      <Tab title="JavaScript">
        ```js title="test-an-action.spec.js" theme={null}
        const { getDefaultArguments, loadAction } = require('@auth0/actions/post-login/v3/test');
        const path = require('path');

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/test-an-action.js');
        const CUSTOM_CLAIM_NAMESPACE = 'https://example.com';

        describe('onExecutePostLogin', () => {
          let loader;
          let event;
          let api;

          beforeEach(async () => {
            jest.resetAllMocks();
            loader = await loadAction(ACTION_PATH);
            [event, api] = getDefaultArguments();
            jest.spyOn(api.access, 'deny');
            jest.spyOn(api.idToken, 'setCustomClaim');
          });

          afterEach(() => {
            jest.resetAllMocks();
          });

          it('Admin user', async () => {
            event.authorization = { roles: ['admin'] };

            await loader.execute('onExecutePostLogin', event, api);

            expect(api.idToken.setCustomClaim).toHaveBeenCalledWith(
              `${CUSTOM_CLAIM_NAMESPACE}/roles`,
              ['admin']
            );
            expect(api.access.deny).not.toHaveBeenCalled();
          });

          it('Restricted User - No authorization', async () => {
            delete event.authorization;

            await loader.execute('onExecutePostLogin', event, api);

            expect(api.idToken.setCustomClaim).not.toHaveBeenCalled();
            expect(api.access.deny).toHaveBeenCalledWith('Restricted');
          });

          it('Restricted User - No roles', async () => {
            event.authorization = { roles: [] };

            await loader.execute('onExecutePostLogin', event, api);

            expect(api.idToken.setCustomClaim).not.toHaveBeenCalled();
            expect(api.access.deny).toHaveBeenCalledWith('Restricted');
          });
        });

        ```

        Ajoutez les dépendances de test au fichier `package.json` :

        ```json title="package.json" theme={null}
        {
          "name": "actions-npm-example-js-jest",
          "version": "1.0.0",
          "description": "Auth0 Actions unit testing example using Jest",
          "license": "MIT",
          "author": "Auth0",
          "type": "commonjs",
          "main": "module-usage.js",
          "scripts": {
            "test": "jest"
          },
          "devDependencies": {
            "@auth0/actions": "^0.33.0",
            "jest": "^30.4.2"
          },
          "jest": {
            "testEnvironment": "node"
          }
        }

        ```

        Configurez `jsconfig.json` pour que l'alias d'importation `actions:` pointe vers `src/` :

        ```json title="jsconfig.json" theme={null}
        {
          "compilerOptions": {
            "target": "ES2020",
            "module": "commonjs",
            "checkJs": false,
            "baseUrl": ".",
            "paths": {
              "actions:*": [
                "src/*"
              ]
            }
          },
          "include": [
            "src/**/*.js"
          ]
        }

        ```
      </Tab>

      <Tab title="TypeScript">
        ```ts title="test-an-action.test.ts" theme={null}
        const { getDefaultArguments, loadAction } = require('@auth0/actions/post-login/v3/test');
        const path = require('path');
        const { compileActionModules } = require('./test-utils/load-compiled-action');

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/test-an-action.ts');
        const CUSTOM_CLAIM_NAMESPACE = 'https://example.com';

        describe('onExecutePostLogin', () => {
          let loader: any;
          let event: any;
          let api: any;

          beforeEach(async () => {
            jest.resetAllMocks();
            const { compiledActionPath } = compileActionModules(ACTION_PATH);
            loader = await loadAction(compiledActionPath);
            [event, api] = getDefaultArguments();
            jest.spyOn(api.access, 'deny');
            jest.spyOn(api.idToken, 'setCustomClaim');
          });

          afterEach(() => {
            jest.resetAllMocks();
          });

          it('Admin user', async () => {
            event.authorization = { roles: ['admin'] };

            await loader.execute('onExecutePostLogin', event, api);

            expect(api.idToken.setCustomClaim).toHaveBeenCalledWith(
              `${CUSTOM_CLAIM_NAMESPACE}/roles`,
              ['admin']
            );
            expect(api.access.deny).not.toHaveBeenCalled();
          });

          it('Restricted User - No authorization', async () => {
            delete event.authorization;

            await loader.execute('onExecutePostLogin', event, api);

            expect(api.idToken.setCustomClaim).not.toHaveBeenCalled();
            expect(api.access.deny).toHaveBeenCalledWith('Restricted');
          });

          it('Restricted User - No roles', async () => {
            event.authorization = { roles: [] };

            await loader.execute('onExecutePostLogin', event, api);

            expect(api.idToken.setCustomClaim).not.toHaveBeenCalled();
            expect(api.access.deny).toHaveBeenCalledWith('Restricted');
          });
        });

        ```

        Cet utilitaire de test compile l'action TypeScript dans un fichier temporaire afin qu'elle puisse être chargée à l'exécution :

        ```ts title="load-compiled-action.ts" theme={null}
        import * as fs from 'fs';
        import * as os from 'os';
        import * as path from 'path';
        import * as ts from 'typescript';

        export interface ModuleToCompile {
          name: string;
          filename: string;
        }

        function transpileToTemp(sourcePath: string): string {
          const source = fs.readFileSync(sourcePath, 'utf8');
          const { outputText } = ts.transpileModule(source, {
            compilerOptions: {
              module: ts.ModuleKind.CommonJS,
              target: ts.ScriptTarget.ES2020,
              esModuleInterop: true,
            },
          });

          const tempPath = path.join(
            os.tmpdir(),
            `${path.basename(sourcePath, path.extname(sourcePath))}-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.js`,
          );
          fs.writeFileSync(tempPath, outputText);
          return tempPath;
        }

        /**
         * loadAction() (de @auth0/actions/*\/test) lit son fichier cible sur le disque et
         * l'exécute au moyen de vm.compileFunction; il ne passe donc jamais par la
         * transformation TS de ts-node/Vitest. Les sources d'actions (et tout module
         * enregistré avec actions:) doivent d'abord être transpilées en JS ordinaire sur le disque.
         */
        export function compileActionModules(actionPath: string, modules: ModuleToCompile[] = []) {
          const compiledActionPath = transpileToTemp(actionPath);
          const compiledModules = modules.map((m) => ({
            name: m.name,
            filename: transpileToTemp(m.filename),
          }));

          return { compiledActionPath, compiledModules };
        }

        ```

        Ajoutez les dépendances de test au fichier `package.json` :

        ```json title="package.json" theme={null}
        {
          "name": "actions-npm-example-ts-jest",
          "version": "1.0.0",
          "description": "Auth0 Actions unit testing example using Jest and TypeScript",
          "main": "example.ts",
          "scripts": {
            "test": "jest"
          },
          "author": "Auth0",
          "license": "MIT",
          "devDependencies": {
            "@auth0/actions": "^0.33.0",
            "@types/jest": "^29.5.12",
            "@types/node": "22.14.0",
            "jest": "^29.7.0",
            "ts-jest": "^29.1.2",
            "typescript": "^5.9.2"
          }
        }

        ```

        Configurez Jest pour qu'il compile le TypeScript à l'aide de `jest.config.js` :

        ```js title="jest.config.js" theme={null}
        module.exports = {
          preset: 'ts-jest',
          testEnvironment: 'node',
        };
        ```

        Configurez `tsconfig.json` pour que l'alias d'importation `actions:` pointe vers `src/` :

        ```json title="tsconfig.json" theme={null}
        {
          "compilerOptions": {
            "target": "ES2020",
            "module": "NodeNext",
            "moduleResolution": "nodenext",
            "esModuleInterop": true,
            "allowSyntheticDefaultImports": true,
            "strict": true,
            "outDir": "dist",
            "declaration": true,
            "sourceMap": true,
            "allowJs": true,
            "checkJs": false,
            "resolveJsonModule": true,
            "skipLibCheck": true,
            "forceConsistentCasingInFileNames": true,
            "isolatedModules": true,
            "noEmit": true,
            "paths": {
              "actions:*": [
                "./src/*"
              ]
            }
          },
          "exclude": [
            "node_modules",
            "dist"
          ],
          "include": [
            "**/*.ts"
          ],
          "ts-node": {
            "transpileOnly": true
          }
        }

        ```
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="Mocha">
    <Tabs>
      <Tab title="JavaScript">
        ```js title="test-an-action.spec.js" theme={null}
        const sinon = require('sinon');
        const { getDefaultArguments, loadAction } = require('@auth0/actions/post-login/v3/test');
        const path = require('path');

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/test-an-action.js');
        const CUSTOM_CLAIM_NAMESPACE = 'https://example.com';

        describe('onExecutePostLogin', () => {
          let loader;
          let event;
          let api;

          beforeEach(async () => {
            loader = await loadAction(ACTION_PATH);
            [event, api] = getDefaultArguments();
            sinon.spy(api.access, 'deny');
            sinon.spy(api.idToken, 'setCustomClaim');
          });

          afterEach(() => {
            sinon.restore();
          });

          it('Admin user', async () => {
            event.authorization = { roles: ['admin'] };

            await loader.execute('onExecutePostLogin', event, api);

            sinon.assert.calledWith(
              api.idToken.setCustomClaim,
              `${CUSTOM_CLAIM_NAMESPACE}/roles`,
              ['admin']
            );
            sinon.assert.notCalled(api.access.deny);
          });

          it('Restricted User - No authorization', async () => {
            delete event.authorization;

            await loader.execute('onExecutePostLogin', event, api);

            sinon.assert.notCalled(api.idToken.setCustomClaim);
            sinon.assert.calledWith(api.access.deny, 'Restricted');
          });

          it('Restricted User - No roles', async () => {
            event.authorization = { roles: [] };

            await loader.execute('onExecutePostLogin', event, api);

            sinon.assert.notCalled(api.idToken.setCustomClaim);
            sinon.assert.calledWith(api.access.deny, 'Restricted');
          });
        });

        ```

        Ajoutez les dépendances de test au fichier `package.json` :

        ```json title="package.json" theme={null}
        {
          "name": "actions-npm-example-js-mocha",
          "version": "1.0.0",
          "description": "Auth0 Actions unit testing example using Mocha",
          "license": "MIT",
          "author": "Auth0",
          "type": "commonjs",
          "main": "module-usage.js",
          "scripts": {
            "test": "mocha"
          },
          "devDependencies": {
            "@auth0/actions": "^0.33.0",
            "chai": "^4.5.0",
            "mocha": "^11.0.0",
            "sinon": "^19.0.0"
          }
        }

        ```

        Configurez Mocha à l'aide de `.mocharc.json` :

        ```json title=".mocharc.json" theme={null}
        {
          "spec": "src/**/*.spec.js"
        }

        ```

        Configurez `jsconfig.json` pour que l'alias d'importation `actions:` pointe vers `src/` :

        ```json title="jsconfig.json" theme={null}
        {
          "compilerOptions": {
            "target": "ES2020",
            "module": "commonjs",
            "checkJs": false,
            "baseUrl": ".",
            "paths": {
              "actions:*": [
                "src/*"
              ]
            }
          },
          "include": [
            "src/**/*.js"
          ]
        }

        ```
      </Tab>

      <Tab title="TypeScript">
        ```ts title="test-an-action.test.ts" theme={null}
        import * as path from 'path';
        import sinon from 'sinon';
        import { compileActionModules } from './test-utils/load-compiled-action';

        const { getDefaultArguments, loadAction } = require('@auth0/actions/post-login/v3/test');

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/test-an-action.ts');
        const CUSTOM_CLAIM_NAMESPACE = 'https://example.com';

        describe('onExecutePostLogin', () => {
          let loader: any;
          let event: any;
          let api: any;

          beforeEach(async () => {
            const { compiledActionPath } = compileActionModules(ACTION_PATH);
            loader = await loadAction(compiledActionPath);
            [event, api] = getDefaultArguments();
            sinon.spy(api.access, 'deny');
            sinon.spy(api.idToken, 'setCustomClaim');
          });

          afterEach(() => {
            sinon.restore();
          });

          it('Admin user', async () => {
            event.authorization = { roles: ['admin'] };

            await loader.execute('onExecutePostLogin', event, api);

            sinon.assert.calledWith(
              api.idToken.setCustomClaim,
              `${CUSTOM_CLAIM_NAMESPACE}/roles`,
              ['admin']
            );
            sinon.assert.notCalled(api.access.deny);
          });

          it('Restricted User - No authorization', async () => {
            delete event.authorization;

            await loader.execute('onExecutePostLogin', event, api);

            sinon.assert.notCalled(api.idToken.setCustomClaim);
            sinon.assert.calledWith(api.access.deny, 'Restricted');
          });

          it('Restricted User - No roles', async () => {
            event.authorization = { roles: [] };

            await loader.execute('onExecutePostLogin', event, api);

            sinon.assert.notCalled(api.idToken.setCustomClaim);
            sinon.assert.calledWith(api.access.deny, 'Restricted');
          });
        });

        ```

        Cet utilitaire de test compile l'action TypeScript dans un fichier temporaire afin qu'elle puisse être chargée à l'exécution :

        ```ts title="load-compiled-action.ts" theme={null}
        import * as fs from 'fs';
        import * as os from 'os';
        import * as path from 'path';
        import * as ts from 'typescript';

        export interface ModuleToCompile {
          name: string;
          filename: string;
        }

        function transpileToTemp(sourcePath: string): string {
          const source = fs.readFileSync(sourcePath, 'utf8');
          const { outputText } = ts.transpileModule(source, {
            compilerOptions: {
              module: ts.ModuleKind.CommonJS,
              target: ts.ScriptTarget.ES2020,
              esModuleInterop: true,
            },
          });

          const tempPath = path.join(
            os.tmpdir(),
            `${path.basename(sourcePath, path.extname(sourcePath))}-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.js`,
          );
          fs.writeFileSync(tempPath, outputText);
          return tempPath;
        }

        /**
         * loadAction() (de @auth0/actions/*\/test) lit son fichier cible sur le disque et
         * l'exécute au moyen de vm.compileFunction : il ne passe donc jamais par la transformation
         * TS propre à ts-node/Vitest. Les sources des actions (et tout module enregistré avec
         * actions:) doivent d'abord être transpilées en JS pur sur le disque.
         */
        export function compileActionModules(actionPath: string, modules: ModuleToCompile[] = []) {
          const compiledActionPath = transpileToTemp(actionPath);
          const compiledModules = modules.map((m) => ({
            name: m.name,
            filename: transpileToTemp(m.filename),
          }));

          return { compiledActionPath, compiledModules };
        }

        ```

        Ajoutez les dépendances de test au fichier `package.json` :

        ```json title="package.json" theme={null}
        {
          "name": "actions-npm-example-ts-mocha",
          "version": "1.0.0",
          "description": "Auth0 Actions unit testing example using Mocha and TypeScript",
          "license": "MIT",
          "author": "Auth0",
          "scripts": {
            "test": "NODE_OPTIONS=--no-experimental-strip-types mocha"
          },
          "devDependencies": {
            "@auth0/actions": "^0.33.0",
            "@types/chai": "^4.3.16",
            "@types/mocha": "^10.0.6",
            "@types/node": "22.14.0",
            "@types/sinon": "^17.0.3",
            "chai": "^4.5.0",
            "mocha": "^11.0.0",
            "sinon": "^19.0.0",
            "ts-node": "^10.9.2",
            "typescript": "^5.9.2"
          }
        }

        ```

        Configurez Mocha à l'aide de `.mocharc.json` :

        ```json title=".mocharc.json" theme={null}
        {
          "require": "ts-node/register",
          "extension": ["ts"],
          "spec": "src/**/*.test.ts"
        }

        ```

        Configurez `tsconfig.json` pour que l'alias d'importation `actions:` pointe vers `src/` :

        ```json title="tsconfig.json" theme={null}
        {
          "compilerOptions": {
            "target": "ES2020",
            "module": "NodeNext",
            "moduleResolution": "nodenext",
            "esModuleInterop": true,
            "allowSyntheticDefaultImports": true,
            "strict": true,
            "outDir": "dist",
            "declaration": true,
            "sourceMap": true,
            "allowJs": true,
            "checkJs": false,
            "resolveJsonModule": true,
            "skipLibCheck": true,
            "forceConsistentCasingInFileNames": true,
            "isolatedModules": true,
            "noEmit": true,
            "paths": {
              "actions:*": [
                "./src/*"
              ]
            }
          },
          "exclude": [
            "node_modules",
            "dist"
          ],
          "include": [
            "**/*.ts"
          ],
          "ts-node": {
            "transpileOnly": true
          }
        }

        ```
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="Node.js Test Runner">
    <Tabs>
      <Tab title="JavaScript">
        ```js title="test-an-action.spec.js" theme={null}
        const assert = require('node:assert');
        const { describe, it, beforeEach, afterEach, mock } = require('node:test');
        const { getDefaultArguments, loadAction } = require('@auth0/actions/post-login/v3/test');
        const path = require('path');

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/test-an-action.js');
        const CUSTOM_CLAIM_NAMESPACE = 'https://example.com';

        describe('onExecutePostLogin', () => {
          let loader;
          let event;
          let api;

          beforeEach(async () => {
            loader = await loadAction(ACTION_PATH);
            [event, api] = getDefaultArguments();
            mock.method(api.access, 'deny');
            mock.method(api.idToken, 'setCustomClaim');
          });

          afterEach(() => {
            mock.reset();
          });

          it('Admin user', async () => {
            event.authorization = { roles: ['admin'] };

            await loader.execute('onExecutePostLogin', event, api);

            assert.deepEqual(api.idToken.setCustomClaim.mock.calls[0].arguments, [
              `${CUSTOM_CLAIM_NAMESPACE}/roles`,
              ['admin'],
            ]);
            assert.strictEqual(api.access.deny.mock.calls.length, 0);
          });

          it('Restricted User - No authorization', async () => {
            delete event.authorization;

            await loader.execute('onExecutePostLogin', event, api);

            assert.strictEqual(api.idToken.setCustomClaim.mock.calls.length, 0);
            assert.deepEqual(api.access.deny.mock.calls[0].arguments, ['Restricted']);
          });

          it('Restricted User - No roles', async () => {
            event.authorization = { roles: [] };

            await loader.execute('onExecutePostLogin', event, api);

            assert.strictEqual(api.idToken.setCustomClaim.mock.calls.length, 0);
            assert.deepEqual(api.access.deny.mock.calls[0].arguments, ['Restricted']);
          });
        });

        ```

        Ajoutez les dépendances de test au fichier `package.json` :

        ```json title="package.json" theme={null}
        {
          "name": "actions-npm-example-js-node-test",
          "version": "1.0.0",
          "description": "Auth0 Actions unit testing example using the Node.js built-in test runner",
          "license": "MIT",
          "author": "Auth0",
          "type": "commonjs",
          "main": "module-usage.js",
          "scripts": {
            "test": "node --test src/*.spec.js"
          },
          "devDependencies": {
            "@auth0/actions": "^0.33.0"
          }
        }

        ```

        Configurez `jsconfig.json` pour que l'alias d'importation `actions:` pointe vers `src/` :

        ```json title="jsconfig.json" theme={null}
        {
          "compilerOptions": {
            "target": "ES2020",
            "module": "commonjs",
            "checkJs": false,
            "baseUrl": ".",
            "paths": {
              "actions:*": [
                "src/*"
              ]
            }
          },
          "include": [
            "src/**/*.js"
          ]
        }

        ```
      </Tab>

      <Tab title="TypeScript">
        ```ts title="test-an-action.test.ts" theme={null}
        const assert = require('node:assert');
        const { describe, it, beforeEach, afterEach, mock } = require('node:test');
        const { getDefaultArguments, loadAction } = require('@auth0/actions/post-login/v3/test');
        const path = require('path');
        const { compileActionModules } = require('./test-utils/load-compiled-action.ts');

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/test-an-action.ts');
        const CUSTOM_CLAIM_NAMESPACE = 'https://example.com';

        describe('onExecutePostLogin', () => {
          let loader;
          let event;
          let api;

          beforeEach(async () => {
            const { compiledActionPath } = compileActionModules(ACTION_PATH);
            loader = await loadAction(compiledActionPath);
            [event, api] = getDefaultArguments();
            mock.method(api.access, 'deny');
            mock.method(api.idToken, 'setCustomClaim');
          });

          afterEach(() => {
            mock.reset();
          });

          it('Admin user', async () => {
            event.authorization = { roles: ['admin'] };

            await loader.execute('onExecutePostLogin', event, api);

            assert.deepEqual(api.idToken.setCustomClaim.mock.calls[0].arguments, [
              `${CUSTOM_CLAIM_NAMESPACE}/roles`,
              ['admin'],
            ]);
            assert.strictEqual(api.access.deny.mock.calls.length, 0);
          });

          it('Restricted User - No authorization', async () => {
            delete event.authorization;

            await loader.execute('onExecutePostLogin', event, api);

            assert.strictEqual(api.idToken.setCustomClaim.mock.calls.length, 0);
            assert.deepEqual(api.access.deny.mock.calls[0].arguments, ['Restricted']);
          });

          it('Restricted User - No roles', async () => {
            event.authorization = { roles: [] };

            await loader.execute('onExecutePostLogin', event, api);

            assert.strictEqual(api.idToken.setCustomClaim.mock.calls.length, 0);
            assert.deepEqual(api.access.deny.mock.calls[0].arguments, ['Restricted']);
          });
        });

        ```

        Cet utilitaire de test compile l'action TypeScript dans un fichier temporaire afin qu'elle puisse être chargée à l'exécution :

        ```ts title="load-compiled-action.ts" theme={null}
        const fs = require('fs');
        const os = require('os');
        const path = require('path');
        const ts = require('typescript');

        interface ModuleToCompile {
          name: string;
          filename: string;
        }

        function transpileToTemp(sourcePath: string): string {
          const source = fs.readFileSync(sourcePath, 'utf8');
          const { outputText } = ts.transpileModule(source, {
            compilerOptions: {
              module: ts.ModuleKind.CommonJS,
              target: ts.ScriptTarget.ES2020,
              esModuleInterop: true,
            },
          });

          const tempPath = path.join(
            os.tmpdir(),
            `${path.basename(sourcePath, path.extname(sourcePath))}-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.js`,
          );
          fs.writeFileSync(tempPath, outputText);
          return tempPath;
        }

        /**
         * loadAction() (de @auth0/actions/*\/test) lit son fichier cible sur le disque et
         * l'exécute avec vm.compileFunction; il ne passe donc jamais par le retrait
         * natif des types TS de node. Les sources d'actions (et tout module enregistré
         * avec actions:) doivent d'abord être transpilées en JS pur sur le disque.
         */
        exports.compileActionModules = function compileActionModules(actionPath: string, modules: ModuleToCompile[] = []) {
          const compiledActionPath = transpileToTemp(actionPath);
          const compiledModules = modules.map((m: ModuleToCompile) => ({
            name: m.name,
            filename: transpileToTemp(m.filename),
          }));

          return { compiledActionPath, compiledModules };
        };

        ```

        Ajoutez les dépendances de test au fichier `package.json` :

        ```json title="package.json" theme={null}
        {
          "name": "actions-npm-example-ts-node-test",
          "version": "1.0.0",
          "description": "Auth0 Actions unit testing example using the Node.js built-in test runner and TypeScript",
          "license": "MIT",
          "author": "Auth0",
          "scripts": {
            "test": "node --test src/*.test.ts"
          },
          "devDependencies": {
            "@auth0/actions": "^0.33.0",
            "@types/node": "22.14.0",
            "typescript": "^5.9.2"
          }
        }

        ```

        Configurez `tsconfig.json` pour que l'alias d'importation `actions:` pointe vers `src/` :

        ```json title="tsconfig.json" theme={null}
        {
          "compilerOptions": {
            "target": "ES2020",
            "module": "NodeNext",
            "moduleResolution": "nodenext",
            "esModuleInterop": true,
            "allowSyntheticDefaultImports": true,
            "strict": true,
            "outDir": "dist",
            "declaration": true,
            "sourceMap": true,
            "allowJs": true,
            "checkJs": false,
            "resolveJsonModule": true,
            "skipLibCheck": true,
            "forceConsistentCasingInFileNames": true,
            "isolatedModules": true,
            "noEmit": true,
            "paths": {
              "actions:*": [
                "./src/*"
              ]
            }
          },
          "exclude": [
            "node_modules",
            "dist"
          ],
          "include": [
            "**/*.ts"
          ],
          "ts-node": {
            "transpileOnly": true
          }
        }

        ```
      </Tab>
    </Tabs>
  </Accordion>
</AccordionGroup>
