use std::path::{Path, PathBuf};
use super::constants::Suite;
pub struct ConfigManager {
config_path: PathBuf,
backup_path: PathBuf,
configs_dir: PathBuf,
backed_up: bool,
}
impl ConfigManager {
pub fn new(project_root: &Path) -> Self {
Self {
config_path: project_root.join(".conproxy/conproxy.toml"),
backup_path: project_root.join(".conproxy/conproxy.toml.e2e-backup"),
configs_dir: project_root.join("tests/e2e/configs"),
backed_up: false,
}
}
pub fn inject_suite_config(&mut self, suite: Suite) {
if let Some(parent) = self.config_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if self.config_path.exists() && !self.backed_up {
std::fs::copy(&self.config_path, &self.backup_path).expect("Failed to back up config");
self.backed_up = true;
eprintln!(" Backed up config to {}", self.backup_path.display());
}
let src = self.configs_dir.join(suite.config_name());
std::fs::copy(&src, &self.config_path).unwrap_or_else(|e| {
panic!(
"Failed to copy {} -> {}: {}",
src.display(),
self.config_path.display(),
e
)
});
eprintln!(
" Injected config: {} (suite={})",
suite.config_name(),
suite
);
}
pub fn write_advanced_config(&mut self) {
if let Some(parent) = self.config_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if self.config_path.exists() && !self.backed_up {
std::fs::copy(&self.config_path, &self.backup_path).expect("Failed to back up config");
self.backed_up = true;
}
let config = r#"[server]
listen = "127.0.0.1:8080"
[upstreams.elasticsearch-primary]
url = "http://localhost:9200"
type = "elasticsearch"
index = "conproxy_test"
search_fields = ["content", "title"]
timeout_secs = 30
[upstreams.elasticsearch-secondary]
url = "http://localhost:9201"
type = "elasticsearch"
index = "conproxy_test"
search_fields = ["content", "title"]
timeout_secs = 30
[contexts.default]
default = true
[[contexts.default.upstreams]]
ref = "elasticsearch-primary"
priority = 0
weight = 1
[[contexts.default.upstreams]]
ref = "elasticsearch-secondary"
priority = 1
weight = 1
[contexts.default.cache]
fresh_secs = 5
stale_secs = 10
max_entries = 1000
[proxy]
api_key = "e2e-test-key"
[proxy.retry]
enabled = true
max_retries = 2
base_delay_ms = 50
max_delay_ms = 2000
[proxy.rate_limit]
enabled = true
requests_per_second = 10
burst_size = 5
"#;
std::fs::write(&self.config_path, config).expect("Failed to write advanced config");
eprintln!(" Wrote advanced config (auth + rate_limit + short TTL)");
}
pub fn write_mock_config(&mut self, mock_url: &str, upstream_type: &str) {
if let Some(parent) = self.config_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if self.config_path.exists() && !self.backed_up {
std::fs::copy(&self.config_path, &self.backup_path).expect("Failed to back up config");
self.backed_up = true;
}
let config = format!(
r#"[server]
listen = "127.0.0.1:8080"
[upstreams.mock]
url = "{mock_url}"
type = "{upstream_type}"
index = "mock_index"
search_fields = ["content", "title"]
timeout_secs = 3
[contexts.default]
default = true
[[contexts.default.upstreams]]
ref = "mock"
priority = 0
weight = 1
[contexts.default.cache]
fresh_secs = 5
stale_secs = 10
max_entries = 1000
[proxy.retry]
enabled = true
max_retries = 3
base_delay_ms = 100
max_delay_ms = 2000
[proxy.circuit_breaker]
failure_threshold = 3
success_threshold = 2
open_duration_secs = 3
failure_window_secs = 10
"#
);
std::fs::write(&self.config_path, config).expect("Failed to write mock config");
eprintln!(" Wrote mock config (upstream_type={upstream_type}, url={mock_url})");
}
pub fn write_agent_config(&mut self, mock_url: &str) {
if let Some(parent) = self.config_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if self.config_path.exists() && !self.backed_up {
std::fs::copy(&self.config_path, &self.backup_path).expect("Failed to back up config");
self.backed_up = true;
}
let config = format!(
r#"[server]
listen = "127.0.0.1:8080"
[upstreams.mock]
url = "{mock_url}"
type = "elasticsearch"
index = "mock_index"
search_fields = ["content", "title"]
timeout_secs = 5
[contexts.default]
default = true
[[contexts.default.upstreams]]
ref = "mock"
priority = 0
weight = 1
[contexts.default.cache]
fresh_secs = 5
stale_secs = 10
max_entries = 1000
[proxy]
api_key = "e2e-global-key"
[[proxy.agents]]
id = "seed-agent"
api_key = "seed-key"
enabled = true
[proxy.retry]
enabled = true
max_retries = 2
base_delay_ms = 50
max_delay_ms = 2000
"#
);
std::fs::write(&self.config_path, config).expect("Failed to write agent config");
eprintln!(" Wrote agent config (url={mock_url})");
}
pub fn write_federated_config(&mut self, mock_url: &str) {
if let Some(parent) = self.config_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if self.config_path.exists() && !self.backed_up {
std::fs::copy(&self.config_path, &self.backup_path).expect("Failed to back up config");
self.backed_up = true;
}
let config = format!(
r#"[server]
listen = "127.0.0.1:8080"
[upstreams.mock]
url = "{mock_url}"
type = "elasticsearch"
index = "mock_index"
search_fields = ["content", "title"]
timeout_secs = 5
[contexts.default]
default = true
[[contexts.default.upstreams]]
ref = "mock"
priority = 0
weight = 1
[contexts.default.cache]
fresh_secs = 5
stale_secs = 10
max_entries = 1000
[contexts.default.federated]
enabled = true
merge_mode = "local_only_fallback"
min_local_results = 3
min_local_confidence = 0.7
[proxy.retry]
enabled = true
max_retries = 2
base_delay_ms = 50
max_delay_ms = 2000
"#
);
std::fs::write(&self.config_path, config).expect("Failed to write federated config");
eprintln!(" Wrote federated config (url={mock_url})");
}
pub fn set_max_entries(&self, value: u64) {
let content = std::fs::read_to_string(&self.config_path)
.expect("Failed to read config for modification");
let updated = content
.lines()
.map(|line| {
if line.trim_start().starts_with("max_entries") {
format!("max_entries = {}", value)
} else {
line.to_string()
}
})
.collect::<Vec<_>>()
.join("\n");
std::fs::write(&self.config_path, updated).expect("Failed to write modified config");
}
pub fn write_context_rooted_mock(&mut self, mock_url: &str, upstream_type: &str) {
if let Some(parent) = self.config_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if self.config_path.exists() && !self.backed_up {
std::fs::copy(&self.config_path, &self.backup_path).expect("Failed to back up config");
self.backed_up = true;
}
let config = format!(
r#"[server]
listen = "127.0.0.1:8080"
[upstreams.mock]
url = "{mock_url}"
type = "{upstream_type}"
index = "mock_index"
search_fields = ["content", "title"]
timeout_secs = 3
[contexts.default]
default = true
[[contexts.default.upstreams]]
ref = "mock"
priority = 0
weight = 1
[contexts.default.cache]
fresh_secs = 5
stale_secs = 10
max_entries = 1000
[proxy.retry]
enabled = true
max_retries = 3
base_delay_ms = 100
max_delay_ms = 2000
[proxy.circuit_breaker]
failure_threshold = 3
success_threshold = 2
open_duration_secs = 3
failure_window_secs = 10
"#
);
std::fs::write(&self.config_path, config).expect("Failed to write context-rooted config");
eprintln!(" Wrote context-rooted mock config (type={upstream_type}, url={mock_url})");
}
pub fn backup_current(&self) {
let reload_backup = self.config_path.with_extension("toml.reload-backup");
let _ = std::fs::copy(&self.config_path, &reload_backup);
}
pub fn restore_reload_backup(&self) {
let reload_backup = self.config_path.with_extension("toml.reload-backup");
if reload_backup.exists() {
let _ = std::fs::rename(&reload_backup, &self.config_path);
}
}
pub fn restore(&mut self) {
if self.backup_path.exists() {
let _ = std::fs::rename(&self.backup_path, &self.config_path);
self.backed_up = false;
eprintln!(" Restored original config from backup");
} else if !self.backed_up {
let _ = std::fs::remove_file(&self.config_path);
}
}
}
impl Drop for ConfigManager {
fn drop(&mut self) {
self.restore();
}
}