mod common;
use agent_block_mcp::McpManager;
use predicates::prelude::*;
use std::path::Path;
use tempfile::tempdir;
fn write(root: &Path, rel: &str, source: &str) {
let path = root.join(rel);
std::fs::create_dir_all(path.parent().expect("path has a parent")).expect("mkdir");
std::fs::write(&path, source).expect("write");
}
const PRINTS_ORIGIN: &str = "print(\"BLOCK_FROM=\" .. _ORIGIN)\n";
fn block_printing(origin: &str) -> String {
format!("-- A block that says where it lives.\nlocal _ORIGIN = \"{origin}\"\n{PRINTS_ORIGIN}")
}
#[test]
fn a_block_runs_by_name_from_the_project_tier() {
let home = tempdir().expect("tempdir");
let project = tempdir().expect("tempdir");
write(
project.path(),
"blocks/hello.lua",
&block_printing("project"),
);
common::agent_block_cmd()
.env("AGENT_BLOCK_HOME", home.path())
.args(["-p", &project.path().to_string_lossy()])
.args(["-b", "hello"])
.assert()
.success()
.stdout(predicate::str::contains("BLOCK_FROM=project"));
}
#[test]
fn a_block_runs_by_name_from_the_user_tier_when_the_project_has_none() {
let home = tempdir().expect("tempdir");
let project = tempdir().expect("tempdir");
write(
home.path(),
"blocks/hello/init.lua",
&block_printing("user"),
);
common::agent_block_cmd()
.env("AGENT_BLOCK_HOME", home.path())
.args(["-p", &project.path().to_string_lossy()])
.args(["--block", "hello"])
.assert()
.success()
.stdout(predicate::str::contains("BLOCK_FROM=user"));
}
#[test]
fn the_project_tier_shadows_the_user_tier_by_name() {
let home = tempdir().expect("tempdir");
let project = tempdir().expect("tempdir");
write(home.path(), "blocks/hello.lua", &block_printing("user"));
write(
project.path(),
"blocks/hello.lua",
&block_printing("project"),
);
common::agent_block_cmd()
.env("AGENT_BLOCK_HOME", home.path())
.args(["-p", &project.path().to_string_lossy()])
.args(["-b", "hello"])
.assert()
.success()
.stdout(predicate::str::contains("BLOCK_FROM=project"));
}
#[test]
fn an_unknown_name_fails_and_lists_what_is_registered() {
let home = tempdir().expect("tempdir");
let project = tempdir().expect("tempdir");
write(
project.path(),
"blocks/hello.lua",
&block_printing("project"),
);
common::agent_block_cmd()
.env("AGENT_BLOCK_HOME", home.path())
.args(["-p", &project.path().to_string_lossy()])
.args(["-b", "nope"])
.assert()
.failure()
.stderr(predicate::str::contains("unknown block 'nope'"))
.stderr(predicate::str::contains("registered: [hello]"));
}
#[test]
fn a_lib_module_is_not_a_block() {
let home = tempdir().expect("tempdir");
let project = tempdir().expect("tempdir");
write(project.path(), "lib/hello.lua", &block_printing("lib"));
common::agent_block_cmd()
.env("AGENT_BLOCK_HOME", home.path())
.args(["-p", &project.path().to_string_lossy()])
.args(["-b", "hello"])
.assert()
.failure()
.stderr(predicate::str::contains("unknown block 'hello'"));
}
#[test]
fn a_module_resolves_from_the_project_lib() {
let home = tempdir().expect("tempdir");
let project = tempdir().expect("tempdir");
write(
project.path(),
"lib/mylib.lua",
"return { from = \"project\" }\n",
);
common::agent_block_cmd()
.env("AGENT_BLOCK_HOME", home.path())
.args(["-p", &project.path().to_string_lossy()])
.args(["-s", &common::fixture("lib_require.lua")])
.assert()
.success()
.stdout(predicate::str::contains("MYLIB_FROM=project"));
}
#[test]
fn a_module_resolves_from_the_user_lib_when_the_project_has_none() {
let home = tempdir().expect("tempdir");
let project = tempdir().expect("tempdir");
write(
home.path(),
"lib/mylib/init.lua",
"return { from = \"user\" }\n",
);
common::agent_block_cmd()
.env("AGENT_BLOCK_HOME", home.path())
.args(["-p", &project.path().to_string_lossy()])
.args(["-s", &common::fixture("lib_require.lua")])
.assert()
.success()
.stdout(predicate::str::contains("MYLIB_FROM=user"));
}
#[test]
fn the_project_lib_shadows_the_user_lib() {
let home = tempdir().expect("tempdir");
let project = tempdir().expect("tempdir");
write(home.path(), "lib/mylib.lua", "return { from = \"user\" }\n");
write(
project.path(),
"lib/mylib.lua",
"return { from = \"project\" }\n",
);
common::agent_block_cmd()
.env("AGENT_BLOCK_HOME", home.path())
.args(["-p", &project.path().to_string_lossy()])
.args(["-s", &common::fixture("lib_require.lua")])
.assert()
.success()
.stdout(predicate::str::contains("MYLIB_FROM=project"));
}
#[test]
fn a_block_cannot_be_required() {
let home = tempdir().expect("tempdir");
let project = tempdir().expect("tempdir");
write(
project.path(),
"blocks/mylib.lua",
"return { from = \"project-blocks\" }\n",
);
write(
home.path(),
"blocks/mylib.lua",
"return { from = \"user-blocks\" }\n",
);
common::agent_block_cmd()
.env("AGENT_BLOCK_HOME", home.path())
.args(["-p", &project.path().to_string_lossy()])
.args(["-s", &common::fixture("lib_require.lua")])
.assert()
.success()
.stdout(predicate::str::contains("MYLIB_MISSING"));
}
async fn connect(project: &Path, home: &Path) -> McpManager {
let bin = assert_cmd::cargo::cargo_bin("agent-block");
let mut mgr = McpManager::new();
mgr.connect(
"blocks",
bin.to_str().expect("binary path is utf-8"),
&[
"mcp".to_string(),
"--project".to_string(),
project.display().to_string(),
],
false,
Some(project),
&[("AGENT_BLOCK_HOME".to_string(), home.display().to_string())],
)
.await
.expect("connect to agent-block mcp");
mgr
}
#[tokio::test]
async fn the_mcp_server_serves_both_tiers_without_a_block_dir() {
let home = tempdir().expect("tempdir");
let project = tempdir().expect("tempdir");
write(
project.path(),
"blocks/from_project/init.lua",
"-- project block\nreturn std.json.encode({ from = \"project\" })\n",
);
write(
home.path(),
"blocks/from_user.lua",
"-- user block\nreturn std.json.encode({ from = \"user\" })\n",
);
write(project.path(), "lib/helper.lua", "return {}\n");
let mgr = connect(project.path(), home.path()).await;
let tools = mgr.list_tools("blocks").await.expect("list_tools");
let tool = tools
.as_array()
.expect("tools array")
.iter()
.find(|t| t["name"] == "run_block")
.expect("run_block is served");
let names: Vec<String> = tool["inputSchema"]["properties"]["block"]["enum"]
.as_array()
.expect("block enum")
.iter()
.map(|v| v.as_str().unwrap_or_default().to_string())
.collect();
assert_eq!(names, ["from_project", "from_user"]);
let result = mgr
.call_tool(
"blocks",
"run_block",
serde_json::json!({ "block": "from_project" }),
)
.await
.expect("call_tool");
let text = result["content"][0]["text"].as_str().unwrap_or_default();
assert_eq!(text, "{\"from\":\"project\"}");
}