use std::time::Duration;
use crate::{
driver::{
pipeline::{
components::{RoutingDecision, TransportMode},
hedging_diagnostics::HedgingStrategyConfig,
},
routing::AccountEndpointState,
},
models::{CosmosOperation, OperationType, ResourceType},
options::{
AvailabilityStrategy, HedgeThreshold, HedgingStrategy, OperationOptionsView, Region,
},
};
const DEFAULT_THRESHOLD_CAP: Duration = Duration::from_millis(1000);
const HEDGEABLE_RESOURCE_TYPES: &[ResourceType] = &[ResourceType::Document];
const HEDGEABLE_OPERATION_TYPES: &[OperationType] = &[OperationType::Read];
pub(crate) fn should_hedge(
strategy: Option<&HedgingStrategy>,
operation: &CosmosOperation,
account_state: &AccountEndpointState,
excluded_regions: &[Region],
) -> bool {
if strategy.is_none() {
return false;
}
if account_state.preferred_read_endpoints.is_empty() {
return false;
}
if !HEDGEABLE_RESOURCE_TYPES.contains(&operation.resource_type()) {
return false;
}
let op = operation.operation_type();
if !op.is_read_only() {
return false;
}
if !HEDGEABLE_OPERATION_TYPES.contains(&op) {
return false;
}
let applicable = account_state
.preferred_read_endpoints
.iter()
.filter(|ep| match ep.region() {
Some(r) => !excluded_regions.contains(r),
None => false,
})
.count();
applicable >= 2
}
pub(crate) fn resolve_availability_strategy(
view: &OperationOptionsView<'_>,
request_timeout: Option<Duration>,
) -> Option<HedgingStrategy> {
match view.hedging_enabled() {
Some(&false) => return None,
Some(&true) => {
return match view.availability_strategy() {
Some(AvailabilityStrategy::Hedging(s)) => Some(*s),
_ => Some(HedgingStrategy::new(default_threshold(request_timeout))),
};
}
None => {}
}
match view.availability_strategy() {
Some(AvailabilityStrategy::Disabled) => return None,
Some(AvailabilityStrategy::Hedging(s)) => return Some(*s),
None => {}
}
Some(HedgingStrategy::new(default_threshold(request_timeout)))
}
fn default_threshold(request_timeout: Option<Duration>) -> HedgeThreshold {
let candidate = match request_timeout {
Some(t) if !t.is_zero() => (t / 2).min(DEFAULT_THRESHOLD_CAP),
_ => DEFAULT_THRESHOLD_CAP,
};
HedgeThreshold::new(candidate)
.or_else(|| request_timeout.and_then(HedgeThreshold::new))
.unwrap_or_else(|| {
HedgeThreshold::new(DEFAULT_THRESHOLD_CAP)
.expect("DEFAULT_THRESHOLD_CAP is statically non-zero")
})
}
#[derive(Debug)]
pub(crate) struct HedgeUpgrade {
pub(crate) secondary_routing: RoutingDecision,
pub(crate) threshold: HedgeThreshold,
pub(crate) strategy_config: HedgingStrategyConfig,
}
pub(crate) fn evaluate_hedge_eligibility(
operation: &CosmosOperation,
options: &OperationOptionsView<'_>,
account_state: &AccountEndpointState,
primary: &RoutingDecision,
request_timeout: Option<Duration>,
) -> Option<HedgeUpgrade> {
let strategy = resolve_availability_strategy(options, request_timeout)?;
let user_excluded: Vec<Region> = options
.excluded_regions()
.map(|r| r.0.clone())
.unwrap_or_default();
if !should_hedge(Some(&strategy), operation, account_state, &user_excluded) {
return None;
}
let applicable_regions: Vec<Region> = account_state
.preferred_read_endpoints
.iter()
.filter_map(|ep| ep.region().cloned())
.filter(|r| !user_excluded.contains(r))
.collect();
if applicable_regions.len() < 2 {
return None;
}
let primary_region = primary.endpoint.region().cloned();
let primary_key = primary.endpoint.endpoint_key();
let secondary_ep = account_state
.preferred_read_endpoints
.iter()
.filter(|ep| ep.region().is_some_and(|r| !user_excluded.contains(r)))
.find(|ep| ep.region() != primary_region.as_ref() && ep.endpoint_key() != primary_key)?
.clone();
let prefer_gateway20 = matches!(primary.transport_mode, TransportMode::Gateway20);
let use_gateway20 = secondary_ep.uses_gateway20(prefer_gateway20);
let transport_mode = if use_gateway20 {
TransportMode::Gateway20
} else {
TransportMode::Gateway
};
let secondary_routing = RoutingDecision {
selected_url: secondary_ep.selected_url(use_gateway20).clone(),
transport_mode,
endpoint: secondary_ep,
};
Some(HedgeUpgrade {
secondary_routing,
threshold: strategy.threshold(),
strategy_config: HedgingStrategyConfig::new(strategy.threshold()),
})
}
#[cfg(test)]
mod tests {
use super::*;
use azure_core::http::StatusCode;
use url::Url;
use crate::{
driver::routing::{AccountEndpointState, CosmosEndpoint},
models::{
AccountReference, ContainerProperties, ContainerReference, CosmosOperation,
CosmosStatus, DatabaseReference, ItemReference, PartitionKey, PartitionKeyDefinition,
SystemProperties,
},
options::{
AvailabilityStrategy, ExcludedRegions, HedgeThreshold, HedgingStrategy,
OperationOptions, OperationOptionsBuilder, OperationOptionsView, Region,
},
};
fn endpoint_for(region: Region) -> CosmosEndpoint {
let url = Url::parse(&format!(
"https://acct-{}.documents.azure.com/",
region.as_str()
))
.expect("valid url");
CosmosEndpoint::regional(region, url)
}
fn account_state_with_regions(regions: &[Region]) -> AccountEndpointState {
let endpoints: Vec<CosmosEndpoint> = regions.iter().cloned().map(endpoint_for).collect();
let default = endpoints.first().cloned().unwrap_or_else(|| {
CosmosEndpoint::global(Url::parse("https://acct.example/").unwrap())
});
AccountEndpointState {
generation: 0,
preferred_read_endpoints: endpoints.clone().into(),
preferred_write_endpoints: endpoints.into(),
unavailable_endpoints: Default::default(),
multiple_write_locations_enabled: false,
default_endpoint: default,
}
}
fn fake_container_reference() -> ContainerReference {
let account = AccountReference::with_master_key(
Url::parse("https://acct.documents.azure.com/").unwrap(),
"k",
);
let container_props = ContainerProperties {
id: std::borrow::Cow::Borrowed("c"),
partition_key: PartitionKeyDefinition::new(vec![std::borrow::Cow::Borrowed("/pk")]),
system_properties: SystemProperties::default(),
};
ContainerReference::new(account, "db", "db_rid", "c", "c_rid", &container_props)
}
fn read_item_operation() -> CosmosOperation {
let container = fake_container_reference();
let item = ItemReference::from_name(&container, PartitionKey::from("pk"), "id");
CosmosOperation::read_item(item)
}
fn create_item_operation() -> CosmosOperation {
let container = fake_container_reference();
let item = ItemReference::from_name(&container, PartitionKey::from("pk"), "id");
CosmosOperation::create_item(item)
}
fn read_database_operation() -> CosmosOperation {
let account = AccountReference::with_master_key(
Url::parse("https://acct.documents.azure.com/").unwrap(),
"k",
);
let db = DatabaseReference::from_name(account, "db");
CosmosOperation::read_database(db)
}
fn enabled_strategy() -> HedgingStrategy {
HedgingStrategy::new(HedgeThreshold::new(Duration::from_millis(500)).unwrap())
}
fn status(code: u16, sub: Option<u32>) -> CosmosStatus {
let mut s = CosmosStatus::new(StatusCode::from(code));
if let Some(v) = sub {
s = s.with_sub_status(v as u16);
}
s
}
#[test]
fn should_hedge_read_multi_region() {
let state = account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2]);
let op = read_item_operation();
assert!(should_hedge(Some(&enabled_strategy()), &op, &state, &[],));
}
#[test]
fn should_hedge_read_single_region() {
let state = account_state_with_regions(&[Region::EAST_US]);
let op = read_item_operation();
assert!(!should_hedge(Some(&enabled_strategy()), &op, &state, &[],));
}
#[test]
fn should_hedge_excluded_to_one_region() {
let state = account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2]);
let op = read_item_operation();
let excluded = [Region::WEST_US_2];
assert!(!should_hedge(
Some(&enabled_strategy()),
&op,
&state,
&excluded,
));
}
#[test]
fn should_hedge_no_preferred_regions() {
let state = account_state_with_regions(&[]);
let op = read_item_operation();
assert!(!should_hedge(Some(&enabled_strategy()), &op, &state, &[],));
}
#[test]
fn should_hedge_write_never() {
let state = account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2]);
let op = create_item_operation();
assert!(!should_hedge(Some(&enabled_strategy()), &op, &state, &[],));
}
#[test]
fn should_hedge_non_document() {
let state = account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2]);
let op = read_database_operation();
assert!(!should_hedge(Some(&enabled_strategy()), &op, &state, &[],));
}
#[test]
fn should_hedge_disabled_override() {
let state = account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2]);
let op = read_item_operation();
assert!(!should_hedge(None, &op, &state, &[]));
}
#[test]
fn is_final_result_success() {
assert!(status(200, None).is_final_result());
assert!(status(201, None).is_final_result());
assert!(status(304, None).is_final_result());
}
#[test]
fn is_final_result_conflict() {
assert!(status(409, None).is_final_result());
}
#[test]
fn is_final_result_503() {
assert!(!status(503, None).is_final_result());
}
#[test]
fn is_final_result_404_0() {
assert!(status(404, None).is_final_result());
assert!(status(404, Some(0)).is_final_result());
}
#[test]
fn is_final_result_404_1002() {
assert!(!status(404, Some(1002)).is_final_result());
}
#[test]
fn is_final_result_429() {
assert!(!status(429, None).is_final_result());
assert!(!status(429, Some(3092)).is_final_result());
}
#[test]
fn is_final_result_429_ru_budget_and_hot_partition_are_final() {
assert!(status(429, Some(3200)).is_final_result()); assert!(status(429, Some(3210)).is_final_result()); assert!(status(429, Some(3214)).is_final_result()); }
#[test]
fn is_final_result_403_is_final_regardless_of_sub_status() {
assert!(status(403, None).is_final_result());
assert!(status(403, Some(3)).is_final_result()); assert!(!status(403, Some(1008)).is_final_result()); assert!(status(403, Some(5)).is_final_result()); }
#[test]
fn is_final_result_protocol_and_policy_codes_are_final() {
for code in [422_u16, 451, 501, 505] {
assert!(
status(code, None).is_final_result(),
"expected {} to be final",
code
);
}
}
#[test]
fn is_final_result_generic_5xx_remain_retriable() {
assert!(!status(500, None).is_final_result());
assert!(!status(502, None).is_final_result());
assert!(!status(504, None).is_final_result());
}
#[test]
fn is_final_result_other_client_errors_final() {
for code in [400_u16, 401, 405, 412, 413] {
assert!(
status(code, None).is_final_result(),
"expected {} to be final",
code
);
}
}
fn empty_view<'a>(op: &'a OperationOptions) -> OperationOptionsView<'a> {
OperationOptionsView::new(None, None, None, Some(op))
}
#[test]
fn resolve_returns_driver_default_when_nothing_set() {
let op = OperationOptions::default();
let view = empty_view(&op);
let strategy = resolve_availability_strategy(&view, None).expect("driver default is Some");
assert_eq!(strategy.threshold().get(), Duration::from_millis(1000));
}
#[test]
fn resolve_driver_default_uses_half_request_timeout_when_under_cap() {
let op = OperationOptions::default();
let view = empty_view(&op);
let strategy =
resolve_availability_strategy(&view, Some(Duration::from_millis(600))).expect("Some");
assert_eq!(strategy.threshold().get(), Duration::from_millis(300));
}
#[test]
fn resolve_driver_default_caps_at_1000ms() {
let op = OperationOptions::default();
let view = empty_view(&op);
let strategy =
resolve_availability_strategy(&view, Some(Duration::from_secs(30))).expect("Some");
assert_eq!(strategy.threshold().get(), Duration::from_millis(1000));
}
#[test]
fn resolve_operation_disabled_returns_none() {
let op = OperationOptionsBuilder::new()
.with_availability_strategy(AvailabilityStrategy::Disabled)
.build();
let view = empty_view(&op);
assert!(resolve_availability_strategy(&view, None).is_none());
}
#[test]
fn resolve_operation_hedging_strategy_used_directly() {
let op_strategy =
HedgingStrategy::new(HedgeThreshold::new(Duration::from_millis(200)).unwrap());
let op = OperationOptionsBuilder::new()
.with_availability_strategy(AvailabilityStrategy::Hedging(op_strategy))
.build();
let view = empty_view(&op);
let strategy = resolve_availability_strategy(&view, None).expect("Some");
assert_eq!(strategy.threshold().get(), Duration::from_millis(200));
}
#[test]
fn resolve_hedging_enabled_flag_false_returns_none() {
let env = OperationOptionsBuilder::new()
.with_hedging_enabled(false)
.build();
let op = OperationOptions::default();
let view = OperationOptionsView::new(Some(std::sync::Arc::new(env)), None, None, Some(&op));
assert!(resolve_availability_strategy(&view, None).is_none());
}
#[test]
fn resolve_hedging_enabled_flag_false_overrides_explicit_strategy() {
let op_strategy =
HedgingStrategy::new(HedgeThreshold::new(Duration::from_millis(200)).unwrap());
let env = OperationOptionsBuilder::new()
.with_hedging_enabled(false)
.build();
let op = OperationOptionsBuilder::new()
.with_availability_strategy(AvailabilityStrategy::Hedging(op_strategy))
.build();
let view = OperationOptionsView::new(Some(std::sync::Arc::new(env)), None, None, Some(&op));
assert!(resolve_availability_strategy(&view, None).is_none());
}
#[test]
fn resolve_hedging_enabled_flag_true_keeps_default_threshold() {
let env = OperationOptionsBuilder::new()
.with_hedging_enabled(true)
.build();
let op = OperationOptions::default();
let view = OperationOptionsView::new(Some(std::sync::Arc::new(env)), None, None, Some(&op));
let strategy = resolve_availability_strategy(&view, None).expect("Some");
assert_eq!(strategy.threshold().get(), Duration::from_millis(1000));
}
#[test]
fn resolve_hedging_enabled_flag_true_overrides_explicit_disabled() {
let env = OperationOptionsBuilder::new()
.with_hedging_enabled(true)
.build();
let op = OperationOptionsBuilder::new()
.with_availability_strategy(AvailabilityStrategy::Disabled)
.build();
let view = OperationOptionsView::new(Some(std::sync::Arc::new(env)), None, None, Some(&op));
let strategy =
resolve_availability_strategy(&view, None).expect("env=true enables hedging");
assert_eq!(strategy.threshold().get(), Duration::from_millis(1000));
}
#[test]
fn resolve_hedging_enabled_flag_true_honors_programmatic_custom_threshold() {
let op_strategy =
HedgingStrategy::new(HedgeThreshold::new(Duration::from_millis(200)).unwrap());
let env = OperationOptionsBuilder::new()
.with_hedging_enabled(true)
.build();
let op = OperationOptionsBuilder::new()
.with_availability_strategy(AvailabilityStrategy::Hedging(op_strategy))
.build();
let view = OperationOptionsView::new(Some(std::sync::Arc::new(env)), None, None, Some(&op));
let strategy = resolve_availability_strategy(&view, None).expect("Some");
assert_eq!(strategy.threshold().get(), Duration::from_millis(200));
}
#[test]
fn resolve_env_override_layer_disables_hedging_over_operation() {
let op_strategy =
HedgingStrategy::new(HedgeThreshold::new(Duration::from_millis(200)).unwrap());
let env_override = OperationOptionsBuilder::new()
.with_hedging_enabled(false)
.build();
let op = OperationOptionsBuilder::new()
.with_hedging_enabled(true)
.with_availability_strategy(AvailabilityStrategy::Hedging(op_strategy))
.build();
let view = OperationOptionsView::new_with_override(
Some(std::sync::Arc::new(env_override)),
None,
None,
None,
Some(&op),
);
assert!(
resolve_availability_strategy(&view, None).is_none(),
"env_override hedging=false must disable hedging through the pipeline resolver",
);
}
#[test]
fn resolve_env_override_layer_enables_hedging_over_disabled_operation() {
let env_override = OperationOptionsBuilder::new()
.with_hedging_enabled(true)
.build();
let op = OperationOptionsBuilder::new()
.with_availability_strategy(AvailabilityStrategy::Disabled)
.build();
let view = OperationOptionsView::new_with_override(
Some(std::sync::Arc::new(env_override)),
None,
None,
None,
Some(&op),
);
let strategy = resolve_availability_strategy(&view, None)
.expect("env_override hedging=true must enable hedging through the pipeline resolver");
assert_eq!(strategy.threshold().get(), Duration::from_millis(1000));
}
#[test]
fn should_hedge_via_excluded_regions_field() {
let state =
account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2, Region::CENTRAL_US]);
let op = read_item_operation();
let excluded: ExcludedRegions = [Region::EAST_US, Region::WEST_US_2].into_iter().collect();
assert!(!should_hedge(
Some(&enabled_strategy()),
&op,
&state,
&excluded.0,
));
}
fn primary_routing_for(account: &AccountEndpointState) -> RoutingDecision {
let ep = account
.preferred_read_endpoints
.first()
.cloned()
.unwrap_or_else(|| account.default_endpoint.clone());
let url = ep.selected_url(false).clone();
RoutingDecision {
selected_url: url,
transport_mode: TransportMode::Gateway,
endpoint: ep,
}
}
fn primary_routing_for_index(account: &AccountEndpointState, index: usize) -> RoutingDecision {
let ep = account
.preferred_read_endpoints
.get(index)
.cloned()
.expect("test fixture must supply enough preferred read endpoints");
let url = ep.selected_url(false).clone();
RoutingDecision {
selected_url: url,
transport_mode: TransportMode::Gateway,
endpoint: ep,
}
}
#[test]
fn evaluate_returns_some_for_eligible_read_multi_region() {
let state = account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2]);
let op = read_item_operation();
let primary = primary_routing_for(&state);
let op_opts = OperationOptions::default();
let view = OperationOptionsView::new(None, None, None, Some(&op_opts));
let upgrade = evaluate_hedge_eligibility(&op, &view, &state, &primary, None)
.expect("eligible multi-region read");
assert_eq!(
upgrade.secondary_routing.endpoint.region(),
Some(&Region::WEST_US_2)
);
assert_eq!(upgrade.threshold.get(), Duration::from_millis(1000));
assert_eq!(
upgrade.strategy_config,
HedgingStrategyConfig::new(upgrade.threshold)
);
}
#[test]
fn evaluate_returns_none_for_single_region_account() {
let state = account_state_with_regions(&[Region::EAST_US]);
let op = read_item_operation();
let primary = primary_routing_for(&state);
let op_opts = OperationOptions::default();
let view = OperationOptionsView::new(None, None, None, Some(&op_opts));
assert!(evaluate_hedge_eligibility(&op, &view, &state, &primary, None).is_none());
}
#[test]
fn evaluate_returns_none_for_write_operation() {
let state = account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2]);
let op = create_item_operation();
let primary = primary_routing_for(&state);
let op_opts = OperationOptions::default();
let view = OperationOptionsView::new(None, None, None, Some(&op_opts));
assert!(evaluate_hedge_eligibility(&op, &view, &state, &primary, None).is_none());
}
#[test]
fn evaluate_returns_none_when_strategy_disabled() {
let state = account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2]);
let op = read_item_operation();
let primary = primary_routing_for(&state);
let op_opts = OperationOptionsBuilder::new()
.with_availability_strategy(AvailabilityStrategy::Disabled)
.build();
let view = OperationOptionsView::new(None, None, None, Some(&op_opts));
assert!(evaluate_hedge_eligibility(&op, &view, &state, &primary, None).is_none());
}
#[test]
fn evaluate_returns_none_when_user_exclusion_leaves_one_region() {
let state = account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2]);
let op = read_item_operation();
let primary = primary_routing_for(&state);
let mut op_opts = OperationOptions::default();
op_opts.excluded_regions = Some([Region::WEST_US_2].into_iter().collect());
let view = OperationOptionsView::new(None, None, None, Some(&op_opts));
assert!(evaluate_hedge_eligibility(&op, &view, &state, &primary, None).is_none());
}
#[test]
fn evaluate_secondary_routing_uses_secondary_endpoint_url() {
let state =
account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2, Region::CENTRAL_US]);
let op = read_item_operation();
let primary = primary_routing_for(&state);
let op_opts = OperationOptions::default();
let view = OperationOptionsView::new(None, None, None, Some(&op_opts));
let upgrade = evaluate_hedge_eligibility(&op, &view, &state, &primary, None)
.expect("eligible three-region read");
let url_str = upgrade.secondary_routing.selected_url.as_str();
assert!(
url_str.contains(Region::WEST_US_2.as_str()),
"secondary URL {} did not contain westus2 region tag",
url_str
);
assert_eq!(
upgrade.secondary_routing.transport_mode,
TransportMode::Gateway
);
}
#[test]
fn evaluate_hedge_eligibility_threshold_stable_across_repeated_calls() {
let state = account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2]);
let op = read_item_operation();
let primary = primary_routing_for(&state);
let op_opts = OperationOptions::default();
let view = OperationOptionsView::new(None, None, None, Some(&op_opts));
let configured_timeout = Some(Duration::from_millis(800));
let first = evaluate_hedge_eligibility(&op, &view, &state, &primary, configured_timeout)
.expect("first call eligible");
std::thread::sleep(Duration::from_millis(10));
let second = evaluate_hedge_eligibility(&op, &view, &state, &primary, configured_timeout)
.expect("second call eligible");
assert_eq!(
first.threshold.get(),
Duration::from_millis(400),
"first call must derive threshold from configured timeout",
);
assert_eq!(
second.threshold.get(),
first.threshold.get(),
"threshold must be stable across calls: the caller must pass \
the configured request_timeout (not remaining deadline) so \
the default does not shrink between attempts",
);
}
#[test]
fn evaluate_secondary_skips_primary_when_primary_is_not_index_zero() {
let state =
account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2, Region::CENTRAL_US]);
let op = read_item_operation();
let primary = primary_routing_for_index(&state, 1);
let op_opts = OperationOptions::default();
let view = OperationOptionsView::new(None, None, None, Some(&op_opts));
let upgrade = evaluate_hedge_eligibility(&op, &view, &state, &primary, None)
.expect("eligible three-region read");
let secondary_region = upgrade.secondary_routing.endpoint.region().cloned();
assert_ne!(
secondary_region.as_ref(),
Some(&Region::WEST_US_2),
"alternate must not target the primary's region",
);
assert_ne!(
upgrade.secondary_routing.endpoint.endpoint_key(),
primary.endpoint.endpoint_key(),
"alternate must not target the primary's endpoint_key",
);
assert_eq!(secondary_region.as_ref(), Some(&Region::EAST_US));
}
#[test]
fn evaluate_hedges_when_primary_is_last_preferred_endpoint() {
let state =
account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2, Region::CENTRAL_US]);
let op = read_item_operation();
let primary = primary_routing_for_index(&state, 2);
let op_opts = OperationOptions::default();
let view = OperationOptionsView::new(None, None, None, Some(&op_opts));
let upgrade = evaluate_hedge_eligibility(&op, &view, &state, &primary, None).expect(
"request must still be hedged when the primary is not \
preferred_read_endpoints[0]",
);
let secondary_region = upgrade.secondary_routing.endpoint.region().cloned();
assert_eq!(secondary_region.as_ref(), Some(&Region::EAST_US));
assert_ne!(
secondary_region.as_ref(),
primary.endpoint.region(),
"alternate must differ from the promoted primary region",
);
assert_ne!(
upgrade.secondary_routing.endpoint.endpoint_key(),
primary.endpoint.endpoint_key(),
"alternate must differ from the promoted primary endpoint_key",
);
}
#[test]
fn evaluate_returns_none_when_only_applicable_region_is_primary() {
let state =
account_state_with_regions(&[Region::EAST_US, Region::WEST_US_2, Region::CENTRAL_US]);
let op = read_item_operation();
let primary = primary_routing_for(&state);
let mut op_opts = OperationOptions::default();
op_opts.excluded_regions = Some(
[Region::WEST_US_2, Region::CENTRAL_US]
.into_iter()
.collect(),
);
let view = OperationOptionsView::new(None, None, None, Some(&op_opts));
assert!(
evaluate_hedge_eligibility(&op, &view, &state, &primary, None).is_none(),
"no distinct alternate exists — must fall back to non-hedged decision",
);
}
}