Skip to content

Commit

Permalink
Test correlation ID
Browse files Browse the repository at this point in the history
  • Loading branch information
Flix committed Aug 24, 2024
1 parent f74e42f commit 59f7b57
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 6 deletions.
12 changes: 6 additions & 6 deletions crates/fhir-sdk/src/client/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl RequestSettings {
/// Make a HTTP request using the settings. Returns the response.
///
/// It is recommended to set the `X-Correlation-Id` header outside, for a whole transaction.
#[tracing::instrument(level = "debug", skip_all, fields(x_request_id, x_correlation_id))]
#[tracing::instrument(level = "debug", skip_all, fields(x_correlation_id, x_request_id))]
pub(crate) async fn make_request(
&self,
mut request: reqwest::RequestBuilder,
Expand All @@ -108,8 +108,8 @@ impl RequestSettings {
#[allow(clippy::expect_used)] // Will not fail.
let id_value = HeaderValue::from_str(&Uuid::new_v4().to_string())
.expect("UUIDs are valid header values");
request.headers_mut().entry("X-Request-Id").or_insert_with(|| id_value.clone());
request.headers_mut().entry("X-Correlation-Id").or_insert(id_value);
request.headers_mut().entry("X-Correlation-Id").or_insert_with(|| id_value.clone());
request.headers_mut().entry("X-Request-Id").or_insert(id_value);

// Construct the dynamic retry strategy iterator.
let strategy: Box<dyn Iterator<Item = Duration> + Send + Sync> = if self.exp_backoff {
Expand All @@ -124,12 +124,12 @@ impl RequestSettings {
};

// Fill in tracing spans to log the informational/correlational headers.
let x_request_id = request.headers().get("X-Request-Id").and_then(|v| v.to_str().ok());
let x_correlation_id =
request.headers().get("X-Correlation-Id").and_then(|v| v.to_str().ok());
let x_request_id = request.headers().get("X-Request-Id").and_then(|v| v.to_str().ok());
tracing::Span::current()
.record("x_request_id", x_request_id)
.record("x_correlation_id", x_correlation_id);
.record("x_correlation_id", x_correlation_id)
.record("x_request_id", x_request_id);

// Send the request, but retry on specific failures.
RetryIf::spawn(
Expand Down
79 changes: 79 additions & 0 deletions crates/fhir-sdk/src/client/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,26 @@ use std::{
time::Duration,
};

use ::tokio::sync::OnceCell;
use header::HeaderValue;
use reqwest::Method;
use serde_json::json;
use wiremock::{matchers, Mock, MockServer, ResponseTemplate};

use super::*;

/// Set up logging for the tests.
pub async fn setup_logging() {
static ONCE: OnceCell<()> = OnceCell::const_new();
ONCE.get_or_init(|| async {
tracing_subscriber::fmt::fmt()
.with_test_writer()
.with_env_filter("DEBUG,hyper=error,reqwest=info")
.init();
})
.await;
}

async fn mock_custom_request() -> MockServer {
let server = MockServer::start().await;

Expand Down Expand Up @@ -52,6 +65,7 @@ async fn mock_custom_request() -> MockServer {

#[tokio::test]
async fn send_custom_request_features() -> anyhow::Result<()> {
setup_logging().await;
let mocks = mock_custom_request().await;

let counter = Arc::new(AtomicUsize::new(0));
Expand Down Expand Up @@ -111,6 +125,7 @@ async fn mock_version_mismatch() -> MockServer {

#[tokio::test]
async fn check_major_fhir_version() -> anyhow::Result<()> {
setup_logging().await;
let mocks = mock_version_mismatch().await;

let client = <Client>::builder().base_url(Url::parse(&mocks.uri())?).build()?;
Expand All @@ -134,6 +149,7 @@ async fn check_major_fhir_version() -> anyhow::Result<()> {

#[tokio::test]
async fn check_url_origin() -> anyhow::Result<()> {
setup_logging().await;
let mocks = mock_version_mismatch().await;

let client = <Client>::builder().base_url("http://localhost/".parse()?).build()?;
Expand All @@ -154,3 +170,66 @@ async fn check_url_origin() -> anyhow::Result<()> {

Ok(())
}


async fn mock_x_correlation_id() -> MockServer {
let server = MockServer::start().await;

Mock::given(matchers::method(Method::GET))
.and(matchers::header("X-Correlation-Id", "custom-id"))
.respond_with(ResponseTemplate::new(StatusCode::UNAUTHORIZED))
.with_priority(1)
.named("Custom correlation ID failure retry")
.expect(1)
.up_to_n_times(1)
.mount(&server)
.await;

Mock::given(matchers::method(Method::GET))
.and(matchers::header("X-Correlation-Id", "custom-id"))
.respond_with(ResponseTemplate::new(StatusCode::OK))
.with_priority(2)
.named("Custom correlation ID success")
.expect(1)
.up_to_n_times(1)
.mount(&server)
.await;

Mock::given(matchers::method(Method::GET))
.and(matchers::header_exists("X-Correlation-Id"))
.respond_with(ResponseTemplate::new(StatusCode::OK))
.with_priority(100)
.named("Any correlation ID success")
.expect(1)
.up_to_n_times(1)
.mount(&server)
.await;

server
}

#[tokio::test]
async fn use_correlation_id() -> anyhow::Result<()> {
setup_logging().await;
let mocks = mock_x_correlation_id().await;

let client = <Client>::builder()
.base_url(Url::parse(&mocks.uri())?)
.auth_callback(move |_http: reqwest::Client| {
std::future::ready(anyhow::Ok(HeaderValue::from_static("Bearer <mock>")))
})
.build()?;

let url = client.base_url();

let response = client
.send_custom_request(|http| http.get(url.clone()).header("X-Correlation-Id", "custom-id"))
.await?;
assert_eq!(response.status(), StatusCode::OK);

let response = client.send_custom_request(|http| http.get(url.clone())).await?;
assert_eq!(response.status(), StatusCode::OK);

mocks.verify().await;
Ok(())
}

0 comments on commit 59f7b57

Please sign in to comment.