ggen-core 26.7.2

Core graph-aware code generation engine
Documentation
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::needless_raw_string_hashes, clippy::duration_suboptimal_units, clippy::branches_sharing_code, clippy::used_underscore_binding, clippy::single_char_pattern, clippy::ignore_without_reason, clippy::cloned_ref_to_slice_refs, clippy::doc_overindented_list_items, clippy::match_wildcard_for_single_variants, clippy::ignored_unit_patterns, clippy::needless_collect, clippy::unnecessary_map_or, clippy::manual_flatten, clippy::manual_strip, clippy::future_not_send, clippy::unnested_or_patterns, clippy::no_effect_underscore_binding, clippy::literal_string_with_formatting_args)]
//! Multi-node scenario tests
//!
//! These tests simulate multiple registry nodes and client interactions

use anyhow::Result;
use ggen_core::registry::{PackMetadata, RegistryClient, VersionMetadata};
use std::collections::HashMap;
use std::fs;
use tempfile::TempDir;
use url::Url;

#[tokio::test]
async fn test_multiple_registry_instances() -> Result<()> {
    // Create two separate registry instances
    let registry1_dir = TempDir::new()?;
    let registry2_dir = TempDir::new()?;

    // Setup registry 1 with package A
    let index1_path = registry1_dir.path().join("index.json");
    let mut packs1 = HashMap::new();
    let mut versions1 = HashMap::new();
    versions1.insert(
        "1.0.0".to_string(),
        VersionMetadata {
            version: "1.0.0".to_string(),
            git_url: "https://github.com/test/package-a.git".to_string(),
            git_rev: "main".to_string(),
            manifest_url: None,
            sha256: "abc123".to_string(),
        },
    );

    packs1.insert(
        "package-a".to_string(),
        PackMetadata {
            id: "package-a".to_string(),
            name: "Package A".to_string(),
            description: "Package from registry 1".to_string(),
            tags: vec!["registry1".to_string()],
            keywords: vec!["test".to_string()],
            category: Some("tools".to_string()),
            author: Some("Registry 1".to_string()),
            latest_version: "1.0.0".to_string(),
            versions: versions1,
            downloads: Some(100),
            updated: Some(chrono::Utc::now()),
            license: Some("MIT".to_string()),
            homepage: None,
            repository: None,
            documentation: None,
        },
    );

    let index1 = ggen_core::registry::RegistryIndex {
        updated: chrono::Utc::now(),
        packs: packs1,
    };

    fs::write(&index1_path, serde_json::to_string_pretty(&index1)?)?;

    // Setup registry 2 with package B
    let index2_path = registry2_dir.path().join("index.json");
    let mut packs2 = HashMap::new();
    let mut versions2 = HashMap::new();
    versions2.insert(
        "2.0.0".to_string(),
        VersionMetadata {
            version: "2.0.0".to_string(),
            git_url: "https://github.com/test/package-b.git".to_string(),
            git_rev: "main".to_string(),
            manifest_url: None,
            sha256: "def456".to_string(),
        },
    );

    packs2.insert(
        "package-b".to_string(),
        PackMetadata {
            id: "package-b".to_string(),
            name: "Package B".to_string(),
            description: "Package from registry 2".to_string(),
            tags: vec!["registry2".to_string()],
            keywords: vec!["test".to_string()],
            category: Some("libraries".to_string()),
            author: Some("Registry 2".to_string()),
            latest_version: "2.0.0".to_string(),
            versions: versions2,
            downloads: Some(200),
            updated: Some(chrono::Utc::now()),
            license: Some("Apache-2.0".to_string()),
            homepage: None,
            repository: None,
            documentation: None,
        },
    );

    let index2 = ggen_core::registry::RegistryIndex {
        updated: chrono::Utc::now(),
        packs: packs2,
    };

    fs::write(&index2_path, serde_json::to_string_pretty(&index2)?)?;

    // Create clients for both registries
    let base_url1 = Url::from_file_path(registry1_dir.path())
        .map_err(|_| anyhow::anyhow!("Failed to create file URL"))?;
    let client1 = RegistryClient::with_base_url(base_url1)?;

    let base_url2 = Url::from_file_path(registry2_dir.path())
        .map_err(|_| anyhow::anyhow!("Failed to create file URL"))?;
    let client2 = RegistryClient::with_base_url(base_url2)?;

    // Verify isolation - each registry only sees its own packages
    let packages1 = client1.list_packages().await?;
    assert_eq!(packages1.len(), 1);
    assert_eq!(packages1[0].id, "package-a");

    let packages2 = client2.list_packages().await?;
    assert_eq!(packages2.len(), 1);
    assert_eq!(packages2[0].id, "package-b");

    // Search in registry 1
    let results1 = client1.search("package").await?;
    assert_eq!(results1.len(), 1);
    assert_eq!(results1[0].id, "package-a");

    // Search in registry 2
    let results2 = client2.search("package").await?;
    assert_eq!(results2.len(), 1);
    assert_eq!(results2[0].id, "package-b");

    Ok(())
}

