Passer au contenu principal

Tests unitaires d’Auth0 Actions

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.

Fonctionnement

Suivez les consignes d’installation et d’importation décrites ici : Fonctionnement d’Actions NPM. 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.

Exemples

Les exemples suivants montrent comment valider une série de scénarios en simulant les objets nécessaires.
Les exemples utilisent Jest (https://www.npmjs.com/package/jest), mais n’importe quelle bibliothèque de test peut être utilisée.

Configuration

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.
{
  "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"
  }
}

Contrôle d’accès avant l’inscription et configuration des métadonnées de l’utilisateur

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.
/** @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);
}
Le test unitaire effectue certaines validations afin de maximiser la couverture du code, en simulant les objets event et api.
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');
  });
});

Fournisseur de courriel personnalisé et requête HTTP

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.
/** @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');
  }
}

Le test unitaire effectue certaines validations pour maximiser la couverture du code en simulant les objets event et api, ainsi que la fonction fetch.
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');
  });
});

Pour en savoir plus sur @auth0/actions, consultez : https://www.npmjs.com/package/@auth0/actions. Pour en savoir plus sur la création d’Actions, consultez Write Your First Action.