mirror of https://github.com/buster-so/buster.git
270 lines
9.2 KiB
Rust
270 lines
9.2 KiB
Rust
use anyhow::Result;
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
use tempfile::TempDir;
|
|
|
|
// Import the crates from the buster CLI library
|
|
// The crate is actually called "buster-cli", but in tests we need to use the package name
|
|
use buster_cli::utils::config::{BusterConfig, ProjectContext};
|
|
|
|
/// Helper functions to create test directories and files
|
|
fn create_test_dir() -> Result<TempDir> {
|
|
Ok(TempDir::new()?)
|
|
}
|
|
|
|
fn create_test_file(dir: &Path, name: &str, content: &str) -> Result<PathBuf> {
|
|
let path = dir.join(name);
|
|
fs::write(&path, content)?;
|
|
Ok(path)
|
|
}
|
|
|
|
fn create_test_dir_with_files(dir: &Path, name: &str) -> Result<PathBuf> {
|
|
let path = dir.join(name);
|
|
fs::create_dir_all(&path)?;
|
|
Ok(path)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_project_based_model_discovery() -> Result<()> {
|
|
// Create a temporary test directory structure
|
|
let temp_dir = create_test_dir()?;
|
|
let root_dir = temp_dir.path();
|
|
|
|
// Create project directories
|
|
let project1_dir = create_test_dir_with_files(root_dir, "project1")?;
|
|
let project2_dir = create_test_dir_with_files(root_dir, "project2")?;
|
|
let project1_models_dir = create_test_dir_with_files(&project1_dir, "models")?;
|
|
|
|
// Create model files
|
|
let model1 = r#"
|
|
models:
|
|
- name: project1_model
|
|
description: "Project 1 Model"
|
|
dimensions:
|
|
- name: dimension1
|
|
description: "Dimension 1"
|
|
type: string
|
|
"#;
|
|
|
|
let model2 = r#"
|
|
models:
|
|
- name: project2_model
|
|
description: "Project 2 Model"
|
|
dimensions:
|
|
- name: dimension2
|
|
description: "Dimension 2"
|
|
type: string
|
|
"#;
|
|
|
|
create_test_file(&project1_models_dir, "model1.yml", model1)?;
|
|
create_test_file(&project2_dir, "model2.yml", model2)?;
|
|
|
|
// Create buster.yml with projects configuration
|
|
let buster_yml = r#"
|
|
data_source_name: global_source
|
|
schema: global_schema
|
|
database: global_database
|
|
|
|
projects:
|
|
- name: Project 1
|
|
path: project1
|
|
model_paths:
|
|
- models
|
|
data_source_name: project1_source
|
|
|
|
- name: Project 2
|
|
path: project2
|
|
schema: project2_schema
|
|
"#;
|
|
|
|
create_test_file(root_dir, "buster.yml", buster_yml)?;
|
|
|
|
// Load the configuration
|
|
let config = BusterConfig::load_from_dir(root_dir)?.expect("Failed to load buster.yml");
|
|
|
|
// Test effective model paths resolution
|
|
let effective_paths = config.resolve_effective_model_paths(root_dir);
|
|
|
|
// Should have two paths: project1/models and project2
|
|
assert_eq!(effective_paths.len(), 2);
|
|
|
|
// First path should be project1/models with Project 1 context
|
|
assert_eq!(effective_paths[0].0, project1_models_dir);
|
|
assert!(effective_paths[0].1.is_some());
|
|
let project1_context = effective_paths[0].1.unwrap();
|
|
assert_eq!(project1_context.name, Some("Project 1".to_string()));
|
|
assert_eq!(project1_context.data_source_name, Some("project1_source".to_string()));
|
|
|
|
// Second path should be project2 with Project 2 context
|
|
assert_eq!(effective_paths[1].0, project2_dir);
|
|
assert!(effective_paths[1].1.is_some());
|
|
let project2_context = effective_paths[1].1.unwrap();
|
|
assert_eq!(project2_context.name, Some("Project 2".to_string()));
|
|
assert_eq!(project2_context.schema, Some("project2_schema".to_string()));
|
|
|
|
// Test configuration resolution for a model in project1
|
|
let model1_path = project1_models_dir.join("model1.yml");
|
|
let model1_context = config.get_context_for_path(&model1_path, root_dir);
|
|
assert!(model1_context.is_some());
|
|
let model1_context = model1_context.unwrap();
|
|
|
|
// Model1 should inherit data_source_name from Project 1 context
|
|
assert_eq!(model1_context.data_source_name, Some("project1_source".to_string()));
|
|
|
|
// Model1 should inherit schema from global context
|
|
assert_eq!(model1_context.schema, None); // Project1 doesn't override schema
|
|
|
|
// Test configuration resolution for a model in project2
|
|
let model2_path = project2_dir.join("model2.yml");
|
|
let model2_context = config.get_context_for_path(&model2_path, root_dir);
|
|
assert!(model2_context.is_some());
|
|
let model2_context = model2_context.unwrap();
|
|
|
|
// Model2 should inherit data_source_name from global context
|
|
assert_eq!(model2_context.data_source_name, None); // Project2 doesn't override data_source_name
|
|
|
|
// Model2 should inherit schema from Project 2 context
|
|
assert_eq!(model2_context.schema, Some("project2_schema".to_string()));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_fallback_to_global_config() -> Result<()> {
|
|
// Create a temporary test directory structure
|
|
let temp_dir = create_test_dir()?;
|
|
let root_dir = temp_dir.path();
|
|
|
|
// Create model files in root
|
|
let model = r#"
|
|
models:
|
|
- name: global_model
|
|
description: "Global Model"
|
|
dimensions:
|
|
- name: dimension
|
|
description: "Dimension"
|
|
type: string
|
|
"#;
|
|
|
|
create_test_file(root_dir, "model.yml", model)?;
|
|
|
|
// Create buster.yml with only global configuration (no projects)
|
|
let buster_yml = r#"
|
|
data_source_name: global_source
|
|
schema: global_schema
|
|
database: global_database
|
|
model_paths:
|
|
- .
|
|
"#;
|
|
|
|
create_test_file(root_dir, "buster.yml", buster_yml)?;
|
|
|
|
// Load the configuration
|
|
let config = BusterConfig::load_from_dir(root_dir)?.expect("Failed to load buster.yml");
|
|
|
|
// Test effective model paths resolution
|
|
let effective_paths = config.resolve_effective_model_paths(root_dir);
|
|
|
|
// Should have one path: the root directory
|
|
assert_eq!(effective_paths.len(), 1);
|
|
assert_eq!(effective_paths[0].0, root_dir);
|
|
assert!(effective_paths[0].1.is_none());
|
|
|
|
// Test configuration resolution for the model
|
|
let model_path = root_dir.join("model.yml");
|
|
let model_context = config.get_context_for_path(&model_path, root_dir);
|
|
assert!(model_context.is_none()); // No project context applies
|
|
|
|
// But the global config is still available
|
|
assert_eq!(config.data_source_name, Some("global_source".to_string()));
|
|
assert_eq!(config.schema, Some("global_schema".to_string()));
|
|
assert_eq!(config.database, Some("global_database".to_string()));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_nested_project_discovery() -> Result<()> {
|
|
// Create a temporary test directory structure with nested projects
|
|
let temp_dir = create_test_dir()?;
|
|
let root_dir = temp_dir.path();
|
|
|
|
// Create nested directories
|
|
let outer_project_dir = create_test_dir_with_files(root_dir, "outer_project")?;
|
|
let inner_project_dir = create_test_dir_with_files(&outer_project_dir, "inner_project")?;
|
|
|
|
// Create model files
|
|
let outer_model = r#"
|
|
models:
|
|
- name: outer_model
|
|
description: "Outer Model"
|
|
dimensions:
|
|
- name: dimension
|
|
description: "Dimension"
|
|
type: string
|
|
"#;
|
|
|
|
let inner_model = r#"
|
|
models:
|
|
- name: inner_model
|
|
description: "Inner Model"
|
|
dimensions:
|
|
- name: dimension
|
|
description: "Dimension"
|
|
type: string
|
|
"#;
|
|
|
|
create_test_file(&outer_project_dir, "outer_model.yml", outer_model)?;
|
|
create_test_file(&inner_project_dir, "inner_model.yml", inner_model)?;
|
|
|
|
// Create root buster.yml with nested projects
|
|
let root_buster_yml = r#"
|
|
data_source_name: root_source
|
|
schema: root_schema
|
|
|
|
projects:
|
|
- name: Outer Project
|
|
path: outer_project
|
|
data_source_name: outer_source
|
|
"#;
|
|
|
|
// Create a nested buster.yml in the outer project
|
|
let outer_buster_yml = r#"
|
|
data_source_name: outer_source_override
|
|
schema: outer_schema
|
|
|
|
projects:
|
|
- name: Inner Project
|
|
path: inner_project
|
|
schema: inner_schema
|
|
"#;
|
|
|
|
create_test_file(root_dir, "buster.yml", root_buster_yml)?;
|
|
create_test_file(&outer_project_dir, "buster.yml", outer_buster_yml)?;
|
|
|
|
// Test root config
|
|
let root_config = BusterConfig::load_from_dir(root_dir)?.expect("Failed to load root buster.yml");
|
|
let outer_config = BusterConfig::load_from_dir(&outer_project_dir)?.expect("Failed to load outer buster.yml");
|
|
|
|
// Root config should have one project
|
|
assert!(root_config.projects.is_some());
|
|
assert_eq!(root_config.projects.as_ref().unwrap().len(), 1);
|
|
assert_eq!(root_config.projects.as_ref().unwrap()[0].name, Some("Outer Project".to_string()));
|
|
|
|
// Outer config should have one project
|
|
assert!(outer_config.projects.is_some());
|
|
assert_eq!(outer_config.projects.as_ref().unwrap().len(), 1);
|
|
assert_eq!(outer_config.projects.as_ref().unwrap()[0].name, Some("Inner Project".to_string()));
|
|
|
|
// Test root config effective paths
|
|
let root_effective_paths = root_config.resolve_effective_model_paths(root_dir);
|
|
assert_eq!(root_effective_paths.len(), 1);
|
|
assert_eq!(root_effective_paths[0].0, outer_project_dir);
|
|
|
|
// Test outer config effective paths
|
|
let outer_effective_paths = outer_config.resolve_effective_model_paths(&outer_project_dir);
|
|
assert_eq!(outer_effective_paths.len(), 1);
|
|
assert_eq!(outer_effective_paths[0].0, inner_project_dir);
|
|
|
|
Ok(())
|
|
} |