> ## Documentation Index
> Fetch the complete documentation index at: https://translations.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Le **package Actions NPM (`@auth0/actions`)** permet le développement en TypeScript dans des projets externes, ce qui aide les développeurs à suivre les pratiques exemplaires et à améliorer leurs **tests unitaires** grâce aux définitions TypeScript.

# Test unitaire d’Actions

<div id="auth0-actions-unit-test">
  # Test unitaire Auth0 Actions
</div>

Le package **Actions NPM (`@auth0/actions`)** permet d’utiliser TypeScript dans des projets externes, aidant ainsi les développeurs à suivre les pratiques exemplaires et à améliorer leurs tests unitaires grâce aux définitions TypeScript.

***

<div id="how-it-works">
  ## Fonctionnement
</div>

Suivez les instructions d’installation et d’importation décrites ici : [Fonctionnement d’Actions NPM](/docs/fr-ca/customize/actions/actions-npm#how-it-works).

Pour effectuer le test unitaire d’une Action, vous devez simuler les objets `event` et `api` que reçoit votre fonction Action. Vous pouvez créer ces simulations à l’aide des définitions TypeScript incluses dans `auth0/actions`, ce qui garantit que vos tests reflètent fidèlement l’environnement de production. Les frameworks de test comme Jest sont idéaux pour gérer les simulations et valider le fonctionnement.

Les tests unitaires peuvent être exécutés dans un environnement local, avec le contrôle de version ou dans un processus CI/CD, ce qui améliore globalement l’assurance qualité et les validations avant que les changements n’aient une incidence sur les tenants Auth0.

***

<div id="examples">
  ## Exemples
</div>

Les exemples suivants permettent de valider différents scénarios en simulant les objets nécessaires.

<Note>
  Les exemples utilisent **Jest** (`https://www.npmjs.com/package/jest`), mais n’importe quelle bibliothèque de test peut être utilisée.
</Note>

<div id="configuration">
  ### Configuration
</div>

Dans votre fichier `package.json`, définissez les dépendances de développement nécessaires pour profiter de l’aide IntelliSense lorsque vous rédigez votre Action.

<Tabs>
  <Tab title="Javascript">
    ```javascript theme={null}
    {
      "name": "actions-js",
      "version": "1.0.0",
      "description": "Actions JS",
      "main": "example.js",
      "scripts": {
        "test": "jest"
      },
      "author": "John Doe",
      "license": "ISC",
      "devDependencies": {
        "@auth0/actions": "^0.7.1",
        "jest": "^29.7.0"
      }
    }
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    {
      "name": "actions-ts",
      "version": "1.0.0",
      "description": "Actions TS",
      "main": "example.ts",
      "scripts": {
        "test": "jest"
      },
      "author": "John Doe",
      "license": "ISC",
      "devDependencies": {
        "@auth0/actions": "^0.7.1",
        "@types/jest": "^29.5.12",
        "@types/node": "22.14.0",
        "jest": "^29.7.0",
        "ts-jest": "^29.1.2",
        "typescript": "^5.9.2"
      }
    }
    ```

    Dans votre fichier `tsconfig.json`, définissez le fonctionnement du compilateur TypeScript :

    ```typescript 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
      },
      "include": [
        "**/*.ts"
      ],
      "exclude": [
        "node_modules",
        "dist"
      ]
    }
    ```

    Dans votre fichier `jest.config.js`, définissez les préréglages de l’environnement `Jest` :

    ```typescript theme={null}
    module.exports = {
      preset: 'ts-jest',
      testEnvironment: 'node',
    };
    ```
  </Tab>
</Tabs>

<div id="pre-user-registration-access-control-and-user-metadata-setup">
  ## Contrôle d’accès et configuration des métadonnées utilisateur pour Pre-User Registration
</div>

L’exemple d’Action suivant vérifie si l’adresse courriel de l’utilisateur utilise un domaine interdit et appelle `api.access.deny()` en cas de correspondance. Sinon, il vérifie si le nom complet a été fourni au moyen des champs supplémentaires de Custom Prompts et, le cas échéant, l’enregistre dans le `user_metadata` du profil utilisateur; sinon, il envoie une erreur de validation à Universal Login.

<Tabs>
  <Tab title="Javascript">
    ```javascript theme={null}
    /** @import {Event, PreUserRegistrationAPI} from "@auth0/actions/pre-user-registration/v2" */

    /**
    * Handler qui sera appelé pendant 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 peuvent être utilisées pour 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);
    }
    ```
  </Tab>

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

    /**
    * Handler qui sera appelé pendant 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 peuvent être utilisées pour 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);
    };
    ```
  </Tab>
</Tabs>

Le test unitaire effectue quelques validations afin de maximiser la couverture du code, en simulant les objets `event` et `api`.

<Tabs>
  <Tab title="Javascript">
    ```javascript theme={null}
    const { onExecutePreUserRegistration } = require('./preUserRegistration');

    describe('onExecutePreUserRegistration', () => {
      const mockApi = {
        access: {
          deny: jest.fn(),
        },
        user: {
          setUserMetadata: jest.fn(),
        },
        validation: {
          error: jest.fn(),
        },
      };

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

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

      it('forbids email domain', async () => {
        const mockEvent = {
          user: {
            email: 'johndoe@example.com',
          }
        };

        await onExecutePreUserRegistration(mockEvent, mockApi);

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

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

        await onExecutePreUserRegistration(mockEvent, mockApi);

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

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

        await onExecutePreUserRegistration(mockEvent, mockApi);

        expect(mockApi.access.deny).not.toHaveBeenCalled();
        expect(mockApi.validation.error).not.toHaveBeenCalled();
        expect(mockApi.user.setUserMetadata).toHaveBeenCalledWith('full_name', 'John Doe');
      });
    });
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const { onExecutePreUserRegistration } = require('./preUserRegistration');

    describe('onExecutePreUserRegistration', () => {
      const mockApi = {
        access: {
          deny: jest.fn(),
        },
        user: {
          setUserMetadata: jest.fn(),
        },
        validation: {
          error: jest.fn(),
        },
      };

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

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

      it('forbids email domain', async () => {
        const mockEvent = {
          user: {
            email: 'johndoe@example.com',
          }
        };

        await onExecutePreUserRegistration(mockEvent, mockApi);

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

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

        await onExecutePreUserRegistration(mockEvent, mockApi);

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

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

        await onExecutePreUserRegistration(mockEvent, mockApi);

        expect(mockApi.access.deny).not.toHaveBeenCalled();
        expect(mockApi.validation.error).not.toHaveBeenCalled();
        expect(mockApi.user.setUserMetadata).toHaveBeenCalledWith('full_name', 'John Doe');
      });
    });
    ```
  </Tab>
</Tabs>

<div id="custom-email-provider-and-http-request">
  ## Fournisseur de courriel personnalisé et requête HTTP
</div>

L’exemple d’Action suivant tente d’envoyer un message au moyen d’une requête HTTP à un service externe et gère les erreurs possibles de la requête afin d’abandonner la notification. Il utilise des secrets pour l’URL du service externe et la clé API d’autorisation.

<Tabs>
  <Tab title="Javascript">
    ```javascript theme={null}
    /** @import {Event, CustomEmailProviderAPI} from "@auth0/actions/custom-email-provider/v1" */

    /**
    * Handler exécuté lors de l’envoi d’une notification par courriel
    *
    * @param {Event} event - Détails sur l’utilisateur et le contexte dans lequel il se connecte.
    * @param {CustomEmailProviderAPI} api - Méthodes et utilitaires permettant de modifier le comportement d’envoi d’une notification par courriel.
    */
    exports.onExecuteCustomEmailProvider = async (event, api) => {
      const notification = event.notification;
      const message = {
        body: notification.html
      };

      try {
        await fetch(event.secrets.URL, {
          method: 'POST',
          headers: {
            'X-API-Key': event.secrets.API_KEY,
          },
          body: JSON.stringify(message),
        });
      } catch (err) {
        api.notification.drop('External service failure');
      }
    }

    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    /** @import {Event, CustomEmailProviderAPI} from "@auth0/actions/custom-email-provider/v1" */

    /**
    * Handler exécuté lors de l’envoi d’une notification par courriel
    *
    * @param {Event} event - Détails sur l’utilisateur et le contexte dans lequel il se connecte.
    * @param {CustomEmailProviderAPI} api - Méthodes et utilitaires permettant de modifier le comportement d’envoi d’une notification par courriel.
    */
    exports.onExecuteCustomEmailProvider = async (event, api) => {
      const notification = event.notification;
      const message = {
        body: notification.html
      };

      try {
        await fetch(event.secrets.URL, {
          method: 'POST',
          headers: {
            'X-API-Key': event.secrets.API_KEY,
          },
          body: JSON.stringify(message),
        });
      } catch (err) {
        api.notification.drop('External service failure');
      }
    }

    ```
  </Tab>
</Tabs>

Le test unitaire effectue certaines validations pour maximiser la couverture du code, en simulant les objets event et api, ainsi que la fonction fetch.

<Tabs>
  <Tab title="Javascript">
    ```javascript theme={null}
    const { onExecuteCustomEmailProvider } = require('./customEmailProvider');

    describe('onExecuteCustomEmailProvider', () => {
      const mockApi = {
        notification: {
          drop: jest.fn(),
        },
      };

      const mockEvent = {
        notification: {
          html: '<h1>Hello world</h1>',
        },
        secrets: {
          URL: 'https://example.com/service',
          API_KEY: 'ApiKeySecret1234.',
        },
        user: {
          email: 'johndoe@example.com',
        },
      };

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

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

      it('succeeds on external service request', async () => {
        jest.spyOn(global, 'fetch').mockImplementationOnce(() => Promise.resolve({
          ok: true,
          status: 200,
          json: async () => ({ message: 'Success!' }),
        }));

        await onExecuteCustomEmailProvider(mockEvent, mockApi);

        expect(global.fetch).toHaveBeenCalled();
        expect(mockApi.notification.drop).not.toHaveBeenCalled();
      });

      it('fails on external service request', async () => {
        jest.spyOn(global, 'fetch').mockImplementationOnce(() => Promise.reject({
          ok: false,
          status: 500,
          json: async () => ({ error: 'Server Error' }),
        }));

        await onExecuteCustomEmailProvider(mockEvent, mockApi);

        expect(mockApi.notification.drop).toHaveBeenCalledWith('External service failure');
      });
    });

    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const { onExecuteCustomEmailProvider } = require('./customEmailProvider');

    describe('onExecuteCustomEmailProvider', () => {
      const mockApi = {
        notification: {
          drop: jest.fn(),
        },
      };

      const mockEvent = {
        notification: {
          html: '<h1>Hello world</h1>',
        },
        secrets: {
          URL: 'https://example.com/service',
          API_KEY: 'ApiKeySecret1234.',
        },
        user: {
          email: 'johndoe@example.com',
        },
      };

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

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

      it('succeeds on external service request', async () => {
        jest.spyOn(global, 'fetch').mockImplementationOnce(() => Promise.resolve({
          ok: true,
          status: 200,
          json: async () => ({ message: 'Success!' }),
        } as Response));

        await onExecuteCustomEmailProvider(mockEvent, mockApi);

        expect(global.fetch).toHaveBeenCalled();
        expect(mockApi.notification.drop).not.toHaveBeenCalled();
      });

      it('fails on external service request', async () => {
        jest.spyOn(global, 'fetch').mockImplementationOnce(() => Promise.reject({
          ok: false,
          status: 500,
          json: async () => ({ error: 'Server Error' }),
        } as Response));

        await onExecuteCustomEmailProvider(mockEvent, mockApi);

        expect(mockApi.notification.drop).toHaveBeenCalledWith('External service failure');
      });
    });

    ```
  </Tab>
</Tabs>

Pour en savoir plus sur `@auth0/actions`, consultez : [https://www.npmjs.com/package/@auth0/actions](https://www.npmjs.com/package/@auth0/actions).

Pour en savoir plus sur la création d’Actions, consultez [Write Your First Action](/docs/fr-ca/customize/actions/write-your-first-action).
