mod common;
use std::time::Duration;
use technitium::error::RequestParams;
use technitium::{Client, Error, RetryPolicy};
#[test]
fn compute_delay_within_bounds() {
let policy = RetryPolicy {
max_attempts: 10,
base_delay: Duration::from_secs(1),
max_delay: Duration::from_secs(30),
};
for attempt in 0..10 {
let delay = policy.compute_delay(attempt);
let max_expected = Duration::from_secs(30);
assert!(
delay <= max_expected,
"attempt {attempt}: delay {delay:?} exceeds max {max_expected:?}"
);
}
}
#[test]
fn compute_delay_never_exceeds_max() {
let policy = RetryPolicy {
max_attempts: 5,
base_delay: Duration::from_millis(100),
max_delay: Duration::from_millis(500),
};
for _ in 0..100 {
for attempt in 0..20 {
let delay = policy.compute_delay(attempt);
assert!(
delay <= Duration::from_millis(500),
"delay {delay:?} exceeds max_delay"
);
}
}
}
#[test]
fn is_retryable_classification() {
let auth = Error::Authentication {
message: "bad creds".to_string(),
};
assert!(!auth.is_retryable(), "auth should not be retryable");
assert!(
!Error::InvalidToken.is_retryable(),
"invalid token should not be retryable"
);
assert!(
!Error::TwoFactorRequired.is_retryable(),
"2fa should not be retryable"
);
let server = Error::Server {
message: "not found".to_string(),
status_code: Some(404),
path: "/api/test".to_string(),
params: RequestParams::default(),
};
assert!(
!server.is_retryable(),
"server error should not be retryable"
);
let config = Error::Config {
reason: "bad url".to_string(),
};
assert!(
!config.is_retryable(),
"config error should not be retryable"
);
}
#[tokio::test]
#[ignore = "requires running Technitium server"]
async fn retry_does_not_break_normal_flow() {
let client = Client::builder()
.base_url(common::server_url())
.retry_policy(RetryPolicy::default())
.build()
.expect("build client");
client
.login(&common::admin_username(), &common::admin_password())
.await
.expect("login should succeed on first try");
let zones = client.list_zones().await;
assert!(
zones.is_ok(),
"list_zones should succeed with retry enabled"
);
}
#[tokio::test]
#[ignore = "requires running Technitium server"]
async fn retry_respects_timeout() {
let client = Client::builder()
.base_url("http://192.0.2.1:5380")
.request_timeout(Duration::from_millis(500))
.connect_timeout(Duration::from_millis(200))
.retry_policy(RetryPolicy {
max_attempts: 10,
base_delay: Duration::from_millis(50),
max_delay: Duration::from_secs(1),
})
.build()
.expect("build client");
let start = std::time::Instant::now();
let result = client.login("admin", "admin").await;
let elapsed = start.elapsed();
assert!(result.is_err(), "should fail");
assert!(
elapsed < Duration::from_secs(5),
"total time should be bounded, took {elapsed:?}"
);
}