use super::*;
use serde_json::json;
fn make_request(id: i64, method: &str, params: Option<Value>) -> JsonRpcRequest {
JsonRpcRequest {
jsonrpc: "2.0".into(),
id: Some(RequestId::Number(id)),
method: method.into(),
params,
}
}
fn make_notification(method: &str) -> JsonRpcRequest {
JsonRpcRequest {
jsonrpc: "2.0".into(),
id: None,
method: method.into(),
params: None,
}
}
fn initialize_server(s: &mut Server) {
let req = make_request(
1,
"initialize",
Some(json!({
"protocolVersion": "2025-11-05",
"capabilities": {},
"clientInfo": { "name": "test", "version": "0.1" }
})),
);
let mut sink = Vec::<u8>::new();
s.handle(&req, &mut sink);
s.handle_notification(&make_notification("notifications/initialized"));
}
fn send(s: &mut Server, id: i64, method: &str, params: Option<Value>) -> Value {
let req = make_request(id, method, params);
let mut sink = Vec::<u8>::new();
let resp = s.handle(&req, &mut sink).unwrap();
serde_json::to_value(&resp).unwrap()
}
#[test]
fn server_starts_uninitialized() {
let s = Server::new();
assert_eq!(s.phase, Phase::Uninitialized);
}
#[test]
fn initialize_request_transitions_to_initializing() {
let mut s = Server::new();
let req = make_request(
1,
"initialize",
Some(json!({
"protocolVersion": "2025-11-05",
"capabilities": {},
"clientInfo": {"name": "test", "version": "1"}
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
assert_eq!(s.phase, Phase::Initializing);
let v: Value = serde_json::from_str(&serde_json::to_string(&resp).unwrap()).unwrap();
assert_eq!(v["result"]["serverInfo"]["name"], "axterminator");
}
#[test]
fn initialized_notification_transitions_to_running() {
let mut s = Server::new();
let req = make_request(
1,
"initialize",
Some(json!({
"protocolVersion": "2025-11-05",
"capabilities": {},
"clientInfo": {"name": "test", "version": "1"}
})),
);
s.handle(&req, &mut Vec::<u8>::new());
assert_eq!(s.phase, Phase::Initializing);
s.handle_notification(&make_notification("notifications/initialized"));
assert_eq!(s.phase, Phase::Running);
}
#[test]
fn ping_returns_empty_object() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(2, "ping", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert_eq!(v["result"], json!({}));
}
#[test]
fn tools_list_returns_correct_count_for_feature_set() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(3, "tools/list", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let count = v["result"]["tools"].as_array().unwrap().len();
let base = 28usize; let extra_spaces: usize = if cfg!(feature = "spaces") { 5 } else { 0 };
let extra_audio: usize = if cfg!(feature = "audio") { 3 } else { 0 };
let extra_camera: usize = if cfg!(feature = "camera") { 3 } else { 0 };
let extra_watch: usize = if cfg!(feature = "watch") { 3 } else { 0 };
let extra_docker: usize = if cfg!(feature = "docker") { 2 } else { 0 };
assert_eq!(
count,
base + extra_spaces + extra_audio + extra_camera + extra_watch + extra_docker
);
}
#[test]
fn tools_list_before_initialized_returns_error() {
let mut s = Server::new();
let req = make_request(1, "tools/list", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v.get("error").is_some());
}
#[test]
fn unknown_method_returns_method_not_found() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(4, "sampling/createMessage", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert_eq!(v["error"]["code"], RpcError::METHOD_NOT_FOUND);
}
#[test]
fn notification_returns_none() {
let mut s = Server::new();
initialize_server(&mut s);
let notif = make_notification("notifications/cancelled");
let resp = s.handle(¬if, &mut Vec::<u8>::new());
assert!(resp.is_none());
}
#[test]
fn tools_call_is_accessible_succeeds() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
5,
"tools/call",
Some(json!({ "name": "ax_is_accessible", "arguments": {} })),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v["result"]["content"].is_array());
}
#[test]
fn invalid_initialize_params_returns_error() {
let mut s = Server::new();
let req = make_request(1, "initialize", Some(json!({"bad": "data"})));
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v.get("error").is_some());
}
#[test]
fn initialize_response_advertises_resources_capability() {
let mut s = Server::new();
let req = make_request(
1,
"initialize",
Some(json!({
"protocolVersion": "2025-11-05",
"capabilities": {},
"clientInfo": {"name": "test", "version": "1"}
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v["result"]["capabilities"]["resources"].is_object());
assert_eq!(v["result"]["capabilities"]["resources"]["subscribe"], true);
}
#[test]
fn initialize_response_advertises_prompts_capability() {
let mut s = Server::new();
let req = make_request(
1,
"initialize",
Some(json!({
"protocolVersion": "2025-11-05",
"capabilities": {},
"clientInfo": {"name": "test", "version": "1"}
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v["result"]["capabilities"]["prompts"].is_object());
}
#[test]
fn initialize_response_advertises_elicitation_capability() {
let mut s = Server::new();
let req = make_request(
1,
"initialize",
Some(json!({
"protocolVersion": "2025-11-05",
"capabilities": {},
"clientInfo": {"name": "test", "version": "1"}
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v["result"]["capabilities"]["elicitation"].is_object());
}
#[test]
fn server_handle_ping_returns_empty_object() {
let mut h = ServerHandle::new();
let init = make_request(
1,
"initialize",
Some(json!({
"protocolVersion": "2025-11-05",
"capabilities": {},
"clientInfo": {"name": "test", "version": "1"}
})),
);
h.handle(&init, &mut Vec::<u8>::new());
h.handle(
&make_notification("notifications/initialized"),
&mut Vec::<u8>::new(),
);
let req = make_request(2, "ping", None);
let resp = h.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert_eq!(v["result"], json!({}));
}
#[test]
fn server_handle_default_creates_uninitialized_instance() {
let mut h = ServerHandle::default();
let req = make_request(1, "tools/list", None);
let resp = h.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v.get("error").is_some());
}
#[test]
fn resources_list_returns_static_resources() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(10, "resources/list", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let resources = v["result"]["resources"].as_array().unwrap();
assert!(!resources.is_empty());
let has_status = resources
.iter()
.any(|r| r["uri"] == "axterminator://system/status");
assert!(has_status);
}
#[test]
fn resources_list_before_initialized_returns_error() {
let mut s = Server::new();
let req = make_request(10, "resources/list", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v.get("error").is_some());
}
#[test]
fn resources_templates_list_returns_templates() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(11, "resources/templates/list", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let templates = v["result"]["resourceTemplates"].as_array().unwrap();
assert!(!templates.is_empty());
let has_tree = templates
.iter()
.any(|t| t["uriTemplate"] == "axterminator://app/{name}/tree");
assert!(has_tree);
}
#[test]
fn resources_read_system_status_returns_contents() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
12,
"resources/read",
Some(json!({ "uri": "axterminator://system/status" })),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let contents = v["result"]["contents"].as_array().unwrap();
assert_eq!(contents.len(), 1);
assert!(contents[0]["text"].as_str().is_some());
}
#[test]
fn resources_read_missing_params_returns_error() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(13, "resources/read", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v.get("error").is_some());
}
#[test]
fn resources_read_unconnected_app_returns_error() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
14,
"resources/read",
Some(json!({ "uri": "axterminator://app/NotConnected/tree" })),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v.get("error").is_some());
}
#[test]
fn prompts_list_returns_ten_prompts() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(20, "prompts/list", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let prompts = v["result"]["prompts"].as_array().unwrap();
assert_eq!(prompts.len(), 10);
}
#[test]
fn prompts_list_before_initialized_returns_error() {
let mut s = Server::new();
let req = make_request(20, "prompts/list", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v.get("error").is_some());
}
#[test]
fn prompts_get_test_app_returns_messages() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
21,
"prompts/get",
Some(json!({
"name": "test-app",
"arguments": { "app_name": "Safari" }
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let msgs = v["result"]["messages"].as_array().unwrap();
assert_eq!(msgs.len(), 2);
assert_eq!(msgs[0]["role"], "user");
assert_eq!(msgs[1]["role"], "assistant");
}
#[test]
fn prompts_get_unknown_prompt_returns_error() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
22,
"prompts/get",
Some(json!({ "name": "nonexistent-prompt" })),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v.get("error").is_some());
}
#[test]
fn prompts_get_missing_required_arg_returns_error() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
23,
"prompts/get",
Some(json!({
"name": "navigate-to",
"arguments": { "app_name": "Finder" }
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v.get("error").is_some());
}
#[test]
fn prompts_get_missing_params_returns_error() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(24, "prompts/get", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v.get("error").is_some());
}
#[test]
fn initialize_response_advertises_tasks_capability() {
let mut s = Server::new();
let req = make_request(
1,
"initialize",
Some(json!({
"protocolVersion": "2025-11-05",
"capabilities": {},
"clientInfo": {"name": "test", "version": "1"}
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v["result"]["capabilities"]["tasks"].is_object());
}
#[test]
fn tasks_list_returns_empty_on_fresh_server() {
let mut s = Server::new();
initialize_server(&mut s);
let v = send(&mut s, 30, "tasks/list", None);
let tasks = v["result"]["tasks"].as_array().unwrap();
assert!(tasks.is_empty());
}
#[test]
fn tasks_list_before_initialized_returns_error() {
let mut s = Server::new();
let v = send(&mut s, 30, "tasks/list", None);
assert!(v.get("error").is_some());
}
#[test]
fn tools_call_with_meta_task_returns_working_status() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
31,
"tools/call",
Some(json!({
"name": "ax_is_accessible",
"arguments": {},
"_meta": { "task": true }
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let task = &v["result"]["task"];
assert!(task.is_object(), "expected task object in result");
assert_eq!(task["status"], "working");
assert!(task["taskId"].as_str().unwrap().starts_with("task-"));
}
#[test]
fn tasks_list_shows_task_after_async_call() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
32,
"tools/call",
Some(json!({
"name": "ax_is_accessible",
"arguments": {},
"_meta": { "task": true }
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let task_id = v["result"]["task"]["taskId"].as_str().unwrap().to_owned();
std::thread::sleep(std::time::Duration::from_millis(100));
let list_v = send(&mut s, 33, "tasks/list", None);
let tasks = list_v["result"]["tasks"].as_array().unwrap();
assert!(!tasks.is_empty());
let found = tasks.iter().any(|t| t["taskId"] == task_id);
assert!(found, "task {task_id} missing from tasks/list");
}
#[test]
fn tasks_result_returns_pending_while_working() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
34,
"tools/call",
Some(json!({
"name": "ax_is_accessible",
"arguments": {},
"_meta": { "task": true }
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let task_id = v["result"]["task"]["taskId"].as_str().unwrap().to_owned();
let result_v = send(
&mut s,
35,
"tasks/result",
Some(json!({ "taskId": task_id })),
);
assert!(
result_v.get("error").is_none(),
"tasks/result for a known task should not error"
);
}
#[test]
fn tasks_result_for_completed_task_returns_tool_result() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
36,
"tools/call",
Some(json!({
"name": "ax_is_accessible",
"arguments": {},
"_meta": { "task": true }
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let task_id = v["result"]["task"]["taskId"].as_str().unwrap().to_owned();
std::thread::sleep(std::time::Duration::from_millis(200));
let result_v = send(
&mut s,
37,
"tasks/result",
Some(json!({ "taskId": task_id })),
);
let result = &result_v["result"];
assert!(
result.get("content").is_some(),
"completed task should return tool result with content"
);
}
#[test]
fn tasks_result_unknown_task_id_returns_error() {
let mut s = Server::new();
initialize_server(&mut s);
let v = send(
&mut s,
38,
"tasks/result",
Some(json!({ "taskId": "task-nonexistent" })),
);
assert!(v.get("error").is_some());
assert_eq!(v["error"]["code"], -32_602);
}
#[test]
fn tasks_result_missing_params_returns_error() {
let mut s = Server::new();
initialize_server(&mut s);
let v = send(&mut s, 39, "tasks/result", None);
assert!(v.get("error").is_some());
}
#[test]
fn tasks_cancel_working_task_marks_it_cancelled() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
40,
"tools/call",
Some(json!({
"name": "ax_is_accessible",
"arguments": {},
"_meta": { "task": true }
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let task_id = v["result"]["task"]["taskId"].as_str().unwrap().to_owned();
let cancel_v = send(
&mut s,
41,
"tasks/cancel",
Some(json!({ "taskId": task_id })),
);
assert!(cancel_v.get("error").is_none());
assert_eq!(cancel_v["result"], json!({}));
std::thread::sleep(std::time::Duration::from_millis(100));
let list_v = send(&mut s, 42, "tasks/list", None);
let tasks = list_v["result"]["tasks"].as_array().unwrap();
let task = tasks.iter().find(|t| t["taskId"] == task_id).unwrap();
let status = task["status"].as_str().unwrap();
assert!(
status == "cancelled" || status == "done" || status == "failed",
"task should be in a terminal state, got: {status}"
);
}
#[test]
fn tasks_cancel_unknown_task_id_returns_error() {
let mut s = Server::new();
initialize_server(&mut s);
let v = send(
&mut s,
43,
"tasks/cancel",
Some(json!({ "taskId": "task-does-not-exist" })),
);
assert!(v.get("error").is_some());
assert_eq!(v["error"]["code"], -32_602);
}
#[test]
fn tasks_cancel_missing_params_returns_error() {
let mut s = Server::new();
initialize_server(&mut s);
let v = send(&mut s, 44, "tasks/cancel", None);
assert!(v.get("error").is_some());
}
#[test]
fn tools_call_without_meta_task_executes_synchronously() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
50,
"tools/call",
Some(json!({ "name": "ax_is_accessible", "arguments": {} })),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(v["result"].get("task").is_none());
assert!(v["result"]["content"].is_array());
}
#[test]
fn task_id_format_is_zero_padded_hex_prefix() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(
60,
"tools/call",
Some(json!({
"name": "ax_is_accessible",
"arguments": {},
"_meta": { "task": true }
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
let task_id = v["result"]["task"]["taskId"].as_str().unwrap();
assert!(task_id.starts_with("task-"));
let digits = &task_id[5..];
assert_eq!(digits.len(), 16, "task ID should have 16 digit suffix");
assert!(digits.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn initialize_response_advertises_sampling_capability() {
let mut s = Server::new();
let req = make_request(
70,
"initialize",
Some(json!({
"protocolVersion": "2025-11-05",
"capabilities": {},
"clientInfo": {"name": "test", "version": "1"}
})),
);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert!(
v["result"]["capabilities"]["sampling"].is_object(),
"expected sampling capability object in server capabilities"
);
}
#[test]
fn client_supports_sampling_false_when_not_advertised() {
let mut s = Server::new();
let req = make_request(
71,
"initialize",
Some(json!({
"protocolVersion": "2025-11-05",
"capabilities": {},
"clientInfo": {"name": "no-sampling-client", "version": "1"}
})),
);
s.handle(&req, &mut Vec::<u8>::new());
assert!(
!s.client_supports_sampling,
"client_supports_sampling should be false when client omits sampling capability"
);
}
#[test]
fn client_supports_sampling_true_when_advertised() {
let mut s = Server::new();
let req = make_request(
72,
"initialize",
Some(json!({
"protocolVersion": "2025-11-05",
"capabilities": {
"sampling": { "createMessage": {} }
},
"clientInfo": {"name": "claude-code", "version": "2.0"}
})),
);
s.handle(&req, &mut Vec::<u8>::new());
assert!(
s.client_supports_sampling,
"client_supports_sampling should be true when client advertises sampling"
);
}
#[test]
fn client_supports_sampling_false_by_default_before_initialize() {
let s = Server::new();
assert!(!s.client_supports_sampling);
}
#[test]
fn sampling_createMessage_is_method_not_found_from_server_side() {
let mut s = Server::new();
initialize_server(&mut s);
let req = make_request(73, "sampling/createMessage", None);
let resp = s.handle(&req, &mut Vec::<u8>::new()).unwrap();
let v: Value = serde_json::to_value(&resp).unwrap();
assert_eq!(v["error"]["code"], RpcError::METHOD_NOT_FOUND);
}