use std::borrow::Cow;
use std::io::Write;
use rmcp::{model::CallToolRequestParams, transport::TokioChildProcess, ServiceExt};
use serde_json::{json, Map, Value};
use algocline_app::PRESET_CATALOG_VERSION;
fn call_params(name: &str, args: Value) -> CallToolRequestParams {
let arguments = match args {
Value::Object(map) => Some(map),
_ => None,
};
CallToolRequestParams {
name: Cow::Owned(name.to_string()),
arguments,
meta: None,
task: None,
}
}
fn call_params_empty(name: &str) -> CallToolRequestParams {
CallToolRequestParams {
name: Cow::Owned(name.to_string()),
arguments: Some(Map::new()),
meta: None,
task: None,
}
}
async fn connect() -> rmcp::service::RunningService<rmcp::RoleClient, ()> {
let bin = std::env::var("CARGO_BIN_EXE_alc")
.unwrap_or_else(|_| format!("{}/target/debug/alc", env!("CARGO_MANIFEST_DIR")));
let transport = TokioChildProcess::new(tokio::process::Command::new(bin))
.expect("failed to spawn alc server");
().serve(transport)
.await
.expect("failed to initialize MCP session")
}
fn extract_text(result: &rmcp::model::CallToolResult) -> &str {
result
.content
.first()
.and_then(|c| c.raw.as_text())
.map(|t| t.text.as_str())
.unwrap_or("")
}
fn redact_uuids(text: &str) -> String {
let re = regex::Regex::new(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
.expect("invalid regex");
re.replace_all(text, "<UUID>").to_string()
}
fn redact_paths(text: &str) -> String {
if let Some(home) = dirs::home_dir() {
text.replace(home.to_str().unwrap_or(""), "<HOME>")
} else {
text.to_string()
}
}
fn redact(text: &str) -> String {
redact_paths(&redact_uuids(text))
}
async fn call_json(
client: &rmcp::service::RunningService<rmcp::RoleClient, ()>,
name: &str,
args: Value,
) -> Value {
let result = client
.call_tool(call_params(name, args))
.await
.expect("call_tool failed");
let text = extract_text(&result);
serde_json::from_str(text).unwrap_or_else(|e| panic!("JSON parse failed: {e}\nraw: {text}"))
}
#[tokio::test]
async fn test_list_tools() {
let client = connect().await;
let tools = client
.list_all_tools()
.await
.expect("list_all_tools failed");
let mut names: Vec<&str> = tools.iter().map(|t| t.name.as_ref()).collect();
names.sort();
insta::assert_json_snapshot!("list_tools", names);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_info() {
let client = connect().await;
let result = client
.call_tool(call_params_empty("alc_info"))
.await
.expect("call_tool failed");
let text = extract_text(&result);
let redacted = redact(text);
insta::assert_snapshot!("alc_info", redacted);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_status_empty() {
let client = connect().await;
let result = client
.call_tool(call_params_empty("alc_status"))
.await
.expect("call_tool failed");
let text = extract_text(&result);
insta::assert_snapshot!("alc_status_empty", text);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_status_preset_meta_empty_registry() {
let client = connect().await;
let resp = call_json(&client, "alc_status", json!({ "pending_filter": "meta" })).await;
assert_eq!(resp["active_sessions"], 0);
assert_eq!(resp["sessions"].as_array().unwrap().len(), 0);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_status_preset_preview_and_full_empty_registry() {
let client = connect().await;
for preset in ["preview", "full"] {
let resp = call_json(&client, "alc_status", json!({ "pending_filter": preset })).await;
assert_eq!(
resp["active_sessions"], 0,
"preset '{preset}' should return empty-registry shape"
);
}
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_status_unknown_preset_errors() {
let client = connect().await;
let result = client
.call_tool(call_params(
"alc_status",
json!({ "pending_filter": "bogus" }),
))
.await
.expect("call_tool failed");
let text = extract_text(&result);
assert!(
text.contains("unknown pending_filter preset"),
"expected typed error, got: {text}"
);
assert!(
text.contains("bogus"),
"error should echo the bad preset name, got: {text}"
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_status_bad_shape_errors() {
let client = connect().await;
let result = client
.call_tool(call_params("alc_status", json!({ "pending_filter": true })))
.await
.expect("call_tool failed");
let text = extract_text(&result);
assert!(
text.contains("pending_filter must be a preset name"),
"expected shape error, got: {text}"
);
assert!(
text.contains("bool"),
"error should name the bad type, got: {text}"
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_status_custom_object_filter() {
let client = connect().await;
let resp = call_json(
&client,
"alc_status",
json!({
"pending_filter": {
"query_id": true,
"prompt": { "mode": "preview", "chars": 50 }
}
}),
)
.await;
assert_eq!(resp["active_sessions"], 0);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_status_paused_session_projection() {
let client = connect().await;
let resp = call_json(
&client,
"alc_run",
json!({ "code": "return alc.llm('What is 2+2?')" }),
)
.await;
assert_eq!(resp["status"], "needs_response");
let session_id = resp["session_id"].as_str().expect("session_id").to_string();
let resp = call_json(
&client,
"alc_status",
json!({
"session_id": session_id,
"pending_filter": "meta",
}),
)
.await;
assert_eq!(resp["pending_queries"], 1, "should report 1 pending query");
let pending = resp["pending"]
.as_array()
.expect("pending array should be emitted when filter is set");
assert_eq!(pending.len(), 1);
assert!(
pending[0]["query_id"].is_string(),
"query_id must be present"
);
assert!(
pending[0]["max_tokens"].is_number(),
"max_tokens must be present"
);
assert!(
pending[0].get("prompt").is_none(),
"meta preset must not project prompt"
);
assert!(
pending[0].get("prompt_preview").is_none(),
"meta preset must not project prompt_preview"
);
let resp = call_json(
&client,
"alc_status",
json!({
"session_id": session_id,
"pending_filter": "preview",
}),
)
.await;
let pending = resp["pending"].as_array().expect("pending array");
assert!(
pending[0]["prompt_preview"].is_string(),
"preview preset must project prompt_preview"
);
let _ = call_json(
&client,
"alc_continue",
json!({ "session_id": session_id, "response": "4" }),
)
.await;
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_run_pure_lua() {
let client = connect().await;
let result = client
.call_tool(call_params("alc_run", json!({ "code": "return 1 + 2" })))
.await
.expect("call_tool failed");
let text = extract_text(&result);
let parsed: Value = serde_json::from_str(text).expect("response should be JSON");
assert_eq!(parsed["status"], "completed");
assert_eq!(parsed["result"], 3);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_run_lua_error() {
let client = connect().await;
let result = client
.call_tool(call_params(
"alc_run",
json!({ "code": "error('intentional test error')" }),
))
.await
.expect("call_tool failed");
let text = extract_text(&result);
assert!(
text.contains("intentional test error"),
"expected error message in response, got: {text}"
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_continue_invalid_session() {
let client = connect().await;
let result = client
.call_tool(call_params(
"alc_continue",
json!({
"session_id": "00000000-0000-0000-0000-000000000000",
"response": "test"
}),
))
.await
.expect("call_tool failed");
let text = extract_text(&result);
assert!(
text.to_lowercase().contains("not found")
|| text.to_lowercase().contains("no session")
|| text.to_lowercase().contains("unknown"),
"expected 'not found' error, got: {text}"
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_llm_single_roundtrip() {
let client = connect().await;
let resp = call_json(
&client,
"alc_run",
json!({ "code": "return alc.llm('What is 2+2?')" }),
)
.await;
assert_eq!(resp["status"], "needs_response");
let session_id = resp["session_id"].as_str().expect("session_id missing");
assert!(resp["prompt"].as_str().is_some(), "prompt missing");
assert!(
resp.get("query_id").is_some(),
"query_id missing in response"
);
let resp = call_json(
&client,
"alc_continue",
json!({ "session_id": session_id, "response": "4" }),
)
.await;
assert_eq!(resp["status"], "completed");
assert_eq!(resp["result"], "4");
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_llm_batch_roundtrip() {
let client = connect().await;
let code = r#"
local results = alc.llm_batch({
{ prompt = "Say A" },
{ prompt = "Say B" },
})
return results
"#;
let resp = call_json(&client, "alc_run", json!({ "code": code })).await;
assert_eq!(resp["status"], "needs_response");
let session_id = resp["session_id"].as_str().expect("session_id missing");
let queries = resp["queries"].as_array().expect("queries array missing");
assert_eq!(queries.len(), 2);
let q0_id = queries[0]["id"].as_str().expect("q-0 id missing");
let q1_id = queries[1]["id"].as_str().expect("q-1 id missing");
let resp = call_json(
&client,
"alc_continue",
json!({
"session_id": session_id,
"responses": [
{ "query_id": q0_id, "response": "Alpha" },
{ "query_id": q1_id, "response": "Beta" },
]
}),
)
.await;
assert_eq!(resp["status"], "completed");
let result = resp["result"].as_array().expect("result should be array");
assert_eq!(result.len(), 2);
assert_eq!(result[0], "Alpha");
assert_eq!(result[1], "Beta");
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_cache_hit_miss() {
let client = connect().await;
let code = r#"
local r1 = alc.cache("cached prompt")
local r2 = alc.cache("cached prompt")
local info = alc.cache_info()
return { r1 = r1, r2 = r2, info = info }
"#;
let resp = call_json(&client, "alc_run", json!({ "code": code })).await;
assert_eq!(resp["status"], "needs_response");
let session_id = resp["session_id"].as_str().expect("session_id missing");
let resp = call_json(
&client,
"alc_continue",
json!({ "session_id": session_id, "response": "cached_value" }),
)
.await;
assert_eq!(resp["status"], "completed");
let result = &resp["result"];
assert_eq!(result["r1"], "cached_value");
assert_eq!(
result["r2"], "cached_value",
"cache hit should return same value"
);
assert_eq!(result["info"]["hits"], 1);
assert_eq!(result["info"]["misses"], 1);
assert_eq!(result["info"]["entries"], 1);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_parallel_roundtrip() {
let client = connect().await;
let code = r#"
local items = {"apple", "banana"}
local results = alc.parallel(items, function(item, i)
return "Describe " .. item
end)
return results
"#;
let resp = call_json(&client, "alc_run", json!({ "code": code })).await;
assert_eq!(resp["status"], "needs_response");
let session_id = resp["session_id"].as_str().expect("session_id missing");
let queries = resp["queries"].as_array().expect("queries missing");
assert_eq!(queries.len(), 2);
let q0_id = queries[0]["id"].as_str().expect("id missing");
let q1_id = queries[1]["id"].as_str().expect("id missing");
let resp = call_json(
&client,
"alc_continue",
json!({
"session_id": session_id,
"responses": [
{ "query_id": q0_id, "response": "A red fruit" },
{ "query_id": q1_id, "response": "A yellow fruit" },
]
}),
)
.await;
assert_eq!(resp["status"], "completed");
let result = resp["result"].as_array().expect("result array");
assert_eq!(result[0], "A red fruit");
assert_eq!(result[1], "A yellow fruit");
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_fork_roundtrip() {
let client = connect().await;
let tmp_dir = tempfile::tempdir().expect("failed to create tempdir");
let pkg_a_dir = tmp_dir.path().join("e2e_fork_a");
std::fs::create_dir_all(&pkg_a_dir).expect("mkdir");
let mut f = std::fs::File::create(pkg_a_dir.join("init.lua")).expect("create init.lua");
write!(
f,
r#"local M = {{}}
M.meta = {{ name = "e2e_fork_a", version = "0.1.0", description = "E2E fork A" }}
function M.run(ctx)
return alc.llm("Fork A: " .. (ctx.task or ""))
end
return M"#
)
.expect("write init.lua");
let pkg_b_dir = tmp_dir.path().join("e2e_fork_b");
std::fs::create_dir_all(&pkg_b_dir).expect("mkdir");
let mut f = std::fs::File::create(pkg_b_dir.join("init.lua")).expect("create init.lua");
write!(
f,
r#"local M = {{}}
M.meta = {{ name = "e2e_fork_b", version = "0.1.0", description = "E2E fork B" }}
function M.run(ctx)
return alc.llm("Fork B: " .. (ctx.task or ""))
end
return M"#
)
.expect("write init.lua");
call_json(
&client,
"alc_pkg_install",
json!({ "url": pkg_a_dir.to_string_lossy() }),
)
.await;
call_json(
&client,
"alc_pkg_install",
json!({ "url": pkg_b_dir.to_string_lossy() }),
)
.await;
let code = r#"
local results = alc.fork({"e2e_fork_a", "e2e_fork_b"}, ctx)
return results
"#;
let resp = call_json(
&client,
"alc_run",
json!({ "code": code, "ctx": { "task": "test" } }),
)
.await;
assert_eq!(resp["status"], "needs_response");
let session_id = resp["session_id"]
.as_str()
.expect("session_id missing")
.to_string();
let mut completed = false;
let mut final_resp = resp;
let mut iterations = 0;
const MAX_ITERATIONS: usize = 20;
while !completed {
iterations += 1;
assert!(
iterations <= MAX_ITERATIONS,
"fork test exceeded {MAX_ITERATIONS} iterations — possible infinite loop"
);
if final_resp["status"] == "needs_response" {
let session = final_resp["session_id"]
.as_str()
.unwrap_or(&session_id)
.to_string();
if let Some(queries) = final_resp["queries"].as_array() {
let responses: Vec<Value> = queries
.iter()
.map(|q| {
let qid = q["id"].as_str().expect("query id");
let prompt = q["prompt"].as_str().unwrap_or("");
let answer = if prompt.contains("Fork A") {
"Answer A"
} else {
"Answer B"
};
json!({ "query_id": qid, "response": answer })
})
.collect();
final_resp = call_json(
&client,
"alc_continue",
json!({ "session_id": session, "responses": responses }),
)
.await;
} else {
let prompt = final_resp["prompt"].as_str().unwrap_or("");
let answer = if prompt.contains("Fork A") {
"Answer A"
} else {
"Answer B"
};
final_resp = call_json(
&client,
"alc_continue",
json!({ "session_id": session, "response": answer }),
)
.await;
}
} else {
completed = true;
}
}
assert_eq!(final_resp["status"], "completed");
let result = final_resp["result"]
.as_array()
.expect("result should be array");
assert_eq!(result.len(), 2);
let strategy_a = &result[0];
assert_eq!(strategy_a["strategy"], "e2e_fork_a");
assert_eq!(strategy_a["ok"], true);
assert_eq!(strategy_a["result"], "Answer A");
let strategy_b = &result[1];
assert_eq!(strategy_b["strategy"], "e2e_fork_b");
assert_eq!(strategy_b["ok"], true);
assert_eq!(strategy_b["result"], "Answer B");
if let Some(home) = dirs::home_dir() {
let pkg_cache = home.join(".algocline").join("packages");
let _ = std::fs::remove_dir_all(pkg_cache.join("e2e_fork_a"));
let _ = std::fs::remove_dir_all(pkg_cache.join("e2e_fork_b"));
}
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_pkg_install_returns_types_path() {
let client = connect().await;
let tmp_dir = tempfile::tempdir().expect("tempdir");
let pkg_dir = tmp_dir.path().join("e2e_types_test");
std::fs::create_dir_all(&pkg_dir).expect("mkdir");
std::fs::write(
pkg_dir.join("init.lua"),
r#"local M = {}
M.meta = { name = "e2e_types_test", version = "0.1.0" }
function M.run(ctx) return "ok" end
return M"#,
)
.expect("write init.lua");
let resp = call_json(
&client,
"alc_pkg_install",
json!({ "url": pkg_dir.to_string_lossy() }),
)
.await;
assert_eq!(resp["installed"], json!(["e2e_types_test"]));
assert!(
resp["types_path"].is_string(),
"types_path should be present in pkg_install response"
);
let types_path = resp["types_path"].as_str().unwrap();
assert!(
types_path.ends_with("types/alc.d.lua"),
"types_path should end with types/alc.d.lua, got: {types_path}"
);
if let Some(home) = dirs::home_dir() {
let pkg_cache = home.join(".algocline").join("packages");
let _ = std::fs::remove_dir_all(pkg_cache.join("e2e_types_test"));
}
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_pkg_remove_scope_global_cleans_manifest_not_files() {
let client = connect().await;
let tmp_dir = tempfile::tempdir().expect("tempdir");
let pkg_name = "e2e_remove_global";
let pkg_dir = tmp_dir.path().join(pkg_name);
std::fs::create_dir_all(&pkg_dir).expect("mkdir");
std::fs::write(
pkg_dir.join("init.lua"),
r#"local M = {}
M.meta = { name = "e2e_remove_global", version = "0.1.0" }
function M.run(ctx) return "ok" end
return M"#,
)
.expect("write init.lua");
call_json(
&client,
"alc_pkg_install",
json!({ "url": pkg_dir.to_string_lossy() }),
)
.await;
let home = dirs::home_dir().expect("home");
let manifest_path = home.join(".algocline").join("installed.json");
let cache_dir = home.join(".algocline").join("packages").join(pkg_name);
let before: Value =
serde_json::from_str(&std::fs::read_to_string(&manifest_path).expect("manifest read"))
.expect("manifest JSON");
assert!(
before["packages"][pkg_name].is_object(),
"precondition: manifest must contain '{pkg_name}' before remove"
);
assert!(cache_dir.exists(), "precondition: cache dir must exist");
let resp = call_json(
&client,
"alc_pkg_remove",
json!({ "name": pkg_name, "scope": "global" }),
)
.await;
assert_eq!(resp["removed"], pkg_name);
assert_eq!(resp["scope"], "global");
let after: Value =
serde_json::from_str(&std::fs::read_to_string(&manifest_path).expect("manifest read"))
.expect("manifest JSON");
assert!(
after["packages"][pkg_name].is_null(),
"manifest still contains '{pkg_name}' after scope=global remove"
);
assert!(
cache_dir.exists(),
"scope=global must not delete ~/.algocline/packages/{pkg_name}/"
);
let _ = std::fs::remove_dir_all(&cache_dir);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_variant_scope_link_then_run_require() {
let client = connect().await;
let tmp = tempfile::tempdir().expect("failed to create tempdir");
let project_root = tmp.path();
std::fs::write(project_root.join("alc.toml"), "[packages]\n").expect("write alc.toml");
let pkg_dir = tmp.path().join("variant_src").join("e2e_variant_pkg");
std::fs::create_dir_all(&pkg_dir).expect("mkdir pkg_dir");
std::fs::write(
pkg_dir.join("init.lua"),
r#"return { value = "from-variant" }"#,
)
.expect("write init.lua");
let link_resp = call_json(
&client,
"alc_pkg_link",
json!({
"path": pkg_dir.to_string_lossy(),
"scope": "variant",
"project_root": project_root.to_string_lossy(),
}),
)
.await;
assert!(
link_resp.get("error").is_none(),
"alc_pkg_link should succeed, got: {link_resp}"
);
assert!(
project_root.join("alc.local.toml").exists(),
"alc.local.toml should have been created"
);
let run_resp = call_json(
&client,
"alc_run",
json!({
"code": r#"return require("e2e_variant_pkg").value"#,
"project_root": project_root.to_string_lossy(),
}),
)
.await;
assert_eq!(
run_resp["status"], "completed",
"alc_run should complete, got: {run_resp}"
);
assert_eq!(
run_resp["result"], "from-variant",
"variant pkg should be resolved and return its sentinel value"
);
let list_resp = call_json(
&client,
"alc_pkg_list",
json!({ "project_root": project_root.to_string_lossy() }),
)
.await;
let packages = list_resp["packages"]
.as_array()
.expect("packages array missing");
let entry = packages
.iter()
.find(|p| p["name"] == "e2e_variant_pkg")
.expect("e2e_variant_pkg not found in alc_pkg_list");
assert_eq!(entry["scope"], "variant");
assert_eq!(entry["active"], true);
assert_eq!(entry["resolved_source_kind"], "variant");
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_pkg_repair_reinstalls_deleted_dir() {
let client = connect().await;
let tmp = tempfile::tempdir().expect("tempdir");
let source = tmp.path().join("e2e_repair_pkg");
std::fs::create_dir_all(&source).expect("mkdir");
std::fs::write(
source.join("init.lua"),
r#"local M = {}
M.meta = { name = "e2e_repair_pkg", version = "0.1.0" }
function M.run(ctx) return "ok" end
return M"#,
)
.expect("write init.lua");
call_json(
&client,
"alc_pkg_install",
json!({ "url": source.to_string_lossy() }),
)
.await;
let dest = dirs::home_dir()
.expect("home")
.join(".algocline")
.join("packages")
.join("e2e_repair_pkg");
assert!(dest.exists(), "dest should exist after install");
std::fs::remove_dir_all(&dest).expect("rm dest");
assert!(!dest.exists());
let resp = call_json(
&client,
"alc_pkg_repair",
json!({ "name": "e2e_repair_pkg" }),
)
.await;
let repaired = resp["repaired"].as_array().expect("repaired array missing");
assert_eq!(repaired.len(), 1, "one repair expected, got: {resp}");
assert_eq!(repaired[0]["name"], "e2e_repair_pkg");
assert_eq!(repaired[0]["kind"], "installed_missing");
assert!(dest.exists(), "dest should be restored after repair");
let _ = std::fs::remove_dir_all(&dest);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_pkg_doctor_reports_installed_missing() {
let client = connect().await;
let tmp = tempfile::tempdir().expect("tempdir");
let source = tmp.path().join("e2e_doctor_pkg");
std::fs::create_dir_all(&source).expect("mkdir");
std::fs::write(
source.join("init.lua"),
r#"local M = {}
M.meta = { name = "e2e_doctor_pkg", version = "0.1.0" }
function M.run(ctx) return "ok" end
return M"#,
)
.expect("write init.lua");
call_json(
&client,
"alc_pkg_install",
json!({ "url": source.to_string_lossy() }),
)
.await;
let dest = dirs::home_dir()
.expect("home")
.join(".algocline")
.join("packages")
.join("e2e_doctor_pkg");
assert!(dest.exists(), "dest should exist after install");
std::fs::remove_dir_all(&dest).expect("rm dest");
assert!(!dest.exists());
let resp = call_json(
&client,
"alc_pkg_doctor",
json!({ "name": "e2e_doctor_pkg" }),
)
.await;
let installed_missing = resp["installed_missing"]
.as_array()
.expect("installed_missing array missing");
let entry = installed_missing
.iter()
.find(|e| e["name"] == "e2e_doctor_pkg")
.unwrap_or_else(|| panic!("e2e_doctor_pkg not found in installed_missing, got: {resp}"));
assert_eq!(entry["kind"], "installed_missing");
assert!(
!dest.exists(),
"dest must not be resurrected by doctor (read-only)"
);
let _ = call_json(
&client,
"alc_pkg_repair",
json!({ "name": "e2e_doctor_pkg" }),
)
.await;
let _ = std::fs::remove_dir_all(&dest);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_pkg_doctor_unknown_pkg_errors() {
let client = connect().await;
let result = client
.call_tool(call_params(
"alc_pkg_doctor",
json!({ "name": "nonexistent_xyz_pkg" }),
))
.await
.expect("call_tool failed");
let text = extract_text(&result);
assert!(
text.contains("not found in installed.json"),
"expected unknown-pkg error message, got: {text}"
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_pkg_doctor_shape_error() {
let client = connect().await;
let outcome = client
.call_tool(call_params("alc_pkg_doctor", json!({ "name": 123 })))
.await;
match outcome {
Ok(result) => {
let is_error = result.is_error.unwrap_or(false);
let text = extract_text(&result);
let has_type_error = text.contains("invalid type")
|| text.contains("expected a string")
|| text.contains("expected string");
assert!(
is_error || has_type_error,
"expected shape error (is_error=true or type-mismatch text), got is_error={is_error:?}, text: {text}"
);
}
Err(e) => {
let msg = format!("{e}");
assert!(
msg.contains("invalid type") && msg.contains("string"),
"expected invalid-type error from param deserialization, got: {msg}"
);
}
}
client.cancel().await.expect("cancel failed");
}
fn setup_hub_fixture() -> tempfile::TempDir {
let tmp = tempfile::tempdir().expect("tempdir");
let pkg_dir = tmp.path().join("fake_pkg");
std::fs::create_dir_all(&pkg_dir).expect("mkdir fake_pkg");
std::fs::write(
pkg_dir.join("init.lua"),
r#"local M = {}
M.meta = {
name = "fake_pkg",
version = "0.1.0",
category = "test",
description = "fake package used by e2e tests",
}
M.spec = {}
return M
"#,
)
.expect("write init.lua");
std::fs::write(
tmp.path().join("configs.toml"),
r#"[context7]
projectTitle = "test"
description = "test"
rules = []
[devin]
project_name = "test"
"#,
)
.expect("write configs.toml");
tmp
}
#[tokio::test]
async fn test_alc_hub_reindex_ok() {
let client = connect().await;
let tmp = setup_hub_fixture();
let source_dir = tmp.path().to_str().expect("utf-8 path").to_string();
let output_path = tmp
.path()
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let resp = call_json(
&client,
"alc_hub_reindex",
json!({
"source_dir": source_dir,
"output_path": output_path,
}),
)
.await;
let pkg_count = resp
.get("package_count")
.and_then(|v| v.as_u64())
.unwrap_or_else(|| panic!("expected package_count u64 in response: {resp}"));
assert!(
pkg_count > 0,
"expected at least one package in reindex output, got {pkg_count}: {resp}"
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_gendoc_ok() {
let client = connect().await;
let tmp = setup_hub_fixture();
let source_dir = tmp.path().to_str().expect("utf-8 path").to_string();
let output_path = tmp
.path()
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let _ = call_json(
&client,
"alc_hub_reindex",
json!({
"source_dir": source_dir.clone(),
"output_path": output_path,
}),
)
.await;
let out_dir_path = tmp.path().join("docs");
let out_dir = out_dir_path.to_str().expect("utf-8 path").to_string();
let resp = call_json(
&client,
"alc_hub_gendoc",
json!({
"source_dir": source_dir,
"out_dir": out_dir,
}),
)
.await;
assert!(
resp.get("source_dir").is_some(),
"expected source_dir in gendoc response, got: {resp}"
);
let narrative = out_dir_path.join("narrative").join("fake_pkg.md");
assert!(
narrative.exists(),
"expected narrative/fake_pkg.md to be generated at {} (gendoc resp: {resp})",
narrative.display()
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_gendoc_with_toml_config_context7() {
let client = connect().await;
let tmp = setup_hub_fixture();
let source_dir = tmp.path().to_str().expect("utf-8 path").to_string();
let output_path = tmp
.path()
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let config_path = tmp
.path()
.join("configs.toml")
.to_str()
.expect("utf-8 path")
.to_string();
let _ = call_json(
&client,
"alc_hub_reindex",
json!({
"source_dir": source_dir.clone(),
"output_path": output_path,
}),
)
.await;
let _resp = call_json(
&client,
"alc_hub_gendoc",
json!({
"source_dir": source_dir.clone(),
"projections": ["context7"],
"config_path": config_path,
}),
)
.await;
let context7_json = tmp.path().join("context7.json");
assert!(
context7_json.exists(),
"expected context7 projection to be generated at {}",
context7_json.display()
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_gendoc_missing_config() {
let client = connect().await;
let tmp = setup_hub_fixture();
let source_dir = tmp.path().to_str().expect("utf-8 path").to_string();
let output_path = tmp
.path()
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let _ = call_json(
&client,
"alc_hub_reindex",
json!({
"source_dir": source_dir.clone(),
"output_path": output_path,
}),
)
.await;
let outcome = client
.call_tool(call_params(
"alc_hub_gendoc",
json!({
"source_dir": source_dir,
"projections": ["context7"],
}),
))
.await;
match outcome {
Ok(result) => {
let is_error = result.is_error.unwrap_or(false);
let text = extract_text(&result);
assert!(
is_error,
"expected is_error=true for missing config_path, got is_error={is_error:?}, text: {text}"
);
assert!(
text.contains("config_path"),
"expected error text to mention config_path, got: {text}"
);
}
Err(e) => panic!("unexpected call_tool Err: {e}"),
}
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_gendoc_unknown_projection_rejected() {
let client = connect().await;
let tmp = setup_hub_fixture();
let source_dir = tmp.path().to_str().expect("utf-8 path").to_string();
let output_path = tmp
.path()
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let _ = call_json(
&client,
"alc_hub_reindex",
json!({
"source_dir": source_dir.clone(),
"output_path": output_path,
}),
)
.await;
let outcome = client
.call_tool(call_params(
"alc_hub_gendoc",
json!({
"source_dir": source_dir,
"projections": ["unknown_projection"],
}),
))
.await;
match outcome {
Ok(result) => {
let is_error = result.is_error.unwrap_or(false);
let text = extract_text(&result);
assert!(
is_error,
"expected is_error=true for unknown projection, got is_error={is_error:?}, text: {text}"
);
assert!(
text.contains("unknown projection"),
"expected unknown projection error text, got: {text}"
);
}
Err(e) => panic!("unexpected call_tool Err: {e}"),
}
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_dist_ok() {
let client = connect().await;
let tmp = setup_hub_fixture();
let source_dir = tmp.path().to_str().expect("utf-8 path").to_string();
let output_path = tmp
.path()
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let out_dir = tmp
.path()
.join("docs")
.to_str()
.expect("utf-8 path")
.to_string();
let resp = call_json(
&client,
"alc_hub_dist",
json!({
"source_dir": source_dir,
"output_path": output_path,
"out_dir": out_dir,
}),
)
.await;
assert_eq!(
resp.get("preset_catalog_version").and_then(|v| v.as_str()),
Some(PRESET_CATALOG_VERSION),
"expected preset_catalog_version in dist response, got: {resp}"
);
assert!(
resp.get("preset").is_none(),
"expected no preset object when preset omitted, got: {resp}"
);
let reindex = resp
.get("reindex")
.unwrap_or_else(|| panic!("expected reindex field, got: {resp}"));
let pkg_count = reindex
.get("package_count")
.and_then(|v| v.as_u64())
.unwrap_or_else(|| panic!("expected reindex.package_count u64, got: {resp}"));
assert!(
pkg_count > 0,
"expected reindex.package_count > 0, got {pkg_count}: {resp}"
);
let gendoc = resp
.get("gendoc")
.unwrap_or_else(|| panic!("expected gendoc field, got: {resp}"));
assert!(
gendoc.is_object(),
"expected gendoc to be a JSON object, got: {resp}"
);
assert!(
gendoc.get("stdout").is_some(),
"dist.gendoc must include stdout field",
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_info_includes_preset_catalog_version() {
let client = connect().await;
let resp = call_json(&client, "alc_info", json!({})).await;
assert_eq!(
resp.get("preset_catalog_version").and_then(|v| v.as_str()),
Some(PRESET_CATALOG_VERSION),
"expected preset_catalog_version in alc_info, got: {resp}"
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_dist_preset_publish_uses_alc_toml_override() {
let client = connect().await;
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
std::fs::write(
root.join("alc.toml"),
r#"[packages]
[hub.dist]
[hub.dist.presets.publish]
projections = ["context7"]
config_path = "configs.toml"
"#,
)
.expect("write alc.toml");
let hub_dir = root.join("hub");
std::fs::create_dir_all(&hub_dir).expect("mkdir hub");
std::fs::write(
hub_dir.join("configs.toml"),
r#"[context7]
projectTitle = "test"
description = "test"
rules = []
"#,
)
.expect("write configs.toml");
let pkg_dir = hub_dir.join("fake_pkg");
std::fs::create_dir_all(&pkg_dir).expect("mkdir fake_pkg");
std::fs::write(
pkg_dir.join("init.lua"),
r#"local M = {}
M.meta = {
name = "fake_pkg",
version = "0.1.0",
category = "test",
description = "fake package used by preset e2e",
}
M.spec = {}
return M
"#,
)
.expect("write init.lua");
let source_dir = hub_dir.to_str().expect("utf-8 path").to_string();
let project_root = root.to_str().expect("utf-8 path").to_string();
let output_path = hub_dir
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let out_dir = hub_dir
.join("docs")
.to_str()
.expect("utf-8 path")
.to_string();
let resp = call_json(
&client,
"alc_hub_dist",
json!({
"source_dir": source_dir,
"project_root": project_root,
"output_path": output_path,
"out_dir": out_dir,
"preset": "publish",
}),
)
.await;
assert_eq!(
resp.get("preset_catalog_version").and_then(|v| v.as_str()),
Some(PRESET_CATALOG_VERSION),
"expected preset_catalog_version in dist response, got: {resp}"
);
let preset = resp
.get("preset")
.unwrap_or_else(|| panic!("expected preset object, got: {resp}"));
assert_eq!(preset.get("name").and_then(|v| v.as_str()), Some("publish"));
let resolved = preset
.get("resolved")
.unwrap_or_else(|| panic!("expected preset.resolved, got: {preset}"));
let projections = resolved
.get("projections")
.and_then(|v| v.as_array())
.unwrap_or_else(|| panic!("expected projections array, got: {resolved}"));
let projection_names: Vec<&str> = projections
.iter()
.map(|v| v.as_str().expect("projection string"))
.collect();
assert_eq!(projection_names, vec!["context7"]);
let context7_json = hub_dir.join("context7.json");
assert!(
context7_json.exists(),
"expected context7.json at {}",
context7_json.display()
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_dist_with_toml_config_context7() {
let client = connect().await;
let tmp = setup_hub_fixture();
let source_dir = tmp.path().to_str().expect("utf-8 path").to_string();
let output_path = tmp
.path()
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let config_path = tmp
.path()
.join("configs.toml")
.to_str()
.expect("utf-8 path")
.to_string();
let _resp = call_json(
&client,
"alc_hub_dist",
json!({
"source_dir": source_dir,
"output_path": output_path,
"projections": ["context7"],
"config_path": config_path,
}),
)
.await;
let context7_json = tmp.path().join("context7.json");
assert!(
context7_json.exists(),
"expected context7 projection to be generated at {}",
context7_json.display()
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_dist_gendoc_failure_includes_reindex_result() {
let client = connect().await;
let tmp = setup_hub_fixture();
let source_dir = tmp.path().to_str().expect("utf-8 path").to_string();
let output_path = tmp
.path()
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let outcome = client
.call_tool(call_params(
"alc_hub_dist",
json!({
"source_dir": source_dir,
"output_path": output_path,
"projections": ["context7"],
}),
))
.await;
match outcome {
Ok(result) => {
let is_error = result.is_error.unwrap_or(false);
let text = extract_text(&result);
assert!(
is_error,
"expected is_error=true when gendoc fails after reindex, got: is_error={is_error:?}, text: {text}"
);
assert!(
text.contains("dist: gendoc failed"),
"expected dist gendoc failure prefix, got: {text}"
);
assert!(
text.contains("reindex result (succeeded):"),
"expected reindex result to be embedded in error text, got: {text}"
);
}
Err(e) => panic!("unexpected call_tool Err: {e}"),
}
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_dist_fixture() {
let client = connect().await;
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
let fixture_src =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hub_dist_sample");
fn copy_dir_all(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(&entry.path(), &dst.join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), dst.join(entry.file_name()))?;
}
}
Ok(())
}
copy_dir_all(&fixture_src, root).expect("copy fixture");
let source_dir = root.to_str().expect("utf-8 path").to_string();
let output_path = root
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let out_dir_path = root.join("docs");
let out_dir = out_dir_path.to_str().expect("utf-8 path").to_string();
let config_path = root
.join("tools/gendoc.toml")
.to_str()
.expect("utf-8 path")
.to_string();
let resp = call_json(
&client,
"alc_hub_dist",
json!({
"source_dir": source_dir,
"output_path": output_path,
"out_dir": out_dir,
"projections": ["hub", "context7", "devin"],
"config_path": config_path,
}),
)
.await;
let reindex = resp
.get("reindex")
.unwrap_or_else(|| panic!("expected reindex field, got: {resp}"));
let _gendoc = resp
.get("gendoc")
.unwrap_or_else(|| panic!("expected gendoc field, got: {resp}"));
let pkg_count = reindex
.get("package_count")
.and_then(|v| v.as_u64())
.unwrap_or_else(|| panic!("expected reindex.package_count u64, got: {resp}"));
assert_eq!(
pkg_count, 3,
"expected exactly 3 packages indexed, got {pkg_count}: {resp}"
);
for pkg in &["pkg_alpha", "pkg_beta", "pkg_gamma"] {
let narrative = out_dir_path.join("narrative").join(format!("{pkg}.md"));
assert!(
narrative.exists(),
"expected narrative/{pkg}.md at {}",
narrative.display()
);
}
let llms_full_path = out_dir_path.join("llms-full.txt");
assert!(
llms_full_path.exists(),
"expected llms-full.txt at {}",
llms_full_path.display()
);
let llms_full = std::fs::read_to_string(&llms_full_path).expect("read llms-full.txt");
for token in &[
"ALPHA_SIGNAL_BOOLEAN_TABLE",
"BETA_SIGNAL_INSTRUMENT_DESCRIBE",
"GAMMA_SIGNAL_NESTED_SHAPE",
] {
assert!(
llms_full.contains(token),
"expected signal token '{token}' in llms-full.txt"
);
}
let llms_path = out_dir_path.join("llms.txt");
assert!(
llms_path.exists(),
"expected llms.txt at {}",
llms_path.display()
);
let llms = std::fs::read_to_string(&llms_path).expect("read llms.txt");
for pkg in &["pkg_alpha", "pkg_beta", "pkg_gamma"] {
assert!(
llms.contains(pkg),
"expected '{pkg}' in llms.txt index, got:\n{llms}"
);
}
let context7 = root.join("context7.json");
assert!(
context7.exists(),
"expected context7.json at {}",
context7.display()
);
let devin_wiki = root.join(".devin/wiki.json");
assert!(
devin_wiki.exists(),
"expected .devin/wiki.json at {}",
devin_wiki.display()
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_dist_fixture_mirror_version_match() {
let client = connect().await;
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
let fixture_src = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/hub_dist_sample_version_match");
fn copy_dir_all(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(&entry.path(), &dst.join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), dst.join(entry.file_name()))?;
}
}
Ok(())
}
copy_dir_all(&fixture_src, root).expect("copy fixture");
let source_dir = root.to_str().expect("utf-8 path").to_string();
let output_path = root
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let out_dir = root.join("docs").to_str().expect("utf-8 path").to_string();
let resp = call_json(
&client,
"alc_hub_dist",
json!({
"source_dir": source_dir,
"output_path": output_path,
"out_dir": out_dir,
}),
)
.await;
assert!(
resp.get("reindex").is_some(),
"expected reindex field on version-match success, got: {resp}"
);
assert!(
resp.get("gendoc").is_some(),
"expected gendoc field on version-match success, got: {resp}"
);
let out_dir_path = root.join("docs");
let llms_full_path = out_dir_path.join("llms-full.txt");
assert!(
llms_full_path.exists(),
"expected llms-full.txt at {}",
llms_full_path.display()
);
let llms_full = std::fs::read_to_string(&llms_full_path).expect("read llms-full.txt");
assert!(
llms_full.contains("VMATCH_SIGNAL_ALPHA"),
"expected VMATCH_SIGNAL_ALPHA signal token in llms-full.txt"
);
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_dist_fixture_mirror_version_mismatch() {
let client = connect().await;
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
let fixture_src = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/hub_dist_sample_version_mismatch");
fn copy_dir_all(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(&entry.path(), &dst.join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), dst.join(entry.file_name()))?;
}
}
Ok(())
}
copy_dir_all(&fixture_src, root).expect("copy fixture");
let source_dir = root.to_str().expect("utf-8 path").to_string();
let output_path = root
.join("hub_index.json")
.to_str()
.expect("utf-8 path")
.to_string();
let out_dir = root.join("docs").to_str().expect("utf-8 path").to_string();
let outcome = client
.call_tool(call_params(
"alc_hub_dist",
json!({
"source_dir": source_dir,
"output_path": output_path,
"out_dir": out_dir,
}),
))
.await;
match outcome {
Ok(result) => {
let is_error = result.is_error.unwrap_or(false);
let text = extract_text(&result);
assert!(
is_error,
"expected is_error=true on version mismatch, got: is_error={is_error:?}, text: {text}"
);
assert!(
text.contains("0.25.1"),
"expected embedded version '0.25.1' in error text, got: {text}"
);
assert!(
text.contains("9.9.9"),
"expected mirror version '9.9.9' in error text, got: {text}"
);
assert!(
text.contains("CHANGELOG"),
"expected CHANGELOG hint in error text, got: {text}"
);
}
Err(e) => panic!("unexpected call_tool Err: {e}"),
}
client.cancel().await.expect("cancel failed");
}
#[tokio::test]
async fn test_alc_hub_dist_reindex_failure() {
let client = connect().await;
let outcome = client
.call_tool(call_params(
"alc_hub_dist",
json!({
"source_dir": "/nonexistent/path/for/dist/test",
}),
))
.await;
match outcome {
Ok(result) => {
let is_error = result.is_error.unwrap_or(false);
let text = extract_text(&result);
assert!(
is_error,
"expected is_error=true on reindex failure, got: is_error={is_error:?}, text: {text}"
);
assert!(
text.contains("reindex failed"),
"expected 'reindex failed' in error text, got: {text}"
);
}
Err(e) => panic!("unexpected call_tool Err: {e}"),
}
client.cancel().await.expect("cancel failed");
}