> ## 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.

# Tester unitairement les dépendances d'une action

> Tests unitaires pour une action Pre User Registration qui bloque un domaine de courriel interdit et valide le nom complet soumis avant de l'enregistrer, avec un identifiant généré, dans les métadonnées de l'utilisateur et de l'application, à l'aide de Jest, de Mocha et du Node.js Test Runner, en JavaScript et en TypeScript.

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

Chaque exemple sépare le code source de l'action, situé dans `src/`, de ses tests unitaires, situés dans `test/`, en suivant 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 Pre User Registration suivante refuse les inscriptions provenant d'un domaine de courriel interdit, exige la saisie d'un nom complet et, le cas échéant, enregistre ce nom dans `user_metadata` ainsi qu'un identifiant généré aléatoirement dans `app_metadata`.

<Tabs>
  <Tab title="JavaScript">
    ```js title="mock-dependencies.js" theme={null}
    /** @import {Event, PreUserRegistrationAPI} from "@auth0/actions/pre-user-registration/v2" */
    const { randomUUID } = require('crypto');

    /**
    * Gestionnaire appelé lors de l’exécution d’un flux PreUserRegistration.
    *
    * @param {Event} event - Détails sur le contexte et l’utilisateur qui tente de s’inscrire.
    * @param {PreUserRegistrationAPI} api - Interface dont les méthodes permettent de modifier le comportement de l’inscription.
    */
    exports.onExecutePreUserRegistration = async (event, api) => {
      const user = event.user;

      if (user.email?.endsWith('@example.com')) {
        api.access.deny('forbidden', 'Forbidden email domain')
        return;
      }

      const fullName = event.request.body['ulp-fullName'];

      if (fullName === undefined) {
        api.validation.error('invalid_payload', 'Missing full name');
        return;
      }

      api.user.setUserMetadata('full_name', fullName);
      api.user.setAppMetadata('custom_id', randomUUID());
    }
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts title="mock-dependencies.ts" theme={null}
    import type { Event, PreUserRegistrationAPI } from '@auth0/actions/pre-user-registration/v2';
    import { randomUUID } from 'crypto';

    /**
    * Gestionnaire appelé durant l’exécution d’un flux PreUserRegistration.
    *
    * @param {Event} event - Détails sur le contexte et l’utilisateur qui tente de s’inscrire.
    * @param {PreUserRegistrationAPI} api - Interface dont les méthodes permettent de modifier le comportement de l’inscription.
    */
    exports.onExecutePreUserRegistration = async (event: Event, api: PreUserRegistrationAPI) => {
      const user = event.user;

      if (user.email?.endsWith('@example.com')) {
        api.access.deny('forbidden', 'Forbidden email domain')
        return;
      }

      const fullName = event.request.body['ulp-fullName'];

      if (fullName === undefined) {
        api.validation.error('invalid_payload', 'Missing full name');
        return;
      }

      api.user.setUserMetadata('full_name', fullName);
      api.user.setAppMetadata('custom_id', randomUUID());
    };
    ```
  </Tab>
</Tabs>

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

Les tests unitaires simulent les objets `event` et `api` afin de vérifier que le domaine interdit est refusé, qu'un nom complet manquant déclenche une erreur de validation et qu'une soumission valide enregistre le nom complet ainsi qu'un UUID correctement formaté.

