Skip to content

Commit

Permalink
Add integration tests for request/response modifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
mksh committed Oct 27, 2023
1 parent 4b5f56e commit a3fe0c4
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Update local toolchain
run: |
rustup update
rustup install nightly
rustup install 1.72
- name: Toolchain info
run: |
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ vcr-cassette = "2"
[dev-dependencies]
tokio = { version = "1.17.0", features = ["full"] }
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] }
url = "2.4"

66 changes: 14 additions & 52 deletions tests/integration/e2e.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,9 @@
use reqwest::Client;
use std::{path::PathBuf, sync::Arc, time::Duration};
use tokio::sync::Mutex;
use std::{path::PathBuf, time::Duration};

use http::header::ACCEPT;
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use rvcr::VCRMiddleware;
use tracing_subscriber::{
filter, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer,
};

lazy_static::lazy_static! {
static ref SCOPE: TestScope = TestScope::default();
static ref ADDRESS: String = String::from("http://127.0.0.1:38282");
}

#[derive(Clone)]
pub struct TestScope {
pub initialized: Arc<Mutex<bool>>,
}

impl Default for TestScope {
fn default() -> Self {
Self {
initialized: Arc::new(Mutex::new(false)),
}
}
}

impl TestScope {
pub async fn init(self) {
let mut inited = self.initialized.lock().await;
if *inited == false {
if std::env::var("TEST_LOG").is_ok() {
let stdout_log = tracing_subscriber::fmt::layer().pretty();
tracing_subscriber::registry()
.with(
stdout_log
// Add an `INFO` filter to the stdout logging layer
.with_filter(filter::LevelFilter::DEBUG),
)
.init();
}
*inited = true;
}
}
}

