cfgmatic-source 5.0.1

Configuration sources (file, env, memory) for cfgmatic framework
Documentation
//! Integration tests for complete workflows.
//!
//! Test coverage:
//! - End-to-end configuration loading
//! - Multi-source composition
//! - Error propagation
//! - Real-world scenarios

use cfgmatic_source::prelude::*;
use serde::Deserialize;
use std::io::Write;
use tempfile::NamedTempFile;

#[derive(Debug, Clone, Deserialize, PartialEq)]
struct AppConfig {
    name: String,
    version: String,
    database: DatabaseConfig,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
struct DatabaseConfig {
    host: String,
    port: u16,
}

fn create_config_file(content: &str, extension: &str) -> NamedTempFile {
    let mut file = NamedTempFile::with_suffix(extension).expect("Failed to create temp file");
    file.write_all(content.as_bytes()).expect("Failed to write");
    file
}

#[test]
fn test_workflow_load_config_from_file() {
    // Arrange
    let toml_content = r#"
name = "my-app"
version = "1.0.0"

[database]
host = "localhost"
port = 5432
"#;
    let file = create_config_file(toml_content, ".toml");

    // Act
    let source = FileSource::new(file.path());
    let raw = source.load_raw().expect("Failed to load");
    let content = raw.as_str().expect("Failed to get string");
    let format = source.detect_format().unwrap_or(Format::Toml);
    let config: AppConfig = format.parse_as(content.as_ref()).expect("Failed to parse");

    // Assert
    assert_eq!(config.name, "my-app");
    assert_eq!(config.version, "1.0.0");
    assert_eq!(config.database.host, "localhost");
    assert_eq!(config.database.port, 5432);
}

#[test]
fn test_workflow_composite_source() {
    // Arrange - base config in file, overrides in memory
    let base_config = r#"{"name": "base-app", "version": "0.1.0", "database": {"host": "localhost", "port": 5432}}"#;
    let file = create_config_file(base_config, ".json");

    let override_config = serde_json::json!({"name": "override-app"});
    let memory_source = MemorySource::from_json(override_config);

    // Act
    let file_source = FileSource::new(file.path());
    let composite = CompositeSource::builder()
        .add_with_priority(memory_source, SourcePriority::HIGH)
        .add_with_priority(file_source, SourcePriority::LOWEST)
        .build();

    let raw = composite.load_raw().expect("Failed to load composite");

    // Assert - should have content from composite source
    let content = raw.as_str().expect("Failed to get string");
    assert!(!content.is_empty());
}

#[test]
fn test_workflow_memory_source_full_config() {
    // Arrange
    let json_content = r#"{
        "name": "test-app",
        "version": "2.0.0",
        "database": {
            "host": "db.example.com",
            "port": 3306
        }
    }"#;

    // Act
    let value: serde_json::Value = serde_json::from_str(json_content).expect("Invalid JSON");
    let source = MemorySource::from_json(value);
    let raw = source.load_raw().expect("Failed to load");
    let content = raw.as_str().expect("Failed to get string");
    let config: AppConfig = Format::Json
        .parse_as(content.as_ref())
        .expect("Failed to parse");

    // Assert
    assert_eq!(config.name, "test-app");
    assert_eq!(config.version, "2.0.0");
    assert_eq!(config.database.host, "db.example.com");
    assert_eq!(config.database.port, 3306);
}

#[test]
fn test_workflow_chain_operations() {
    // Arrange
    let content = r#"{"name": "chain-test"}"#;
    let value: serde_json::Value = serde_json::from_str(content).expect("Invalid JSON");
    let source = MemorySource::from_json(value);

    // Act - chain load and parse
    let raw = source.load_raw().expect("Failed to load");
    let content = raw.as_str().expect("Failed to get string");
    let result: serde_json::Value = Format::Json
        .parse_as(content.as_ref())
        .expect("Failed to parse");

    // Assert
    assert_eq!(result["name"], "chain-test");
}

/// Simple config for testing.
#[derive(Debug, Clone, Deserialize, PartialEq)]
struct SimpleConfig {
    name: String,
}

#[test]
fn test_workflow_multiple_formats() {
    // Arrange
    let json_content = r#"{"name": "json-app"}"#;

    // Act - Create memory sources with different content
    let toml_value: serde_json::Value = serde_json::json!({"name": "toml-app"});
    let json_value: serde_json::Value = serde_json::from_str(json_content).expect("Invalid JSON");

    let toml_source = MemorySource::from_json(toml_value);
    let json_source = MemorySource::from_json(json_value);

    let toml_raw = toml_source.load_raw().expect("Failed to load TOML");
    let json_raw = json_source.load_raw().expect("Failed to load JSON");

    // Assert
    let toml_content = toml_raw.as_str().expect("Failed to get string");
    let json_parsed = json_raw.as_str().expect("Failed to get string");

    let toml_config: SimpleConfig = Format::Json
        .parse_as(toml_content.as_ref())
        .expect("Failed to parse TOML");
    let json_config: SimpleConfig = Format::Json
        .parse_as(json_parsed.as_ref())
        .expect("Failed to parse JSON");

    assert_eq!(toml_config.name, "toml-app");
    assert_eq!(json_config.name, "json-app");
}