use super::RefactorServer;
use crate::mcp::adapters::ToolSurface;
use crate::write_gate::WriteGate;
use mcport::{ToolReply, ToolServer};
use std::fs;
fn fixture() -> std::path::PathBuf {
use std::sync::atomic::{AtomicU32, Ordering};
static NEXT: AtomicU32 = AtomicU32::new(0);
let root = std::env::temp_dir().join(format!(
"wvxr-host-{}-{}",
std::process::id(),
NEXT.fetch_add(1, Ordering::Relaxed)
));
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(root.join("src")).expect("fixture root");
fs::write(root.join("src/lib.rs"), "pub fn one() -> u32 { 1 }\n").expect("fixture source");
root
}
fn status_of(reply: &ToolReply) -> String {
match reply {
ToolReply::Success { value, .. } => value
.get("status")
.and_then(|status| status.as_str())
.unwrap_or_default()
.to_owned(),
_ => "NO_STATUS".to_owned(),
}
}
#[test]
fn the_default_catalog_exposes_only_all_twelve_refactor_tools() {
let mut server = RefactorServer::new(fixture(), WriteGate::closed()).expect("server");
let catalog = server.catalog();
let tools = catalog.as_array().expect("catalog is an array");
assert_eq!(tools.len(), 12);
for name in weavatrix_rust_refactor::operations::catalog_names() {
assert_eq!(server.has_tool(&name), Some(true), "{name} is not exposed");
}
assert_eq!(server.has_tool("graph_stats"), Some(false));
assert_eq!(server.has_tool("reformat_universe"), Some(false));
}
#[test]
fn the_rename_profile_hides_every_unrelated_tool() {
let mut server =
RefactorServer::new_for_surface(fixture(), WriteGate::closed(), ToolSurface::Rename)
.expect("server");
let tools = server
.catalog()
.as_array()
.expect("catalog is an array")
.clone();
let names = tools
.iter()
.filter_map(|tool| tool.get("name").and_then(blazingly_json::Value::as_str))
.collect::<Vec<_>>();
assert_eq!(names, ["rename_symbol", "rollback_last_apply"]);
assert_eq!(server.has_tool("graph_stats"), Some(false));
assert!(!matches!(
server.call("graph_stats", blazingly_json::json!({})),
ToolReply::Success { .. }
));
let instructions = server.identity().instructions;
assert!(instructions.contains("Start with the bare symbol name"));
assert!(instructions.contains("Do not invent a symbol id"));
}
#[test]
fn the_refactor_profile_hides_core_tools_without_losing_refactor_capabilities() {
let mut server =
RefactorServer::new_for_surface(fixture(), WriteGate::closed(), ToolSurface::Refactor)
.expect("server");
let names = server
.catalog()
.as_array()
.expect("catalog is an array")
.iter()
.filter_map(|tool| {
tool.get("name")
.and_then(blazingly_json::Value::as_str)
.map(str::to_owned)
})
.collect::<std::collections::BTreeSet<_>>();
assert_eq!(names.len(), 12);
assert_eq!(server.has_tool("graph_stats"), Some(false));
for name in weavatrix_rust_refactor::operations::catalog_names() {
assert_eq!(server.has_tool(&name), Some(true), "{name} is not exposed");
}
assert!(server.identity().instructions.contains("extension only"));
}
#[test]
fn a_closed_gate_refuses_every_writing_tool_and_no_reading_one() {
let mut server = RefactorServer::new(fixture(), WriteGate::closed()).expect("server");
for (name, arguments) in [
(
"rename_symbol",
blazingly_json::json!({"symbol": "one", "new_name": "two"}),
),
(
"rename_related_symbols",
blazingly_json::json!({"renames": []}),
),
("apply_edit_plan", blazingly_json::json!({})),
] {
let reply = server.call(name, arguments);
assert_ne!(
status_of(&reply),
"WRITE_GATE_CLOSED",
"{name} preview is a read and must reach the engine"
);
}
for name in [
"delete_readiness",
"delete_closure",
"move_symbol",
"bulk_replace",
"organize_imports",
] {
let reply = server.call(name, blazingly_json::json!({}));
assert_ne!(
status_of(&reply),
"WRITE_GATE_CLOSED",
"{name} is a read and must not refuse"
);
}
let apply = server.call(
"apply_edit_plan",
blazingly_json::json!({"mode": "apply", "confirm_token": "unissued"}),
);
assert_eq!(status_of(&apply), "WRITE_GATE_CLOSED");
let rollback = server.call("rollback_last_apply", blazingly_json::json!({}));
assert_eq!(status_of(&rollback), "WRITE_GATE_CLOSED");
}
#[test]
fn the_linked_refactor_engine_supports_same_method_rename_preview_and_apply() {
use blazingly_json::Value;
use weavatrix_rust::Weavatrix;
use weavatrix_rust_refactor::operations::RefactorSession;
let root = fixture();
let engine = Weavatrix::open(&root).expect("repository opens");
let state = engine.state().clone();
let Some(id) = state
.graph()
.nodes()
.iter()
.find(|node| node.label.starts_with("one"))
.map(|node| node.id.as_str().to_owned())
else {
panic!("fixture symbol must be indexed");
};
let session = RefactorSession::new(true);
let preview = session
.call(
&state,
"rename_symbol",
&blazingly_json::json!({"symbol": id.clone(), "new_name": "two"}),
)
.expect("declared tool");
let Some(token) = preview.get("confirmToken").and_then(Value::as_str) else {
panic!("rename must preview its own plan: {preview:?}");
};
let applied = session
.call(
&state,
"rename_symbol",
&blazingly_json::json!({
"symbol": id,
"new_name": "two",
"mode": "apply",
"confirm_token": token.to_owned(),
}),
)
.expect("declared tool");
assert_eq!(
applied.get("status").and_then(Value::as_str),
Some("APPLIED"),
"{applied:?}"
);
assert!(
fs::read_to_string(root.join("src/lib.rs"))
.expect("source")
.contains("fn two()")
);
}
#[test]
fn identity_names_the_refactor_host_and_its_own_version() {
let server = RefactorServer::new(fixture(), WriteGate::closed()).expect("server");
let identity = server.identity();
assert_eq!(identity.name, "weavatrix-refactor");
assert_eq!(identity.version, env!("CARGO_PKG_VERSION"));
}
#[test]
fn registry_manifest_advertises_only_environment_variables_the_native_host_reads() {
let manifest: blazingly_json::Value =
blazingly_json::from_str(include_str!("../../../server.json")).expect("server manifest");
let names = manifest
.get("packages")
.and_then(blazingly_json::Value::as_array)
.and_then(|packages| packages.first())
.and_then(|package| package.get("environmentVariables"))
.and_then(blazingly_json::Value::as_array)
.expect("environment variable declarations")
.iter()
.filter_map(|variable| variable.get("name"))
.filter_map(blazingly_json::Value::as_str)
.collect::<Vec<_>>();
assert_eq!(names, ["WEAVATRIX_ALLOW_SOURCE_EDITS"]);
}