#[cfg(feature = "editor")]
mod editor_tests {
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
use settings_loader::editor::json::JsonLayerEditor;
use settings_loader::editor::toml::TomlLayerEditor;
use settings_loader::editor::yaml::YamlLayerEditor;
use settings_loader::editor::{
ConfigFormat, Editor, EditorError, LayerEditor, SettingsEditor, SettingsLoaderEditor,
};
use trim_margin::MarginTrimmable;
#[test]
fn test_config_format_enum_variants() {
let toml = ConfigFormat::Toml;
let json = ConfigFormat::Json;
let yaml = ConfigFormat::Yaml;
let toml_cloned = toml;
assert_eq!(toml, toml_cloned);
let _copy1 = toml;
let _copy2 = toml;
let debug_str = format!("{:?}", toml);
assert!(!debug_str.is_empty());
assert!(debug_str.contains("Toml"));
assert_eq!(toml, ConfigFormat::Toml);
assert_eq!(json, ConfigFormat::Json);
assert_eq!(yaml, ConfigFormat::Yaml);
assert_ne!(toml, json);
assert_ne!(json, yaml);
assert_ne!(toml, yaml);
assert!(toml == toml);
assert!(json == json);
assert!(yaml == yaml);
let all_formats = [toml, json, yaml];
for (i, fmt1) in all_formats.iter().enumerate() {
for (j, fmt2) in all_formats.iter().enumerate() {
if i == j {
assert_eq!(fmt1, fmt2, "Same format should be equal");
} else {
assert_ne!(fmt1, fmt2, "Different formats should not be equal");
}
}
}
}
#[test]
fn test_config_format_from_path_toml() {
let toml_path = PathBuf::from("settings.toml");
assert_eq!(ConfigFormat::from_path(&toml_path), Some(ConfigFormat::Toml));
}
#[test]
fn test_config_format_from_path_json() {
let json_path = PathBuf::from("config.json");
assert_eq!(ConfigFormat::from_path(&json_path), Some(ConfigFormat::Json));
}
#[test]
fn test_config_format_from_path_yaml() {
let yaml_path = PathBuf::from("settings.yaml");
assert_eq!(ConfigFormat::from_path(&yaml_path), Some(ConfigFormat::Yaml));
let yml_path = PathBuf::from("settings.yml");
assert_eq!(ConfigFormat::from_path(&yml_path), Some(ConfigFormat::Yaml));
}
#[test]
fn test_config_format_from_path_unknown() {
let unknown_path = PathBuf::from("settings.txt");
assert_eq!(ConfigFormat::from_path(&unknown_path), None);
}
#[test]
fn test_toml_layer_editor_open_existing_file() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "app_name = \"test_app\"\nversion = \"1.0.0\"").unwrap();
let editor = TomlLayerEditor::open(&toml_path).expect("Failed to open TOML");
assert!(!editor.is_dirty());
}
#[test]
fn test_toml_layer_editor_get_string_value() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "app_name = \"my_app\"").unwrap();
let editor = TomlLayerEditor::open(&toml_path).unwrap();
let value: String = editor.get("app_name").expect("Failed to get value");
assert_eq!(value, "my_app");
}
#[test]
fn test_toml_layer_editor_set_string_value() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "app_name = \"old_app\"").unwrap();
let mut editor = TomlLayerEditor::open(&toml_path).unwrap();
editor.set("app_name", "new_app").expect("Failed to set value");
assert!(editor.is_dirty());
let value: String = editor.get("app_name").unwrap();
assert_eq!(value, "new_app");
}
#[test]
fn test_toml_layer_editor_unset_key() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "app_name = \"test\"\nversion = \"1.0\"").unwrap();
let mut editor = TomlLayerEditor::open(&toml_path).unwrap();
editor.unset("version").expect("Failed to unset key");
assert!(editor.is_dirty());
let value: Option<String> = editor.get("version");
assert!(value.is_none());
}
#[test]
fn test_toml_layer_editor_keys() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "app_name = \"test\"\nversion = \"1.0\"\ndebug = true").unwrap();
let editor = TomlLayerEditor::open(&toml_path).unwrap();
let keys = editor.keys();
assert!(keys.contains(&"app_name".to_string()));
assert!(keys.contains(&"version".to_string()));
assert!(keys.contains(&"debug".to_string()));
}
#[test]
fn test_toml_layer_editor_get_nested_dotted_path() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "[database]\nhost = \"localhost\"\nport = 5432").unwrap();
let editor = TomlLayerEditor::open(&toml_path).unwrap();
let host: String = editor.get("database.host").unwrap();
assert_eq!(host, "localhost");
let port: u16 = editor.get("database.port").unwrap();
assert_eq!(port, 5432);
}
#[test]
fn test_toml_layer_editor_set_nested_dotted_path() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "# Empty config").unwrap();
let mut editor = TomlLayerEditor::open(&toml_path).unwrap();
editor.set("database.host", "db.example.com").unwrap();
editor.set("database.port", 3306).unwrap();
let host: String = editor.get("database.host").unwrap();
assert_eq!(host, "db.example.com");
}
#[test]
fn test_toml_layer_editor_unset_nested_dotted_path() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "[database]\nhost = \"localhost\"\nport = 5432").unwrap();
let mut editor = TomlLayerEditor::open(&toml_path).unwrap();
editor.unset("database.port").unwrap();
let port: Option<u16> = editor.get("database.port");
assert!(port.is_none());
}
#[test]
fn test_toml_layer_editor_dirty_flag_initial_state() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "app_name = \"test\"").unwrap();
let editor = TomlLayerEditor::open(&toml_path).unwrap();
assert!(!editor.is_dirty());
}
#[test]
fn test_toml_layer_editor_dirty_flag_after_modification() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "app_name = \"test\"").unwrap();
let mut editor = TomlLayerEditor::open(&toml_path).unwrap();
editor.set("app_name", "modified").unwrap();
assert!(editor.is_dirty());
}
#[test]
fn test_toml_layer_editor_save_persists_changes() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "app_name = \"original\"").unwrap();
let mut editor = TomlLayerEditor::open(&toml_path).unwrap();
editor.set("app_name", "updated").unwrap();
editor.save().expect("Failed to save");
assert!(!editor.is_dirty());
let content = fs::read_to_string(&toml_path).unwrap();
assert!(content.contains("updated"));
}
#[test]
fn test_toml_layer_editor_save_atomic_write() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "app_name = \"test\"").unwrap();
let mut editor = TomlLayerEditor::open(&toml_path).unwrap();
editor.set("app_name", "modified").unwrap();
editor.save().unwrap();
let content = fs::read_to_string(&toml_path).unwrap();
assert!(content.contains("modified"));
let entries: Vec<_> = fs::read_dir(temp_dir.path()).unwrap().filter_map(Result::ok).collect();
assert_eq!(entries.len(), 1); }
#[test]
fn test_toml_layer_editor_save_error_leaves_original_untouched() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
let original_content = "app_name = \"original\"";
fs::write(&toml_path, original_content).unwrap();
let mut editor = TomlLayerEditor::open(&toml_path).unwrap();
editor.set("app_name", "modified").unwrap();
let content_before = fs::read_to_string(&toml_path).unwrap();
assert_eq!(content_before, original_content);
editor.save().unwrap();
let content_after = fs::read_to_string(&toml_path).unwrap();
assert!(content_after.contains("modified"));
}
#[test]
fn test_toml_layer_editor_preserves_comments() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
let original_toml = r#"# Application Configuration
app_name = "my_app" # The application name
version = "1.0.0" # Version number
# Database Settings
[database]
host = "localhost" # Database host
port = 5432 # Database port
"#;
fs::write(&toml_path, original_toml).unwrap();
let mut editor = TomlLayerEditor::open(&toml_path).unwrap();
editor.set("version", "2.0.0").unwrap();
editor.save().unwrap();
let modified_toml = fs::read_to_string(&toml_path).unwrap();
assert!(modified_toml.contains("# Application Configuration"));
assert!(modified_toml.contains("# The application name"));
assert!(modified_toml.contains("# Database Settings"));
assert!(modified_toml.contains("# Database host"));
assert!(modified_toml.contains("# Database port"));
assert!(modified_toml.contains("version = \"2.0.0\""));
assert!(!modified_toml.contains("version = \"1.0.0\""));
}
#[test]
fn test_toml_layer_editor_preserves_formatting() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
let original_toml = r#"[database]
# Critical settings
host = "localhost"
# Optional settings
ssl_enabled = true
"#;
fs::write(&toml_path, original_toml).unwrap();
let mut editor = TomlLayerEditor::open(&toml_path).unwrap();
editor.set("database.ssl_enabled", false).unwrap();
editor.save().unwrap();
let modified_toml = fs::read_to_string(&toml_path).unwrap();
assert!(modified_toml.contains("# Critical settings"));
assert!(modified_toml.contains("# Optional settings"));
assert_eq!(modified_toml.matches('\n').count(), original_toml.matches('\n').count());
}
#[test]
fn test_json_layer_editor_roundtrip() {
let temp_dir = TempDir::new().unwrap();
let json_path = temp_dir.path().join("settings.json");
let json_content = r#"{
"app_name": "test_app",
"version": "1.0.0"
}"#;
fs::write(&json_path, json_content).unwrap();
let mut editor = JsonLayerEditor::open(&json_path).unwrap();
let app_name: String = editor.get("app_name").unwrap();
assert_eq!(app_name, "test_app");
editor.set("version", "2.0.0").unwrap();
editor.save().unwrap();
let modified_json = fs::read_to_string(&json_path).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&modified_json).unwrap();
assert_eq!(parsed["version"], "2.0.0");
}
#[test]
fn test_json_layer_editor_nested_paths() {
let temp_dir = TempDir::new().unwrap();
let json_path = temp_dir.path().join("settings.json");
let json_content = r#"{
"database": {
"host": "localhost",
"port": 5432
}
}"#;
fs::write(&json_path, json_content).unwrap();
let editor = JsonLayerEditor::open(&json_path).unwrap();
let host: String = editor.get("database.host").unwrap();
assert_eq!(host, "localhost");
let port: u16 = editor.get("database.port").unwrap();
assert_eq!(port, 5432);
}
#[test]
fn test_yaml_layer_editor_roundtrip() {
let temp_dir = TempDir::new().unwrap();
let yaml_path = temp_dir.path().join("settings.yaml");
let yaml_content = r#"
|app_name: test_app
|version: "1.0.0"
|"#
.trim_margin()
.unwrap();
fs::write(&yaml_path, yaml_content).unwrap();
let mut editor = YamlLayerEditor::open(&yaml_path).unwrap();
let app_name: String = editor.get("app_name").unwrap();
assert_eq!(app_name, "test_app");
editor.set("version", "2.0.0").unwrap();
editor.save().unwrap();
let modified_yaml = fs::read_to_string(&yaml_path).unwrap();
assert!(modified_yaml.contains("version: \"2.0.0\"") || modified_yaml.contains("version: 2.0.0"));
}
#[test]
fn test_yaml_layer_editor_nested_structures() {
let temp_dir = TempDir::new().unwrap();
let yaml_path = temp_dir.path().join("settings.yaml");
let yaml_content = r#"
|database:
| host: localhost
| port: 5432
| enabled: true
|"#
.trim_margin()
.unwrap();
fs::write(&yaml_path, yaml_content).unwrap();
let editor = YamlLayerEditor::open(&yaml_path).unwrap();
let host: String = editor.get("database.host").unwrap();
assert_eq!(host, "localhost");
}
#[test]
fn test_layer_editor_open_nonexistent_file_error() {
let _nonexistent_path = PathBuf::from("/tmp/nonexistent_config_12345.toml");
let result = TomlLayerEditor::open(&_nonexistent_path);
assert!(result.is_err());
match result.unwrap_err() {
EditorError::IoError(_) => {},
_ => panic!("Expected IoError"),
}
}
#[test]
fn test_layer_editor_get_nonexistent_key() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "existing_key = \"value\"").unwrap();
let editor = TomlLayerEditor::open(&toml_path).unwrap();
let result: Option<String> = editor.get("nonexistent_key");
assert!(result.is_none());
}
#[test]
fn test_layer_editor_unset_nonexistent_key_error() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "existing_key = \"value\"").unwrap();
let mut editor = TomlLayerEditor::open(&toml_path).unwrap();
let result = editor.unset("nonexistent_key");
assert!(result.is_err());
match result.unwrap_err() {
EditorError::KeyNotFound(_) => {},
_ => panic!("Expected KeyNotFound"),
}
}
#[test]
fn test_layer_editor_type_mismatch_error() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "port = 5432").unwrap();
let editor = TomlLayerEditor::open(&toml_path).unwrap();
let result: Option<String> = editor.get("port");
assert!(result.is_none());
}
#[test]
fn test_settings_editor_open_format_detection() {
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("config.toml");
fs::write(&toml_path, "test = true").unwrap();
let editor = SettingsLoaderEditor::open(&toml_path).unwrap();
match editor {
Editor::Toml(_) => {}, _ => panic!("Expected Toml editor"),
}
let json_path = temp_dir.path().join("config.json");
fs::write(&json_path, "{ \"test\": true }").unwrap();
let editor = SettingsLoaderEditor::open(&json_path).unwrap();
match editor {
Editor::Json(_) => {}, _ => panic!("Expected Json editor"),
}
let yaml_path = temp_dir.path().join("config.yaml");
fs::write(&yaml_path, "test: true").unwrap();
let editor = SettingsLoaderEditor::open(&yaml_path).unwrap();
match editor {
Editor::Yaml(_) => {}, _ => panic!("Expected Yaml editor"),
}
}
#[test]
fn test_settings_editor_create_with_format() {
let temp_dir = TempDir::new().unwrap();
let new_toml_path = temp_dir.path().join("new_config.toml");
let editor = SettingsLoaderEditor::create(&new_toml_path, ConfigFormat::Toml).unwrap();
assert!(!editor.is_dirty());
assert!(new_toml_path.exists());
match editor {
Editor::Toml(_) => {}, _ => panic!("Expected Toml editor"),
}
let new_json_path = temp_dir.path().join("new_config.json");
let editor = SettingsLoaderEditor::create(&new_json_path, ConfigFormat::Json).unwrap();
assert!(!editor.is_dirty());
assert!(new_json_path.exists());
match editor {
Editor::Json(_) => {},
_ => panic!("Expected Json editor"),
}
let new_yaml_path = temp_dir.path().join("new_config.yaml");
let editor = SettingsLoaderEditor::create(&new_yaml_path, ConfigFormat::Yaml).unwrap();
assert!(!editor.is_dirty());
assert!(new_yaml_path.exists());
match editor {
Editor::Yaml(_) => {},
_ => panic!("Expected Yaml editor"),
}
}
#[test]
fn test_turtle_tui_configuration_editing_scenario() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("turtle_settings.toml");
let turtle_config = r#"# Spark Turtle Configuration
[app]
name = "Spark Turtle"
version = "0.1.0"
# Logging Configuration
[logging]
level = "info"
format = "json"
# TUI Settings
[tui]
theme = "dark"
enabled = true
"#;
fs::write(&config_path, turtle_config).unwrap();
let mut editor = SettingsLoaderEditor::open(&config_path).unwrap();
let current_level: String = editor.get("logging.level").unwrap();
assert_eq!(current_level, "info");
editor.set("logging.level", "debug").unwrap();
assert!(editor.is_dirty());
editor.set("tui.theme", "light").unwrap();
editor.save().unwrap();
assert!(!editor.is_dirty());
let updated_content = fs::read_to_string(&config_path).unwrap();
assert!(updated_content.contains("level = \"debug\""));
assert!(updated_content.contains("theme = \"light\""));
assert!(updated_content.contains("# Logging Configuration"));
assert!(updated_content.contains("# TUI Settings"));
}
#[test]
fn test_editor_error_variants() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let editor_error = EditorError::from(io_err);
let error_msg = format!("{}", editor_error);
assert!(!error_msg.is_empty());
let debug_msg = format!("{:?}", editor_error);
assert!(!debug_msg.is_empty());
}
#[test]
fn test_config_editor_multi_layer_orchestration() {
use serde::Deserialize;
use settings_loader::{ConfigEditor, LayerBuilder, SettingsLoader};
let temp_dir = TempDir::new().unwrap();
let user_path = temp_dir.path().join("user.toml");
let project_path = temp_dir.path().join("project.json");
fs::write(&user_path, "theme = \"dark\"\nfont_size = 12").unwrap();
fs::write(&project_path, r#"{ "project_name": "Test", "font_size": 14 }"#).unwrap();
#[derive(Debug, Deserialize)]
struct MySettings {
theme: String,
font_size: u8,
project_name: String,
}
impl SettingsLoader for MySettings {
type Options = settings_loader::NoOptions;
}
let initial_builder = LayerBuilder::new();
let builder = initial_builder
.with_path(user_path.clone())
.with_path(project_path.clone());
let (config_builder, source_map) = builder.build_with_provenance().unwrap();
let config = config_builder.build().unwrap();
let settings: MySettings = config.try_deserialize().unwrap();
assert_eq!(settings.theme, "dark");
assert_eq!(settings.project_name, "Test");
assert_eq!(settings.font_size, 14);
let mut config_editor = ConfigEditor::new(source_map);
let theme: String = config_editor.get("theme").unwrap().unwrap();
assert_eq!(theme, "dark");
let project_name: String = config_editor.get("project_name").unwrap().unwrap();
assert_eq!(project_name, "Test");
let font_size: u8 = config_editor.get("font_size").unwrap().unwrap();
assert_eq!(font_size, 14);
config_editor.set("theme", "light").unwrap(); config_editor.set("project_name", "Updated Project").unwrap();
config_editor.save().unwrap();
let user_content = fs::read_to_string(&user_path).unwrap();
assert!(user_content.contains("theme = \"light\""));
let project_content = fs::read_to_string(&project_path).unwrap();
assert!(project_content.contains("\"Updated Project\""));
}
}
#[cfg(not(feature = "editor"))]
mod tests_without_feature {
#[test]
fn editor_feature_not_enabled() {
}
}