use gradatum_admin::{generate_server_toml_template, merge_user_config};
use std::path::Path;
#[test]
fn merge_preserves_user_curator_customizations() {
let existing = r#"# Config LIVE
[server]
bind = "127.0.0.1:19090"
metrics_bind = "127.0.0.1:19091"
[storage]
root = "/var/lib/gradatum"
vault_index_path = "/var/lib/gradatum/db/index.sqlite"
[auth]
jwt_public_key_path = "/var/lib/gradatum/config/jwt.public.pem"
jwt_private_key_path = "/var/lib/gradatum/config/jwt.private.pem"
jwt_ttl_human_secs = 3600
jwt_ttl_service_secs = 86400
revocation_store = "sqlite"
revocation_db_path = "/var/lib/gradatum/db/revocation.sqlite"
api_keys_db_path = "/var/lib/gradatum/db/api_keys.sqlite"
[acl]
preset_path = "/var/lib/gradatum/config/bearer.toml"
[log]
format = "json"
[curator]
backend = "openai_compat"
heuristic_admit_threshold = 0.85
[curator.llm]
backend = "openai_compat"
base_url = "http://localhost:8435"
model = "extract"
api_key_env = "GRADATUM_LLM_BEARER"
timeout_ms = 60000
"#;
let new_template =
generate_server_toml_template(Path::new("/var/lib/gradatum"), "127.0.0.1:19090");
let merged = merge_user_config(existing, &new_template).expect("merge ne doit pas échouer");
assert!(
merged.contains("bind = \"127.0.0.1:19090\""),
"bind préservé depuis backup"
);
assert!(
merged.contains("jwt_ttl_human_secs = 3600"),
"jwt_ttl_human_secs préservé"
);
assert!(
merged.contains("jwt_ttl_service_secs = 86400"),
"jwt_ttl_service_secs préservé"
);
assert!(
merged.contains("vault_index_path = \"/var/lib/gradatum/db/index.sqlite\""),
"vault_index_path préservé depuis backup"
);
assert!(
merged.contains("heuristic_admit_threshold = 0.85"),
"curator.heuristic_admit_threshold doit être préservé"
);
assert!(
merged.contains("base_url = \"http://localhost:8435\""),
"curator.llm.base_url doit être préservé"
);
assert!(
merged.contains("api_key_env = \"GRADATUM_LLM_BEARER\""),
"curator.llm.api_key_env doit être préservé"
);
assert!(
merged.contains("timeout_ms = 60000"),
"curator.llm.timeout_ms doit être préservé"
);
assert!(
merged.parse::<toml_edit::DocumentMut>().is_ok(),
"résultat merge doit être du TOML valide"
);
}
#[test]
fn merge_drops_legacy_db_path_via_rename_migration() {
let existing = r#"[storage]
root = "/var/lib/gradatum"
db_path = "/custom/legacy/index.sqlite"
"#;
let new_template =
generate_server_toml_template(Path::new("/var/lib/gradatum"), "127.0.0.1:19090");
let merged = merge_user_config(existing, &new_template).expect("merge");
assert!(
merged.contains("vault_index_path = \"/custom/legacy/index.sqlite\""),
"db_path legacy doit être migré vers vault_index_path avec la valeur user"
);
assert!(
!merged.contains("\ndb_path ="),
"db_path (clé legacy) ne doit pas apparaître dans le résultat"
);
}
#[test]
fn merge_keeps_new_keys_with_defaults() {
let existing = r#"[server]
bind = "0.0.0.0:9090"
"#;
let new_template =
generate_server_toml_template(Path::new("/var/lib/gradatum"), "127.0.0.1:19090");
let merged = merge_user_config(existing, &new_template).expect("merge");
assert!(
merged.contains("bind = \"0.0.0.0:9090\""),
"bind user (0.0.0.0:9090) doit être préservé"
);
assert!(
merged.contains("[auth]"),
"section [auth] doit être présente"
);
assert!(
merged.contains("vault_index_path"),
"vault_index_path doit être présent avec défaut"
);
assert!(merged.contains("[log]"), "section [log] doit être présente");
assert!(
merged.contains("format = \"json\""),
"log.format défaut doit être présent"
);
assert!(
merged.contains("jwt_ttl_human_secs = 3600"),
"jwt_ttl_human_secs défaut doit être présent"
);
}
#[test]
fn merge_preserves_backup_only_sections_curator() {
let existing = r#"
[server]
bind = "127.0.0.1:19090"
metrics_bind = "127.0.0.1:19091"
[storage]
root = "/var/lib/gradatum"
vault_index_path = "/var/lib/gradatum/db/index.sqlite"
[curator]
backend = "openai_compat"
llm_review_enabled = true
heuristic_admit_threshold = 0.8
[curator.llm]
backend = "openai_compat"
base_url = "http://localhost:8435"
model = "extract"
api_key_env = "GRADATUM_LLM_BEARER"
timeout_ms = 60000
"#;
let new_template =
generate_server_toml_template(Path::new("/var/lib/gradatum"), "127.0.0.1:19090");
let merged = merge_user_config(existing, &new_template).expect("merge ne doit pas échouer");
assert!(
merged.contains("llm_review_enabled = true"),
"curator.llm_review_enabled doit être préservé"
);
assert!(
merged.contains("heuristic_admit_threshold = 0.8"),
"curator.heuristic_admit_threshold doit être préservé"
);
assert!(
merged.contains("base_url = \"http://localhost:8435\""),
"curator.llm.base_url doit être préservé"
);
assert!(
merged.contains("model = \"extract\""),
"curator.llm.model doit être préservé"
);
assert!(
merged.contains("api_key_env = \"GRADATUM_LLM_BEARER\""),
"curator.llm.api_key_env doit être préservé"
);
assert!(
merged.contains("timeout_ms = 60000"),
"curator.llm.timeout_ms doit être préservé"
);
assert!(
merged.contains("[auth]"),
"section [auth] du template doit être présente"
);
assert!(
merged.contains("[log]"),
"section [log] du template doit être présente"
);
assert!(
merged.parse::<toml_edit::DocumentMut>().is_ok(),
"résultat merge doit être du TOML valide"
);
}
#[test]
fn merge_adds_embed_section_when_backup_lacks_it() {
let backup = r#"
[server]
bind = "127.0.0.1:19090"
metrics_bind = "127.0.0.1:19091"
[storage]
root = "/var/lib/gradatum"
vault_index_path = "/var/lib/gradatum/db/index.sqlite"
[auth]
jwt_public_key_path = "/var/lib/gradatum/config/jwt.public.pem"
jwt_private_key_path = "/var/lib/gradatum/config/jwt.private.pem"
jwt_ttl_human_secs = 3600
jwt_ttl_service_secs = 86400
revocation_store = "sqlite"
revocation_db_path = "/var/lib/gradatum/db/revocation.sqlite"
api_keys_db_path = "/var/lib/gradatum/db/api_keys.sqlite"
[acl]
preset_path = "/var/lib/gradatum/config/bearer.toml"
[log]
format = "json"
[curator]
backend = "openai_compat"
[curator.llm]
base_url = "http://localhost:8435"
model = "extract"
"#;
let new_template =
generate_server_toml_template(Path::new("/var/lib/gradatum"), "127.0.0.1:19090");
assert!(
new_template.contains("[embed]"),
"le template generate_server_toml_template doit contenir [embed] après patch.1"
);
let merged = merge_user_config(backup, &new_template).expect("merge ne doit pas échouer");
assert!(
merged.contains("[embed]"),
"section [embed] doit être présente dans le résultat (NEW template l'apporte)"
);
assert!(
merged.contains("enabled = true"),
"embed.enabled défaut (true) doit être présent"
);
assert!(
merged.contains("endpoint = \"http://localhost:8431/v1/embeddings\""),
"embed.endpoint défaut doit être présent"
);
assert!(
merged.contains("model = \"bge-m3-Q8_0\""),
"embed.model default must be bge-m3-Q8_0"
);
assert!(
merged.contains("dim = 1024"),
"embed.dim défaut doit être 1024 (bge-m3-Q8_0)"
);
assert!(
merged.contains("timeout_ms = 5000"),
"embed.timeout_ms défaut doit être présent"
);
assert!(
merged.contains("base_url = \"http://localhost:8435\""),
"curator.llm.base_url user doit être préservé"
);
assert!(
merged.contains("bind = \"127.0.0.1:19090\""),
"server.bind user doit être préservé"
);
assert!(
merged.parse::<toml_edit::DocumentMut>().is_ok(),
"résultat merge doit être du TOML valide"
);
}
#[test]
fn merge_adds_user_only_top_level_section() {
let existing = r#"
[server]
bind = "127.0.0.1:19090"
metrics_bind = "127.0.0.1:19091"
[storage]
root = "/var/lib/gradatum"
vault_index_path = "/var/lib/gradatum/db/index.sqlite"
[my_custom_section]
foo = "bar"
number = 42
"#;
let new_template =
generate_server_toml_template(Path::new("/var/lib/gradatum"), "127.0.0.1:19090");
let merged = merge_user_config(existing, &new_template).expect("merge ne doit pas échouer");
assert!(
merged.contains("foo = \"bar\""),
"my_custom_section.foo doit être préservé"
);
assert!(
merged.contains("number = 42"),
"my_custom_section.number doit être préservé"
);
assert!(
merged.parse::<toml_edit::DocumentMut>().is_ok(),
"résultat merge doit être du TOML valide"
);
}