|
1 | 1 | use mockito::Server; |
2 | 2 | use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; |
3 | | -use reqwest_retry::policies::ExponentialBackoff; |
| 3 | +use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; |
4 | 4 | use serde::Serialize; |
5 | 5 | use serde_json::{json, Value}; |
6 | 6 | use std::sync::Arc; |
@@ -333,3 +333,169 @@ async fn test_rotate_url_connection_failure() { |
333 | 333 | &vec![invalid_url.to_string()] |
334 | 334 | ); |
335 | 335 | } |
| 336 | + |
| 337 | +#[tokio::test] |
| 338 | +async fn test_update_client() { |
| 339 | + let mut server = Server::new_async().await; |
| 340 | + |
| 341 | + // Set up two different responses to differentiate between clients |
| 342 | + let initial_mock = server |
| 343 | + .mock("POST", "/") |
| 344 | + .with_status(200) |
| 345 | + .with_header("content-type", "application/json") |
| 346 | + .with_body(r#"{"jsonrpc": "2.0", "result": "initial_client", "id": 1}"#) |
| 347 | + .expect(1) |
| 348 | + .create_async() |
| 349 | + .await; |
| 350 | + |
| 351 | + let mut manager = |
| 352 | + EndpointManager::new(get_mock_client_builder(), server.url().as_ref(), vec![]); |
| 353 | + |
| 354 | + // Test initial client |
| 355 | + let transport = MockTransport::new(); |
| 356 | + let initial_result = manager |
| 357 | + .send_raw_request(&transport, "test_method", Some(json!(["param1"]))) |
| 358 | + .await |
| 359 | + .unwrap(); |
| 360 | + assert_eq!(initial_result["result"], "initial_client"); |
| 361 | + initial_mock.assert(); |
| 362 | + |
| 363 | + // Set up mock for new client |
| 364 | + let updated_mock = server |
| 365 | + .mock("POST", "/") |
| 366 | + .with_status(200) |
| 367 | + .with_header("content-type", "application/json") |
| 368 | + .with_body(r#"{"jsonrpc": "2.0", "result": "updated_client", "id": 1}"#) |
| 369 | + .expect(1) |
| 370 | + .create_async() |
| 371 | + .await; |
| 372 | + |
| 373 | + // Create and update to new client with different configuration |
| 374 | + let new_client = ClientBuilder::new(reqwest::Client::new()) |
| 375 | + .with(RetryTransientMiddleware::new_with_policy( |
| 376 | + ExponentialBackoff::builder().build_with_max_retries(3), |
| 377 | + )) |
| 378 | + .build(); |
| 379 | + manager.update_client(new_client); |
| 380 | + |
| 381 | + // Test updated client |
| 382 | + let updated_result = manager |
| 383 | + .send_raw_request(&transport, "test_method", Some(json!(["param1"]))) |
| 384 | + .await |
| 385 | + .unwrap(); |
| 386 | + assert_eq!(updated_result["result"], "updated_client"); |
| 387 | + updated_mock.assert(); |
| 388 | +} |
| 389 | + |
| 390 | +#[tokio::test] |
| 391 | +async fn test_set_retry_policy() { |
| 392 | + let mut server = Server::new_async().await; |
| 393 | + |
| 394 | + // Set up a sequence of responses to test retry behavior |
| 395 | + let retry_mock = server |
| 396 | + .mock("POST", "/") |
| 397 | + .with_status(429) // Too Many Requests |
| 398 | + .with_body("Rate limited") |
| 399 | + .expect(2) // Expect 2 retries |
| 400 | + .create_async() |
| 401 | + .await; |
| 402 | + |
| 403 | + let success_mock = server |
| 404 | + .mock("POST", "/") |
| 405 | + .with_status(200) |
| 406 | + .with_header("content-type", "application/json") |
| 407 | + .with_body(r#"{"jsonrpc": "2.0", "result": "success_after_retry", "id": 1}"#) |
| 408 | + .expect(1) |
| 409 | + .create_async() |
| 410 | + .await; |
| 411 | + |
| 412 | + let mut manager = EndpointManager::new( |
| 413 | + get_mock_client_builder(), // Initial client with no retry policy |
| 414 | + server.url().as_ref(), |
| 415 | + vec![], |
| 416 | + ); |
| 417 | + |
| 418 | + // Set a custom retry policy with exactly 2 retries |
| 419 | + let retry_policy = ExponentialBackoff::builder().build_with_max_retries(2); |
| 420 | + |
| 421 | + manager.set_retry_policy(retry_policy, TransientErrorRetryStrategy); |
| 422 | + |
| 423 | + // Make request that should trigger retries |
| 424 | + let transport = MockTransport::new(); |
| 425 | + let result = manager |
| 426 | + .send_raw_request(&transport, "test_method", Some(json!(["param1"]))) |
| 427 | + .await |
| 428 | + .unwrap(); |
| 429 | + |
| 430 | + // Verify that we got the successful response after retries |
| 431 | + assert_eq!(result["result"], "success_after_retry"); |
| 432 | + |
| 433 | + // Verify that both mocks were called the expected number of times |
| 434 | + retry_mock.assert(); |
| 435 | + success_mock.assert(); |
| 436 | +} |
| 437 | + |
| 438 | +#[tokio::test] |
| 439 | +async fn test_send_raw_request_network_error() { |
| 440 | + // Set up with an invalid primary URL that will cause a network error |
| 441 | + let invalid_url = "http://invalid-domain-that-will-fail:12345"; |
| 442 | + let mut valid_server = Server::new_async().await; |
| 443 | + |
| 444 | + // Set up mock for fallback server |
| 445 | + let success_mock = valid_server |
| 446 | + .mock("POST", "/") |
| 447 | + .with_status(200) |
| 448 | + .with_header("content-type", "application/json") |
| 449 | + .with_body(r#"{"jsonrpc": "2.0", "result": "success", "id": 1}"#) |
| 450 | + .expect(1) |
| 451 | + .create_async() |
| 452 | + .await; |
| 453 | + |
| 454 | + let manager = EndpointManager::new( |
| 455 | + get_mock_client_builder(), |
| 456 | + invalid_url, |
| 457 | + vec![valid_server.url()], // Add valid fallback URL |
| 458 | + ); |
| 459 | + let transport = MockTransport::new(); |
| 460 | + |
| 461 | + // Send request - should fail first with network error, then rotate and succeed |
| 462 | + let result = manager |
| 463 | + .send_raw_request(&transport, "test_method", Some(json!(["param1"]))) |
| 464 | + .await; |
| 465 | + |
| 466 | + // Verify success after rotation |
| 467 | + assert!(result.is_ok()); |
| 468 | + let response = result.unwrap(); |
| 469 | + assert_eq!(response["result"], "success"); |
| 470 | + success_mock.assert(); |
| 471 | + |
| 472 | + // Verify URL rotation occurred |
| 473 | + assert_eq!(&*manager.active_url.read().await, &valid_server.url()); |
| 474 | +} |
| 475 | + |
| 476 | +#[tokio::test] |
| 477 | +async fn test_send_raw_request_network_error_no_fallback() { |
| 478 | + // Set up with an invalid URL and no fallbacks |
| 479 | + let invalid_url = "http://invalid-domain-that-will-fail:12345"; |
| 480 | + let manager = EndpointManager::new( |
| 481 | + get_mock_client_builder(), |
| 482 | + invalid_url, |
| 483 | + vec![], // No fallback URLs |
| 484 | + ); |
| 485 | + let transport = MockTransport::new(); |
| 486 | + |
| 487 | + // Send request - should fail with network error and no rotation possible |
| 488 | + let result = manager |
| 489 | + .send_raw_request(&transport, "test_method", Some(json!(["param1"]))) |
| 490 | + .await; |
| 491 | + |
| 492 | + // Verify error |
| 493 | + assert!(result.is_err()); |
| 494 | + assert!(result |
| 495 | + .unwrap_err() |
| 496 | + .to_string() |
| 497 | + .contains("Failed to send request")); |
| 498 | + |
| 499 | + // Verify URL didn't change |
| 500 | + assert_eq!(&*manager.active_url.read().await, invalid_url); |
| 501 | +} |
0 commit comments