175 lines
8.2 KiB
Rust
175 lines
8.2 KiB
Rust
// file: crates/ksp-onchain-transport-lib/unit_tests/pool.rs
|
|
// version: 2
|
|
|
|
fn role(name: &str, priority: u32, request_kinds: std::vec::Vec<crate::HttpRequestKind>) -> crate::HttpEndpointRoleSettings {
|
|
return crate::HttpEndpointRoleSettings::new(
|
|
crate::HttpRoleName::new(name),
|
|
true,
|
|
request_kinds,
|
|
priority,
|
|
crate::HttpRoleLimits::new(std::option::Option::None, std::option::Option::None, std::option::Option::None, std::option::Option::None),
|
|
);
|
|
}
|
|
|
|
fn endpoint(name: &str, enabled: bool, priority: u32, request_kinds: std::vec::Vec<crate::HttpRequestKind>) -> crate::HttpEndpointSettings {
|
|
return crate::HttpEndpointSettings::new(
|
|
name,
|
|
enabled,
|
|
crate::HttpProviderName::new("provider"),
|
|
crate::HttpClusterName::new("devnet"),
|
|
crate::HttpEndpointUrl::parse(format!("https://{name}.invalid/rpc?token=SECRET-CANARY")).expect("test URL must parse"),
|
|
std::time::Duration::from_secs(1),
|
|
std::time::Duration::from_secs(2),
|
|
std::option::Option::Some(4),
|
|
std::vec![role("default", priority, request_kinds)],
|
|
);
|
|
}
|
|
|
|
fn settings(endpoints: std::vec::Vec<crate::HttpEndpointSettings>) -> crate::HttpTransportSettings {
|
|
return crate::HttpTransportSettings::new(
|
|
endpoints,
|
|
crate::HttpRetrySettings::new(2, std::time::Duration::from_millis(10), std::time::Duration::from_millis(50)),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn pool_prefers_lowest_priority_tier() {
|
|
let pool = super::HttpTransportPool::new(settings(std::vec![
|
|
endpoint("secondary", true, 20, std::vec![crate::HttpRequestKind::wildcard()]),
|
|
endpoint("primary", true, 10, std::vec![crate::HttpRequestKind::wildcard()]),
|
|
]))
|
|
.expect("pool must build");
|
|
let selection = pool
|
|
.select_for_request_kind(&crate::HttpRoleName::new("default"), &crate::HttpRequestKind::new("get_balance"))
|
|
.expect("selection must succeed");
|
|
assert_eq!(selection.endpoint_name(), "primary");
|
|
assert_eq!(selection.priority(), 10);
|
|
}
|
|
|
|
#[test]
|
|
fn pool_round_robins_fairly_inside_best_priority_tier() {
|
|
let pool = super::HttpTransportPool::new(settings(std::vec![
|
|
endpoint("one", true, 10, std::vec![crate::HttpRequestKind::wildcard()]),
|
|
endpoint("two", true, 10, std::vec![crate::HttpRequestKind::wildcard()]),
|
|
endpoint("fallback", true, 20, std::vec![crate::HttpRequestKind::wildcard()]),
|
|
]))
|
|
.expect("pool must build");
|
|
let role = crate::HttpRoleName::new("default");
|
|
let kind = crate::HttpRequestKind::new("get_balance");
|
|
let first = pool.select_for_request_kind(&role, &kind).expect("first selection must succeed");
|
|
let second = pool.select_for_request_kind(&role, &kind).expect("second selection must succeed");
|
|
let third = pool.select_for_request_kind(&role, &kind).expect("third selection must succeed");
|
|
assert_eq!(first.endpoint_name(), "one");
|
|
assert_eq!(second.endpoint_name(), "two");
|
|
assert_eq!(third.endpoint_name(), "one");
|
|
}
|
|
|
|
#[test]
|
|
fn disabled_best_priority_endpoint_falls_back_to_next_tier() {
|
|
let pool = super::HttpTransportPool::new(settings(std::vec![
|
|
endpoint("disabled-primary", false, 1, std::vec![crate::HttpRequestKind::wildcard()]),
|
|
endpoint("fallback", true, 20, std::vec![crate::HttpRequestKind::wildcard()]),
|
|
]))
|
|
.expect("pool must build");
|
|
let selection = pool
|
|
.select_for_request_kind(&crate::HttpRoleName::new("default"), &crate::HttpRequestKind::new("get_balance"))
|
|
.expect("fallback must be selected");
|
|
assert_eq!(selection.endpoint_name(), "fallback");
|
|
}
|
|
|
|
#[test]
|
|
fn pool_filters_role_and_capability_before_priority() {
|
|
let pool = super::HttpTransportPool::new(settings(std::vec![
|
|
endpoint("wrong-capability", true, 1, std::vec![crate::HttpRequestKind::new("send_transaction")]),
|
|
endpoint("matching", true, 50, std::vec![crate::HttpRequestKind::new("get_balance")]),
|
|
]))
|
|
.expect("pool must build");
|
|
let selection = pool
|
|
.select_for_request_kind(&crate::HttpRoleName::new("default"), &crate::HttpRequestKind::new("get_balance"))
|
|
.expect("matching capability must be selected");
|
|
assert_eq!(selection.endpoint_name(), "matching");
|
|
}
|
|
|
|
#[test]
|
|
fn pool_returns_structured_error_when_no_endpoint_matches() {
|
|
let pool = super::HttpTransportPool::new(settings(std::vec![endpoint("read-only", true, 10, std::vec![crate::HttpRequestKind::new("get_balance")],)]))
|
|
.expect("pool must build");
|
|
let error = pool
|
|
.select_for_request_kind(&crate::HttpRoleName::new("default"), &crate::HttpRequestKind::new("send_transaction"))
|
|
.expect_err("unsupported request kind must fail selection");
|
|
assert_eq!(error.code(), crate::ERROR_CODE_ENDPOINT_SELECTION_FAILED);
|
|
assert!(!format!("{error:?}").contains("SECRET-CANARY"));
|
|
}
|
|
|
|
#[test]
|
|
fn standard_method_selection_uses_registry_request_kind() {
|
|
let pool = super::HttpTransportPool::new(settings(std::vec![endpoint("balance", true, 10, std::vec![crate::HttpRequestKind::new("get_balance")],)]))
|
|
.expect("pool must build");
|
|
let method = crate::find_http_rpc_method("getBalance").expect("audited method must exist");
|
|
let selection = pool.select_for_method(&crate::HttpRoleName::new("default"), method).expect("standard method must route");
|
|
assert_eq!(selection.request_kind().as_str(), "get_balance");
|
|
}
|
|
|
|
#[test]
|
|
fn pool_snapshot_is_safe_and_preserves_disabled_endpoints() {
|
|
let pool = super::HttpTransportPool::new(settings(std::vec![
|
|
endpoint("enabled", true, 10, std::vec![crate::HttpRequestKind::wildcard()]),
|
|
endpoint("disabled", false, 10, std::vec![crate::HttpRequestKind::wildcard()]),
|
|
]))
|
|
.expect("pool must build");
|
|
let snapshot = pool.snapshot();
|
|
let rendered = format!("{snapshot:?} {pool:?}");
|
|
assert_eq!(snapshot.endpoint_count(), 2);
|
|
assert_eq!(snapshot.available_endpoint_count(), 1);
|
|
assert!(!rendered.contains("SECRET-CANARY"));
|
|
assert!(!rendered.contains(".invalid/rpc"));
|
|
}
|
|
|
|
#[test]
|
|
fn disabled_role_is_excluded_before_priority_selection() {
|
|
let base = endpoint("disabled-role", true, 1, std::vec![crate::HttpRequestKind::wildcard()]);
|
|
let disabled_role = crate::HttpEndpointRoleSettings::new(
|
|
crate::HttpRoleName::new("default"),
|
|
false,
|
|
std::vec![crate::HttpRequestKind::wildcard()],
|
|
1,
|
|
crate::HttpRoleLimits::new(std::option::Option::None, std::option::Option::None, std::option::Option::None, std::option::Option::None),
|
|
);
|
|
let enabled_non_matching_role = crate::HttpEndpointRoleSettings::new(
|
|
crate::HttpRoleName::new("maintenance"),
|
|
true,
|
|
std::vec![crate::HttpRequestKind::wildcard()],
|
|
1,
|
|
crate::HttpRoleLimits::new(std::option::Option::None, std::option::Option::None, std::option::Option::None, std::option::Option::None),
|
|
);
|
|
let disabled_role_endpoint = crate::HttpEndpointSettings::new(
|
|
base.name(),
|
|
true,
|
|
base.provider().clone(),
|
|
base.cluster().clone(),
|
|
base.url().clone(),
|
|
base.connect_timeout(),
|
|
base.request_timeout(),
|
|
base.max_idle_connections_per_host(),
|
|
std::vec![disabled_role, enabled_non_matching_role],
|
|
);
|
|
let pool = super::HttpTransportPool::new(settings(std::vec![
|
|
disabled_role_endpoint,
|
|
endpoint("fallback", true, 20, std::vec![crate::HttpRequestKind::wildcard()]),
|
|
]))
|
|
.expect("pool must build");
|
|
let selection = pool
|
|
.select_for_request_kind(&crate::HttpRoleName::new("default"), &crate::HttpRequestKind::new("get_balance"))
|
|
.expect("enabled fallback role must be selected");
|
|
assert_eq!(selection.endpoint_name(), "fallback");
|
|
}
|
|
|
|
#[test]
|
|
fn removed_standard_method_is_rejected_before_endpoint_routing() {
|
|
let pool = super::HttpTransportPool::new(settings(std::vec![endpoint("wildcard", true, 10, std::vec![crate::HttpRequestKind::wildcard()],)]))
|
|
.expect("pool must build");
|
|
let method = crate::find_http_rpc_method("confirmTransaction").expect("historical method must exist");
|
|
let error = pool.select_for_method(&crate::HttpRoleName::new("default"), method).expect_err("removed standard method must be rejected before routing");
|
|
assert_eq!(error.code(), crate::ERROR_CODE_METHOD_REMOVED);
|
|
}
|