use apcore::errors::ErrorCode;
use apcore::{guard_call_chain, guard_call_chain_with_repeat, Context};
fn anon_ctx() -> Context<serde_json::Value> {
Context::<serde_json::Value>::anonymous()
}
fn ctx_with_chain(chain: Vec<&str>) -> Context<serde_json::Value> {
let mut ctx = anon_ctx();
ctx.call_chain = chain.into_iter().map(String::from).collect();
ctx
}
#[test]
fn guard_empty_chain_passes() {
let ctx = anon_ctx();
assert!(guard_call_chain(&ctx, "mod.a", 10).is_ok());
}
#[test]
fn guard_single_module_in_chain_passes() {
let ctx = ctx_with_chain(vec!["mod.a"]);
assert!(guard_call_chain(&ctx, "mod.b", 10).is_ok());
}
#[test]
fn guard_short_diverse_chain_passes() {
let ctx = ctx_with_chain(vec!["mod.a", "mod.b"]);
assert!(guard_call_chain(&ctx, "mod.c", 10).is_ok());
}
#[test]
fn guard_module_repeated_below_limit_passes() {
let ctx = ctx_with_chain(vec!["mod.a", "mod.b", "mod.a"]);
assert!(guard_call_chain(&ctx, "mod.c", 100).is_ok());
}
#[test]
fn guard_depth_exceeded_returns_error() {
let ctx = ctx_with_chain(vec!["a", "b", "c", "d"]);
let result = guard_call_chain(&ctx, "e", 3);
assert!(result.is_err());
assert_eq!(result.unwrap_err().code, ErrorCode::CallDepthExceeded);
}
#[test]
fn guard_depth_exactly_at_limit_passes() {
let ctx = ctx_with_chain(vec!["a", "b", "c"]);
assert!(guard_call_chain(&ctx, "d", 3).is_ok());
}
#[test]
fn guard_depth_one_above_limit_errors() {
let ctx = ctx_with_chain(vec!["a", "b", "c", "d"]);
let result = guard_call_chain(&ctx, "e", 3);
assert!(result.is_err());
assert_eq!(result.unwrap_err().code, ErrorCode::CallDepthExceeded);
}
#[test]
fn guard_max_depth_one_empty_chain_passes() {
let ctx = anon_ctx();
assert!(guard_call_chain(&ctx, "mod.a", 1).is_ok());
}
#[test]
fn guard_max_depth_one_chain_of_one_passes() {
let ctx = ctx_with_chain(vec!["mod.a"]);
assert!(guard_call_chain(&ctx, "mod.b", 1).is_ok());
}
#[test]
fn guard_max_depth_one_chain_of_two_errors() {
let ctx = ctx_with_chain(vec!["a", "b"]);
let result = guard_call_chain(&ctx, "c", 1);
assert!(result.is_err());
assert_eq!(result.unwrap_err().code, ErrorCode::CallDepthExceeded);
}
#[test]
fn guard_circular_call_returns_error() {
let ctx = ctx_with_chain(vec!["mod.a", "mod.b", "mod.a"]);
let result = guard_call_chain(&ctx, "mod.a", 100);
assert!(result.is_err());
assert_eq!(result.unwrap_err().code, ErrorCode::CircularCall);
}
#[test]
fn guard_circular_longer_chain_detected() {
let ctx = ctx_with_chain(vec!["a", "b", "c", "a"]);
let result = guard_call_chain(&ctx, "a", 100);
assert!(result.is_err());
assert_eq!(result.unwrap_err().code, ErrorCode::CircularCall);
}
#[test]
fn guard_frequency_at_default_limit_passes() {
let ctx = ctx_with_chain(vec!["mod.a", "mod.a", "mod.a"]);
assert!(guard_call_chain(&ctx, "mod.a", 100).is_ok());
}
#[test]
fn guard_frequency_exceeded_returns_error() {
let ctx = ctx_with_chain(vec!["mod.a", "mod.a", "mod.a", "mod.a"]);
let result = guard_call_chain(&ctx, "mod.a", 100);
assert!(result.is_err());
assert_eq!(result.unwrap_err().code, ErrorCode::CallFrequencyExceeded);
}
#[test]
fn guard_frequency_two_occurrences_passes_with_default_limit() {
let ctx = ctx_with_chain(vec!["mod.a", "mod.b", "mod.a"]);
let ctx2 = ctx_with_chain(vec!["mod.a", "mod.b", "mod.a", "mod.c"]);
assert!(guard_call_chain(&ctx2, "mod.d", 100).is_ok());
let result = guard_call_chain(&ctx, "mod.a", 100);
assert!(result.is_err());
let code = result.unwrap_err().code;
assert!(
code == ErrorCode::CircularCall || code == ErrorCode::CallFrequencyExceeded,
"expected CircularCall or CallFrequencyExceeded, got {code:?}"
);
}
#[test]
fn guard_with_repeat_max_repeat_one_single_occurrence_passes() {
let ctx = ctx_with_chain(vec!["mod.a"]);
assert!(guard_call_chain_with_repeat(&ctx, "mod.a", 100, 1).is_ok());
}
#[test]
fn guard_with_repeat_max_repeat_one_second_occurrence_errors() {
let ctx = ctx_with_chain(vec!["mod.a", "mod.a"]);
let result = guard_call_chain_with_repeat(&ctx, "mod.a", 100, 1);
assert!(result.is_err());
assert_eq!(result.unwrap_err().code, ErrorCode::CallFrequencyExceeded);
}
#[test]
fn guard_with_repeat_max_repeat_one_different_module_passes() {
let ctx = ctx_with_chain(vec!["mod.a"]);
assert!(guard_call_chain_with_repeat(&ctx, "mod.b", 100, 1).is_ok());
}
#[test]
fn guard_with_repeat_custom_limit_two_passes_at_one_occurrence() {
let ctx = ctx_with_chain(vec!["mod.a"]);
assert!(guard_call_chain_with_repeat(&ctx, "mod.a", 100, 2).is_ok());
}
#[test]
fn guard_with_repeat_custom_limit_two_passes_at_two_occurrences() {
let ctx = ctx_with_chain(vec!["mod.a", "mod.a"]);
assert!(guard_call_chain_with_repeat(&ctx, "mod.a", 100, 2).is_ok());
}
#[test]
fn guard_with_repeat_custom_limit_two_errors_at_three_occurrences() {
let ctx = ctx_with_chain(vec!["mod.a", "mod.a", "mod.a"]);
let result = guard_call_chain_with_repeat(&ctx, "mod.a", 100, 2);
assert!(result.is_err());
assert_eq!(result.unwrap_err().code, ErrorCode::CallFrequencyExceeded);
}
#[test]
fn guard_with_repeat_depth_check_still_applies() {
let ctx = ctx_with_chain(vec!["a", "b"]);
let result = guard_call_chain_with_repeat(&ctx, "c", 1, 10);
assert!(result.is_err());
assert_eq!(result.unwrap_err().code, ErrorCode::CallDepthExceeded);
}
#[test]
fn guard_empty_chain_max_depth_zero_empty_passes() {
let ctx = anon_ctx();
assert!(guard_call_chain(&ctx, "mod.a", 0).is_ok());
}
#[test]
fn guard_module_name_not_in_chain_passes() {
let ctx = ctx_with_chain(vec!["x", "y", "z"]);
assert!(guard_call_chain(&ctx, "mod.new", 100).is_ok());
}