use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
fn get_binary_path() -> PathBuf {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let debug_path = Path::new(manifest_dir).join("target/debug/trilogy-parser-cli");
let release_path = Path::new(manifest_dir).join("target/release/trilogy-parser-cli");
#[cfg(target_os = "windows")]
{
let debug_exe = debug_path.with_extension("exe");
let release_exe = release_path.with_extension("exe");
if release_exe.exists() {
return release_exe;
}
debug_exe
}
#[cfg(not(target_os = "windows"))]
{
if release_path.exists() {
return release_path;
}
debug_path
}
}
fn create_test_file(dir: &Path, name: &str, content: &str) -> PathBuf {
let path = dir.join(name);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(&path, content).unwrap();
path
}
#[test]
fn test_parse_simple_file() {
let temp = TempDir::new().unwrap();
let root = temp.path();
let test_file = create_test_file(
root,
"test.preql",
"import models.customer;\nimport models.orders as ord;",
);
let output = Command::new(get_binary_path())
.arg("parse")
.arg(&test_file)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("models.customer"));
assert!(stdout.contains("models.orders"));
assert!(stdout.contains("\"alias\":\"ord\""));
}
#[test]
fn test_parse_with_datasource() {
let temp = TempDir::new().unwrap();
let root = temp.path();
let test_file = create_test_file(
root,
"test.preql",
r#"
datasource orders (
id: key,
amount: metric
)
address db.orders;
"#,
);
let output = Command::new(get_binary_path())
.arg("parse")
.arg(&test_file)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("\"datasources\""));
assert!(stdout.contains("\"name\":\"orders\""));
assert!(stdout.contains("\"address\":\"db.orders\""));
assert!(stdout.contains("\"address_kind\":\"literal\""));
}
#[test]
fn test_parse_datasource_metadata_json() {
let temp = TempDir::new().unwrap();
let root = temp.path();
let test_file = create_test_file(
root,
"test.preql",
r#"
root partial datasource raw_events (
id: key
)
file `./ingest.py`;
datasource events (
id: key
)
address warehouse.events
partition by id;
datasource event_view (
id: key
)
query '''select 1 as id''';
"#,
);
let output = Command::new(get_binary_path())
.arg("parse")
.arg(&test_file)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let parsed: serde_json::Value =
serde_json::from_slice(&output.stdout).expect("output should be valid json");
let datasources = parsed["datasources"].as_array().unwrap();
assert_eq!(datasources.len(), 3);
let by_name = |name: &str| {
datasources
.iter()
.find(|d| d["name"] == name)
.unwrap_or_else(|| panic!("missing datasource {name}"))
.clone()
};
let raw = by_name("raw_events");
assert_eq!(raw["address_kind"], "file");
assert_eq!(raw["address"], "`./ingest.py`");
assert_eq!(raw["is_root"], true);
assert_eq!(raw["is_partial"], true);
assert_eq!(raw["is_partitioned"], false);
let events = by_name("events");
assert_eq!(events["address_kind"], "literal");
assert_eq!(events["address"], "warehouse.events");
assert_eq!(events["is_root"], false);
assert_eq!(events["is_partitioned"], true);
let view = by_name("event_view");
assert_eq!(view["address_kind"], "query");
assert!(view.get("address").is_none());
}
#[test]
fn test_parse_with_persist() {
let temp = TempDir::new().unwrap();
let root = temp.path();
let test_file = create_test_file(
root,
"test.preql",
"persist orders;\nappend customers where status = 'active';",
);
let output = Command::new(get_binary_path())
.arg("parse")
.arg(&test_file)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("\"persists\""));
assert!(stdout.contains("\"target_datasource\":\"orders\""));
assert!(stdout.contains("\"target_datasource\":\"customers\""));
assert!(stdout.contains("\"mode\":\"persist\""));
assert!(stdout.contains("\"mode\":\"append\""));
}
#[test]
fn test_parse_directory() {
let temp = TempDir::new().unwrap();
let root = temp.path();
create_test_file(root, "a.preql", "import b;");
create_test_file(root, "b.preql", "// no imports");
create_test_file(root, "c.preql", "import a;");
let output = Command::new(get_binary_path())
.arg("parse")
.arg(root)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("a.preql"));
assert!(stdout.contains("b.preql"));
assert!(stdout.contains("c.preql"));
}
#[test]
fn test_resolve_simple_dependency() {
let temp = TempDir::new().unwrap();
let root = temp.path();
create_test_file(root, "a.preql", "import b;");
create_test_file(root, "b.preql", "// no imports");
let a_path = root.join("a.preql");
let output = Command::new(get_binary_path())
.arg("resolve")
.arg(&a_path)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("\"order\""));
assert!(stdout.contains("b.preql"));
assert!(stdout.contains("a.preql"));
}
#[test]
fn test_resolve_with_datasources() {
let temp = TempDir::new().unwrap();
let root = temp.path();
create_test_file(
root,
"base.preql",
r#"
datasource orders (
id,
amount
)
address db.orders;
"#,
);
create_test_file(root, "updater.preql", "persist orders;");
create_test_file(root, "consumer.preql", "import base;");
create_test_file(root, "main.preql", "import updater;\nimport consumer;");
let main_path = root.join("main.preql");
let output = Command::new(get_binary_path())
.arg("resolve")
.arg(&main_path)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("\"datasource_declarations\""));
assert!(stdout.contains("\"datasource_updaters\""));
}
#[test]
fn test_datasources_command() {
let temp = TempDir::new().unwrap();
let root = temp.path();
create_test_file(
root,
"models.preql",
r#"
datasource customers (
id: key
)
address db.customers;
datasource orders (
id: key,
customer_id
)
address db.orders;
"#,
);
create_test_file(root, "updater.preql", "persist orders;");
let output = Command::new(get_binary_path())
.arg("datasources")
.arg(root)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("\"declarations\""));
assert!(stdout.contains("\"updaters\""));
assert!(stdout.contains("customers"));
assert!(stdout.contains("orders"));
}
#[test]
fn test_pretty_format() {
let temp = TempDir::new().unwrap();
let root = temp.path();
let test_file = create_test_file(root, "test.preql", "import models.customer;");
let output = Command::new(get_binary_path())
.arg("parse")
.arg(&test_file)
.arg("--format")
.arg("pretty")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains(" ") || stdout.contains("\n"));
}
#[test]
fn test_direct_only_flag() {
let temp = TempDir::new().unwrap();
let root = temp.path();
create_test_file(root, "a.preql", "import b;");
create_test_file(root, "b.preql", "import c;");
create_test_file(root, "c.preql", "// no imports");
let a_path = root.join("a.preql");
let output = Command::new(get_binary_path())
.arg("parse")
.arg(&a_path)
.arg("--direct-only")
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(!stdout.contains("\"resolved_dependencies\"") || stdout.contains("\"resolved_dependencies\":null"));
}
#[test]
fn test_invalid_file() {
let temp = TempDir::new().unwrap();
let root = temp.path();
let nonexistent = root.join("nonexistent.preql");
let output = Command::new(get_binary_path())
.arg("parse")
.arg(&nonexistent)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(!output.status.success(), "Command should fail for nonexistent file");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("error") || stderr.contains("Error"));
}
#[test]
fn test_recursive_directory_parse() {
let temp = TempDir::new().unwrap();
let root = temp.path();
create_test_file(root, "top.preql", "import nested.a;");
create_test_file(root, "nested/a.preql", "import b;");
create_test_file(root, "nested/b.preql", "// no imports");
let output = Command::new(get_binary_path())
.arg("parse")
.arg(root)
.arg("--recursive")
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("top.preql"));
assert!(stdout.contains("a.preql"));
assert!(stdout.contains("b.preql"));
}
#[test]
fn test_order_only_flag() {
let temp = TempDir::new().unwrap();
let root = temp.path();
create_test_file(root, "a.preql", "import b;");
create_test_file(root, "b.preql", "// no imports");
let a_path = root.join("a.preql");
let output = Command::new(get_binary_path())
.arg("resolve")
.arg(&a_path)
.arg("--order-only")
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute command");
assert!(output.status.success(), "Command failed: {:?}", output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.starts_with("["));
assert!(stdout.ends_with("]\n") || stdout.ends_with("]"));
}