#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::disallowed_methods,
clippy::disallowed_macros,
clippy::err_expect,
clippy::print_stdout,
clippy::useless_conversion
)]
mod support;
use std::time::{Duration, Instant};
use agent_first_http::sdk::Client;
use agent_first_http::sdk::fetch::RenderMode;
use agent_first_http::shared::artifacts::Artifact;
use agent_first_http::shared::error::ErrorCode;
const UNREACHABLE_URL: &str = "http://127.0.0.1:1/will-fail";
#[tokio::test]
async fn retry_zero_runs_a_single_attempt() {
let tmpdir = tempfile::tempdir().expect("tempdir");
let client = Client::connect("ws://localhost:9999").expect("client");
let start = Instant::now();
let err = client
.fetch(UNREACHABLE_URL)
.render(RenderMode::None)
.want([Artifact::Body])
.out_dir(tmpdir.path().to_path_buf())
.send()
.await
.err()
.expect("fetch should fail");
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_millis(1500),
"single-attempt fetch took {elapsed:?}, retry not configured but loop kicked in?",
);
assert!(matches!(
err.error_code,
ErrorCode::HostUnreachable | ErrorCode::TargetUnreachable
));
}
#[tokio::test]
async fn retry_with_fixed_backoff_inserts_delays_between_attempts() {
let tmpdir = tempfile::tempdir().expect("tempdir");
let client = Client::connect("ws://localhost:9999").expect("client");
let start = Instant::now();
let err = client
.fetch(UNREACHABLE_URL)
.render(RenderMode::None)
.retry(2)
.backoff_ms(150)
.want([Artifact::Body])
.out_dir(tmpdir.path().to_path_buf())
.send()
.await
.err()
.expect("fetch should still fail after retries");
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(300),
"fetch with retry=2 backoff=150ms took only {elapsed:?}; the loop is not actually retrying",
);
assert!(matches!(
err.error_code,
ErrorCode::HostUnreachable | ErrorCode::TargetUnreachable
));
assert!(err.retryable);
}
#[tokio::test]
async fn retry_does_not_fire_for_non_retryable_errors() {
let tmpdir = tempfile::tempdir().expect("tempdir");
let client = Client::connect("ws://localhost:9999").expect("client");
let start = Instant::now();
let err = client
.fetch(UNREACHABLE_URL)
.render(RenderMode::None)
.retry(3)
.backoff_ms(500)
.header("User-Agent", "from-header/1")
.user_agent("from-method/1")
.want([Artifact::Body])
.out_dir(tmpdir.path().to_path_buf())
.send()
.await
.err()
.expect("should error before any retry");
let elapsed = start.elapsed();
assert_eq!(err.error_code, ErrorCode::InvalidArgument);
assert!(
elapsed < Duration::from_millis(400),
"non-retryable error took {elapsed:?}; the retry loop fired anyway",
);
}