Why ExtProctor?
Implementing and evolving Envoy External Processing (ExtProc) services is error-prone. Behaviours depend on correct protobuf message structures and a sequence of callbacks that are difficult to test manually.
[!NOTE]
This project is heavily inspired by Google Service Extensions where they use the same approach to test their service extensions.
ExtProctor provides a dedicated test runner that enables:
- β
Automated regression testing for ExtProc implementations
- β
Fast feedback loops during local development
- β
CI/CD integration with machine-readable outputs
- β
Version-controlled test cases using human-readable prototext manifests
Features
| Feature |
Description |
| π Prototext Manifests |
Define test cases using human-readable prototext format |
| π Full ExtProc Support |
Test all processing phases: headers, body, and trailers |
| πΈ Golden Files |
Capture and compare responses using the golden file pattern |
| β‘ Parallel Execution |
Run tests concurrently for faster feedback |
| π·οΈ Flexible Filtering |
Filter tests by name pattern or tags |
| π Multiple Output Formats |
Human-readable or JSON output for CI integration |
| π Unix Socket Support |
Connect to ExtProc services via Unix domain sockets |
| π TLS Support |
Secure gRPC connections with client certificates |
Installation
Using Go
go install zntr.io/extproctor/cmd/extproctor@latest
From Source
git clone https://github.com/zntrio/extproctor.git
cd extproctor
go build -o extproctor ./cmd/extproctor
Verify Installation
extproctor --help
Quick Start
1. Create a Test Manifest
Create a file tests/basic.textproto:
name: "basic-test"
description: "Basic ExtProc test"
test_cases: {
name: "add-header"
description: "Verify ExtProc adds a custom header"
tags: ["smoke"]
request: {
method: "GET"
path: "/api/v1/users"
scheme: "https"
authority: "api.example.com"
headers: {
key: "content-type"
value: "application/json"
}
}
expectations: {
phase: REQUEST_HEADERS
headers_response: {
set_headers: {
key: "x-custom-header"
value: "custom-value"
}
}
}
}
2. Run the Tests
extproctor run ./tests/ --target localhost:50051
3. View Results
Running tests from 1 manifest(s)...
β basic-test/add-header (12ms)
Results: 1 passed, 0 failed, 0 skipped
Documentation
CLI Commands
extproctor run
Execute tests against an ExtProc service.
# Run all tests in a directory
extproctor run ./tests/ --target localhost:50051
# Run with Unix domain socket
extproctor run ./tests/ --unix-socket /var/run/extproc.sock
# Run with parallel execution
extproctor run ./tests/ --target localhost:50051 --parallel 4
# Filter by test name pattern
extproctor run ./tests/ --target localhost:50051 --filter "auth*"
# Filter by tags
extproctor run ./tests/ --target localhost:50051 --tags "smoke,regression"
# JSON output for CI pipelines
extproctor run ./tests/ --target localhost:50051 --output json
# Verbose mode for debugging
extproctor run ./tests/ --target localhost:50051 -v
# Update golden files
extproctor run ./tests/ --target localhost:50051 --update-golden
extproctor validate
Validate manifest syntax without running tests.
# Validate all manifests in a directory
extproctor validate ./tests/
# Validate specific files
extproctor validate test1.textproto test2.textproto
Command-Line Options
| Flag |
Description |
Default |
--target |
ExtProc service address (host:port) |
localhost:50051 |
--unix-socket |
Unix domain socket path |
β |
--tls |
Enable TLS for gRPC connection |
false |
--tls-cert |
TLS client certificate file |
β |
--tls-key |
TLS client key file |
β |
--tls-ca |
TLS CA certificate file |
β |
-p, --parallel |
Number of parallel test executions |
1 |
-o, --output |
Output format (human, json) |
human |
-v, --verbose |
Enable verbose output |
false |
--filter |
Filter tests by name pattern |
β |
--tags |
Filter tests by tags (comma-separated) |
β |
--update-golden |
Update golden files with actual responses |
false |
Note: --target and --unix-socket are mutually exclusive.
Test manifests are written in Prototext format.
Structure
name: "manifest-name"
description: "Description of the test suite"
test_cases: {
name: "test-case-name"
description: "What this test validates"
tags: ["tag1", "tag2"]
request: {
method: "POST"
path: "/api/endpoint"
scheme: "https"
authority: "api.example.com"
headers: {
key: "content-type"
value: "application/json"
}
body: '{"key": "value"}'
process_request_body: true
process_response_headers: true
}
expectations: {
phase: REQUEST_HEADERS
headers_response: {
set_headers: {
key: "x-custom"
value: "value"
}
}
}
}
Processing Phases
| Phase |
Description |
REQUEST_HEADERS |
Processing request headers |
REQUEST_BODY |
Processing request body |
REQUEST_TRAILERS |
Processing request trailers |
RESPONSE_HEADERS |
Processing response headers |
RESPONSE_BODY |
Processing response body |
RESPONSE_TRAILERS |
Processing response trailers |
Expectation Types
Headers Response
expectations: {
phase: REQUEST_HEADERS
headers_response: {
set_headers: {
key: "x-custom"
value: "value"
}
remove_headers: "x-internal"
append_headers: {
key: "x-multi"
value: "value"
}
}
}
Body Response
expectations: {
phase: REQUEST_BODY
body_response: {
body: '{"modified": true}'
common_response: {
status: CONTINUE_AND_REPLACE
}
}
}
Trailers Response
expectations: {
phase: REQUEST_TRAILERS
trailers_response: {
set_trailers: {
key: "x-checksum-validated"
value: "true"
}
}
}
Immediate Response (Short-circuit)
expectations: {
phase: REQUEST_HEADERS
immediate_response: {
status_code: 403
headers: {
key: "content-type"
value: "application/json"
}
body: '{"error": "forbidden"}'
}
}
Golden Files
Use golden files for snapshot testing:
test_cases: {
name: "golden-test"
request: { ... }
golden_file: "golden/test-response.textproto"
}
Update golden files when behavior changes intentionally:
extproctor run ./tests/ --target localhost:50051 --update-golden
Examples
The testdata/examples/ directory contains complete example manifests:
Sample ExtProc Server
A sample ExtProc server is included for testing and reference:
# Start the sample server
go run ./sample/extproc/ --addr :50051
# Run tests against it
extproctor run ./sample/extproc/test/ --target localhost:50051
The sample server demonstrates:
- Request headers processing with custom header injection
- Request/response body handling
- Response headers modification
- gRPC health check endpoint
Development
Prerequisites
Building
go build -o extproctor ./cmd/extproctor
Running Tests
go test ./...
Regenerating Protobuf Code
buf generate
Project Structure
extproctor/
βββ cmd/extproctor/ # CLI entry point
βββ internal/
β βββ cli/ # Command-line interface
β βββ client/ # ExtProc gRPC client
β βββ comparator/ # Response comparison logic
β βββ golden/ # Golden file handling
β βββ manifest/ # Manifest loading and validation
β βββ reporter/ # Test result reporting
β βββ runner/ # Test execution engine
βββ proto/ # Protobuf definitions
βββ sample/extproc/ # Sample ExtProc server
βββ testdata/examples/ # Example test manifests
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature)
- Commit your changes (
git commit -m 'Add some amazing feature')
- Push to the branch (
git push origin feature/amazing-feature)
- Open a Pull Request
Please make sure to:
- Update tests as appropriate
- Follow the existing code style
- Update documentation for any new features
License
This project is licensed under the MIT License - see the LICENSE file for details.
Made with β€οΈ for the Envoy community