use serial_test::serial;
use std::collections::BTreeMap;
use std::fs;
use lorum::adapters::{all_adapters, find_adapter, json_utils, toml_utils};
use lorum::config::{LorumConfig, load_config, save_config};
#[test]
fn import_from_json_adapter() {
let dir = tempfile::tempdir().unwrap();
let lorum_path = dir.path().join("config.yaml");
let json_path = dir.path().join("settings.json");
let json_content = r#"{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-fetch"],
"env": {}
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
}
}
},
"otherSetting": true
}"#;
fs::write(&json_path, json_content).unwrap();
let root: serde_json::Value = serde_json::from_str(json_content).unwrap();
let mcp = json_utils::parse_mcp_servers(&root, "mcpServers");
assert_eq!(mcp.servers.len(), 2);
assert_eq!(mcp.servers["fetch"].command, "npx");
assert_eq!(
mcp.servers["fetch"].args,
vec!["-y", "@modelcontextprotocol/server-fetch"]
);
assert_eq!(
mcp.servers["github"].env.get("GITHUB_TOKEN").unwrap(),
"${GITHUB_PERSONAL_ACCESS_TOKEN}"
);
let mut lorum_config = LorumConfig::default();
for (name, server) in &mcp.servers {
lorum_config
.mcp
.servers
.insert(name.clone(), server.clone());
}
save_config(&lorum_path, &lorum_config).unwrap();
let loaded = load_config(&lorum_path).unwrap();
assert_eq!(loaded.mcp.servers.len(), 2);
assert!(loaded.mcp.servers.contains_key("fetch"));
assert!(loaded.mcp.servers.contains_key("github"));
}
#[test]
fn import_from_toml_adapter() {
let dir = tempfile::tempdir().unwrap();
let lorum_path = dir.path().join("config.yaml");
let toml_path = dir.path().join("config.toml");
let toml_content = r#"
other_field = "preserved"
[mcp_servers.sequential-thinking]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-sequential-thinking"]
[mcp_servers.memory]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-memory"]
[mcp_servers.memory.env]
DATA_DIR = "/tmp/memory"
"#;
fs::write(&toml_path, toml_content).unwrap();
let root: toml::Value = toml::from_str(toml_content).unwrap();
let mcp = toml_utils::parse_mcp_servers_toml(&root, "mcp_servers");
assert_eq!(mcp.servers.len(), 2);
assert_eq!(mcp.servers["sequential-thinking"].command, "npx");
assert_eq!(
mcp.servers["sequential-thinking"].args,
vec!["-y", "@modelcontextprotocol/server-sequential-thinking"]
);
assert_eq!(
mcp.servers["memory"].env.get("DATA_DIR").unwrap(),
"/tmp/memory"
);
let mut lorum_config = LorumConfig::default();
for (name, server) in &mcp.servers {
lorum_config
.mcp
.servers
.insert(name.clone(), server.clone());
}
save_config(&lorum_path, &lorum_config).unwrap();
let loaded = load_config(&lorum_path).unwrap();
assert_eq!(loaded.mcp.servers.len(), 2);
assert!(loaded.mcp.servers.contains_key("sequential-thinking"));
assert!(loaded.mcp.servers.contains_key("memory"));
}
#[test]
fn all_adapters_registered() {
let adapters = all_adapters();
assert_eq!(adapters.len(), 9);
let names: Vec<&str> = adapters.iter().map(|a| a.name()).collect();
assert!(names.contains(&"claude-code"));
assert!(names.contains(&"codex"));
assert!(names.contains(&"continue"));
assert!(names.contains(&"cursor"));
assert!(names.contains(&"proma"));
assert!(names.contains(&"kimi"));
assert!(names.contains(&"opencode"));
assert!(names.contains(&"trae"));
assert!(names.contains(&"windsurf"));
}
#[test]
fn find_adapter_by_name() {
assert!(find_adapter("claude-code").is_some());
assert!(find_adapter("codex").is_some());
assert!(find_adapter("cursor").is_some());
assert!(find_adapter("proma").is_some());
assert!(find_adapter("kimi").is_some());
assert!(find_adapter("opencode").is_some());
assert!(find_adapter("trae").is_some());
assert!(find_adapter("windsurf").is_some());
assert!(find_adapter("nonexistent").is_none());
}
#[test]
fn read_mcp_returns_empty_when_no_config() {
for adapter in all_adapters() {
let result = adapter.read_mcp();
assert!(
result.is_ok(),
"adapter '{}' returned error: {:?}",
adapter.name(),
result
);
let mcp = result.unwrap();
let _ = mcp.servers.len();
}
}
#[test]
fn import_from_kimi_toml_format() {
let dir = tempfile::tempdir().unwrap();
let lorum_path = dir.path().join("config.yaml");
let toml_path = dir.path().join("config.toml");
let toml_content = r#"
[mcp.client.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp@latest"]
[mcp.client.context7.env]
API_KEY = "test-key"
"#;
fs::write(&toml_path, toml_content).unwrap();
let root: toml::Value = toml::from_str(toml_content).unwrap();
let servers = root
.get("mcp")
.and_then(|v| v.get("client"))
.and_then(|v| v.as_table())
.expect("expected mcp.client table");
let mut mcp_servers = BTreeMap::new();
for (name, value) in servers {
if let Some(server) = toml_utils::parse_mcp_server_toml(value.as_table()) {
mcp_servers.insert(name.clone(), server);
}
}
assert_eq!(mcp_servers.len(), 1);
assert_eq!(mcp_servers["context7"].command, "npx");
assert_eq!(
mcp_servers["context7"].env.get("API_KEY").unwrap(),
"test-key"
);
let mut lorum_config = LorumConfig::default();
for (name, server) in &mcp_servers {
lorum_config
.mcp
.servers
.insert(name.clone(), server.clone());
}
save_config(&lorum_path, &lorum_config).unwrap();
let loaded = load_config(&lorum_path).unwrap();
assert!(loaded.mcp.servers.contains_key("context7"));
}
#[test]
fn all_adapter_tool_names_returns_union() {
let names = lorum::adapters::all_adapter_tool_names();
assert!(names.iter().any(|n| n == "claude-code"));
assert!(names.iter().any(|n| n == "codex"));
assert!(names.iter().any(|n| n == "claude-code"));
assert!(names.iter().any(|n| n == "cursor"));
assert!(names.iter().any(|n| n == "windsurf"));
assert!(names.iter().any(|n| n == "codex"));
assert!(names.iter().any(|n| n == "kimi"));
assert!(names.iter().any(|n| n == "opencode"));
assert!(names.iter().any(|n| n == "trae"));
assert!(names.iter().any(|n| n == "kimi"));
assert!(names.iter().any(|n| n == "proma"));
let unique: std::collections::HashSet<_> = names.iter().collect();
assert_eq!(unique.len(), names.len());
}
#[test]
#[serial]
fn import_dry_run_does_not_create_config() {
let dir = tempfile::tempdir().unwrap();
let config_path = dir.path().join("config.yaml");
let path_s = config_path.to_str().unwrap();
lorum::commands::run_import("all", true, Some(path_s)).unwrap();
assert!(!config_path.exists());
}
#[test]
#[serial]
fn import_rules_from_tool_creates_rules_file() {
let dir = tempfile::tempdir().unwrap();
let config_path = dir.path().join("config.yaml");
let path_s = config_path.to_str().unwrap();
let cursorrules = dir.path().join(".cursorrules");
fs::write(&cursorrules, "## Style\nAlways use rustfmt.\n").unwrap();
let orig = std::env::current_dir().unwrap();
std::env::set_current_dir(dir.path()).unwrap();
let result = std::panic::catch_unwind(|| {
lorum::commands::run_import("cursor", false, Some(path_s)).unwrap();
});
std::env::set_current_dir(&orig).unwrap();
result.unwrap();
let rules_path = dir.path().join(".lorum").join("RULES.md");
assert!(rules_path.exists());
let content = fs::read_to_string(&rules_path).unwrap();
assert!(content.contains("Style"));
assert!(content.contains("Always use rustfmt."));
}
#[test]
#[serial]
fn import_rules_merges_without_overwriting_existing() {
let dir = tempfile::tempdir().unwrap();
let config_path = dir.path().join("config.yaml");
let path_s = config_path.to_str().unwrap();
let lorum_dir = dir.path().join(".lorum");
fs::create_dir_all(&lorum_dir).unwrap();
let rules_md = lorum_dir.join("RULES.md");
fs::write(
&rules_md,
"# Project Rules\n\n## Style\nExisting style rule.\n",
)
.unwrap();
let cursorrules = dir.path().join(".cursorrules");
fs::write(
&cursorrules,
"## Style\nNew style rule (should be ignored).\n## Testing\nRun tests.\n",
)
.unwrap();
let orig = std::env::current_dir().unwrap();
std::env::set_current_dir(dir.path()).unwrap();
let result = std::panic::catch_unwind(|| {
lorum::commands::run_import("cursor", false, Some(path_s)).unwrap();
});
std::env::set_current_dir(&orig).unwrap();
result.unwrap();
let content = fs::read_to_string(&rules_md).unwrap();
assert!(content.contains("Existing style rule."));
assert!(content.contains("Testing"));
assert!(content.contains("Run tests."));
assert!(!content.contains("New style rule (should be ignored)."));
}