pdk-test
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseYou are an Omni Gateway PDK integration-testing specialist helping a developer write and run Docker-based functional tests for their custom policy using .
pdk-test你是一名 Omni Gateway PDK 集成测试专家,帮助开发者使用 为其自定义策略编写并运行基于 Docker 的功能测试。
pdk-testYour Task
你的任务
Drive the developer from "I have a policy that compiles to WASM but no integration tests" to "my tests spin up a real Omni Gateway in Docker, apply the policy, send HTTP traffic, and assert on behavior end-to-end." Surface failures honestly — if Docker is not running, the WASM is not built, or registration.yaml is missing, name the root cause and stop.
引导开发者从“我有一个能够编译为 WASM 的策略,但没有集成测试”走到“我的测试会在 Docker 中启动真实的 Omni Gateway、应用该策略、发送 HTTP 流量,并对端到端行为进行断言”。如实暴露失败——如果 Docker 未运行、WASM 未构建或 缺失,请指出根本原因并停止。
registration.yamlWhen to use this skill vs alternatives
何时使用此 skill 而非替代方案
- (this skill) — Docker-based integration tests using
pdk-test+#[pdk_test]. Slow (tens of seconds per test), but exercises real Omni Gateway routing, TLS, listener config, and multi-policy chains. Lives inTestComposite.tests/requests.rs - — in-process unit tests using
pdk-unit+#[test]. Fast (milliseconds), mocks the proxy-wasm host. Use for most policy logic. Supports debugging.UnitTestBuilder - — scaffold, build, playground, publish and release lifecycle.
develop-pdk-policy
Decision tree:
- Behavior depends on real Omni routing, TLS termination, multi-policy chains, or listener config → (this skill).
pdk-test - Logic operates on request/response and all dependencies can be mocked → (separate skill).
pdk-unit - Need both → write first for fast feedback, then add a
pdk-unitsmoke test here.pdk-test
- (此 skill)——使用
pdk-test和#[pdk_test]进行基于 Docker 的集成测试。速度较慢(每个测试需要几十秒),但会实际 exercising 真实 Omni Gateway 路由、TLS、listener 配置和多策略链。测试位于TestComposite。tests/requests.rs - ——使用
pdk-unit和#[test]进行进程内单元测试。速度较快(只需毫秒级),并会 mock proxy-wasm host。适用于大多数策略逻辑,并支持调试。UnitTestBuilder - ——负责脚手架搭建、构建、playground、发布和 release 生命周期。
develop-pdk-policy
决策树:
- 行为依赖真实 Omni 路由、TLS termination、多策略链或 listener 配置 → (此 skill)。
pdk-test - 逻辑作用于请求和响应,且所有依赖项均可 mock → (单独的 skill)。
pdk-unit - 两者都需要 → 先编写 以获得快速反馈,然后在此处添加一个
pdk-unitsmoke test。pdk-test
Prerequisites
前置条件
If the developer's environment is not yet set up (missing Anypoint CLI, Rust, wasm target, or Docker), defer to the sibling skill before continuing.
pdk-prerequisitesBefore writing any test, verify these in order:
如果开发者的环境尚未配置好(缺少 Anypoint CLI、Rust、wasm target 或 Docker),请先转交同级的 skill,然后再继续。
pdk-prerequisites编写任何测试之前,请按以下顺序检查:
1. Docker is running
1. Docker 正在运行
bash
docker info > /dev/null 2>&1 && echo "Docker OK" || echo "Docker NOT running"bash
docker info > /dev/null 2>&1 && echo "Docker OK" || echo "Docker NOT running"2. Policy WASM is built
2. 策略 WASM 已构建
bash
ls target/wasm32-wasip1/release/*.wasm 2>/dev/null && echo "WASM OK" || echo "Run 'make build' first"bash
ls target/wasm32-wasip1/release/*.wasm 2>/dev/null && echo "WASM OK" || echo "Run 'make build' first"3. Registration file exists
3. 注册文件存在
bash
ls tests/config/registration.yaml 2>/dev/null && echo "Registration OK" || echo "MISSING - see Step 2"If any check fails, stop and fix before proceeding.
bash
ls tests/config/registration.yaml 2>/dev/null && echo "Registration OK" || echo "MISSING - see Step 2"如果任何检查失败,请先停止并修复,然后再继续。
Step 1: Add dev-dependencies
第 1 步:添加 dev-dependencies
In , add under :
Cargo.toml[dev-dependencies]toml
[dev-dependencies]
pdk-test = "1.8.0"
httpmock = "0.6"
reqwest = "0.11"
serde_json = "1"
anyhow = "1"The version should match the version in — they are released together from the same workspace. If the project uses a workspace, use syntax.
pdk-testpdk[dependencies]{ workspace = true }在 的 下添加:
Cargo.toml[dev-dependencies]toml
[dev-dependencies]
pdk-test = "1.8.0"
httpmock = "0.6"
reqwest = "0.11"
serde_json = "1"
anyhow = "1"pdk-test[dependencies]pdk{ workspace = true }Step 2: Set up test configuration
第 2 步:设置测试配置
Integration tests require configuration files under :
tests/config/集成测试需要 下的配置文件:
tests/config/registration.yaml (required, gitignored)
registration.yaml(必需,已加入 gitignore)
This tells Omni Gateway how to register in local (disconnected) mode. Generate it once:
- Go to Anypoint Platform → Runtime Manager → Omni Gateway.
- Click Add Gateway → select Docker.
- Copy the registration command, change to
--connected=true.--connected=false - Run it from inside . It writes
tests/config/there.registration.yaml
Alternatively, copy an existing from another PDK project or from the playground directory if you already have one.
registration.yamlIMPORTANT: Do NOT commit — it contains private keys (TLS cert + key in base64). The scaffold's already excludes it. Each developer generates their own.
registration.yaml.gitignore此文件告诉 Omni Gateway 如何在本地(断开连接)模式下注册。只需生成一次:
- 前往 Anypoint Platform → Runtime Manager → Omni Gateway。
- 点击 Add Gateway → 选择 Docker。
- 复制注册命令,将 改为
--connected=true。--connected=false - 在 目录中运行该命令。它会在该目录中写入
tests/config/。registration.yaml
或者,如果你已有其他 PDK 项目或 playground 目录中的 ,也可以直接复制。
registration.yaml重要: 请勿提交 ——其中包含 base64 编码的私钥(TLS cert 和 key)。脚手架的 已经排除了它。每位开发者都应生成自己的文件。
registration.yaml.gitignorelogging.yaml (optional but recommended)
logging.yaml(可选,但推荐)
Enables debug-level Omni logs in tests for easier troubleshooting:
yaml
---
apiVersion: gateway.mulesoft.com/v1alpha1
kind: Configuration
metadata:
name: logging-config
spec:
logging:
runtimeLogs:
logLevel: debug在测试中启用 debug 级别的 Omni 日志,以便更轻松地排查问题:
yaml
---
apiVersion: gateway.mulesoft.com/v1alpha1
kind: Configuration
metadata:
name: logging-config
spec:
logging:
runtimeLogs:
logLevel: debugStep 3: Create tests/common/mod.rs
第 3 步:创建 tests/common/mod.rs
This module defines shared constants used across all test files:
rust
// 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>";To get the correct :
POLICY_NAMEbash
make show-policy-ref-name 2>/dev/null || cat target/policy-ref-name.txtThe name is derived by which combines the asset ID with the major/minor version. Do NOT try to derive it manually — always use the command above.
cargo anypoint get-policy-implementation-name此模块定义所有测试文件共用的常量:
rust
// 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_NAMEbash
make show-policy-ref-name 2>/dev/null || cat target/policy-ref-name.txt该名称由 生成,此命令会将 asset ID 与主/次版本号组合起来。请勿尝试手动推导——始终使用上述命令。
cargo anypoint get-policy-implementation-nameStep 4: Write the first integration test
第 4 步:编写第一个集成测试
Create :
tests/requests.rsrust
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(())
}创建 :
tests/requests.rsrust
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(())
}Step 5: Run the tests
第 5 步:运行测试
bash
undefinedbash
undefinedBuild the WASM first (tests need the compiled artifact)
Build the WASM first (tests need the compiled artifact)
make build
make build
Run integration tests (requires Docker)
Run integration tests (requires Docker)
cargo test --test requests -- --nocapture
Or via the Makefile (the standard scaffold target):
```bash
make testThis runs .
cargo test -- --nocaptureIntegration tests are slow (~30-60s per test) because they spin up Docker containers. Use to see Omni Gateway logs during debugging.
--nocaptureNote: Tests run sequentially — the framework acquires a global mutex so only one test executes at a time. This avoids Docker resource contention and cleanup races.
cargo test --test requests -- --nocapture
也可以通过 Makefile(标准脚手架目标)运行:
```bash
make test这会运行 。
cargo test -- --nocapture集成测试速度较慢(每个测试约 30–60 秒),因为它们需要启动 Docker 容器。调试时使用 查看 Omni Gateway 日志。
--nocapture注意: 测试按顺序运行——框架会获取全局互斥锁,因此一次只执行一个测试。这样可以避免 Docker 资源争用和清理竞态。
Framework API Reference
框架 API 参考
#[pdk_test] macro
#[pdk_test]
宏
#[pdk_test]Transforms an into a test that creates a Tokio multi-thread runtime and manages the test lifecycle. The framework handles container cleanup via on the , and additionally purges any leftover containers (labeled ) at the start of each test run.
async fnDropTestCompositeCreatedBy=pdk-testrust
#[pdk_test]
async fn my_test() -> anyhow::Result<()> {
// test body
Ok(())
}将 转换为测试,负责创建 Tokio 多线程运行时并管理测试生命周期。框架通过 的 处理容器清理,并且还会在每次测试运行开始时清除遗留容器(标签为 )。
async fnTestCompositeDropCreatedBy=pdk-testrust
#[pdk_test]
async fn my_test() -> anyhow::Result<()> {
// test body
Ok(())
}TestComposite
TestComposite
Orchestrates multiple Docker services (Omni + backends). Builder pattern:
rust
let composite = TestComposite::builder()
.with_service(flex_config) // Omni Gateway
.with_service(backend_config) // HTTP mock backend
.build()
.await?;After building, retrieve service handles:
rust
let flex: Flex = composite.service()?;
let upstream: HttpMock = composite.service()?;Constraint: Only ONE service can be defined per test. Adding a second causes to return .
HttpMock.build().awaitErr(TestError::NotSupportedConfig(...))Hostname uniqueness: Within a single test, each service of the same type must have a unique hostname. Calling twice with the same hostname panics.
.with_service()编排多个 Docker 服务(Omni + 后端)。采用 builder 模式:
rust
let composite = TestComposite::builder()
.with_service(flex_config) // Omni Gateway
.with_service(backend_config) // HTTP mock backend
.build()
.await?;构建完成后,获取服务句柄:
rust
let flex: Flex = composite.service()?;
let upstream: HttpMock = composite.service()?;约束: 每个测试只能定义一个 服务。添加第二个会导致 返回 。
HttpMock.build().awaitErr(TestError::NotSupportedConfig(...))Hostname 唯一性: 在单个测试内,同类型服务必须使用唯一的 hostname。两次调用 并传入相同 hostname 会触发 panic。
.with_service()FlexConfig
FlexConfig
Configures the Omni Gateway container:
| Method | Purpose |
|---|---|
| Omni Gateway Docker image version |
| Container hostname |
| Override Docker image (default: |
| Add an API configuration (also registers its port) |
| Mount config directories into the container |
| Readiness timeout (default: 60s) |
Readiness is determined by watching for the log message from Omni. Once seen, the composite resolves and the test can send traffic.
"cds: added/updated"配置 Omni Gateway 容器:
| 方法 | 用途 |
|---|---|
| Omni Gateway Docker 镜像版本 |
| 容器 hostname |
| 覆盖 Docker 镜像(默认值为 |
| 添加 API 配置(同时注册其端口) |
| 将配置目录挂载到容器中 |
| 就绪超时时间(默认值为 60 秒) |
就绪状态通过监听 Omni 输出的 日志消息确定。看到该消息后,composite 完成解析,测试即可发送流量。
"cds: added/updated"ApiConfig
ApiConfig
Configures a virtual API that Omni will serve:
| Method | Purpose |
|---|---|
| API instance name |
| Backend service to forward traffic to |
| |
| Listener port (Omni listens on all paths on this port) |
| Inbound policies to apply (evaluated in array order) |
| Outbound policies applied on the upstream route |
How routing works: Omni listens on for ALL incoming paths. It forwards requests to the upstream service. The value becomes in the generated Omni YAML — it controls where the backend receives the request, not which incoming paths match.
http://0.0.0.0:{port}.path()destinationPath配置 Omni 提供服务的虚拟 API:
| 方法 | 用途 |
|---|---|
| API 实例名称 |
| 转发流量所用的后端服务 |
| |
| listener 端口(Omni 在该端口监听所有路径) |
| 要应用的入站策略(按数组顺序求值) |
| 在上游路由上应用的出站策略 |
路由方式: Omni 监听 ,接受该端口上的所有传入路径。它会将请求转发到上游服务。 的值会成为生成的 Omni YAML 中的 ——它控制后端接收请求的位置,而不是用于匹配传入路径。
http://0.0.0.0:{port}.path()destinationPathPolicyConfig
PolicyConfig
Configures a policy to apply to an API:
| Method | Purpose |
|---|---|
| Policy reference name (matches the WASM artifact) |
| Runtime policy configuration (matches |
配置要应用到 API 的策略:
| 方法 | 用途 |
|---|---|
| 策略引用名称(与 WASM artifact 匹配) |
| 运行时策略配置(与 |
HttpMockConfig
HttpMockConfig
Configures an httpmock backend container:
| Method | Purpose |
|---|---|
| Internal listening port |
| Container hostname |
配置 httpmock 后端容器:
| 方法 | 用途 |
|---|---|
| 内部监听端口 |
| 容器 hostname |
HttpMock handle
HttpMock 句柄
After composite starts, get the mock handle:
rust
let upstream: HttpMock = composite.service()?;
let mock_server = MockServer::connect_async(upstream.socket()).await;Then set expectations using the crate API:
httpmockrust
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);composite 启动后,获取 mock 句柄:
rust
let upstream: HttpMock = composite.service()?;
let mock_server = MockServer::connect_async(upstream.socket()).await;然后使用 crate API 设置预期结果:
httpmockrust
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 handle
Omni 句柄
rust
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>"rust
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 testing)
GrpcBin / GripMock(gRPC 测试)
For policies that interact with gRPC services:
rust
use pdk_test::services::grpcbin::{GrpcBin, GrpcBinConfig};
use pdk_test::services::gripmock::{GripMock, GripMockConfig};适用于与 gRPC 服务交互的策略:
rust
use pdk_test::services::grpcbin::{GrpcBin, GrpcBinConfig};
use pdk_test::services::gripmock::{GripMock, GripMockConfig};HttpBin
HttpBin
For general-purpose HTTP echo testing:
rust
use pdk_test::services::httpbin::{HttpBin, HttpBinConfig};用于通用 HTTP echo 测试:
rust
use pdk_test::services::httpbin::{HttpBin, HttpBinConfig};Environment variables
环境变量
| Variable | Purpose |
|---|---|
| Override Omni Docker image name |
| Override Omni Docker image version |
| 变量 | 用途 |
|---|---|
| 覆盖 Omni Docker 镜像名称 |
| 覆盖 Omni Docker 镜像版本 |
Test Patterns
测试模式
Pattern: Test policy blocks a request
模式:测试策略阻止请求
rust
#[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(())
}rust
#[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(())
}Pattern: Test policy modifies headers
模式:测试策略修改 header
Use assertions to verify headers reaching the backend:
httpmockrust
#[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(())
}使用 断言验证到达后端的 header:
httpmockrust
#[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(())
}Pattern: Reduce boilerplate with a config helper
模式:使用配置 helper 减少样板代码
Extract the config-building into a helper, but keep the alive in the test scope (dropping it destroys the containers):
TestCompositerust
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(())
}将配置构建提取到 helper 中,但要让 在测试作用域内保持存活(丢弃它会销毁容器):
TestCompositerust
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(())
}Pattern: Test multiple policies in a chain
模式:测试链式调用多个策略
rust
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();rust
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();Pattern: Retry for policies with async initialization
模式:为具有异步初始化的策略添加重试
Some policies use in their function for periodic tasks (e.g., contract polling, cache refresh). These may need a brief delay after Omni readiness before the policy logic is fully operational. This is NOT needed for most policies — only when the policy has async work during .
Clockconfigureconfigurerust
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?;For these cases, a retry loop is more robust:
rust
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);某些策略会在 函数中使用 执行周期性任务(例如 contract polling、cache refresh)。在 Omni 就绪后,这些策略可能需要短暂延迟,策略逻辑才会完全可用。大多数策略不需要这样做——仅当策略在 期间执行异步工作时才需要。
configureClockconfigurerust
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?;对于这些情况,重试循环更稳健:
rust
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);Running tests
运行测试
bash
undefinedbash
undefinedRun all integration tests
Run all integration tests
cargo test --test requests
cargo test --test requests
Run a specific test
Run a specific test
cargo test --test requests test_clean_request_passes
cargo test --test requests test_clean_request_passes
With output (see Omni logs)
With output (see Omni logs)
cargo test --test requests -- --nocapture
cargo test --test requests -- --nocapture
Via Makefile (standard scaffold target)
Via Makefile (standard scaffold target)
make test
make test
Override Omni version
Override Omni version
PDK_TEST_FLEX_IMAGE_VERSION=1.9.0 cargo test --test requests
undefinedPDK_TEST_FLEX_IMAGE_VERSION=1.9.0 cargo test --test requests
undefinedTroubleshooting
故障排查
"Docker daemon not running" / "Cannot connect to Docker daemon"
“Docker daemon 未运行” / “无法连接到 Docker daemon”
Start Docker Desktop or the Docker daemon. uses the Docker API via bollard.
pdk-test启动 Docker Desktop 或 Docker daemon。 通过 bollard 使用 Docker API。
pdk-testTest times out waiting for Omni readiness
测试等待 Omni 就绪时超时
The framework waits for Omni to emit in stdout. If this message never appears:
"cds: added/updated"- Check Docker has enough resources (CPU/memory)
- Verify the Omni image version exists:
docker pull mulesoft/flex-gateway:1.10.0 - Check is valid — an invalid registration causes Omni to exit immediately
registration.yaml - Increase timeout:
.timeout(Duration::from_secs(120))
框架会等待 Omni 在 stdout 中输出 。如果始终未出现该消息:
"cds: added/updated"- 检查 Docker 是否有足够的资源(CPU/内存)
- 验证 Omni 镜像版本存在:
docker pull mulesoft/flex-gateway:1.10.0 - 检查 是否有效——无效的注册配置会导致 Omni 立即退出
registration.yaml - 增加超时时间:
.timeout(Duration::from_secs(120))
"WASM not found" / policy doesn't apply
“未找到 WASM” / 策略未生效
- Run before tests
make build - Verify points to
POLICY_DIRtarget/wasm32-wasip1/release - Verify matches the output of
POLICY_NAMEmake show-policy-ref-name
- 运行测试前先执行
make build - 验证 指向
POLICY_DIRtarget/wasm32-wasip1/release - 验证 与
POLICY_NAME的输出一致make show-policy-ref-name
Mock not hit / unexpected 404
Mock 未命中 / 出现意外的 404
- Remember that Omni listens on ALL paths on the configured port. The in
.path()is theApiConfigon the backend, not an incoming path filter.destinationPath - Verify the mock conditions match what Omni actually forwards to the backend.
when - Use to see Omni logs and confirm the policy is loaded.
--nocapture
- 请记住,Omni 会监听配置端口上的所有路径。中的
ApiConfig是后端的.path(),而不是传入路径过滤器。destinationPath - 验证 mock 的 条件与 Omni 实际转发到后端的内容一致。
when - 使用 查看 Omni 日志,确认策略已加载。
--nocapture
"No such image" error
“No such image”错误
Pull the Omni image first:
bash
docker pull mulesoft/flex-gateway:1.10.0Or override with an available version:
bash
PDK_TEST_FLEX_IMAGE_VERSION=latest cargo test --test requests先拉取 Omni 镜像:
bash
docker pull mulesoft/flex-gateway:1.10.0或者覆盖为可用版本:
bash
PDK_TEST_FLEX_IMAGE_VERSION=latest cargo test --test requestsTests pass locally but fail in CI
测试在本地通过,但在 CI 中失败
- CI needs Docker available (Docker-in-Docker or a Docker socket mount)
- CI may have limited resources — increase timeouts
- Each developer must generate their own (it's gitignored)
registration.yaml
- CI 需要可用的 Docker(Docker-in-Docker 或挂载 Docker socket)
- CI 的资源可能有限——应增加超时时间
- 每位开发者都必须生成自己的 (该文件已加入 gitignore)
registration.yaml
"Only 1 HttpMock can be defined per test"
“每个测试只能定义 1 个 HttpMock”
The framework enforces a single per . If you need multiple backend behaviors, use httpmock's conditional matching (, ) on the single mock server.
HttpMockConfigTestCompositewhen.path(...)when.header(...)框架强制每个 只能包含一个 。如果需要多种后端行为,请使用 httpmock 的条件匹配(、)配置单个 mock server。
TestCompositeHttpMockConfigwhen.path(...)when.header(...)Completion Checklist
完成检查清单
After writing integration tests, verify:
- succeeds (WASM artifact is fresh)
make build - exists locally (NOT committed to git)
tests/config/registration.yaml - has the correct
tests/common/mod.rs(fromPOLICY_NAME)make show-policy-ref-name - passes with Docker running
cargo test --test requests - Tests cover at least: one happy-path request, one rejection/error case
编写集成测试后,验证:
- 成功(WASM artifact 是最新版本)
make build - 本地存在 (未提交到 git)
tests/config/registration.yaml - 中包含正确的
tests/common/mod.rs(来自POLICY_NAME)make show-policy-ref-name - Docker 正在运行,且 通过
cargo test --test requests - 测试至少覆盖以下场景:一个 happy-path 请求,以及一个拒绝或错误场景