mcpField
P
pdk-test

pdk-test

github.com/mulesoftテスト・品質14217 回インストール今日更新
リポジトリ

pdk-test について

Omni Gateway PDKのカスタムポリシー開発者向けのDockerベースの統合テストスキルです。`pdk-test`で実際のOmni Gateway上でポリシーを実行してエンドツーエンドの動作を検証し、テスト設定、サービス構成、よくある環境問題を扱います。

主な機能

  1. 01`#[pdk_test]`による統合テスト作成
  2. 02`TestComposite`によるDockerサービスの構成
  3. 03Omni Gateway、ポリシー、バックエンドの設定
  4. 04HTTPトラフィック送信と動作検証
  5. 05前提条件とテスト失敗の確認

活用シーン

  1. 01実際のルーティング、TLS、リスナー設定の検証
  2. 02ポリシーチェーンのエンドツーエンドテスト
  3. 03ポリシーがバックエンドリクエストに与える影響の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mulesoft/mulesoft-dx --skill pdk-test

インストール後、Claude Code で/pdk-testを入力して読み込まれたか確認します。

詳細

pdk-test を使用して、カスタムポリシーの Docker ベースの機能テストを作成・実行する開発者を支援する、Omni Gateway PDK の統合テストスペシャリストです。

タスク

「WASM にコンパイルできるポリシーはあるが、統合テストはない」という状態から、「テストで実際の Omni Gateway を Docker で起動し、ポリシーを適用して HTTP トラフィックを送信し、エンドツーエンドで動作を検証できる」という状態まで、開発者を導いてください。失敗があれば率直に伝えてください。Docker が実行されていない、WASM がビルドされていない、または registration.yaml が見つからない場合は、根本原因を示して中止してください。

このスキルと代替手段の使い分け

  • pdk-test(このスキル) — #[pdk_test] と TestComposite を使用した Docker ベースの統合テストです。時間はかかります(テストあたり数十秒)が、実際の Omni Gateway のルーティング、TLS、リスナー設定、複数ポリシーの連鎖をテストできます。配置先は tests/requests.rs です。
  • pdk-unit — #[test] と UnitTestBuilder を使用した、プロセス内のユニットテストです。高速(数ミリ秒)で、proxy-wasm ホストをモックします。大半のポリシーロジックにはこちらを使います。デバッグにも対応しています。
  • develop-pdk-policy — スキャフォールドの作成、ビルド、プレイグラウンド、公開、リリースのライフサイクルを扱います。

判断基準:

  • 動作が実際の Omni のルーティング、TLS 終端、複数ポリシーの連鎖、またはリスナー設定に依存する → pdk-test(このスキル)。
  • ロジックがリクエスト/レスポンスを処理し、すべての依存関係をモックできる → pdk-unit(別のスキル)。
  • 両方必要 → まず迅速なフィードバックが得られる pdk-unit を作成し、その後、ここで pdk-test のスモークテストを追加します。

前提条件

開発者の環境がまだ整っていない(Anypoint CLI、Rust、wasm ターゲット、または Docker が不足している)場合は、続行する前に同じ階層の pdk-prerequisites スキルを案内してください。

テストを作成する前に、次の項目を順番に確認してください。

