> ## 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 NPM **Actions (`@auth0/actions`)** permet d’utiliser TypeScript dans des projets externes, afin d’aider 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">
  # Tests unitaires d’Auth0 Actions
</div>

Le package **Actions NPM (`@auth0/actions`)** permet d’utiliser TypeScript dans des projets externes, afin que les développeurs puissent 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 consignes d’installation et d’importation décrites ici : [Fonctionnement d’Actions NPM](/fr-CA/docs/customize/actions/actions-npm#how-it-works).

Pour effectuer un 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 objets simulés et valider le comportement.

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

***

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

Les exemples suivants montrent comment valider une série de 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 `package.json`, définissez les dépendances de développement nécessaires pour profiter de l’aide IntelliSense lorsque vous écrivez 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 comportement 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 `jest.config.js`, définissez les préréglages d’environnement de `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 avant l’inscription et configuration des métadonnées de l’utilisateur
</div>

L’exemple d’Action suivant vérifie si le courriel de l’utilisateur appartient à un domaine de courriel 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, puis l’enregistre dans le `user_metadata` du profil de l’utilisateur; à défaut, 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" */

    /**
    * Gestionnaire 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';

    /**
    * Gestionnaire 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 certaines 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 vers un service externe et, si la requête échoue, abandonne 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" */

    /**
    * Gestionnaire à exécuter 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 pour 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" */

    /**
    * Gestionnaire à exécuter 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 pour 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](/fr-CA/docs/customize/actions/write-your-first-action).
