tests ├── common.rs ├── requests.rs └── common └── logging.yaml └── registration.yaml
Writing Integration Tests
Flex Gateway Policy Development Kit (PDK) provides an automated testing framework to detect integration regressions in your custom policy.
The integration testing framework provided by PDK only supports Flex Gateway running in Local Mode in a Docker container. If you plan to deploy or policy to Flex Gateway running in Connected Mode or to a different platform, you should still first test your policy in PDK by using Flex Gateway running in Local Mode in a Docker container.
You can run Flex Gateway in Local Mode on Docker in Windows only to develop and test your policies. However, Flex Gateway does not support Windows in production environments. |
To test your policy, complete the following tasks:
Integration Tests Directory Structure
By default, the <root-directory>/tests
directory contains the following files:
The /tests
directory contains the integration tests. By default, the folder contains:
-
requests.rs
file: An individual test module and executable. -
commons.rs
file: A test module that contains functionalities, API configurations, and policy configurations that must be included in every integration test module.
Register a Flex Gateway Instance in Local Mode
To begin using the make test
command to debug your custom policy, register a Flex Gateway in Local Mode.
For the make test
command to work, a Flex Gateway registration.yaml
file must exist in the <root-directory>/tests/common
directory. Create this file by running the registration command in the directory, or move a registration.yaml
file of a previously registered Flex Gateway to the directory.
To register Flex Gateway, see:
Run only the registration command. Do not run the Docker start command because the make test command completes this step.
|
Create a different registration file on each device testing the policy.
The registration file is ignored for storage in remote repositories and is listed in the .gitignore
file located at the root of the project directory.
Configure Your Custom Policy
Each test function requires the configuration of the API and policies for that request.
To configure the custom policy:
-
Print the policy ID by running the
show-policy-ref-name
:make show-policy-ref-name
-
Copy the result.
-
Override
{{project-name}}
incommons.rs
with your policy ID, for example:pub const POLICY_NAME: &str = "<custom-policy-id>";
-
Configure the policy’s configuration parameters.
To find your configuration parameters, see the
gcl.yaml
file.
Create a Test Function
Add a new test module by placing a new test function in an existing test module, for example /tests/requests.rs
.
PDK test functions are async
functions decorated with the #[pdk_test]
attribute:
use pdk_test::pdk_test;
#[pdk_test]
async fn say_hello() {
}
Because the body is empty, this test function never fails. Run make test to ensure everything is working fine.
|
Run an Integration Test
To run the integrations tests modules in the /tests
folder, execute the make test
command:
make test
Run this test as you add additional features to your integration test to ensure proper configuration.
The make test command compiles the policy before running the integration tests.
|
Configure Services for Integration Testing
PDK integration tests contain a composite of services running inside a Docker network. You can register service instances in a TestComposite
object. Every service has a configuration, hostname, and instance.
The pdk-test
crate contains all the functionality for writing integration tests for custom policies in PDK and is included in the [dev-dependencies]
section of the Cargo.toml
file.
Supported Services
PDK’s test library supports the following services:
Service | Description |
---|---|
|
Flex Gateway service instance. |
|
Service binding to the Rust’s httpmock server. |
The supported services cover the majority of use cases for integration testing. It is not possible to define different types of service.
httpmock Service
httpmock
is a mock server that enables you to write request mocks in Rust. Configure an httpmock service by using an HttpMockConfig
value. It is a best practice to return a Result
from the test function. The anyhow Rust crate enables you to return different default error types.
To configure an httpmock
service, see the following code example:
use pdk_test::{pdk_test, TestComposite};
use pdk_test::services::httpmock::HttpMockConfig;
#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {
// Configure HttpMock service
let backend_config = HttpMockConfig::builder()
.hostname("backend")
.port(80) // Port where the service will accept requests
.build();
// Register HTTPBin service and start the docker network
let composite = TestComposite::builder()
.with_service(backend_config)
.build()
.await?;
Ok(())
}
If the Docker engine is properly configured, this test passes. Run make test to ensure proper configuration.
|
Get a Service’s Handle
Every configured service instance in the composite has a handle for custom interaction. The handle returns the sockets where the service endpoints are available.
To find a service’s handle, see the following code example:
use pdk_test::{pdk_test, TestComposite};
use pdk_test::services::httpmock::{HttpMock, HttpMockConfig};
#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {
// Configure HTTPMock service
let backend_config = HttpMockConfig::builder()
.hostname("backend")
.build();
// Register HTTPMock service and start the docker network
let composite = TestComposite::builder()
.with_service(backend_config)
.port(80) // Port where the service accepts requests
.build()
.await?;
// Get the httpmock handle
let httpmock: HttpMock = composite.service()?;
Ok(())
}
If the httpmock instance is properly configured, this test passes. Run make test to ensure proper configuration.
|
HTTP Endpoint Requests
To mock endpoints, use the httpmock When/Then API.
After mocking the endpoint, use an HTTP client to make endpoint requests.
The reqwest crate offers the most comprehensive and flexible Rust HTTP client.
To mock endpoints and make requests, see the following code example:
use pdk_test::{pdk_test, TestComposite};
use pdk_test::services::httpmock::{HttpMock, HttpMockConfig};
#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {
// Configure HttpMock service
let backend_config = HttpMockConfig::builder()
.hostname("backend")
.port(80) // Port where the service will accept requests
.build();
// Register HTTPBin service and start the docker network
let composite = TestComposite::builder()
.with_service(backend_config)
.build()
.await?;
// Get the httpmock handle
let httpmock: HttpMock = composite.service()?;
// Connect the mock server
let mock_server = httpmock::MockServer::connect_async(httpmock.socket()).await;
// Configure the endpoint mocks
mock_server.mock_async(|when, then| {
when.path_contains("/hello");
then.status(202).body("World!");
}).await;
let base_url = mock_server.base_url();
// Hit the endpoint
let response = reqwest::get(format!("{base_url}/hello")).await?;
// Assert on response
assert_eq!(response.status(), 202);
assert_eq!(response.text().await?, "World!");
Ok(())
}
Run make test to ensure proper configuration.
|
Configure a Flex Service Instance
Configure a Flex
service by registering an instance of the FlexConfig
struct in TestComposite
. To complete the FlexConfig
instance, you must also define a ApiConfig
and PolicyConfig
instance.
The FlexConfig
struct has the following properties:
Property | Content |
---|---|
|
Flex Gateway version to test. |
|
Hostname of the |
|
A map of local directories where the |
|
The |
|
List of ports where the |
The ApiConfig
has the following properties:
Property | Content |
---|---|
|
Name of the API. |
|
The reference to the upstream service. |
|
Path to which the upstream requests are sent. |
|
Ports where the API listens for requests. |
|
A List of |
The PolicyConfig
has the following properties:
Property | Content |
---|---|
|
Policy to apply. |
|
Parameters for the policy. |
The following code example defines a complete Flex
service:
mod common;
use common::*;
use pdk_test::{pdk_test, TestComposite};
use pdk_test::port::Port;
use pdk_test::services::flex::FlexConfig;
// Port where Flex listens for requests
const FLEX_PORT: Port = 8081;
#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {
// Configure an upstream service for the API
let httpmock_config = HttpMockConfig::builder()
.port(80)
.version("latest")
.hostname("backend")
.build();
// Configure a policy for the API
let policy_config = PolicyConfig::builder()
.name(POLICY_NAME)
.configuration(serde_json::json!({"source": "http://backend/blocked", "frequency": 60}))
.build();
// Configure the API to deploy to the Flex
let api_config = ApiConfig::builder()
.name("ingress-http")
.upstream(&httpmock_config)
.path("/anything/echo/")
.port(FLEX_PORT)
.policies([policy_config])
.build();
// Configure the Flex service
let flex_config = FlexConfig::builder()
.version("1.6.1")
.hostname("local-flex")
.config_mounts([
(POLICY_DIR, "policy"),
(COMMON_CONFIG_DIR, "common")
])
.with_api(api_config)
.build();
let composite = TestComposite::builder()
.with_service(flex_config)
.with_service(httpmock_config)
.build()
.await?;
}
Run |
Flex Service Endpoint Requests
Flex
service exposes endpoints with external base URLs dependent on the port. To access those URLs, the
Flex service instance handle provides the external_url()
method indexed by port, for example:
use pdk_test::{pdk_test, TestComposite, port::Port};
use pdk_test::services::flex::Flex;
const FLEX_PORT: Port = 8081;
async fn build_composite() -> anyhow::Result<TestComposite> {
todo!("Configuration stuff comes here")
}
#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {
// Invoke a helper composite builder function for simplicity
let composite = build_composite().await?;
// Get the Flex service handle
let flex: Flex = composite.service()?;
// Get the URL for our configured port
let flex_url = flex.external_url(FLEX_PORT).unwrap();
let response = reqwest::get(format!("{flex_url}/hello")).await?;
// Make assertions
Ok(())
}
Combine Multiple Services
Usually a PDK integration test includes a Flex
service and at least one backend service.
The following code is the default test generated when the custom policy is created:
mod common;
use httpmock::MockServer;
use pdk_test::{pdk_test, TestComposite};
use pdk_test::port::Port;
use pdk_test::services::flex::{FlexConfig, Flex};
use pdk_test::services::httpmock::{HttpMockConfig, HttpMock};
use common::*;
// Directory with the configurations for the `hello` test.
const HELLO_CONFIG_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/requests/hello");
// Flex port for the internal test network
const FLEX_PORT: Port = 8081;
// This integration test shows how to build a test to compose a local-flex instance
// with a MockServer backend
#[pdk_test]
async fn hello() -> anyhow::Result<()> {
// Configure an HttpMock service
let httpmock_config = HttpMockConfig::builder()
.port(80)
.version("latest")
.hostname("backend")
.build();
// Configure a policy for the API
let policy_config = PolicyConfig::builder()
.name(POLICY_NAME)
.configuration(serde_json::json!({"source": "http://backend/blocked", "frequency": 60}))
.build();
// Configure the API to deploy to the Flex
let api_config = ApiConfig::builder()
.name("ingress-http")
.upstream(&httpmock_config)
.path("/anything/echo/")
.port(FLEX_PORT)
.policies([policy_config])
.build();
// Configure the Flex service
let flex_config = FlexConfig::builder()
.version("1.6.1")
.hostname("local-flex")
.config_mounts([
(POLICY_DIR, "policy"),
(COMMON_CONFIG_DIR, "common")
])
.with_api(api_config)
.build();
// Compose the services
let composite = TestComposite::builder()
.with_service(flex_config)
.with_service(httpmock_config)
.build()
.await?;
// Get a handle to the Flex service
let flex: Flex = composite.service()?;
// Get an external URL to point the Flex service
let flex_url = flex.external_url(FLEX_PORT).unwrap();
// Get a handle to the HttpMock service
let httpmock: HttpMock = composite.service()?;
// Create a MockServer
let mock_server = MockServer::connect_async(httpmock.socket()).await;
// Mock a /hello request
mock_server.mock_async(|when, then| {
when.path_contains("/hello");
then.status(202).body("World!");
}).await;
// Perform an actual request
let response = reqwest::get(format!("{flex_url}/hello")).await?;
// Assert on the response
assert_eq!(response.status(), 202);
Ok(())
}
Review Service Logs
After an integration test fails, you can view the service logs to find errors. To improve the debugging experience, there is a dedicated service log in each test folder at <root-directory>/target/pdk-test/<module-name>/<test>/<service>.log
.
For the example, the say_hello
test has the following service log:
<root-directory>
├── Cargo.lock
├── Cargo.toml
├── playground
├── src
├── tests
└── target
└── pdk-test
└── requests
└── say_hello
└── <service>.log