apicurio-cli 0.1.2

A powerful CLI tool for managing schema artifacts from Apicurio Registry with lockfile-based dependency management
#[cfg(test)]
mod integration_tests {
    use std::fs;
    use tempfile::TempDir;

    fn create_test_config_file(dir: &TempDir, content: &str) -> std::path::PathBuf {
        let config_path = dir.path().join("apicurioconfig.yaml");
        fs::write(&config_path, content).unwrap();
        config_path
    }

    fn create_test_lockfile(
        dir: &TempDir,
        lockfile: &crate::lockfile::LockFile,
    ) -> std::path::PathBuf {
        let lock_path = dir.path().join("apicuriolock.yaml");
        lockfile.save(&lock_path).unwrap();
        lock_path
    }

    #[test]
    fn test_lockfile_regeneration_scenarios() {
        let temp_dir = TempDir::new().unwrap();

        // Create initial config
        let config_content = r#"
externalRegistriesFile: null
registries: []
dependencies:
  - name: "service1"
    groupId: "com.example"
    artifactId: "service1"
    version: "^1.0.0"
    registry: "default"
    outputPath: "./protos/service1.proto"
"#;

        let config_path = create_test_config_file(&temp_dir, config_content);

        let deps = vec![crate::config::DependencyConfig {
            name: "service1".to_string(),
            group_id: "com.example".to_string(),
            artifact_id: "service1".to_string(),
            version: "^1.0.0".to_string(),
            registry: "default".to_string(),
            output_path: "./protos/service1.proto".to_string(),
        }];

        // Create lockfile
        let config_hash = crate::lockfile::LockFile::compute_config_hash(config_content, &deps);
        let config_modified =
            crate::lockfile::LockFile::get_config_modification_time(&config_path).ok();

        let locked_dep = crate::lockfile::LockedDependency {
            name: "service1".to_string(),
            registry: "default".to_string(),
            resolved_version: "1.0.5".to_string(),
            download_url: "https://example.com/service1/1.0.5".to_string(),
            sha256: "abcd1234".to_string(),
            output_path: "./protos/service1.proto".to_string(),
            group_id: "com.example".to_string(),
            artifact_id: "service1".to_string(),
            version_spec: "^1.0.0".to_string(),
        };

        let lockfile = crate::lockfile::LockFile::with_config_modified(
            vec![locked_dep.clone()],
            config_hash.clone(),
            config_modified,
        );

        let _lock_path = create_test_lockfile(&temp_dir, &lockfile);

        // Test 1: Same config should be compatible
        assert!(lockfile.is_compatible_with_config(&config_hash));

        // Test 2: Config modification time check
        assert!(lockfile.is_newer_than_config(&config_path).unwrap());

        // Test 3: Dependencies match
        assert!(lockfile.dependencies_match(&[locked_dep.clone()]));

        // Test 4: Modified config should trigger regeneration
        let modified_config = r#"
externalRegistriesFile: null
registries: []
dependencies:
  - name: "service1"
    groupId: "com.example"
    artifactId: "service1"
    version: "^1.1.0"  # Version changed
    registry: "default"
    outputPath: "./protos/service1.proto"
"#;

        let modified_deps = vec![crate::config::DependencyConfig {
            name: "service1".to_string(),
            group_id: "com.example".to_string(),
            artifact_id: "service1".to_string(),
            version: "^1.1.0".to_string(), // Changed version
            registry: "default".to_string(),
            output_path: "./protos/service1.proto".to_string(),
        }];

        let new_config_hash =
            crate::lockfile::LockFile::compute_config_hash(modified_config, &modified_deps);
        assert_ne!(
            config_hash, new_config_hash,
            "Config hash should change when version requirements change"
        );
        assert!(!lockfile.is_compatible_with_config(&new_config_hash));
    }

    #[test]
    fn test_formatting_changes_dont_trigger_regeneration() {
        let _temp_dir = TempDir::new().unwrap();

        let deps = vec![crate::config::DependencyConfig {
            name: "service1".to_string(),
            group_id: "com.example".to_string(),
            artifact_id: "service1".to_string(),
            version: "^1.0.0".to_string(),
            registry: "default".to_string(),
            output_path: "./protos/service1.proto".to_string(),
        }];

        // Original config
        let config1 = r#"
externalRegistriesFile: null
registries: []
dependencies:
  - name: "service1"
    groupId: "com.example"
    artifactId: "service1"
    version: "^1.0.0"
    registry: "default"
    outputPath: "./protos/service1.proto"
"#;

        // Same config with different formatting and comments
        let config2 = r#"
# Added a comment
externalRegistriesFile: null
registries: []  # Empty registries
dependencies:
  - name: "service1"
    groupId: "com.example"
    artifactId: "service1"
    version: "^1.0.0"
    registry: "default"
    outputPath: "./protos"
    # End of dependency
"#;

        let hash1 = crate::lockfile::LockFile::compute_config_hash(config1, &deps);
        let hash2 = crate::lockfile::LockFile::compute_config_hash(config2, &deps);

        assert_eq!(
            hash1, hash2,
            "Hash should be the same for formatting-only changes"
        );
    }

    #[test]
    fn test_registry_changes_trigger_regeneration() {
        let deps = vec![crate::config::DependencyConfig {
            name: "service1".to_string(),
            group_id: "com.example".to_string(),
            artifact_id: "service1".to_string(),
            version: "^1.0.0".to_string(),
            registry: "default".to_string(),
            output_path: "./protos".to_string(),
        }];

        // Config with one registry
        let config1 = r#"
externalRegistriesFile: null
registries:
  - name: "default"
    url: "https://registry1.example.com"
dependencies:
  - name: "service1"
    groupId: "com.example"
    artifactId: "service1"
    version: "^1.0.0"
    registry: "default"
    outputPath: "./protos"
"#;

        // Config with different registry URL
        let config2 = r#"
externalRegistriesFile: null
registries:
  - name: "default"
    url: "https://registry2.example.com"  # Different URL
dependencies:
  - name: "service1"
    groupId: "com.example"
    artifactId: "service1"
    version: "^1.0.0"
    registry: "default"
    outputPath: "./protos"
"#;

        let hash1 = crate::lockfile::LockFile::compute_config_hash(config1, &deps);
        let hash2 = crate::lockfile::LockFile::compute_config_hash(config2, &deps);

        assert_ne!(hash1, hash2, "Hash should change when registry URLs change");
    }

    #[test]
    fn test_external_registry_file_changes_trigger_regeneration() {
        let deps = vec![crate::config::DependencyConfig {
            name: "service1".to_string(),
            group_id: "com.example".to_string(),
            artifact_id: "service1".to_string(),
            version: "^1.0.0".to_string(),
            registry: "default".to_string(),
            output_path: "./protos".to_string(),
        }];

        // Config without external registries file
        let config1 = r#"
externalRegistriesFile: null
registries: []
dependencies:
  - name: "service1"
    groupId: "com.example"
    artifactId: "service1"
    version: "^1.0.0"
    registry: "default"
    outputPath: "./protos"
"#;

        // Config with external registries file
        let config2 = r#"
externalRegistriesFile: "external-registries.yaml"
registries: []
dependencies:
  - name: "service1"
    groupId: "com.example"
    artifactId: "service1"
    version: "^1.0.0"
    registry: "default"
    outputPath: "./protos"
"#;

        let hash1 = crate::lockfile::LockFile::compute_config_hash(config1, &deps);
        let hash2 = crate::lockfile::LockFile::compute_config_hash(config2, &deps);

        assert_ne!(
            hash1, hash2,
            "Hash should change when external registries file is added"
        );
    }
}