HTTP DIFF
Tool to ensure multiple versions of the web server work the same. Useful for big refactors and sanity tests, where you need to check a lot of endpoints work the same and return the same data.
Dual-licensed under MIT or the UNLICENSE.
The tool works by looking at the configuration file that can be specified by --config
argument.
http-diff --config=./config.json
./config.json
- is the default value for this argument so it can be omitted.
Config example:
{
"domains": [
{
"domain": "http://domain-a.com/",
"headers": {
"cookie": "auth=secret"
}
},
"http://stage.domain-a.com"
],
"endpoints": [
{
"endpoint": "/health"
},
{
"endpoint": "/api/v1/users/<userId>",
"http_method": "GET",
"response_processor": [
"jq",
"del(.body.timestamp)"
],
"headers": {
"X-custom": "custom-header"
}
},
{
"endpoint": "/api/v1/products?omit_id=<productId>&include_empty=<include_empty>",
"request_builder": [
"python3",
"add_auth_to_requests.py"
],
"variables": {
"include_empty": [
"true",
"false"
]
}
}
],
"variables": {
"userId": [
123,
444
],
"productId": "UUID"
}
}
this config will be translated to following:
-
GET
request with{"cookie":"auth=secret"}
in headers will be issued tohttp://domain-a.com/health
and response be compared to response ofGET http://stage.domain-a.com/health
without any headers. -
Next endpoint
/api/v1/users/<userId>
has variable defined in it -<userId>
. Anything within the brackets considered a variable name. In this case -userId
. Variable then is looked up in the global variables property. In this caseuserId
has two values:123
and444
. This will be mapped to following requests:GET http://domain-a.com/users/123
with{"cookie":"auth=secret","X-custom":"custom-header"}
in headers and compared withGET http://stage.domain-a.com/users/123
with{"X-custom":"custom-header"}
in headers.GET http://domain-a.com/users/444
with{"cookie":"auth=secret","X-custom":"custom-header"}
in headers and compared withGET http://stage.domain-a.com/users/444
with{"X-custom":"custom-header"}
in headers.
Before comparing any response, response_processor will be applied. This endpoint has following preprocessor:
["jq", "del(.body.timestamp)"]
. Preprocessor is an external command that will be called with any arguments, before comparing responses. In this case after request tohttp://domain-a.com/users/123
and request tohttp://stage.domain-a.com/users/123
each response will passed tojq 'del(.body.timestamp)'
which basically will remove thetimestamp
field from the body.timestamp
field will be omitted from the compare process.response_processor
can be any program, script or cli tool you can think of. Anything program that acceptsstdin
, and outputs tostdout
. Output then will be compared. Not the original response. -
Next endpoint (
/api/v1/products?omit_id=&include_empty=<include_empty>
) will be mapped to:GET http://domain-a.com/api/v1/products?omit_id=22fae888-7bf9-45d6-87f4-83087cc80ba1&include_empty=true
with{"cookie":"auth=secret"}
in headers and compared withGET http://domain-a.com/api/v1/products?omit_id=22fae888-7bf9-45d6-87f4-83087cc80ba1&include_empty=true
without custom headers.productId
variable hasUUID
value. This is a 'generator' variable. Random uuid will be generated and passed to both requests.include_empty
variable has two values:true
andfalse
. These requests will usetrue
, next one will befalse
.GET http://domain-a.com/api/v1/products?omit_id=4558903b-d4a6-46e8-869e-1b77677832f4&include_empty=false
with{"cookie":"auth=secret"}
in headers and compared withGET http://domain-a.com/api/v1/products?omit_id=4558903b-d4a6-46e8-869e-1b77677832f4&include_empty=false
without custom headers.productId
gets new random uuid for both requests.include_empty
becomesfalse
.
Before executing any requests,
request_builder
command will be applied on each request. In this casepython3 add_auth_to_requests.py
will be executed 4 times, before each request.request_builder
gives you option to modify request in any possible way. We can add headers here, remove, modify the body, handle complicated authentication methods etc. Same as theresponse_processor
,request_builder
gets an stdin in the following structure, and expects stdout with the same structure:{ "uri": "string", "http_method": "GET | POST | PUT | PATCH | DELETE", "headers": "key value headers map", "body": "string" }
Requirements
- Latest rust installed when building from source
Running
cargo run
- for developmentcargo test
- to run testscargo build -r
- to build in release mode./build-osx.sh
- to build binary for apple as a target on linux machine./build-deb.sh
- to build debian package .deb