Skip to content

Commit

Permalink
add tests for retrying 429 response status codes
Browse files Browse the repository at this point in the history
  • Loading branch information
evanharmon committed May 23, 2024
1 parent 5ccc49c commit 92cd6da
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 3 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Versioning].

## [Unreleased] <!-- #release:date -->

* Automatically retry HTTP requests on status code 429.
* Automatically retry HTTP requests that return status code 429. (too many requests)

## [0.11.0] - 2024-03-29

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ tokio = { version = "1.23.0", features = ["macros"] }
tokio-stream = "0.1.11"
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
wiremock = "0.5.19"

[package.metadata.docs.rs]
all-features = true
Expand Down
3 changes: 2 additions & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ impl Client {
url.path_segments_mut()
.expect("builder validated URL can be a base")
.extend(path);
// All request methods and paths are assumed to be retryable.
// All request methods and paths are included to support retries for
// 429 status code.
self.client_retryable
.request(method, url)
.bearer_auth(&self.api_key)
Expand Down
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ impl RetryableStrategy for Retry429 {
impl ClientBuilder {
/// Sets the policy for retrying failed API calls.
///
/// Note that the created [`Client`] will retry all API calls.
/// Note that the created [`Client`] will retry all API calls that return a 429 status code.
pub fn with_retry_policy(mut self, policy: ExponentialBackoff) -> Self {
self.retry_policy = Some(policy);
self
Expand Down
44 changes: 44 additions & 0 deletions tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ use futures::stream::TryStreamExt;
use once_cell::sync::Lazy;
use rand::Rng;
use reqwest::StatusCode;
use reqwest_retry::policies::ExponentialBackoff;
use test_log::test;
use tokio::time::{self, Duration};
use tracing::info;
use wiremock::{matchers, Mock, MockServer, ResponseTemplate};

use orb_billing::{
AddIncrementCreditLedgerEntryRequestParams, AddVoidCreditLedgerEntryRequestParams, Address,
Expand Down Expand Up @@ -769,3 +771,45 @@ async fn test_errors() {
let res = client.get_customer_by_external_id("$NOEXIST$").await;
assert_error_with_status_code(res, StatusCode::NOT_FOUND);
}

// Tests that 429 responses are retried automatically by the client for API calls
#[test(tokio::test)]
async fn test_retry_429() {
// Start a mock orb API server and a client configured to target that
// server. The retry policy disables backoff to speed up the tests.
const MAX_RETRIES: u32 = 3;
let server = MockServer::start().await;
let client = Client::builder()
.with_endpoint(server.uri().parse().unwrap())
.with_retry_policy(
ExponentialBackoff::builder()
.retry_bounds(Duration::from_millis(1), Duration::from_millis(1))
.build_with_max_retries(MAX_RETRIES),
)
.build(ClientConfig { api_key: "".into() });

// register a mock for the /customers endpoint that returns a 429 response
// code. Ensure the client repeatedly retries the API call until giving
// up after `MAX_RETRIES` attempts and returning the error.
let mock = Mock::given(matchers::method("POST"))
.and(matchers::path("/customers"))
.respond_with(ResponseTemplate::new(429))
.expect(u64::from(MAX_RETRIES) + 1)
.named("put customers");
server.register(mock).await;
let customer_idx = 0;
let res = client
.create_customer(&CreateCustomerRequest {
name: &format!("{TEST_PREFIX}-{customer_idx}"),
email: &format!("orb-testing-{customer_idx}@materialize.com"),
external_id: None,
payment_provider: Some(CustomerPaymentProviderRequest {
kind: PaymentProvider::Stripe,
id: &format!("cus_fake_{customer_idx}"),
}),
..Default::default()
})
.await;

assert!(res.is_err());
}

0 comments on commit 92cd6da

Please sign in to comment.