use earl::expression::ast::CallExpression;
use earl::expression::binder::{BindError, bind_arguments};
use earl::template::schema::{ParamSpec, ParamType};
fn params() -> Vec<ParamSpec> {
vec![
ParamSpec {
name: "query".to_string(),
r#type: ParamType::String,
required: true,
default: None,
description: None,
},
ParamSpec {
name: "per_page".to_string(),
r#type: ParamType::Integer,
required: false,
default: Some(serde_json::json!(20)),
description: None,
},
ParamSpec {
name: "include".to_string(),
r#type: ParamType::Boolean,
required: false,
default: Some(serde_json::json!(false)),
description: None,
},
]
}
fn params_with_optional() -> Vec<ParamSpec> {
let mut p = params();
p.push(ParamSpec {
name: "filter".to_string(),
r#type: ParamType::String,
required: false,
default: None,
description: None,
});
p
}
#[test]
fn positional_argument_binds_by_position() {
let expr = CallExpression {
provider: "github".to_string(),
command: "search_issues".to_string(),
positional_args: vec![serde_json::json!("hello")],
named_args: vec![("per_page".to_string(), serde_json::json!(10))],
};
let bound = bind_arguments(&expr, ¶ms()).unwrap();
assert_eq!(bound.get("query").unwrap(), "hello");
}
#[test]
fn named_argument_binds_by_name() {
let expr = CallExpression {
provider: "github".to_string(),
command: "search_issues".to_string(),
positional_args: vec![serde_json::json!("hello")],
named_args: vec![("per_page".to_string(), serde_json::json!(10))],
};
let bound = bind_arguments(&expr, ¶ms()).unwrap();
assert_eq!(bound.get("per_page").unwrap(), 10);
}
#[test]
fn optional_param_with_default_is_populated() {
let expr = CallExpression {
provider: "github".to_string(),
command: "search_issues".to_string(),
positional_args: vec![serde_json::json!("hello")],
named_args: vec![],
};
let bound = bind_arguments(&expr, ¶ms()).unwrap();
assert_eq!(bound.get("include").unwrap(), false);
}
#[test]
fn optional_param_without_default_is_absent() {
let expr = CallExpression {
provider: "github".to_string(),
command: "search_issues".to_string(),
positional_args: vec![serde_json::json!("hello")],
named_args: vec![],
};
let bound = bind_arguments(&expr, ¶ms_with_optional()).unwrap();
assert!(!bound.contains_key("filter"));
}
#[test]
fn fails_on_missing_required_argument() {
let expr = CallExpression {
provider: "github".to_string(),
command: "search_issues".to_string(),
positional_args: vec![],
named_args: vec![],
};
let err = bind_arguments(&expr, ¶ms()).unwrap_err();
assert!(matches!(err, BindError::MissingRequired(_)));
}
#[test]
fn fails_on_unknown_argument() {
let expr = CallExpression {
provider: "github".to_string(),
command: "search_issues".to_string(),
positional_args: vec![],
named_args: vec![
("query".to_string(), serde_json::json!("x")),
("unknown".to_string(), serde_json::json!(1)),
],
};
let err = bind_arguments(&expr, ¶ms()).unwrap_err();
assert!(matches!(err, BindError::UnknownArgument(_)));
}
#[test]
fn fails_on_too_many_positional_arguments() {
let expr = CallExpression {
provider: "github".to_string(),
command: "search_issues".to_string(),
positional_args: vec![
serde_json::json!("a"),
serde_json::json!(1),
serde_json::json!(true),
serde_json::json!("extra"),
],
named_args: vec![],
};
let err = bind_arguments(&expr, ¶ms()).unwrap_err();
assert!(matches!(err, BindError::TooManyPositional { .. }));
}
#[test]
fn fails_on_invalid_argument_type() {
let expr = CallExpression {
provider: "github".to_string(),
command: "search_issues".to_string(),
positional_args: vec![],
named_args: vec![
("query".to_string(), serde_json::json!("x")),
("per_page".to_string(), serde_json::json!("ten")),
],
};
let err = bind_arguments(&expr, ¶ms()).unwrap_err();
assert!(matches!(err, BindError::InvalidType { .. }));
}