#[cfg(test)]
mod tests {
use super::super::reinforcement::*;
#[test]
fn test_fixed_rate_success() {
let strategy = FixedRate::default();
let context = ReinforcementContext::new();
let new_confidence = strategy.update_confidence(0.5, true, &context);
assert!((new_confidence - 0.6).abs() < 0.001);
}
#[test]
fn test_fixed_rate_failure() {
let strategy = FixedRate::default();
let context = ReinforcementContext::new();
let new_confidence = strategy.update_confidence(0.5, false, &context);
assert!((new_confidence - 0.45).abs() < 0.001);
}
#[test]
fn test_fixed_rate_clamp_max() {
let strategy = FixedRate::default();
let context = ReinforcementContext::new();
let new_confidence = strategy.update_confidence(0.95, true, &context);
assert!((new_confidence - 1.0).abs() < 0.001);
}
#[test]
fn test_fixed_rate_clamp_min() {
let strategy = FixedRate::default();
let context = ReinforcementContext::new();
let new_confidence = strategy.update_confidence(0.02, false, &context);
assert!((new_confidence - 0.0).abs() < 0.001);
}
#[test]
fn test_adaptive_learning_rate() {
let strategy = AdaptiveLearningRate::default();
let context = ReinforcementContext::new().with_usage_count(10);
let new_confidence = strategy.update_confidence(0.5, true, &context);
assert!(
(new_confidence - 0.6).abs() < 0.001,
"AdaptiveLearningRate at usage=10 (half-life=10) should give 0.6, got {new_confidence}"
);
}
#[test]
fn test_temporal_decay() {
let strategy = TemporalDecay::default();
let context = ReinforcementContext::new()
.with_created_at(0)
.with_last_used(0);
let new_confidence = strategy.update_confidence(0.5, true, &context);
assert!(
(new_confidence - 0.55).abs() < 0.01,
"TemporalDecay at max decay (0.1 cap): 0.5*0.9 + 0.1 success_delta = ~0.55, got {new_confidence}"
);
}
#[test]
fn test_temporal_decay_failure_applies_decay_then_penalty() {
let strategy = TemporalDecay::default();
let context = ReinforcementContext::new()
.with_created_at(0)
.with_last_used(0);
let new_confidence = strategy.update_confidence(0.5, false, &context);
assert!((new_confidence - 0.40).abs() < 0.01, "got {new_confidence}");
assert!(
new_confidence < 0.45,
"decay must apply before the failure penalty"
);
}
#[test]
fn test_contextual_reinforcement() {
let strategy = ContextualReinforcement::default();
let mut context = ReinforcementContext::new()
.with_usage_count(5)
.with_success_rate(0.8);
context.last_used = context.current_time; let new_confidence = strategy.update_confidence(0.5, true, &context);
assert!(
(new_confidence - 0.6791).abs() < 0.01,
"ContextualReinforcement (recency=1.0) should produce ~0.6791, got {new_confidence}"
);
}
#[test]
fn test_composite_strategy() {
let mut strategy = CompositeStrategy::new();
strategy = strategy.add_strategy(FixedRate::default(), 1.0);
let context = ReinforcementContext::new();
let new_confidence = strategy.update_confidence(0.5, true, &context);
assert!(new_confidence > 0.5);
}
#[test]
fn test_diminishing_returns_first_success_equals_fixed_rate() {
let strategy = DiminishingReturns::default();
let mut ctx = ReinforcementContext::new();
ctx.custom.insert("success_count".to_string(), 0.0);
ctx.custom.insert("failure_count".to_string(), 0.0);
let new_confidence = strategy.update_confidence(0.5, true, &ctx);
assert!((new_confidence - 0.6).abs() < 0.001, "got {new_confidence}");
}
#[test]
#[allow(clippy::similar_names)]
fn test_diminishing_returns_decreases_with_practice() {
let strategy = DiminishingReturns::default();
let mut ctx0 = ReinforcementContext::new();
ctx0.custom.insert("success_count".to_string(), 0.0);
ctx0.custom.insert("failure_count".to_string(), 0.0);
let mut ctx10 = ReinforcementContext::new();
ctx10.custom.insert("success_count".to_string(), 10.0);
ctx10.custom.insert("failure_count".to_string(), 0.0);
let d0 = strategy.update_confidence(0.5, true, &ctx0) - 0.5;
let d10 = strategy.update_confidence(0.5, true, &ctx10) - 0.5;
assert!(
d10 < d0,
"delta should decrease with practice: {d10} < {d0}"
);
}
#[test]
fn test_diminishing_returns_failure_decrements() {
let strategy = DiminishingReturns::default();
let mut ctx = ReinforcementContext::new();
ctx.custom.insert("success_count".to_string(), 0.0);
ctx.custom.insert("failure_count".to_string(), 0.0);
let new_confidence = strategy.update_confidence(0.5, false, &ctx);
assert!(new_confidence < 0.5, "failure should lower confidence");
}
#[test]
fn test_diminishing_returns_clamp() {
let strategy = DiminishingReturns::new(0.5, 0.5, 0.0);
let mut ctx = ReinforcementContext::new();
ctx.custom.insert("success_count".to_string(), 0.0);
ctx.custom.insert("failure_count".to_string(), 0.0);
let high = strategy.update_confidence(0.9, true, &ctx);
assert!(
(high - 1.0).abs() < 0.001,
"expected 1.0 after clamp, got {high}"
);
let low = strategy.update_confidence(0.1, false, &ctx);
assert!(
(low - 0.0).abs() < 0.001,
"expected 0.0 after clamp, got {low}"
);
}
#[test]
fn test_power_law_decay_zero_elapsed_no_decay() {
let result = power_law_decay(0.8, 0, 0.5);
assert!((result - 0.8).abs() < 0.001, "got {result}");
}
#[test]
fn test_power_law_decay_four_days_halves_with_d05() {
let secs = 4 * 24 * 3600;
let result = power_law_decay(1.0, secs, 0.5);
assert!((result - 0.5).abs() < 0.001, "got {result}");
}
#[test]
fn test_power_law_decay_less_than_one_day_no_extra_decay() {
let secs_12h = 12 * 3600;
let result = power_law_decay(0.7, secs_12h, 0.5);
assert!((result - 0.7).abs() < 0.001, "got {result}");
}
#[test]
#[allow(clippy::similar_names)]
fn test_power_law_decay_monotone_decreasing() {
let d = 0.5;
let c = 1.0;
let r1d = power_law_decay(c, 86_400, d); let r4d = power_law_decay(c, 4 * 86_400, d); let r16d = power_law_decay(c, 16 * 86_400, d); assert!(r1d >= r4d, "decay should increase with time");
assert!(r4d >= r16d, "decay should increase with time");
}
}