use std::collections::BTreeMap;
use std::fs;
use lorum::adapters::ToolAdapter;
use lorum::adapters::continue_dev::ContinueDevAdapter;
use lorum::config::{McpConfig, McpServer};
fn make_server(cmd: &str, args: &[&str], env: &[(&str, &str)]) -> McpServer {
McpServer {
command: cmd.into(),
args: args.iter().map(|a| a.to_string()).collect(),
env: env
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
}
}
#[test]
fn yaml_read_write_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let continue_dir = dir.path().join(".continue");
fs::create_dir_all(&continue_dir).unwrap();
let yaml_path = continue_dir.join("config.yaml");
let yaml = r#"
mcpServers:
- name: test-server
command: npx
args:
- "-y"
- "some-pkg"
env:
KEY: value
models:
- title: GPT-4
prompts:
- name: my-prompt
"#;
fs::write(&yaml_path, yaml).unwrap();
let adapter = ContinueDevAdapter::with_project_root(dir.path().to_path_buf());
let mcp = adapter.read_mcp().unwrap();
assert_eq!(mcp.servers.len(), 1);
let server = &mcp.servers["test-server"];
assert_eq!(server.command, "npx");
assert_eq!(server.args, vec!["-y", "some-pkg"]);
assert_eq!(server.env.get("KEY").unwrap(), "value");
let mut modified = mcp.clone();
modified.servers.insert(
"new-server".into(),
make_server("python", &["main.py"], &[("PORT", "3000")]),
);
adapter.write_mcp(&modified).unwrap();
let reparsed = adapter.read_mcp().unwrap();
assert_eq!(reparsed.servers.len(), 2);
assert_eq!(reparsed.servers["test-server"].command, "npx");
assert_eq!(reparsed.servers["new-server"].command, "python");
assert_eq!(reparsed.servers["new-server"].args, vec!["main.py"]);
assert_eq!(
reparsed.servers["new-server"].env.get("PORT").unwrap(),
"3000"
);
}
#[test]
fn json_read_write_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let continue_dir = dir.path().join(".continue");
fs::create_dir_all(&continue_dir).unwrap();
let json_path = continue_dir.join("config.json");
let json = r#"{
"experimental": {
"modelContextProtocolServers": [
{
"name": "test-server",
"transport": {
"type": "stdio",
"command": "npx",
"args": ["-y", "some-pkg"],
"env": { "KEY": "value" }
}
}
]
},
"otherField": true
}"#;
fs::write(&json_path, json).unwrap();
let adapter = ContinueDevAdapter::with_project_root(dir.path().to_path_buf());
let mcp = adapter.read_mcp().unwrap();
assert_eq!(mcp.servers.len(), 1);
let server = &mcp.servers["test-server"];
assert_eq!(server.command, "npx");
assert_eq!(server.args, vec!["-y", "some-pkg"]);
assert_eq!(server.env.get("KEY").unwrap(), "value");
let mut modified = mcp.clone();
modified.servers.insert(
"new-server".into(),
make_server("python", &["main.py"], &[("PORT", "3000")]),
);
adapter.write_mcp(&modified).unwrap();
let reparsed = adapter.read_mcp().unwrap();
assert_eq!(reparsed.servers.len(), 2);
assert_eq!(reparsed.servers["test-server"].command, "npx");
assert_eq!(reparsed.servers["new-server"].command, "python");
assert_eq!(reparsed.servers["new-server"].args, vec!["main.py"]);
assert_eq!(
reparsed.servers["new-server"].env.get("PORT").unwrap(),
"3000"
);
let raw = fs::read_to_string(&json_path).unwrap();
let root: serde_json::Value = serde_json::from_str(&raw).unwrap();
let servers = root["experimental"]["modelContextProtocolServers"]
.as_array()
.unwrap();
assert_eq!(servers.len(), 2);
assert_eq!(servers[0]["transport"]["type"], "stdio");
assert_eq!(servers[1]["transport"]["type"], "stdio");
}
#[test]
fn yaml_format_preferred_over_json() {
let dir = tempfile::tempdir().unwrap();
let continue_dir = dir.path().join(".continue");
fs::create_dir_all(&continue_dir).unwrap();
let yaml_path = continue_dir.join("config.yaml");
let yaml = r#"
mcpServers:
- name: yaml-server
command: npx
args: ["-y", "yaml-pkg"]
"#;
fs::write(&yaml_path, yaml).unwrap();
let json_path = continue_dir.join("config.json");
let json = r#"{
"experimental": {
"modelContextProtocolServers": [
{
"name": "json-server",
"transport": {
"type": "stdio",
"command": "npx",
"args": ["-y", "json-pkg"]
}
}
]
}
}"#;
fs::write(&json_path, json).unwrap();
let adapter = ContinueDevAdapter::with_project_root(dir.path().to_path_buf());
let mcp = adapter.read_mcp().unwrap();
assert_eq!(mcp.servers.len(), 1);
assert!(mcp.servers.contains_key("yaml-server"));
assert!(!mcp.servers.contains_key("json-server"));
}
#[test]
fn preserves_non_mcp_fields_yaml() {
let dir = tempfile::tempdir().unwrap();
let continue_dir = dir.path().join(".continue");
fs::create_dir_all(&continue_dir).unwrap();
let yaml_path = continue_dir.join("config.yaml");
let yaml = r#"
mcpServers:
- name: test-server
command: npx
args: ["-y", "pkg"]
models:
- title: GPT-4
prompts:
- name: my-prompt
"#;
fs::write(&yaml_path, yaml).unwrap();
let adapter = ContinueDevAdapter::with_project_root(dir.path().to_path_buf());
let mcp = adapter.read_mcp().unwrap();
adapter.write_mcp(&mcp).unwrap();
let raw = fs::read_to_string(&yaml_path).unwrap();
let root: serde_yaml::Value = serde_yaml::from_str(&raw).unwrap();
assert!(root.get("models").is_some());
assert!(root.get("prompts").is_some());
let models = root["models"].as_sequence().unwrap();
assert_eq!(models.len(), 1);
assert_eq!(
models[0].get("title").and_then(|v| v.as_str()),
Some("GPT-4")
);
let prompts = root["prompts"].as_sequence().unwrap();
assert_eq!(prompts.len(), 1);
assert_eq!(
prompts[0].get("name").and_then(|v| v.as_str()),
Some("my-prompt")
);
}
#[test]
fn preserves_non_mcp_fields_json() {
let dir = tempfile::tempdir().unwrap();
let continue_dir = dir.path().join(".continue");
fs::create_dir_all(&continue_dir).unwrap();
let json_path = continue_dir.join("config.json");
let json = r#"{
"experimental": {
"modelContextProtocolServers": [
{
"name": "test-server",
"transport": {
"type": "stdio",
"command": "npx",
"args": ["-y", "pkg"]
}
}
]
},
"otherField": true
}"#;
fs::write(&json_path, json).unwrap();
let adapter = ContinueDevAdapter::with_project_root(dir.path().to_path_buf());
let mcp = adapter.read_mcp().unwrap();
adapter.write_mcp(&mcp).unwrap();
let raw = fs::read_to_string(&json_path).unwrap();
let root: serde_json::Value = serde_json::from_str(&raw).unwrap();
assert_eq!(root["otherField"], true);
assert!(root["experimental"].is_object());
}
#[test]
fn synthetic_name_for_missing_name() {
let dir = tempfile::tempdir().unwrap();
let continue_dir = dir.path().join(".continue");
fs::create_dir_all(&continue_dir).unwrap();
let yaml_path = continue_dir.join("config.yaml");
let yaml = r#"
mcpServers:
- command: npx
args: ["-y", "some-pkg"]
"#;
fs::write(&yaml_path, yaml).unwrap();
let adapter = ContinueDevAdapter::with_project_root(dir.path().to_path_buf());
let mcp = adapter.read_mcp().unwrap();
assert_eq!(mcp.servers.len(), 1);
assert!(mcp.servers.contains_key("unnamed-server-0"));
assert_eq!(mcp.servers["unnamed-server-0"].command, "npx");
}
#[test]
fn defaults_to_yaml_when_no_file_exists() {
let dir = tempfile::tempdir().unwrap();
let adapter = ContinueDevAdapter::with_project_root(dir.path().to_path_buf());
let config = McpConfig {
servers: {
let mut m = BTreeMap::new();
m.insert(
"test-server".into(),
make_server("npx", &["-y", "pkg"], &[]),
);
m
},
};
adapter.write_mcp(&config).unwrap();
let yaml_path = dir.path().join(".continue").join("config.yaml");
assert!(yaml_path.exists());
let reparsed = adapter.read_mcp().unwrap();
assert_eq!(reparsed.servers.len(), 1);
assert!(reparsed.servers.contains_key("test-server"));
}
#[test]
fn config_paths_returns_all_four_paths() {
let dir = tempfile::tempdir().unwrap();
let adapter = ContinueDevAdapter::with_project_root(dir.path().to_path_buf());
let paths = adapter.config_paths();
assert_eq!(paths.len(), 4);
assert_eq!(paths[0], dir.path().join(".continue").join("config.yaml"));
assert!(paths[1].ends_with(".continue/config.yaml"));
assert_eq!(paths[2], dir.path().join(".continue").join("config.json"));
assert!(paths[3].ends_with(".continue/config.json"));
}