async fn send_and_compare(
method: reqwest::Method,
Expand All @@ -54,9 +13,12 @@ async fn send_and_compare(
vcr_client: ClientWithMiddleware,
real_client: reqwest::Client,
) {
let mut req1 = vcr_client.request(method.clone(), format!("{}{}", ADDRESS.to_string(), path));
let mut req1 = vcr_client.request(
method.clone(),
format!("{}{}", crate::ADDRESS.to_string(), path),
);

let mut req2 = real_client.request(method, format!("{}{}", ADDRESS.to_string(), path));
let mut req2 = real_client.request(method, format!("{}{}", crate::ADDRESS.to_string(), path));

for (header_name, header_value) in headers {
req1 = req1.header(header_name.clone(), header_value);
Expand Down Expand Up @@ -97,7 +59,7 @@ async fn send_and_compare(

#[tokio::test]
async fn test_rvcr_replay() {
SCOPE.clone().init().await;
crate::SCOPE.clone().init().await;
let mut bundle = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
bundle.push("tests/resources/replay.vcr.json");

Expand Down Expand Up @@ -142,7 +104,7 @@ async fn test_rvcr_replay() {

#[tokio::test]
async fn test_rvcr_replay_search_all() {
SCOPE.clone().init().await;
crate::SCOPE.clone().init().await;
let mut bundle = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
bundle.push("tests/resources/search-all.vcr.json");

Expand Down Expand Up @@ -188,7 +150,7 @@ async fn test_rvcr_replay_search_all() {
let req1 = vcr_client
.request(
reqwest::Method::POST,
format!("{}{}", ADDRESS.to_string(), "/post"),
format!("{}{}", crate::ADDRESS.to_string(), "/post"),
)
.send()
.await
Expand All @@ -201,7 +163,7 @@ async fn test_rvcr_replay_search_all() {
let req2 = vcr_client
.request(
reqwest::Method::POST,
format!("{}{}", ADDRESS.to_string(), "/post"),
format!("{}{}", crate::ADDRESS.to_string(), "/post"),
)
.send()
.await
Expand All @@ -217,7 +179,7 @@ async fn test_rvcr_replay_search_all() {

#[tokio::test]
async fn test_rvcr_replay_skip_found() {
SCOPE.clone().init().await;
crate::SCOPE.clone().init().await;
let mut bundle = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
bundle.push("tests/resources/skip-found.vcr.json");

Expand Down Expand Up @@ -263,7 +225,7 @@ async fn test_rvcr_replay_skip_found() {
let req1 = vcr_client
.request(
reqwest::Method::POST,
format!("{}{}", ADDRESS.to_string(), "/post"),
format!("{}{}", crate::ADDRESS.to_string(), "/post"),
)
.send()
.await
Expand All @@ -274,7 +236,7 @@ async fn test_rvcr_replay_skip_found() {
let req2 = vcr_client
.request(
reqwest::Method::POST,
format!("{}{}", ADDRESS.to_string(), "/post"),
format!("{}{}", crate::ADDRESS.to_string(), "/post"),
)
.send()
.await
Expand All @@ -291,7 +253,7 @@ async fn test_rvcr_replay_skip_found() {
#[cfg(feature = "compress")]
#[tokio::test]
async fn test_rvcr_replay_compressed() {
SCOPE.clone().init().await;
crate::SCOPE.clone().init().await;
let mut bundle = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
bundle.push("tests/resources/replay.vcr.zip");

Expand Down
45 changes: 45 additions & 0 deletions tests/integration/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,46 @@
use std::sync::Arc;

use tokio::sync::Mutex;
use tracing_subscriber::{
filter, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer,
};

mod e2e;
mod modifiers;

#[derive(Clone)]
pub struct TestScope {
pub initialized: Arc<Mutex<bool>>,
}

impl Default for TestScope {
fn default() -> Self {
Self {
initialized: Arc::new(Mutex::new(false)),
}
}
}

impl TestScope {
pub async fn init(self) {
let mut inited = self.initialized.lock().await;
if *inited == false {
if std::env::var("TEST_LOG").is_ok() {
let stdout_log = tracing_subscriber::fmt::layer().pretty();
tracing_subscriber::registry()
.with(
stdout_log
// Add an `INFO` filter to the stdout logging layer
.with_filter(filter::LevelFilter::DEBUG),
)
.init();
}
*inited = true;
}
}
}

lazy_static::lazy_static! {
static ref SCOPE: TestScope = TestScope::default();
static ref ADDRESS: String = String::from("http://127.0.0.1:38282");
}
135 changes: 135 additions & 0 deletions tests/integration/modifiers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use std::borrow::Cow;
use std::fs;
use std::path::PathBuf;

use reqwest_middleware::ClientBuilder;
use reqwest_middleware::ClientWithMiddleware;
use rvcr::VCRMiddleware;
use rvcr::VCRMode;
use vcr_cassette::{Request, Response};

// Replace access_token and secret header values with dummy ones
fn filter_query_params(mut uri: url::Url) -> url::Url {
let sensitive_query_params = ["access_token", "secret"];
let cloned = uri.clone();
let filtered_query_params = cloned.query_pairs().map(|(k, v)| {
if sensitive_query_params.contains(&k.as_ref()) {
(k.clone(), Cow::from(format!("__{}__", k.to_uppercase())))
} else {
(k, v)
}
});
uri.query_pairs_mut()
.clear()
.extend_pairs(filtered_query_params)
.finish();
uri
}

fn request_modifier(req: &mut Request) {
// Overwrite query params with filtered ones
req.uri = filter_query_params(req.uri.clone());
}

fn response_modifier(resp: &mut Response) {
for (name, value) in &mut resp.headers {
if name == "server" {
(*value).pop().unwrap();
(*value).push("Test Server Header Emulated Expect".to_string());
}
}
}

fn saved_fixture_path(path: &str) -> PathBuf {
let mut bundle = PathBuf::from(std::env::temp_dir());
bundle.push(path);

if bundle.exists() {
std::fs::remove_file(bundle.clone()).unwrap();
}
bundle
}

#[tokio::test]
pub async fn test_modifier_request() {
crate::SCOPE.clone().init().await;
let bundle = saved_fixture_path("request-modifer-test-case.vcr.json");

let middleware = VCRMiddleware::try_from(bundle.clone())
.unwrap()
.with_mode(VCRMode::Record)
.with_modify_request(request_modifier);

let vcr_client: ClientWithMiddleware = ClientBuilder::new(reqwest::Client::new())
.with(middleware)
.build();

vcr_client
.request(
reqwest::Method::POST,
format!(
"{}{}",
crate::ADDRESS.to_string(),
"/post?access_token=s3cr3t&spam=eggs&secret=s3cr3t",
),
)
.send()
.await
.expect("Can not send request");
// Drop triggers recording
drop(vcr_client);

let vcr_content = fs::read(bundle.clone()).expect("VCR file not created during test case");

let cassette: vcr_cassette::Cassette = serde_json::from_slice(&vcr_content).unwrap();
let interaction = cassette.http_interactions.first().unwrap();
let recorded_url = interaction.request.uri.to_string();
// Secret parameters were replaced
assert_eq!(
format!(
"{}/post?access_token=__ACCESS_TOKEN__&spam=eggs&secret=__SECRET__",
crate::ADDRESS.to_string()
),
recorded_url,
)
}

#[tokio::test]
pub async fn test_modifier_response() {
crate::SCOPE.clone().init().await;
let bundle = saved_fixture_path("response-modifer-test-case.vcr.json");
let middleware = VCRMiddleware::try_from(bundle.clone())
.unwrap()
.with_mode(VCRMode::Record)
.with_modify_response(response_modifier);

let vcr_client: ClientWithMiddleware = ClientBuilder::new(reqwest::Client::new())
.with(middleware)
.build();

vcr_client
.request(
reqwest::Method::POST,
format!("{}{}", crate::ADDRESS.to_string(), "/post",),
)
.send()
.await
.expect("Can not send request");
// Drop triggers recording
drop(vcr_client);

let vcr_content = fs::read(bundle.clone()).expect("VCR file not created during test case");

let cassette: vcr_cassette::Cassette = serde_json::from_slice(&vcr_content).unwrap();
let interaction = cassette.http_interactions.first().unwrap();
let recorded_server_header = interaction
.response
.headers
.get("server")
.unwrap()
.clone()
.pop()
.unwrap();
// Server header was replaced
assert_eq!(recorded_server_header, "Test Server Header Emulated Expect",)
}

0 comments on commit a3fe0c4

Please sign in to comment.