use apcore::config::Config;
use apcore::errors::ModuleError;
use apcore::pipeline::{ExecutionStrategy, PipelineContext, Step, StepResult};
use apcore::registry::registry::Registry;
use apcore::{
build_internal_strategy, build_performance_strategy, build_standard_strategy,
build_testing_strategy, list_strategies, register_strategy, Executor,
};
use async_trait::async_trait;
use serde_json::Value;
struct OutputStep {
output: Value,
}
impl OutputStep {
fn new(output: Value) -> Self {
Self { output }
}
}
#[async_trait]
impl Step for OutputStep {
fn name(&self) -> &'static str {
"output_step"
}
fn description(&self) -> &'static str {
"Sets output on the pipeline context"
}
fn removable(&self) -> bool {
true
}
fn replaceable(&self) -> bool {
true
}
async fn execute(&self, ctx: &mut PipelineContext) -> Result<StepResult, ModuleError> {
ctx.output = Some(self.output.clone());
Ok(StepResult::continue_step())
}
}
#[test]
fn test_executor_with_strategy_stores_strategy() {
let registry = Registry::new();
let config = Config::default();
let strategy = build_testing_strategy();
let executor = Executor::with_strategy(registry, config, strategy);
let stored = executor.strategy();
assert_eq!(stored.name(), "testing");
}
#[test]
fn test_executor_new_has_standard_strategy() {
let registry = Registry::new();
let config = Config::default();
let executor = Executor::new(registry, config);
assert_eq!(executor.strategy().name(), "standard");
}
#[test]
fn test_preset_internal_strategy() {
let strategy = build_internal_strategy();
assert_eq!(strategy.name(), "internal");
assert_eq!(strategy.steps().len(), 9);
let names = strategy.step_names();
assert!(names.contains(&"context_creation".to_string()));
assert!(names.contains(&"execute".to_string()));
assert!(!names.contains(&"acl_check".to_string()));
assert!(!names.contains(&"approval_gate".to_string()));
}
#[test]
fn test_preset_testing_strategy() {
let strategy = build_testing_strategy();
assert_eq!(strategy.name(), "testing");
assert_eq!(strategy.steps().len(), 8);
let names = strategy.step_names();
assert!(names.contains(&"context_creation".to_string()));
assert!(names.contains(&"execute".to_string()));
assert!(!names.contains(&"call_chain_guard".to_string()));
assert!(!names.contains(&"acl_check".to_string()));
assert!(!names.contains(&"approval_gate".to_string()));
}
#[test]
fn test_preset_performance_strategy() {
let strategy = build_performance_strategy();
assert_eq!(strategy.name(), "performance");
assert_eq!(strategy.steps().len(), 9);
let names = strategy.step_names();
assert!(names.contains(&"context_creation".to_string()));
assert!(names.contains(&"execute".to_string()));
assert!(!names.contains(&"middleware_before".to_string()));
assert!(!names.contains(&"middleware_after".to_string()));
}
#[tokio::test]
async fn test_call_with_trace_returns_output_and_trace() {
let registry = Registry::new();
let config = Config::default();
let strategy = ExecutionStrategy::new(
"simple",
vec![Box::new(OutputStep::new(serde_json::json!({"result": 42})))],
)
.unwrap();
let executor = Executor::with_strategy(registry, config, strategy);
let (output, trace) = executor
.call_with_trace("test_mod", serde_json::json!({}), None, None, None)
.await
.unwrap();
assert_eq!(output, serde_json::json!({"result": 42}));
assert!(trace.success);
assert_eq!(trace.steps.len(), 1);
assert_eq!(trace.steps[0].name, "output_step");
assert_eq!(trace.strategy_name, "simple");
}
#[tokio::test]
async fn test_call_with_trace_accepts_version_hint() {
let registry = Registry::new();
let config = Config::default();
let strategy = ExecutionStrategy::new(
"simple",
vec![Box::new(OutputStep::new(serde_json::json!({"result": 7})))],
)
.unwrap();
let executor = Executor::with_strategy(registry, config, strategy);
let (output, trace) = executor
.call_with_trace("test_mod", serde_json::json!({}), None, Some("1.0.0"), None)
.await
.unwrap();
assert_eq!(output, serde_json::json!({"result": 7}));
assert!(trace.success);
}
#[tokio::test]
async fn test_call_with_trace_strategy_override() {
let registry = Registry::new();
let config = Config::default();
let default_strategy = ExecutionStrategy::new(
"default",
vec![Box::new(OutputStep::new(
serde_json::json!({"from": "default"}),
))],
)
.unwrap();
let override_strategy = ExecutionStrategy::new(
"override",
vec![Box::new(OutputStep::new(
serde_json::json!({"from": "override"}),
))],
)
.unwrap();
let executor = Executor::with_strategy(registry, config, default_strategy);
let (output, trace) = executor
.call_with_trace(
"mod",
serde_json::json!({}),
None,
None,
Some(&override_strategy),
)
.await
.unwrap();
assert_eq!(output, serde_json::json!({"from": "override"}));
assert_eq!(trace.strategy_name, "override");
}
#[tokio::test]
async fn test_call_with_trace_no_override_uses_default_strategy() {
let registry = Registry::new();
let config = Config::default();
let executor = Executor::new(registry, config);
let result = executor
.call_with_trace("nonexistent", serde_json::json!({}), None, None, None)
.await;
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, apcore::errors::ErrorCode::ModuleNotFound);
}
#[test]
fn test_register_and_list_strategies() {
let strategy = build_internal_strategy();
let info = strategy.info();
assert_eq!(info.name, "internal");
assert_eq!(info.step_count, 9);
register_strategy(info);
let all = list_strategies();
assert!(
all.iter().any(|s| s.name == "internal"),
"internal strategy should be in the registry"
);
}
#[test]
fn test_describe_pipeline() {
let strategy = build_standard_strategy();
let info = strategy.info();
assert_eq!(info.name, "standard");
assert_eq!(info.step_count, 11);
assert!(info.description.contains("context_creation"));
assert!(info.description.contains("execute"));
}