1. Docker が実行中であること
docker info > /dev/null 2>&1 && echo "Docker OK" || echo "Docker NOT running"
2. ポリシーの WASM がビルド済みであること
ls target/wasm32-wasip1/release/*.wasm 2>/dev/null && echo "WASM OK" || echo "Run 'make build' first"
3. 登録ファイルが存在すること
ls tests/config/registration.yaml 2>/dev/null && echo "Registration OK" || echo "MISSING - see Step 2"

いずれかの確認に失敗した場合は、先に修正してから進めてください。

ステップ 1:dev-dependencies を追加する

Cargo.toml の [dev-dependencies] の下に追加します。

[dev-dependencies]
pdk-test = "1.8.0"
httpmock = "0.6"
reqwest = "0.11"
serde_json = "1"
anyhow = "1"

pdk-test のバージョンは、[dependencies] の pdk のバージョンと一致させてください。これらは同じワークスペースから同時にリリースされます。プロジェクトでワークスペースを使用している場合は、{ workspace = true } 構文を使ってください。

ステップ 2:テスト設定を行う

統合テストでは、tests/config/ 内に設定ファイルが必要です。

registration.yaml(必須、gitignore 対象)

これは、ローカル(非接続)モードで Omni Gateway を登録する方法を指定します。一度だけ生成します。

  1. Anypoint Platform → Runtime Manager → Omni Gateway に移動します。
  2. Add Gateway をクリックし、Docker を選択します。
  3. 登録コマンドをコピーし、--connected=true を --connected=false に変更します。
  4. tests/config/ 内で実行します。そこに registration.yaml が書き込まれます。

または、すでに registration.yaml がある場合は、別の PDK プロジェクトやプレイグラウンドディレクトリからコピーできます。

重要: registration.yaml はコミットしないでください。秘密鍵(base64 エンコードされた TLS 証明書とキー)が含まれています。スキャフォールドの .gitignore では、すでに除外されています。開発者ごとにそれぞれ生成してください。

logging.yaml(任意ですが推奨)

テスト時に Omni のログをデバッグレベルで有効にし、トラブルシューティングを容易にします。

---
apiVersion: gateway.mulesoft.com/v1alpha1
kind: Configuration
metadata:
  name: logging-config
spec:
  logging:
    runtimeLogs:
      logLevel: debug

ステップ 3:tests/common/mod.rs を作成する

このモジュールでは、すべてのテストファイルで使用する共通定数を定義します。

// Copyright 2026 Salesforce, Inc. All rights reserved.

pub const POLICY_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/target/wasm32-wasip1/release");
pub const COMMON_CONFIG_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/config");

// The policy reference name used by Omni to identify the WASM artifact.
// Run `make show-policy-ref-name` or read `target/policy-ref-name.txt` after building.
pub const POLICY_NAME: &str = "<policy-ref-name>";

正しい POLICY_NAME を取得するには、次のコマンドを実行します。

make show-policy-ref-name 2>/dev/null || cat target/policy-ref-name.txt

名前は、アセット ID とメジャー/マイナーバージョンを組み合わせる cargo anypoint get-policy-implementation-name によって導出されます。手動で導出しようとせず、必ず上記のコマンドを使用してください。

ステップ 4:最初の統合テストを作成する

tests/requests.rs を作成します。

mod common;

use httpmock::MockServer;
use pdk_test::port::Port;
use pdk_test::services::flex::{ApiConfig, Flex, FlexConfig, PolicyConfig};
use pdk_test::services::httpmock::{HttpMock, HttpMockConfig};
use pdk_test::{pdk_test, TestComposite};

use common::*;

const FLEX_PORT: Port = 8081;

#[pdk_test]
async fn test_request_passes_through() -> anyhow::Result<()> {
    // 1. Configure the mock backend
    let backend_config = HttpMockConfig::builder()
        .port(80)
        .hostname("backend")
        .build();

    // 2. Configure the policy under test
    let policy_config = PolicyConfig::builder()
        .name(POLICY_NAME)
        .configuration(serde_json::json!({
            // Fill with your policy's configuration
        }))
        .build();

    // 3. Configure the API that Omni will serve
    let api_config = ApiConfig::builder()
        .name("ingress-http")
        .upstream(&backend_config)
        .path("/anything/echo/")
        .port(FLEX_PORT)
        .policies([policy_config])
        .build();

    // 4. Configure Omni Gateway
    let flex_config = FlexConfig::builder()
        .version("1.10.0")
        .hostname("local-flex")
        .with_api(api_config)
        .config_mounts([(POLICY_DIR, "policy"), (COMMON_CONFIG_DIR, "common")])
        .build();

    // 5. Start the test composite (spins up Docker containers)
    let composite = TestComposite::builder()
        .with_service(flex_config)
        .with_service(backend_config)
        .build()
        .await?;

    // 6. Get handles to services
    let flex: Flex = composite.service()?;
    let flex_url = flex.external_url(FLEX_PORT).unwrap();
    let upstream: HttpMock = composite.service()?;
    let mock_server = MockServer::connect_async(upstream.socket()).await;

    // 7. Set up mock expectations
    mock_server
        .mock_async(|when, then| {
            when.path_contains("/hello");
            then.status(200).body("OK");
        })
        .await;

    // 8. Send request through Omni and assert
    let response = reqwest::Client::new()
        .get(format!("{flex_url}/hello"))
        .send()
        .await?;

    assert_eq!(response.status().as_u16(), 200);
    assert_eq!(response.text().await?, "OK");

    Ok(())
}

ステップ 5:テストを実行する

# Build the WASM first (tests need the compiled artifact)
make build

# Run integration tests (requires Docker)
cargo test --test requests -- --nocapture

または、Makefile 経由で実行します(標準のスキャフォールドターゲット)。

make test

これにより cargo test -- --nocapture が実行されます。

統合テストは Docker コンテナを起動するため、実行に時間がかかります(テストあたり約 30~60 秒)。デバッグ中に Omni Gateway のログを確認するには、--nocapture を使用してください。

注: テストは順番に実行されます。フレームワークがグローバルミューテックスを取得し、同時に実行されるテストを 1 つに制限します。これにより、Docker リソースの競合やクリーンアップ時の競合を回避できます。

フレームワーク API リファレンス

#[pdk_test] マクロ

async fn を、Tokio のマルチスレッドランタイムを作成してテストのライフサイクルを管理するテストに変換します。フレームワークは TestComposite の Drop によってコンテナをクリーンアップし、各テスト実行の開始時に残っているコンテナ(CreatedBy=pdk-test ラベル付き)も削除します。

#[pdk_test]
async fn my_test() -> anyhow::Result<()> {
    // test body
    Ok(())
}
TestComposite

複数の Docker サービス(Omni とバックエンド)を統括します。ビルダーパターンを使用します。

let composite = TestComposite::builder()
    .with_service(flex_config)      // Omni Gateway
    .with_service(backend_config)   // HTTP mock backend
    .build()
    .await?;

ビルド後、サービスハンドルを取得します。

let flex: Flex = composite.service()?;
let upstream: HttpMock = composite.service()?;

制約: 1 つのテストで定義できる HttpMock サービスは 1 つだけです。2 つ目を追加すると、.build().await は Err(TestError::NotSupportedConfig(...)) を返します。

ホスト名の一意性: 1 つのテスト内で、同じ種類のサービスにはそれぞれ一意のホスト名が必要です。同じホスト名で .with_service() を 2 回呼び出すと、パニックが発生します。

FlexConfig

Omni Gateway コンテナを設定します。

メソッド用途
.version("1.10.0")Omni Gateway の Docker イメージのバージョン
.hostname("local-flex")コンテナのホスト名
.image_name("custom/image")Docker イメージを上書きします(デフォルト:mulesoft/flex-gateway)
.with_api(api_config)API 設定を追加します(ポートも登録します)
.config_mounts([(host_path, flex_subdir)])設定ディレクトリをコンテナにマウントします
.timeout(Duration::from_secs(90))準備完了のタイムアウト(デフォルト:60 秒)

準備完了は、Omni のログメッセージ "cds: added/updated" を監視して判定します。このメッセージが確認されると、コンポジットが準備完了となり、テストからトラフィックを送信できるようになります。

ApiConfig

Omni が提供する仮想 API を設定します。

メソッド用途
.name("ingress-http")API インスタンス名
.upstream(&backend_config)トラフィックの転送先となるバックエンドサービス
.path("/anything/echo/")destinationPath — リクエストの転送先となる、バックエンド上のベースパス
.port(8081)リスナーポート(Omni はこのポートですべてのパスをリッスンします)
.policies([policy_config])適用するインバウンドポリシー(配列の順序で評価されます)
.outbound_policies([policy_config])アップストリームのルートに適用するアウトバウンドポリシー

ルーティングの仕組み: Omni は、すべての受信パスを http://0.0.0.0:{port} でリッスンします。リクエストはアップストリームサービスに転送されます。.path() の値は、生成された Omni YAML の destinationPath になります。これはバックエンドがリクエストを受け取る場所を制御するものであり、一致する受信パスを制御するものではありません。

PolicyConfig

API に適用するポリシーを設定します。

メソッド用途
.name(POLICY_NAME)ポリシーの参照名(WASM アーティファクトと一致します)
.configuration(serde_json::json!({...}))実行時のポリシー設定(gcl.yaml のスキーマと一致します)
HttpMockConfig

httpmock バックエンドコンテナを設定します。

メソッド用途
.port(80)内部リッスンポート
.hostname("backend")コンテナのホスト名
HttpMock ハンドル

コンポジットの起動後、モックハンドルを取得します。

let upstream: HttpMock = composite.service()?;
let mock_server = MockServer::connect_async(upstream.socket()).await;

次に、httpmock クレートの API を使用して期待値を設定します。

let mock = mock_server
    .mock_async(|when, then| {
        when.method("GET").path("/api/resource");
        then.status(200)
            .header("content-type", "application/json")
            .body(r#"{"result": "ok"}"#);
    })
    .await;

// After sending a request, assert the mock was called:
mock.assert();
mock.assert_hits(1);
Omni ハンドル
let flex: Flex = composite.service()?;
let flex_url = flex.external_url(FLEX_PORT).unwrap();
// flex_url is like "http://127.0.0.1:<random-port>"
GrpcBin / GripMock(gRPC テスト)

gRPC サービスと連携するポリシー向けです。

use pdk_test::services::grpcbin::{GrpcBin, GrpcBinConfig};
use pdk_test::services::gripmock::{GripMock, GripMockConfig};
HttpBin

汎用 HTTP エコーテスト向けです。

use pdk_test::services::httpbin::{HttpBin, HttpBinConfig};
環境変数
変数用途
PDK_TEST_FLEX_IMAGE_NAMEOmni の Docker イメージ名を上書きします
PDK_TEST_FLEX_IMAGE_VERSIONOmni の Docker イメージのバージョンを上書きします

テストパターン

パターン:ポリシーがリクエストをブロックすることをテストする
#[pdk_test]
async fn test_policy_blocks_unauthorized() -> anyhow::Result<()> {
    // ... setup composite ...

    let response = reqwest::Client::new()
        .get(format!("{flex_url}/protected"))
        .send()  // No auth header
        .await?;

    assert_eq!(response.status().as_u16(), 401);

    Ok(())
}
パターン:ポリシーがヘッダーを変更することをテストする

httpmock のアサーションを使用して、バックエンドに届くヘッダーを検証します。

#[pdk_test]
async fn test_policy_adds_header() -> anyhow::Result<()> {
    // ... setup composite ...

    let mock = mock_server
        .mock_async(|when, then| {
            when.header_exists("x-injected-header");
            then.status(200);
        })
        .await;

    let response = reqwest::Client::new()
        .get(format!("{flex_url}/hello"))
        .send()
        .await?;

    assert_eq!(response.status().as_u16(), 200);
    mock.assert();  // Proves the header reached the backend

    Ok(())
}
パターン:設定ヘルパーで定型コードを減らす

設定の構築処理をヘルパーに切り出します。ただし、TestComposite をテストのスコープ内で保持してください(ドロップするとコンテナが破棄されます)。

fn build_flex_config(policy_json: serde_json::Value) -> (FlexConfig, HttpMockConfig) {
    let backend_config = HttpMockConfig::builder()
        .port(80)
        .hostname("backend")
        .build();

    let policy_config = PolicyConfig::builder()
        .name(POLICY_NAME)
        .configuration(policy_json)
        .build();

    let api_config = ApiConfig::builder()
        .name("ingress-http")
        .upstream(&backend_config)
        .path("/anything/echo/")
        .port(FLEX_PORT)
        .policies([policy_config])
        .build();

    let flex_config = FlexConfig::builder()
        .version("1.10.0")
        .hostname("local-flex")
        .with_api(api_config)
        .config_mounts([(POLICY_DIR, "policy"), (COMMON_CONFIG_DIR, "common")])
        .build();

    (flex_config, backend_config)
}

#[pdk_test]
async fn test_with_custom_config() -> anyhow::Result<()> {
    let (flex_config, backend_config) = build_flex_config(serde_json::json!({
        "my_param": "value"
    }));

    // composite must live for the duration of the test
    let composite = TestComposite::builder()
        .with_service(flex_config)
        .with_service(backend_config)
        .build()
        .await?;

    let flex: Flex = composite.service()?;
    let flex_url = flex.external_url(FLEX_PORT).unwrap();
    // ... send requests and assert ...
    Ok(())
}
パターン:複数のポリシーを連鎖させてテストする
let api_config = ApiConfig::builder()
    .name("ingress-http")
    .upstream(&backend_config)
    .path("/anything/echo/")
    .port(FLEX_PORT)
    .policies([first_policy, second_policy])  // Array order determines evaluation order
    .build();
パターン:非同期初期化を伴うポリシーのリトライ

一部のポリシーでは、定期タスク(契約のポーリング、キャッシュの更新など)のために、configure 関数で Clock を使用します。ポリシーのロジックが完全に動作するようになるまで、Omni の準備完了後に少し待つ必要がある場合があります。ほとんどのポリシーでは必要ありません。必要なのは、configure 中に非同期処理を行うポリシーだけです。

use tokio::time::{sleep, Duration};

// Only needed for policies that do async work in configure (Clock-based init)
sleep(Duration::from_secs(2)).await;

let response = reqwest::Client::new()
    .get(format!("{flex_url}/hello"))
    .send()
    .await?;

このような場合は、リトライループを使うとより堅牢です。

let mut last_status = 0;
for _ in 0..10 {
    let resp = reqwest::get(format!("{flex_url}/hello")).await?;
    last_status = resp.status().as_u16();
    if last_status != 503 {
        break;
    }
    sleep(Duration::from_millis(500)).await;
}
assert_eq!(last_status, 200);

テストの実行

# Run all integration tests
cargo test --test requests

# Run a specific test
cargo test --test requests test_clean_request_passes

# With output (see Omni logs)
cargo test --test requests -- --nocapture

# Via Makefile (standard scaffold target)
make test

# Override Omni version
PDK_TEST_FLEX_IMAGE_VERSION=1.9.0 cargo test --test requests

トラブルシューティング

「Docker デーモンが実行されていません」/「Docker デーモンに接続できません」

Docker Desktop または Docker デーモンを起動してください。pdk-test は bollard を介して Docker API を使用します。

Omni の準備完了を待つテストがタイムアウトする

フレームワークは、Omni が stdout に "cds: added/updated" を出力するまで待機します。このメッセージが表示されない場合:

  • Docker に十分なリソース(CPU/メモリ)があることを確認します
  • Omni のイメージバージョンが存在することを確認します:docker pull mulesoft/flex-gateway:1.10.0
  • registration.yaml が有効であることを確認します。有効でない場合、Omni はすぐに終了します
  • タイムアウトを延長します:.timeout(Duration::from_secs(120))
「WASM が見つかりません」/ポリシーが適用されない
  • テストの前に make build を実行します
  • POLICY_DIR が target/wasm32-wasip1/release を指していることを確認します
  • POLICY_NAME が make show-policy-ref-name の出力と一致することを確認します
モックに到達しない/予期しない 404
  • Omni は設定されたポート上のすべてのパスをリッスンします。ApiConfig の .path() はバックエンドの destinationPath であり、受信パスのフィルターではありません。
  • モックの when 条件が、Omni からバックエンドに実際に転送される内容と一致していることを確認します。
  • --nocapture を使って Omni のログを表示し、ポリシーが読み込まれていることを確認します。
「イメージがありません」というエラー

まず Omni イメージを取得します:

docker pull mulesoft/flex-gateway:1.10.0

または、利用可能なバージョンで上書きします:

PDK_TEST_FLEX_IMAGE_VERSION=latest cargo test --test requests
ローカルではテストが成功するが、CI では失敗する
  • CI で Docker を利用できる必要があります(Docker-in-Docker または Docker ソケットのマウント)
  • CI ではリソースが限られている場合があります。タイムアウトを延長してください
  • 各開発者はそれぞれ registration.yaml を生成する必要があります(このファイルは gitignore 対象です)
「テストごとに定義できる HttpMock は 1 つだけです」

フレームワークでは、TestComposite ごとに HttpMockConfig を 1 つに制限しています。複数のバックエンド動作が必要な場合は、単一のモックサーバーで httpmock の条件付きマッチング(when.path(...)、when.header(...))を使用してください。

完了チェックリスト

統合テストを作成した後、次の項目を確認してください:

  • make build が成功する(WASM アーティファクトが最新である)
  • tests/config/registration.yaml がローカルに存在する(git にコミットしない)
  • tests/common/mod.rs の POLICY_NAME が正しい(make show-policy-ref-name の出力に基づく)
  • Docker の実行中に cargo test --test requests が成功する
  • テストに少なくとも、正常系のリクエストと拒否/エラーケースが 1 つずつ含まれている

コメント