メインコンテンツへスキップ

Auth0 Actions Unit Test

Actions NPM (@auth0/actions) パッケージを使用すると、外部プロジェクトで TypeScript を用いた開発が可能になり、開発者はベストプラクティスに従って、TypeScript の型定義に基づくユニットテストを強化できます。

仕組み

インストールとインポートのガイドラインについては、How Actions NPM works を参照してください。 Action をユニットテストするには、Action 関数が受け取る event オブジェクトと api オブジェクトをモックする必要があります。これらのモックは、auth0/actions に含まれる TypeScript の定義を使用して作成できるため、テストで本番環境を正確に反映できます。モックや機能の検証を管理するには、Jest のようなテストフレームワークが適しています。 ユニットテストは、ローカル環境、バージョン管理、または CI/CD プロセスで実行できます。これにより、Auth0 テナントに変更を反映する前に、全体的な品質保証と検証を強化できます。

以下の例では、必要なオブジェクトをモックして、一連のシナリオを検証します。
これらの例では Jest (https://www.npmjs.com/package/jest) を使用していますが、任意のテストライブラリを使用できます。

設定

package.json で開発依存関係を定義すると、Action の作成時に IntelliSense の補完支援を利用できます。
{
  "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"
  }
}

ユーザー登録前のアクセス制御とユーザーメタデータの設定

次の Action の例では、ユーザーのメールアドレスのドメインが禁止対象かどうかを確認し、一致した場合は api.access.deny() を呼び出します。一致しない場合は、Custom Prompts の追加フィールド経由で氏名が提供されているかどうかを確認し、提供されていればユーザープロファイルの user_metadata に氏名を設定します。提供されていない場合は、Universal Login にバリデーションエラーを返します。
/** @import {Event, PreUserRegistrationAPI} from "@auth0/actions/pre-user-registration/v2" */

/**
* PreUserRegistration フローの実行中に呼び出されるハンドラー。
*
* @param {Event} event - 登録を試行しているコンテキストとユーザーの詳細。
* @param {PreUserRegistrationAPI} api - サインアップの動作を変更するために使用できるメソッドを持つインターフェース。
*/
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);
}
このユニットテストでは、event オブジェクトと 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');
  });
});

カスタムメールプロバイダーとHTTPリクエスト

次の Action の例では、外部サービスに対して HTTP リクエストでメッセージを送信し、リクエスト エラーが発生した場合は通知を破棄するよう処理しています。外部サービスの URL と認証用 API キーにはシークレットを使用します。
/** @import {Event, CustomEmailProviderAPI} from "@auth0/actions/custom-email-provider/v1" */

/**
* メール通知の送信時に実行されるハンドラー
*
* @param {Event} event - ユーザーと、そのユーザーのログイン時のコンテキストに関する詳細。
* @param {CustomEmailProviderAPI} api - メール通知の送信動作を変更するためのメソッドとユーティリティ。
*/
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');
  }
}

ユニットテストでは、コード カバレッジを最大化するために、fetch 関数に加えて event オブジェクトと api オブジェクトをモック化し、いくつかの検証を行います。
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');
  });
});

@auth0/actions の詳細については、https://www.npmjs.com/package/@auth0/actions を参照してください。 Actions の作成方法について詳しくは、初めての Action を作成する を参照してください。