use apcore::bindings::{BindingDefinition, BindingHandler, BindingLoader, BindingTarget};
use apcore::context::Context;
use apcore::errors::ModuleError;
use apcore::registry::registry::Registry;
use serde_json::{json, Value};
use std::collections::HashMap;
use std::sync::Arc;
fn make_echo_handler() -> BindingHandler {
Arc::new(|inputs: Value, _ctx: &Context<Value>| Box::pin(async move { Ok(inputs) }))
}
fn make_error_handler() -> BindingHandler {
Arc::new(|_inputs: Value, _ctx: &Context<Value>| {
Box::pin(async move {
Err(ModuleError::new(
apcore::errors::ErrorCode::GeneralInternalError,
"handler intentionally failed".to_string(),
))
})
})
}
fn binding_def(name: &str, module_name: &str) -> BindingDefinition {
BindingDefinition {
name: name.to_string(),
target: BindingTarget {
module_name: module_name.to_string(),
callable: "handler".to_string(),
schema_path: None,
},
metadata: HashMap::new(),
}
}
#[test]
fn register_single_binding_into_registry() {
let registry = Registry::new();
let mut loader = BindingLoader::new();
let def = binding_def("echo_binding", "test.echo");
let mut inner = HashMap::new();
inner.insert("echo_binding".to_string(), def);
let dir = tempfile::tempdir().unwrap();
let file_path = dir.path().join("bindings.json");
std::fs::write(
&file_path,
serde_json::to_string(&json!([
{"name": "echo_binding", "target": {"module_name": "test.echo", "callable": "h"}, "metadata": {}}
]))
.unwrap(),
)
.unwrap();
loader.load_from_file(&file_path).unwrap();
let mut handlers: HashMap<String, BindingHandler> = HashMap::new();
handlers.insert("echo_binding".to_string(), make_echo_handler());
let count = loader
.register_into_with_handlers(®istry, handlers)
.unwrap();
assert_eq!(count, 1);
assert!(registry.get("test.echo").is_some());
}
#[test]
fn register_multiple_bindings_into_registry() {
let registry = Registry::new();
let dir = tempfile::tempdir().unwrap();
let file_path = dir.path().join("bindings.json");
std::fs::write(
&file_path,
serde_json::to_string(&json!([
{"name": "b1", "target": {"module_name": "test.b1", "callable": "h"}, "metadata": {}},
{"name": "b2", "target": {"module_name": "test.b2", "callable": "h"}, "metadata": {}},
{"name": "b3", "target": {"module_name": "test.b3", "callable": "h"}, "metadata": {}}
]))
.unwrap(),
)
.unwrap();
let mut loader = BindingLoader::new();
loader.load_from_file(&file_path).unwrap();
let mut handlers: HashMap<String, BindingHandler> = HashMap::new();
for name in ["b1", "b2", "b3"] {
handlers.insert(name.to_string(), make_echo_handler());
}
let count = loader
.register_into_with_handlers(®istry, handlers)
.unwrap();
assert_eq!(count, 3);
assert!(registry.get("test.b1").is_some());
assert!(registry.get("test.b2").is_some());
assert!(registry.get("test.b3").is_some());
}
#[test]
fn register_binding_with_description_in_metadata() {
let registry = Registry::new();
let dir = tempfile::tempdir().unwrap();
let file_path = dir.path().join("bindings.json");
std::fs::write(
&file_path,
serde_json::to_string(&json!([
{
"name": "described",
"target": {"module_name": "test.described", "callable": "h"},
"metadata": {"description": "A well-described module"}
}
]))
.unwrap(),
)
.unwrap();
let mut loader = BindingLoader::new();
loader.load_from_file(&file_path).unwrap();
let mut handlers: HashMap<String, BindingHandler> = HashMap::new();
handlers.insert("described".to_string(), make_echo_handler());
let count = loader
.register_into_with_handlers(®istry, handlers)
.unwrap();
assert_eq!(count, 1);
assert!(registry.get("test.described").is_some());
}
#[test]
fn register_fails_when_handler_missing_for_binding() {
let registry = Registry::new();
let dir = tempfile::tempdir().unwrap();
let file_path = dir.path().join("bindings.json");
std::fs::write(
&file_path,
serde_json::to_string(&json!([
{"name": "needs_handler", "target": {"module_name": "test.x", "callable": "h"}, "metadata": {}}
]))
.unwrap(),
)
.unwrap();
let mut loader = BindingLoader::new();
loader.load_from_file(&file_path).unwrap();
let result = loader.register_into_with_handlers(®istry, HashMap::new());
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, apcore::errors::ErrorCode::BindingModuleNotFound);
assert!(err.message.contains("needs_handler"));
}
#[test]
fn register_empty_loader_succeeds_with_zero_count() {
let registry = Registry::new();
let loader = BindingLoader::new();
let count = loader
.register_into_with_handlers(®istry, HashMap::new())
.unwrap();
assert_eq!(count, 0);
}
#[tokio::test]
async fn end_to_end_yaml_load_and_registry_execution() {
let dir = tempfile::tempdir().unwrap();
let yaml = r"
- name: yaml_echo
target:
module_name: test.yaml_echo
callable: echo_fn
metadata:
description: Echo inputs back as output
";
std::fs::write(dir.path().join("echo.binding.yaml"), yaml).unwrap();
let mut loader = BindingLoader::new();
let n_loaded = loader.load_binding_dir(dir.path(), None).unwrap();
assert_eq!(n_loaded, 1);
let registry = Registry::new();
let mut handlers: HashMap<String, BindingHandler> = HashMap::new();
handlers.insert("yaml_echo".to_string(), make_echo_handler());
let registered = loader
.register_into_with_handlers(®istry, handlers)
.unwrap();
assert_eq!(registered, 1);
let module = registry
.get("test.yaml_echo")
.expect("module should be registered");
let identity = apcore::context::Identity::new(
"tester".to_string(),
"service".to_string(),
vec![],
HashMap::new(),
);
let ctx: Context<Value> = Context::new(identity);
let inputs = json!({"key": "value"});
let output = module.execute(inputs.clone(), &ctx).await.unwrap();
assert_eq!(output, inputs);
}
#[tokio::test]
async fn registered_handler_propagates_error() {
let dir = tempfile::tempdir().unwrap();
let yaml = r"
- name: failing_binding
target:
module_name: test.failing
callable: fail_fn
metadata: {}
";
std::fs::write(dir.path().join("fail.binding.yaml"), yaml).unwrap();
let mut loader = BindingLoader::new();
loader.load_binding_dir(dir.path(), None).unwrap();
let registry = Registry::new();
let mut handlers: HashMap<String, BindingHandler> = HashMap::new();
handlers.insert("failing_binding".to_string(), make_error_handler());
loader
.register_into_with_handlers(®istry, handlers)
.unwrap();
let module = registry
.get("test.failing")
.expect("module should be registered");
let identity = apcore::context::Identity::new(
"tester".to_string(),
"service".to_string(),
vec![],
HashMap::new(),
);
let ctx: Context<Value> = Context::new(identity);
let result = module.execute(json!({}), &ctx).await;
assert!(result.is_err());
assert!(result
.unwrap_err()
.message
.contains("handler intentionally failed"));
}