use anyhow::Result;
use serial_test::serial;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use std::sync::Mutex;
mod common;
use common::get_test_binary_path;
static TEST_MUTEX: Mutex<()> = Mutex::new(());
fn get_config_dir() -> PathBuf {
lc::config::Config::config_dir().expect("Could not get config directory")
}
fn backup_config() -> Result<()> {
let config_path = get_config_dir().join("search_config.toml");
if config_path.exists() {
fs::copy(&config_path, config_path.with_extension("toml.bak"))?;
}
Ok(())
}
fn restore_config() -> Result<()> {
let config_path = get_config_dir().join("search_config.toml");
let backup_path = config_path.with_extension("toml.bak");
if backup_path.exists() {
fs::copy(&backup_path, &config_path)?;
fs::remove_file(&backup_path)?;
}
Ok(())
}
fn cleanup_config() -> Result<()> {
let config_path = get_config_dir().join("search_config.toml");
if config_path.exists() {
fs::remove_file(&config_path)?;
}
Ok(())
}
#[test]
#[serial]
fn test_search_provider_add() -> Result<()> {
use lc::search::SearchConfig;
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backup_config()?;
cleanup_config()?;
let mut config = SearchConfig::load()?;
let result = config.add_provider_auto(
"brave".to_string(),
"https://api.search.brave.com/res/v1".to_string(),
);
assert!(
result.is_ok(),
"Failed to add search provider: {:?}",
result.err()
);
config.save()?;
let config_path = get_config_dir().join("search_config.toml");
assert!(config_path.exists());
let reloaded_config = SearchConfig::load()?;
assert!(reloaded_config.has_provider("brave"));
cleanup_config()?;
restore_config()?;
Ok(())
}
#[test]
#[serial]
fn test_search_provider_list() -> Result<()> {
use lc::search::SearchConfig;
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backup_config()?;
cleanup_config()?;
let mut config = SearchConfig::load()?;
config.add_provider_auto(
"brave_list_test".to_string(),
"https://api.search.brave.com/res/v1".to_string(),
)?;
config.save()?;
let providers = config.list_providers();
assert!(!providers.is_empty(), "Should have at least one provider");
assert!(
providers.contains_key("brave_list_test"),
"Should contain the added provider"
);
let provider = providers.get("brave_list_test").unwrap();
assert_eq!(provider.url, "https://api.search.brave.com/res/v1");
cleanup_config()?;
restore_config()?;
Ok(())
}
#[test]
#[serial]
fn test_search_provider_delete() -> Result<()> {
use lc::search::SearchConfig;
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backup_config()?;
cleanup_config()?;
let mut config = SearchConfig::load()?;
config.add_provider_auto(
"brave_delete_test".to_string(),
"https://api.search.brave.com/res/v1".to_string(),
)?;
config.save()?;
assert!(config.has_provider("brave_delete_test"));
let result = config.delete_provider("brave_delete_test");
assert!(
result.is_ok(),
"Failed to delete provider: {:?}",
result.err()
);
config.save()?;
assert!(
!config.has_provider("brave_delete_test"),
"Provider should be deleted"
);
let reloaded_config = SearchConfig::load()?;
assert!(
!reloaded_config.has_provider("brave_delete_test"),
"Provider should still be deleted after reload"
);
cleanup_config()?;
restore_config()?;
Ok(())
}
#[test]
#[serial]
fn test_search_provider_set_header() -> Result<()> {
use lc::search::SearchConfig;
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backup_config()?;
cleanup_config()?;
let mut config = SearchConfig::load()?;
config.add_provider_auto(
"brave".to_string(),
"https://api.search.brave.com/res/v1".to_string(),
)?;
config.save()?;
config.set_header(
"brave",
"X-Subscription-Token".to_string(),
"test-key".to_string(),
)?;
config.save()?;
let provider = config.get_provider("brave")?;
assert!(provider.headers.contains_key("X-Subscription-Token"));
assert_eq!(
provider.headers.get("X-Subscription-Token"),
Some(&"test-key".to_string())
);
cleanup_config()?;
restore_config()?;
Ok(())
}
#[test]
#[serial]
fn test_default_search_provider_config() -> Result<()> {
use lc::search::SearchConfig;
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backup_config()?;
cleanup_config()?;
let mut config = SearchConfig::load()?;
config.add_provider_auto(
"brave".to_string(),
"https://api.search.brave.com/res/v1".to_string(),
)?;
config.add_provider_auto("exa".to_string(), "https://api.exa.ai/search".to_string())?;
config.save()?;
assert_eq!(config.get_default_provider(), Some(&"brave".to_string()));
config.set_default_provider("exa".to_string())?;
config.save()?;
assert_eq!(config.get_default_provider(), Some(&"exa".to_string()));
let reloaded_config = SearchConfig::load()?;
assert_eq!(
reloaded_config.get_default_provider(),
Some(&"exa".to_string())
);
cleanup_config()?;
restore_config()?;
Ok(())
}
#[test]
#[serial]
fn test_default_search_provider_config_cli() -> Result<()> {
use lc::search::SearchConfig;
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backup_config()?;
cleanup_config()?;
let mut config = SearchConfig::load()?;
config.add_provider_auto(
"brave_config_test".to_string(),
"https://api.search.brave.com/res/v1".to_string(),
)?;
config.add_provider_auto(
"test_config".to_string(),
"https://api.search.brave.com/res/v1/test".to_string(),
)?;
config.save()?;
assert_eq!(
config.get_default_provider(),
Some(&"brave_config_test".to_string())
);
config.set_default_provider("test_config".to_string())?;
config.save()?;
assert_eq!(
config.get_default_provider(),
Some(&"test_config".to_string())
);
let reloaded_config = SearchConfig::load()?;
assert_eq!(
reloaded_config.get_default_provider(),
Some(&"test_config".to_string())
);
let mut config = reloaded_config;
config.set_default_provider("".to_string())?;
config.save()?;
let final_config = SearchConfig::load()?;
assert_eq!(final_config.get_default_provider(), None);
cleanup_config()?;
restore_config()?;
Ok(())
}
#[test]
#[serial]
fn test_search_query_missing_provider() -> Result<()> {
use lc::search::SearchConfig;
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backup_config()?;
cleanup_config()?;
let config = SearchConfig::load()?;
let result = config.get_provider("nonexistent");
assert!(result.is_err(), "Should not find nonexistent provider");
assert!(
config.list_providers().is_empty(),
"Should have no providers configured"
);
cleanup_config()?;
restore_config()?;
Ok(())
}
#[test]
#[serial]
fn test_search_integration_flag() -> Result<()> {
use lc::search::SearchConfig;
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backup_config()?;
cleanup_config()?;
let mut config = SearchConfig::load()?;
config.add_provider_auto(
"brave".to_string(),
"https://api.search.brave.com/res/v1".to_string(),
)?;
config.save()?;
assert!(config.has_provider("brave"));
let provider = config.get_provider("brave")?;
assert_eq!(provider.url, "https://api.search.brave.com/res/v1");
let query_str = "brave:test query";
if let Some((provider_name, query)) = query_str.split_once(':') {
assert_eq!(provider_name, "brave");
assert_eq!(query, "test query");
assert!(config.has_provider(provider_name));
} else {
panic!("Failed to parse provider:query format");
}
cleanup_config()?;
restore_config()?;
Ok(())
}
#[test]
#[serial]
fn test_search_provider_duplicate_add() -> Result<()> {
use lc::search::SearchConfig;
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backup_config()?;
cleanup_config()?;
let mut config = SearchConfig::load()?;
config.add_provider_auto(
"brave_dup_test".to_string(),
"https://api.search.brave.com/res/v1".to_string(),
)?;
config.save()?;
assert!(config.has_provider("brave_dup_test"));
let provider = config.get_provider("brave_dup_test")?;
assert_eq!(provider.url, "https://api.search.brave.com/res/v1");
config.add_provider_auto(
"brave_dup_test".to_string(),
"https://api.search.brave.com/res/v1/different".to_string(),
)?;
config.save()?;
let updated_provider = config.get_provider("brave_dup_test")?;
assert_eq!(
updated_provider.url,
"https://api.search.brave.com/res/v1/different"
);
let providers = config.list_providers();
let brave_providers: Vec<_> = providers
.keys()
.filter(|k| k.contains("brave_dup_test"))
.collect();
assert_eq!(brave_providers.len(), 1);
cleanup_config()?;
restore_config()?;
Ok(())
}
#[test]
#[serial]
fn test_search_output_formats() -> Result<()> {
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backup_config()?;
cleanup_config()?;
Command::new(get_test_binary_path())
.args(&[
"search",
"provider",
"add",
"brave",
"https://api.search.brave.com/res/v1",
])
.output()?;
Command::new(get_test_binary_path())
.args(&[
"search",
"provider",
"set",
"brave",
"X-Subscription-Token",
"dummy-key",
])
.output()?;
let output = Command::new(get_test_binary_path())
.args(&[
"run", "--", "search", "query", "brave", "test", "-f", "json",
])
.output()?;
let output2 = Command::new(get_test_binary_path())
.args(&["search", "query", "brave", "test", "-f", "md"])
.output()?;
assert!(!output.status.success() || !output2.status.success());
cleanup_config()?;
restore_config()?;
Ok(())
}
#[test]
#[serial]
fn test_search_result_count() -> Result<()> {
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backup_config()?;
cleanup_config()?;
Command::new(get_test_binary_path())
.args(&[
"search",
"provider",
"add",
"brave",
"https://api.search.brave.com/res/v1",
])
.output()?;
let _output = Command::new(get_test_binary_path())
.args(&["search", "query", "brave", "test", "-n", "10"])
.output()?;
assert!(true);
cleanup_config()?;
restore_config()?;
Ok(())
}