mod common;
use std::fs;
use std::path::Path;
use xls_rs::{Converter, WorkflowConfig, WorkflowExecutor, WorkflowStep};
fn unique_path(prefix: &str, ext: &str) -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_micros();
format!("test_wf_{prefix}_{timestamp}.{ext}")
}
fn setup_csv(path: &str, content: &str) {
fs::write(path, content).unwrap();
}
#[test]
fn test_workflow_single_step_read_write() {
let executor = WorkflowExecutor::new();
let input = unique_path("input", "csv");
let output = unique_path("output", "csv");
setup_csv(&input, "A,B\n1,2\n3,4\n");
let config = WorkflowConfig {
name: "read_write_test".to_string(),
description: None,
steps: vec![WorkflowStep {
operation: "read".to_string(),
input: Some(input.clone()),
output: Some(output.clone()),
args: None,
}],
};
executor.execute_config(&config).unwrap();
assert!(Path::new(&output).exists());
let content = fs::read_to_string(&output).unwrap();
assert!(content.contains("A"));
assert!(content.contains("1"));
fs::remove_file(&input).ok();
fs::remove_file(&output).ok();
}
#[test]
fn test_workflow_filter_step() {
let executor = WorkflowExecutor::new();
let input = unique_path("filter_in", "csv");
let output = unique_path("filter_out", "csv");
setup_csv(&input, "Name,Score\na,1\nb,2\nc,1\n");
let config = WorkflowConfig {
name: "filter_test".to_string(),
description: None,
steps: vec![WorkflowStep {
operation: "filter".to_string(),
input: Some(input.clone()),
output: Some(output.clone()),
args: Some(serde_json::json!({
"column": 1,
"where": "contains"
})),
}],
};
executor.execute_config(&config).unwrap();
let converter = Converter::new();
let data = converter.read_any_data(&output, None).unwrap();
assert!(data.len() >= 3);
fs::remove_file(&input).ok();
fs::remove_file(&output).ok();
}
#[test]
fn test_workflow_sort_step() {
let executor = WorkflowExecutor::new();
let input = unique_path("sort_in", "csv");
let output = unique_path("sort_out", "csv");
setup_csv(&input, "A,B\n3,z\n1,x\n2,y\n");
let config = WorkflowConfig {
name: "sort_test".to_string(),
description: None,
steps: vec![WorkflowStep {
operation: "sort".to_string(),
input: Some(input.clone()),
output: Some(output.clone()),
args: Some(serde_json::json!({
"column": 0,
"ascending": true
})),
}],
};
executor.execute_config(&config).unwrap();
let converter = Converter::new();
let data = converter.read_any_data(&output, None).unwrap();
assert_eq!(data.len(), 4); assert_eq!(data[3][0], "A");
fs::remove_file(&input).ok();
fs::remove_file(&output).ok();
}
#[test]
fn test_workflow_multi_step_pipeline() {
let executor = WorkflowExecutor::new();
let input = unique_path("multi_in", "csv");
let temp = unique_path("multi_temp", "csv");
let output = unique_path("multi_out", "csv");
setup_csv(&input, "A,B\n3,z\n1,x\n2,y\n3,z\n");
let config = WorkflowConfig {
name: "multi_step_test".to_string(),
description: None,
steps: vec![
WorkflowStep {
operation: "sort".to_string(),
input: Some(input.clone()),
output: Some(temp.clone()),
args: Some(serde_json::json!({
"column": 0,
"ascending": true
})),
},
WorkflowStep {
operation: "transform".to_string(),
input: Some(temp.clone()),
output: Some(output.clone()),
args: Some(serde_json::json!({
"operation": "dedupe"
})),
},
],
};
executor.execute_config(&config).unwrap();
let converter = Converter::new();
let data = converter.read_any_data(&output, None).unwrap();
assert_eq!(data.len(), 4);
fs::remove_file(&input).ok();
fs::remove_file(&temp).ok();
fs::remove_file(&output).ok();
}
#[test]
fn test_workflow_transform_replace() {
let executor = WorkflowExecutor::new();
let input = unique_path("replace_in", "csv");
let output = unique_path("replace_out", "csv");
setup_csv(&input, "A,B\nold,1\nnew,2\nold,3\n");
let config = WorkflowConfig {
name: "replace_test".to_string(),
description: None,
steps: vec![WorkflowStep {
operation: "transform".to_string(),
input: Some(input.clone()),
output: Some(output.clone()),
args: Some(serde_json::json!({
"operation": "replace",
"find": "old",
"replace": "replaced",
"column": 0
})),
}],
};
executor.execute_config(&config).unwrap();
let converter = Converter::new();
let data = converter.read_any_data(&output, None).unwrap();
assert!(data.iter().any(|row| row[0] == "replaced"));
assert!(!data.iter().any(|row| row[0] == "old"));
fs::remove_file(&input).ok();
fs::remove_file(&output).ok();
}
#[test]
fn test_workflow_transform_transpose() {
let executor = WorkflowExecutor::new();
let input = unique_path("transpose_in", "csv");
let output = unique_path("transpose_out", "csv");
setup_csv(&input, "A,B,C\n1,2,3\n4,5,6\n");
let config = WorkflowConfig {
name: "transpose_test".to_string(),
description: None,
steps: vec![WorkflowStep {
operation: "transform".to_string(),
input: Some(input.clone()),
output: Some(output.clone()),
args: Some(serde_json::json!({
"operation": "transpose"
})),
}],
};
executor.execute_config(&config).unwrap();
let converter = Converter::new();
let data = converter.read_any_data(&output, None).unwrap();
assert_eq!(data.len(), 3);
fs::remove_file(&input).ok();
fs::remove_file(&output).ok();
}
#[test]
fn test_workflow_select_columns() {
let executor = WorkflowExecutor::new();
let input = unique_path("select_in", "csv");
let output = unique_path("select_out", "csv");
setup_csv(&input, "A,B,C,D\n1,2,3,4\n5,6,7,8\n");
let config = WorkflowConfig {
name: "select_test".to_string(),
description: None,
steps: vec![WorkflowStep {
operation: "select".to_string(),
input: Some(input.clone()),
output: Some(output.clone()),
args: Some(serde_json::json!({
"columns": ["A", "C"]
})),
}],
};
executor.execute_config(&config).unwrap();
let converter = Converter::new();
let data = converter.read_any_data(&output, None).unwrap();
assert_eq!(data[0].len(), 2);
assert_eq!(data[0][0], "A");
assert_eq!(data[0][1], "C");
fs::remove_file(&input).ok();
fs::remove_file(&output).ok();
}
#[test]
fn test_workflow_describe_step() {
let executor = WorkflowExecutor::new();
let input = unique_path("describe_in", "csv");
setup_csv(&input, "A,B\n1,10\n2,20\n3,30\n");
let config = WorkflowConfig {
name: "describe_test".to_string(),
description: None,
steps: vec![WorkflowStep {
operation: "describe".to_string(),
input: Some(input.clone()),
output: None,
args: None,
}],
};
executor.execute_config(&config).unwrap();
fs::remove_file(&input).ok();
}