<AccordionGroup>
  <Accordion title="Jest">
    <Tabs>
      <Tab title="JavaScript">
        ```js title="mock-dependencies.spec.js" theme={null}
        const { getDefaultArguments, loadAction } = require('@auth0/actions/pre-user-registration/v2/test');
        const path = require('path');

        const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.js');

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

          beforeEach(async () => {
            loader = await loadAction(ACTION_PATH);
            [event, api] = getDefaultArguments();
            jest.spyOn(api.access, 'deny');
            jest.spyOn(api.validation, 'error');
            jest.spyOn(api.user, 'setUserMetadata');
            jest.spyOn(api.user, 'setAppMetadata');
          });

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

          it('forbids email domain', async () => {
            event.user.email = 'johndoe@example.com';

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

            expect(api.access.deny).toHaveBeenCalledWith('forbidden', 'Forbidden email domain');
            expect(api.validation.error).not.toHaveBeenCalled();
            expect(api.user.setUserMetadata).not.toHaveBeenCalled();
          });

          it('allows email domain without full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {};

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

            expect(api.access.deny).not.toHaveBeenCalled();
            expect(api.validation.error).toHaveBeenCalledWith('invalid_payload', 'Missing full name');
            expect(api.user.setUserMetadata).not.toHaveBeenCalled();
            expect(api.user.setAppMetadata).not.toHaveBeenCalled();
          });

          it('allows email domain with full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {
              'ulp-fullName': 'John Doe',
            };

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

            expect(api.access.deny).not.toHaveBeenCalled();
            expect(api.validation.error).not.toHaveBeenCalled();
            expect(api.user.setUserMetadata).toHaveBeenCalledWith('full_name', 'John Doe');
            expect(api.user.setAppMetadata).toHaveBeenCalledWith('custom_id', expect.stringMatching(UUID_V4_PATTERN));
          });
        });

        ```

        Ajoutez les dépendances de test à `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="mock-dependencies.test.ts" theme={null}
        const { getDefaultArguments, loadAction } = require('@auth0/actions/pre-user-registration/v2/test');
        const path = require('path');
        const { compileActionModules } = require('./test-utils/load-compiled-action');

        const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.ts');

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

          beforeEach(async () => {
            const { compiledActionPath } = compileActionModules(ACTION_PATH);
            loader = await loadAction(compiledActionPath);
            [event, api] = getDefaultArguments();
            jest.spyOn(api.access, 'deny');
            jest.spyOn(api.validation, 'error');
            jest.spyOn(api.user, 'setUserMetadata');
            jest.spyOn(api.user, 'setAppMetadata');
          });

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

          it('forbids email domain', async () => {
            event.user.email = 'johndoe@example.com';

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

            expect(api.access.deny).toHaveBeenCalledWith('forbidden', 'Forbidden email domain');
            expect(api.validation.error).not.toHaveBeenCalled();
            expect(api.user.setUserMetadata).not.toHaveBeenCalled();
          });

          it('allows email domain without full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {};

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

            expect(api.access.deny).not.toHaveBeenCalled();
            expect(api.validation.error).toHaveBeenCalledWith('invalid_payload', 'Missing full name');
            expect(api.user.setUserMetadata).not.toHaveBeenCalled();
            expect(api.user.setAppMetadata).not.toHaveBeenCalled();
          });

          it('allows email domain with full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {
              'ulp-fullName': 'John Doe',
            };

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

            expect(api.access.deny).not.toHaveBeenCalled();
            expect(api.validation.error).not.toHaveBeenCalled();
            expect(api.user.setUserMetadata).toHaveBeenCalledWith('full_name', 'John Doe');
            expect(api.user.setAppMetadata).toHaveBeenCalledWith('custom_id', expect.stringMatching(UUID_V4_PATTERN));
          });
        });

        ```

        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 à `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 compiler le TypeScript avec `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="mock-dependencies.spec.js" theme={null}
        const sinon = require('sinon');
        const { getDefaultArguments, loadAction } = require('@auth0/actions/pre-user-registration/v2/test');
        const path = require('path');

        const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.js');

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

          beforeEach(async () => {
            loader = await loadAction(ACTION_PATH);
            [event, api] = getDefaultArguments();
            sinon.spy(api.access, 'deny');
            sinon.spy(api.validation, 'error');
            sinon.spy(api.user, 'setUserMetadata');
            sinon.spy(api.user, 'setAppMetadata');
          });

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

          it('forbids email domain', async () => {
            event.user.email = 'johndoe@example.com';

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

            sinon.assert.calledWith(api.access.deny, 'forbidden', 'Forbidden email domain');
            sinon.assert.notCalled(api.validation.error);
            sinon.assert.notCalled(api.user.setUserMetadata);
          });

          it('allows email domain without full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {};

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

            sinon.assert.notCalled(api.access.deny);
            sinon.assert.calledWith(api.validation.error, 'invalid_payload', 'Missing full name');
            sinon.assert.notCalled(api.user.setUserMetadata);
            sinon.assert.notCalled(api.user.setAppMetadata);
          });

          it('allows email domain with full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {
              'ulp-fullName': 'John Doe',
            };

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

            sinon.assert.notCalled(api.access.deny);
            sinon.assert.notCalled(api.validation.error);
            sinon.assert.calledWith(api.user.setUserMetadata, 'full_name', 'John Doe');
            sinon.assert.calledWith(api.user.setAppMetadata, 'custom_id', sinon.match(UUID_V4_PATTERN));
          });
        });

        ```

        Ajoutez les dépendances de test à `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 avec `.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="mock-dependencies.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/pre-user-registration/v2/test');

        const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.ts');

        describe('onExecutePreUserRegistration', () => {
          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.validation, 'error');
            sinon.spy(api.user, 'setUserMetadata');
            sinon.spy(api.user, 'setAppMetadata');
          });

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

          it('forbids email domain', async () => {
            event.user.email = 'johndoe@example.com';

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

            sinon.assert.calledWith(api.access.deny, 'forbidden', 'Forbidden email domain');
            sinon.assert.notCalled(api.validation.error);
            sinon.assert.notCalled(api.user.setUserMetadata);
          });

          it('allows email domain without full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {};

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

            sinon.assert.notCalled(api.access.deny);
            sinon.assert.calledWith(api.validation.error, 'invalid_payload', 'Missing full name');
            sinon.assert.notCalled(api.user.setUserMetadata);
            sinon.assert.notCalled(api.user.setAppMetadata);
          });

          it('allows email domain with full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {
              'ulp-fullName': 'John Doe',
            };

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

            sinon.assert.notCalled(api.access.deny);
            sinon.assert.notCalled(api.validation.error);
            sinon.assert.calledWith(api.user.setUserMetadata, 'full_name', 'John Doe');
            sinon.assert.calledWith(api.user.setAppMetadata, 'custom_id', sinon.match(UUID_V4_PATTERN));
          });
        });

        ```

        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 à `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 avec `.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="mock-dependencies.spec.js" theme={null}
        const assert = require('node:assert');
        const { describe, it, beforeEach, afterEach, mock } = require('node:test');
        const { getDefaultArguments, loadAction } = require('@auth0/actions/pre-user-registration/v2/test');
        const path = require('path');

        const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.js');

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

          beforeEach(async () => {
            loader = await loadAction(ACTION_PATH);
            [event, api] = getDefaultArguments();
            mock.method(api.access, 'deny');
            mock.method(api.validation, 'error');
            mock.method(api.user, 'setUserMetadata');
            mock.method(api.user, 'setAppMetadata');
          });

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

          it('forbids email domain', async () => {
            event.user.email = 'johndoe@example.com';

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

            assert.deepEqual(api.access.deny.mock.calls[0].arguments, ['forbidden', 'Forbidden email domain']);
            assert.strictEqual(api.validation.error.mock.calls.length, 0);
            assert.strictEqual(api.user.setUserMetadata.mock.calls.length, 0);
          });

          it('allows email domain without full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {};

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

            assert.strictEqual(api.access.deny.mock.calls.length, 0);
            assert.deepEqual(api.validation.error.mock.calls[0].arguments, ['invalid_payload', 'Missing full name']);
            assert.strictEqual(api.user.setUserMetadata.mock.calls.length, 0);
            assert.strictEqual(api.user.setAppMetadata.mock.calls.length, 0);
          });

          it('allows email domain with full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {
              'ulp-fullName': 'John Doe',
            };

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

            assert.strictEqual(api.access.deny.mock.calls.length, 0);
            assert.strictEqual(api.validation.error.mock.calls.length, 0);
            assert.deepEqual(api.user.setUserMetadata.mock.calls[0].arguments, ['full_name', 'John Doe']);

            const appMetadataCall = api.user.setAppMetadata.mock.calls[0];
            assert.strictEqual(appMetadataCall.arguments[0], 'custom_id');
            assert.match(appMetadataCall.arguments[1], UUID_V4_PATTERN);
          });
        });

        ```

        Ajoutez les dépendances de test à `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="mock-dependencies.test.ts" theme={null}
        const assert = require('node:assert');
        const { describe, it, beforeEach, afterEach, mock } = require('node:test');
        const { getDefaultArguments, loadAction } = require('@auth0/actions/pre-user-registration/v2/test');
        const path = require('path');
        const { compileActionModules } = require('./test-utils/load-compiled-action.ts');

        const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

        const DIRNAME = path.dirname('../');
        const ACTION_PATH = path.resolve(DIRNAME, './src/mock-dependencies.ts');

        describe('onExecutePreUserRegistration', () => {
          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.validation, 'error');
            mock.method(api.user, 'setUserMetadata');
            mock.method(api.user, 'setAppMetadata');
          });

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

          it('forbids email domain', async () => {
            event.user.email = 'johndoe@example.com';

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

            assert.deepEqual(api.access.deny.mock.calls[0].arguments, ['forbidden', 'Forbidden email domain']);
            assert.strictEqual(api.validation.error.mock.calls.length, 0);
            assert.strictEqual(api.user.setUserMetadata.mock.calls.length, 0);
          });

          it('allows email domain without full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {};

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

            assert.strictEqual(api.access.deny.mock.calls.length, 0);
            assert.deepEqual(api.validation.error.mock.calls[0].arguments, ['invalid_payload', 'Missing full name']);
            assert.strictEqual(api.user.setUserMetadata.mock.calls.length, 0);
            assert.strictEqual(api.user.setAppMetadata.mock.calls.length, 0);
          });

          it('allows email domain with full name', async () => {
            event.user.email = 'johndoe@test.com';
            event.request.body = {
              'ulp-fullName': 'John Doe',
            };

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

            assert.strictEqual(api.access.deny.mock.calls.length, 0);
            assert.strictEqual(api.validation.error.mock.calls.length, 0);
            assert.deepEqual(api.user.setUserMetadata.mock.calls[0].arguments, ['full_name', 'John Doe']);

            const appMetadataCall = api.user.setAppMetadata.mock.calls[0];
            assert.strictEqual(appMetadataCall.arguments[0], 'custom_id');
            assert.match(appMetadataCall.arguments[1], UUID_V4_PATTERN);
          });
        });

        ```

        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 à `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>