#[tokio::test]
async fn test_concurrent_registry_access() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let index_path = temp_dir.path().join("index.json");

    // Create a registry with multiple packages
    let mut packs = HashMap::new();
    for i in 0..10 {
        let id = format!("package-{}", i);
        let mut versions = HashMap::new();
        versions.insert(
            "1.0.0".to_string(),
            VersionMetadata {
                version: "1.0.0".to_string(),
                git_url: format!("https://github.com/test/{}.git", id),
                git_rev: "main".to_string(),
                manifest_url: None,
                sha256: format!("hash{}", i),
            },
        );

        packs.insert(
            id.clone(),
            PackMetadata {
                id: id.clone(),
                name: format!("Package {}", i),
                description: format!("Test package {}", i),
                tags: vec!["test".to_string()],
                keywords: vec!["concurrent".to_string()],
                category: Some("test".to_string()),
                author: Some("Test".to_string()),
                latest_version: "1.0.0".to_string(),
                versions,
                downloads: Some(i as u64 * 10),
                updated: Some(chrono::Utc::now()),
                license: Some("MIT".to_string()),
                homepage: None,
                repository: None,
                documentation: None,
            },
        );
    }

    let index = ggen_core::registry::RegistryIndex {
        updated: chrono::Utc::now(),
        packs,
    };

    fs::write(&index_path, serde_json::to_string_pretty(&index)?)?;

    let base_url = Url::from_file_path(temp_dir.path())
        .map_err(|_| anyhow::anyhow!("Failed to create file URL"))?;

    // Create multiple clients and perform concurrent operations
    let mut handles = vec![];

    for i in 0..5 {
        let base_url_clone = base_url.clone();
        let handle = tokio::spawn(async move {
            let client = RegistryClient::with_base_url(base_url_clone).unwrap();

            // Each task performs different operations
            match i % 3 {
                0 => {
                    // Search operation
                    client.search("package").await.unwrap()
                }
                1 => {
                    // List operation
                    let packages = client.list_packages().await.unwrap();
                    packages
                        .into_iter()
                        .map(|p| ggen_core::registry::SearchResult {
                            id: p.id,
                            name: p.name,
                            description: p.description,
                            tags: p.tags,
                            keywords: p.keywords,
                            category: p.category,
                            author: p.author,
                            latest_version: p.latest_version,
                            downloads: p.downloads,
                            updated: p.updated,
                            license: p.license,
                            homepage: p.homepage,
                            repository: p.repository,
                            documentation: p.documentation,
                        })
                        .collect()
                }
                _ => {
                    // Fetch index operation
                    let index = client.fetch_index().await.unwrap();
                    index
                        .packs
                        .into_iter()
                        .map(|(_, p)| ggen_core::registry::SearchResult {
                            id: p.id,
                            name: p.name,
                            description: p.description,
                            tags: p.tags,
                            keywords: p.keywords,
                            category: p.category,
                            author: p.author,
                            latest_version: p.latest_version,
                            downloads: p.downloads,
                            updated: p.updated,
                            license: p.license,
                            homepage: p.homepage,
                            repository: p.repository,
                            documentation: p.documentation,
                        })
                        .collect()
                }
            }
        });

        handles.push(handle);
    }

    // Wait for all tasks to complete
    for handle in handles {
        let results = handle.await?;
        assert!(!results.is_empty());
    }

    Ok(())
}

#[tokio::test]
async fn test_registry_failover_simulation() -> Result<()> {
    // Primary registry
    let primary_dir = TempDir::new()?;
    let primary_index_path = primary_dir.path().join("index.json");

    // Fallback registry
    let fallback_dir = TempDir::new()?;
    let fallback_index_path = fallback_dir.path().join("index.json");

    // Setup both registries with same package but different versions
    let mut packs = HashMap::new();
    let mut versions = HashMap::new();
    versions.insert(
        "1.0.0".to_string(),
        VersionMetadata {
            version: "1.0.0".to_string(),
            git_url: "https://github.com/test/package.git".to_string(),
            git_rev: "v1.0.0".to_string(),
            manifest_url: None,
            sha256: "primary".to_string(),
        },
    );

    packs.insert(
        "shared-package".to_string(),
        PackMetadata {
            id: "shared-package".to_string(),
            name: "Shared Package".to_string(),
            description: "Package available in multiple registries".to_string(),
            tags: vec!["shared".to_string()],
            keywords: vec!["test".to_string()],
            category: Some("test".to_string()),
            author: Some("Test".to_string()),
            latest_version: "1.0.0".to_string(),
            versions,
            downloads: Some(100),
            updated: Some(chrono::Utc::now()),
            license: Some("MIT".to_string()),
            homepage: None,
            repository: None,
            documentation: None,
        },
    );

    let primary_index = ggen_core::registry::RegistryIndex {
        updated: chrono::Utc::now(),
        packs: packs.clone(),
    };

    fs::write(
        &primary_index_path,
        serde_json::to_string_pretty(&primary_index)?,
    )?;
    fs::write(
        &fallback_index_path,
        serde_json::to_string_pretty(&primary_index)?,
    )?;

    // Test primary registry
    let primary_url = Url::from_file_path(primary_dir.path())
        .map_err(|_| anyhow::anyhow!("Failed to create file URL"))?;
    let primary_client = RegistryClient::with_base_url(primary_url)?;

    let primary_result = primary_client.resolve("shared-package", None).await?;
    assert_eq!(primary_result.version, "1.0.0");

    // Test fallback registry
    let fallback_url = Url::from_file_path(fallback_dir.path())
        .map_err(|_| anyhow::anyhow!("Failed to create file URL"))?;
    let fallback_client = RegistryClient::with_base_url(fallback_url)?;

    let fallback_result = fallback_client.resolve("shared-package", None).await?;
    assert_eq!(fallback_result.version, "1.0.0");

    // Simulate primary failure by removing index
    fs::remove_file(&primary_index_path)?;

    // Primary should fail
    let primary_fail = primary_client.fetch_index().await;
    assert!(primary_fail.is_err());

    // Fallback should still work
    let fallback_index = fallback_client.fetch_index().await?;
    assert_eq!(fallback_index.packs.len(), 1);

    Ok(())
}