use super::*;
use crate::testing::fixtures::isolation::TestWorkingDir;
use anyhow::Result;
use serde_json::json;
use std::collections::HashMap;
use tempfile::TempDir;
#[tokio::test]
async fn test_arguments_provider() {
let provider = arguments::ArgumentsInputProvider;
let mut config = provider::InputConfig::new();
config.set("args".to_string(), json!("arg1,arg2,key=value"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 3);
assert_eq!(inputs[0].id, "arg_0");
assert_eq!(inputs[0].variables.get("arg").unwrap().to_string(), "arg1");
assert_eq!(inputs[2].id, "arg_2");
assert_eq!(
inputs[2].variables.get("arg_key").unwrap().to_string(),
"key"
);
assert_eq!(
inputs[2].variables.get("arg_value").unwrap().to_string(),
"value"
);
}
#[tokio::test]
async fn test_empty_input_source() {
let processor = processor::InputProcessor::new();
let config = config::InputConfig::default();
let inputs = processor.process_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 1);
assert_eq!(inputs[0].id, "empty");
}
#[test]
fn test_variable_value_conversions() {
use types::VariableValue;
let str_val = VariableValue::String("test".to_string());
assert_eq!(str_val.to_string(), "test");
let num_val = VariableValue::Number(42);
assert_eq!(num_val.as_number().unwrap(), 42);
assert_eq!(num_val.to_string(), "42");
let path_val = VariableValue::Path(std::path::PathBuf::from("/tmp/test"));
assert_eq!(
path_val.as_path().unwrap(),
std::path::PathBuf::from("/tmp/test")
);
}
#[test]
fn test_execution_input_variable_substitution() {
use types::{ExecutionInput, InputType, VariableValue};
let mut input = ExecutionInput::new(
"test_input".to_string(),
InputType::Arguments {
separator: Some(",".to_string()),
},
);
input.add_variable(
"name".to_string(),
VariableValue::String("test".to_string()),
);
input.add_variable("count".to_string(), VariableValue::Number(5));
let template = "Processing {name} with count {count} (ID: {input_id})";
let result = input.substitute_in_template(template).unwrap();
assert_eq!(result, "Processing test with count 5 (ID: test_input)");
}
#[test]
fn test_execution_input_helper_functions() {
use types::{ExecutionInput, InputType, VariableValue};
let mut input = ExecutionInput::new("test".to_string(), InputType::Empty);
input.add_variable(
"path".to_string(),
VariableValue::String("/path/to/file.txt".to_string()),
);
input.add_variable(
"name".to_string(),
VariableValue::String("Hello World".to_string()),
);
let template = "{path|basename} - {name|lowercase}";
let result = input.substitute_in_template(template).unwrap();
assert_eq!(result, "file.txt - hello world");
}
#[test]
fn test_legacy_adapter() {
use crate::cook::command::CookCommand;
use legacy_adapter::LegacyInputAdapter;
let cmd = CookCommand {
playbook: std::path::PathBuf::from("test.yml"),
path: None,
max_iterations: 1,
map: vec!["*.rs".to_string()],
args: vec!["arg1".to_string(), "arg2".to_string()],
fail_fast: false,
auto_accept: false,
resume: None,
verbosity: 0,
quiet: false,
dry_run: false,
params: std::collections::HashMap::new(),
};
let config = LegacyInputAdapter::from_cook_command(&cmd).unwrap();
match &config.sources[0] {
config::InputSource::Composite { sources, .. } => {
assert_eq!(sources.len(), 2);
}
_ => panic!("Expected composite source"),
}
}
#[tokio::test]
async fn test_input_processor_with_transformations() {
use config::{InputConfig, InputSource, TransformationConfig};
let processor = processor::InputProcessor::new();
let mut transformation = TransformationConfig::default();
transformation
.variable_transformations
.insert("arg".to_string(), "uppercase".to_string());
let config = InputConfig {
sources: vec![InputSource::Arguments {
value: "hello".to_string(),
separator: Some(",".to_string()),
validation: None,
}],
validation: Default::default(),
transformation,
caching: Default::default(),
};
let inputs = processor.process_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 1);
assert_eq!(inputs[0].variables.get("arg").unwrap().to_string(), "HELLO");
}
#[tokio::test]
async fn test_composite_input_source() {
use config::{InputConfig, InputSource, MergeStrategy};
let processor = processor::InputProcessor::new();
let config = InputConfig {
sources: vec![InputSource::Composite {
sources: vec![
InputSource::Arguments {
value: "a,b".to_string(),
separator: Some(",".to_string()),
validation: None,
},
InputSource::Arguments {
value: "c,d".to_string(),
separator: Some(",".to_string()),
validation: None,
},
],
merge_strategy: MergeStrategy::Sequential,
}],
validation: Default::default(),
transformation: Default::default(),
caching: Default::default(),
};
let inputs = processor.process_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 4);
}
#[tokio::test]
async fn test_interleaved_merge_strategy() {
use config::{InputConfig, InputSource, MergeStrategy};
let processor = processor::InputProcessor::new();
let config = InputConfig {
sources: vec![InputSource::Composite {
sources: vec![
InputSource::Arguments {
value: "a,b".to_string(),
separator: Some(",".to_string()),
validation: None,
},
InputSource::Arguments {
value: "1,2".to_string(),
separator: Some(",".to_string()),
validation: None,
},
],
merge_strategy: MergeStrategy::Interleaved,
}],
validation: Default::default(),
transformation: Default::default(),
caching: Default::default(),
};
let inputs = processor.process_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 4);
assert_eq!(inputs[0].variables.get("arg").unwrap().to_string(), "a");
assert_eq!(inputs[1].variables.get("arg").unwrap().to_string(), "1");
assert_eq!(inputs[2].variables.get("arg").unwrap().to_string(), "b");
assert_eq!(inputs[3].variables.get("arg").unwrap().to_string(), "2");
}
#[tokio::test]
async fn test_standard_input_format_detection() {
let provider = standard_input::StandardInputProvider;
let mut config = provider::InputConfig::new();
let orig_automation = std::env::var("PRODIGY_AUTOMATION").ok();
std::env::remove_var("PRODIGY_AUTOMATION");
config.set("format".to_string(), json!("json"));
let validation = provider.validate(&config).await.unwrap();
assert!(validation
.iter()
.all(|v| v.severity != provider::ValidationSeverity::Error));
config.set("format".to_string(), json!("invalid_format"));
let validation = provider.validate(&config).await.unwrap();
assert!(validation
.iter()
.any(|v| v.field == "format" && v.severity == provider::ValidationSeverity::Error));
if let Some(val) = orig_automation {
std::env::set_var("PRODIGY_AUTOMATION", val);
}
}
#[tokio::test]
#[serial_test::serial] async fn test_standard_input_automation_mode() {
let provider = standard_input::StandardInputProvider;
let mut config = provider::InputConfig::new();
std::env::set_var("PRODIGY_AUTOMATION", "true");
let validation = provider.validate(&config).await.unwrap();
assert!(validation
.iter()
.any(|v| v.field == "stdin" && v.severity == provider::ValidationSeverity::Error));
config.set("allow_in_automation".to_string(), json!(true));
let validation = provider.validate(&config).await.unwrap();
assert!(validation.iter().all(|v| v.field != "stdin"));
std::env::remove_var("PRODIGY_AUTOMATION");
}
#[tokio::test]
async fn test_standard_input_supported_formats() {
let provider = standard_input::StandardInputProvider;
let supported_formats = vec!["json", "yaml", "csv", "lines", "text", "auto"];
for format in supported_formats {
let mut config = provider::InputConfig::new();
config.set("format".to_string(), json!(format));
config.set("allow_in_automation".to_string(), json!(true));
let validation = provider.validate(&config).await.unwrap();
assert!(
validation
.iter()
.all(|v| v.field != "format" || v.severity != provider::ValidationSeverity::Error),
"Format {} should be supported",
format
);
}
}
#[tokio::test]
async fn test_structured_data_validation() {
let provider = structured_data::StructuredDataInputProvider;
let mut config = provider::InputConfig::new();
let validation = provider.validate(&config).await.unwrap();
assert!(validation
.iter()
.any(|v| v.field == "source" && v.severity == provider::ValidationSeverity::Error));
config.set("file_path".to_string(), json!("/tmp/data.json"));
let validation = provider.validate(&config).await.unwrap();
assert!(validation.iter().all(|v| v.field != "source"));
let mut config2 = provider::InputConfig::new();
config2.set("data".to_string(), json!("{\"key\": \"value\"}")); let validation = provider.validate(&config2).await.unwrap();
assert!(validation.iter().all(|v| v.field != "source"));
}
#[tokio::test]
async fn test_structured_data_format_validation() {
let provider = structured_data::StructuredDataInputProvider;
let supported = vec!["json", "yaml", "toml", "csv", "xml", "text", "auto"];
let unsupported = vec!["invalid", "unknown", "binary"];
for format in supported {
let mut config = provider::InputConfig::new();
config.set("data".to_string(), json!("test"));
config.set("format".to_string(), json!(format));
let validation = provider.validate(&config).await.unwrap();
assert!(
validation
.iter()
.all(|v| v.field != "format" || v.severity != provider::ValidationSeverity::Error),
"Format {} should be supported",
format
);
}
for format in unsupported {
let mut config = provider::InputConfig::new();
config.set("data".to_string(), json!("test"));
config.set("format".to_string(), json!(format));
let validation = provider.validate(&config).await.unwrap();
assert!(
validation
.iter()
.any(|v| v.field == "format" && v.severity == provider::ValidationSeverity::Error),
"Format {} should not be supported",
format
);
}
}
#[tokio::test]
async fn test_structured_data_yaml_anchors() {
let provider = structured_data::StructuredDataInputProvider;
let yaml_with_anchors = r#"
- &defaults
timeout: 30
retries: 3
type: default
- name: job1
<<: *defaults
command: echo "Job 1"
- name: job2
<<: *defaults
command: echo "Job 2"
timeout: 60 # Override default
- &template1
type: test
enabled: true
template_name: template1
- <<: *template1
instance_id: 1
"#;
let mut config = provider::InputConfig::new();
config.set("data".to_string(), json!(yaml_with_anchors));
config.set("format".to_string(), json!("yaml"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(
inputs.len(),
5,
"Should generate 5 inputs for 5 array items"
);
let job1_data = &inputs[1].variables.get("data").unwrap();
use super::types::VariableValue;
match job1_data {
VariableValue::Object(obj) => {
assert!(obj.contains_key("name"), "job1 should have name field");
assert_eq!(obj.get("name").unwrap().to_string(), "job1");
assert!(
obj.contains_key("command"),
"job1 should have command field"
);
assert!(obj.contains_key("<<"), "job1 should have merge key");
}
_ => panic!("Expected job1 data to be an object"),
}
let job2_data = &inputs[2].variables.get("data").unwrap();
match job2_data {
VariableValue::Object(obj) => {
assert!(obj.contains_key("name"), "job2 should have name field");
assert_eq!(obj.get("name").unwrap().to_string(), "job2");
assert!(
obj.contains_key("timeout"),
"job2 should have timeout field"
);
assert_eq!(obj.get("timeout").unwrap().to_string(), "60");
assert!(
obj.contains_key("command"),
"job2 should have command field"
);
}
_ => panic!("Expected job2 data to be an object"),
}
let template_data = &inputs[3].variables.get("data").unwrap();
match template_data {
VariableValue::Object(obj) => {
assert!(obj.contains_key("type"), "template should have type field");
assert_eq!(obj.get("type").unwrap().to_string(), "test");
assert!(
obj.contains_key("enabled"),
"template should have enabled field"
);
assert_eq!(obj.get("enabled").unwrap().to_string(), "true");
assert!(
obj.contains_key("template_name"),
"template should have template_name field"
);
}
_ => panic!("Expected template data to be an object"),
}
let ref_data = &inputs[4].variables.get("data").unwrap();
match ref_data {
VariableValue::Object(obj) => {
assert!(
obj.contains_key("instance_id"),
"reference should have instance_id"
);
assert_eq!(obj.get("instance_id").unwrap().to_string(), "1");
assert!(obj.contains_key("<<"), "reference should have merge key");
}
_ => panic!("Expected reference data to be an object"),
}
}
#[tokio::test]
async fn test_file_pattern_validation() {
let provider = file_pattern::FilePatternInputProvider::new();
let config = provider::InputConfig::new();
let validation = provider.validate(&config).await.unwrap();
assert!(validation
.iter()
.any(|v| v.field == "patterns" && v.severity == provider::ValidationSeverity::Warning));
let mut config_with_patterns = provider::InputConfig::new();
config_with_patterns.set("patterns".to_string(), json!(["*.rs", "**/*.txt"]));
let validation = provider.validate(&config_with_patterns).await.unwrap();
assert!(validation
.iter()
.all(|v| v.field != "patterns" || v.severity != provider::ValidationSeverity::Warning));
}
#[tokio::test]
async fn test_file_pattern_symlink_handling() {
use std::os::unix::fs as unix_fs;
let _wd = TestWorkingDir::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let file1_path = temp_path.join("file1.txt");
std::fs::write(&file1_path, "content1").unwrap();
let dir1_path = temp_path.join("dir1");
std::fs::create_dir(&dir1_path).unwrap();
std::fs::write(dir1_path.join("file2.txt"), "content2").unwrap();
let symlink_file = temp_path.join("link_to_file.txt");
unix_fs::symlink(&file1_path, &symlink_file).unwrap();
let symlink_dir = temp_path.join("link_to_dir");
unix_fs::symlink(&dir1_path, &symlink_dir).unwrap();
let provider = file_pattern::FilePatternInputProvider::new();
let mut config = provider::InputConfig::new();
std::env::set_current_dir(temp_path).unwrap();
config.set("patterns".to_string(), json!(["*.txt"]));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert!(!inputs.is_empty(), "Should find at least the original file");
config.set("patterns".to_string(), json!(["**/*.txt"]));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert!(
inputs.len() >= 2,
"Should find files in symlinked directories"
);
let broken_symlink = temp_path.join("broken_link.txt");
unix_fs::symlink("/nonexistent/file.txt", &broken_symlink).unwrap();
config.set("patterns".to_string(), json!(["*.txt"]));
let result = provider.generate_inputs(&config).await;
assert!(result.is_ok(), "Broken symlinks should not cause failures");
}
#[tokio::test]
async fn test_file_pattern_glob_expansion() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let test_files = vec![
"file1.txt",
"file2.txt",
"src/main.rs",
"src/lib.rs",
"tests/test.rs",
];
for file in &test_files {
let file_path = temp_path.join(file);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(&file_path, b"test content").unwrap();
assert!(file_path.exists(), "File should exist: {:?}", file_path);
assert!(
std::fs::metadata(&file_path).is_ok(),
"File should be accessible: {:?}",
file_path
);
}
#[cfg(target_os = "linux")]
{
use std::process::Command;
let _ = Command::new("sync").status();
}
let fs = file_pattern::FileSystem::with_base_dir(temp_path.to_path_buf());
let provider = file_pattern::FilePatternInputProvider::with_filesystem(fs);
let mut config = provider::InputConfig::new();
config.set("patterns".to_string(), json!(["*.txt"]));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 2, "Should find 2 .txt files");
config.set("patterns".to_string(), json!(["**/*.rs"]));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 3, "Should find 3 .rs files");
}
#[tokio::test]
async fn test_processor_validation_with_rules() {
use config::{CustomValidationRule, InputConfig, InputSource, ValidationConfig};
let processor = processor::InputProcessor::new();
let mut validation = ValidationConfig::default();
validation.custom_rules.push(CustomValidationRule {
name: "arg_required".to_string(),
expression: "arg.is_present".to_string(),
error_message: "Argument is required".to_string(),
});
validation.strict = true;
let config = InputConfig {
sources: vec![InputSource::Empty],
validation,
transformation: Default::default(),
caching: Default::default(),
};
let result = processor.process_inputs(&config).await;
assert!(result.is_ok(), "Should process even with validation issues");
}
#[test]
fn test_variable_value_type_conversions() {
use types::VariableValue;
let str_num = VariableValue::String("123".to_string());
assert_eq!(str_num.as_number().unwrap(), 123);
let str_invalid = VariableValue::String("not_a_number".to_string());
assert!(str_invalid.as_number().is_err());
let bool_true = VariableValue::Boolean(true);
assert_eq!(bool_true.to_string(), "true");
let array = VariableValue::Array(vec![
VariableValue::String("a".to_string()),
VariableValue::String("b".to_string()),
]);
assert!(array.to_string().contains("a"));
assert!(array.to_string().contains("b"));
}
#[test]
fn test_execution_input_metadata() {
use types::{ExecutionInput, InputType};
let mut input = ExecutionInput::new("test".to_string(), InputType::Empty);
input.metadata.source = "/path/to/file".to_string();
input.metadata.size_bytes = Some(42);
input.metadata.checksum = Some("abc123".to_string());
input
.metadata
.custom_fields
.insert("line_number".to_string(), json!(10));
assert_eq!(input.metadata.source, "/path/to/file");
assert_eq!(input.metadata.size_bytes, Some(42));
assert_eq!(input.metadata.checksum, Some("abc123".to_string()));
assert_eq!(
input.metadata.custom_fields.get("line_number"),
Some(&json!(10))
);
}
#[test]
fn test_input_transformation_helpers() {
use types::{ExecutionInput, InputType, VariableValue};
let mut input = ExecutionInput::new("test".to_string(), InputType::Empty);
input.add_variable(
"mixed_case".to_string(),
VariableValue::String("HeLLo WoRLd".to_string()),
);
input.add_variable(
"file_path".to_string(),
VariableValue::String("/path/to/some/file.txt".to_string()),
);
let template = "{mixed_case|uppercase} - {mixed_case|lowercase}";
let result = input.substitute_in_template(template).unwrap();
assert_eq!(result, "HELLO WORLD - hello world");
let path_template = "{file_path|basename} in {file_path|dirname}";
let result = input.substitute_in_template(path_template).unwrap();
assert_eq!(result, "file.txt in /path/to/some");
}
#[test]
fn test_input_type_serialization() {
use types::InputType;
let empty = InputType::Empty;
let json_str = serde_json::to_string(&empty).unwrap();
assert!(json_str.contains("empty"));
let args = InputType::Arguments {
separator: Some(",".to_string()),
};
let json_str = serde_json::to_string(&args).unwrap();
assert!(json_str.contains("arguments"));
}
#[test]
fn test_data_format_enum() {
use types::DataFormat;
let formats = vec![
DataFormat::Json,
DataFormat::Yaml,
DataFormat::Toml,
DataFormat::Csv,
DataFormat::Xml,
DataFormat::PlainText,
DataFormat::Auto,
];
for format in formats {
let json_str = serde_json::to_string(&format).unwrap();
let deserialized: DataFormat = serde_json::from_str(&json_str).unwrap();
assert_eq!(json_str, serde_json::to_string(&deserialized).unwrap());
}
}
#[test]
fn test_validation_rule_types() {
use types::ValidationRule;
let rules = vec![
ValidationRule::FileExists,
ValidationRule::Range {
min: Some(0),
max: Some(100),
},
ValidationRule::Pattern {
regex: "^[a-z]+$".to_string(),
},
ValidationRule::OneOf {
values: vec!["option1".to_string(), "option2".to_string()],
},
ValidationRule::Custom {
validator: "custom_validator".to_string(),
},
];
for rule in rules {
let json_str = serde_json::to_string(&rule).unwrap();
let deserialized: ValidationRule = serde_json::from_str(&json_str).unwrap();
assert_eq!(json_str, serde_json::to_string(&deserialized).unwrap());
}
}
#[test]
fn test_variable_definition_complete() {
use types::{ValidationRule, VariableDefinition, VariableType};
let var_def = VariableDefinition {
name: "test_var".to_string(),
var_type: VariableType::Number,
description: "Test variable".to_string(),
required: true,
default_value: Some("42".to_string()),
validation_rules: vec![ValidationRule::Range {
min: Some(0),
max: Some(100),
}],
};
assert_eq!(var_def.name, "test_var");
assert!(var_def.required);
assert_eq!(var_def.default_value, Some("42".to_string()));
assert_eq!(var_def.validation_rules.len(), 1);
}
#[tokio::test]
async fn test_file_pattern_with_exclusions() {
use crate::testing::fixtures::isolation::TestWorkingDir;
use std::fs::create_dir_all;
use tempfile::TempDir;
let wd = TestWorkingDir::new().unwrap();
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let test_files = vec![
"src/main.rs",
"src/test.rs",
"src/lib.rs",
"target/debug/main",
"target/release/main",
"tests/integration_test.rs",
".git/config",
"README.md",
];
for file in &test_files {
let file_path = temp_path.join(file);
if let Some(parent) = file_path.parent() {
create_dir_all(parent).unwrap();
}
let f = std::fs::File::create(file_path).unwrap();
f.sync_all().unwrap();
}
let provider = file_pattern::FilePatternInputProvider::new();
let mut config = provider::InputConfig::new();
wd.change_to(temp_path).unwrap();
config.set("patterns".to_string(), json!(["src/**/*.rs"]));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 3, "Should find 3 .rs files in src/");
for input in &inputs {
if let Some(file_var) = input.variables.get("file") {
assert!(!file_var.to_string().contains("target"));
}
}
}
#[test]
fn test_variable_value_complex_types() {
use std::collections::HashMap;
use types::VariableValue;
let mut obj = HashMap::new();
obj.insert(
"key1".to_string(),
VariableValue::String("value1".to_string()),
);
obj.insert("key2".to_string(), VariableValue::Number(42));
let obj_value = VariableValue::Object(obj);
let obj_str = obj_value.to_string();
assert!(
obj_str.is_empty() || obj_str.contains("{"),
"Object should either serialize to JSON or be empty on error"
);
let nested_array = VariableValue::Array(vec![
VariableValue::Number(1),
VariableValue::Array(vec![
VariableValue::String("nested".to_string()),
VariableValue::Boolean(true),
]),
VariableValue::Null,
]);
let array_str = nested_array.to_string();
assert!(array_str.contains("1"));
assert!(array_str.contains("nested"));
assert!(array_str.contains("true"));
assert!(array_str.contains("null"));
}
#[test]
fn test_variable_value_path_conversion() {
use std::path::PathBuf;
use types::VariableValue;
let path = PathBuf::from("/tmp/test");
let path_value = VariableValue::Path(path.clone());
assert_eq!(path_value.as_path().unwrap(), path);
let str_value = VariableValue::String("/tmp/test".to_string());
assert_eq!(str_value.as_path().unwrap(), PathBuf::from("/tmp/test"));
let num_value = VariableValue::Number(42);
assert!(num_value.as_path().is_err());
}
#[test]
fn test_execution_input_dependencies() {
use types::{ExecutionInput, InputType};
let mut input = ExecutionInput::new("test".to_string(), InputType::Empty);
input.dependencies.push("dep1".to_string());
input.dependencies.push("dep2".to_string());
assert_eq!(input.dependencies.len(), 2);
assert_eq!(input.dependencies[0], "dep1");
assert_eq!(input.dependencies[1], "dep2");
}
#[tokio::test]
async fn test_processor_empty_source_handling() {
use config::{InputConfig, InputSource};
let processor = processor::InputProcessor::new();
let config = InputConfig {
sources: vec![InputSource::Empty],
validation: Default::default(),
transformation: Default::default(),
caching: Default::default(),
};
let inputs = processor.process_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 1);
assert_eq!(inputs[0].id, "empty");
}
#[tokio::test]
async fn test_arguments_with_key_value_pairs() {
let provider = arguments::ArgumentsInputProvider;
let mut config = provider::InputConfig::new();
config.set("args".to_string(), json!("name=John,age=30,active=true"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 3);
assert!(inputs[0].variables.contains_key("arg_key"));
assert!(inputs[0].variables.contains_key("arg_value"));
assert_eq!(
inputs[0].variables.get("arg_key").unwrap().to_string(),
"name"
);
assert_eq!(
inputs[0].variables.get("arg_value").unwrap().to_string(),
"John"
);
}
#[tokio::test]
#[serial_test::serial] async fn test_environment_provider_single_input_mode() {
use environment::EnvironmentInputProvider;
let provider = EnvironmentInputProvider;
let mut config = provider::InputConfig::new();
std::env::set_var("TEST_VAR_1", "value1");
std::env::set_var("TEST_VAR_2", "value2");
std::env::set_var("OTHER_VAR", "other");
config.set("single_input".to_string(), json!(true));
config.set("prefix".to_string(), json!("TEST_"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 1);
let input = &inputs[0];
assert_eq!(input.id, "env_all");
let env_obj = input.variables.get("env").unwrap();
match env_obj {
types::VariableValue::Object(map) => {
assert!(map.contains_key("TEST_VAR_1"));
assert!(map.contains_key("TEST_VAR_2"));
assert!(!map.contains_key("OTHER_VAR")); }
_ => panic!("Expected env to be an object"),
}
assert!(
input
.variables
.get("env_count")
.unwrap()
.as_number()
.unwrap()
>= 2,
"Should have at least 2 TEST_ prefixed variables"
);
assert_eq!(
input.variables.get("env_prefix").unwrap().to_string(),
"TEST_"
);
std::env::remove_var("TEST_VAR_1");
std::env::remove_var("TEST_VAR_2");
std::env::remove_var("OTHER_VAR");
}
#[tokio::test]
#[serial_test::serial] async fn test_environment_provider_multiple_inputs_mode() {
use environment::EnvironmentInputProvider;
let provider = EnvironmentInputProvider;
let mut config = provider::InputConfig::new();
std::env::set_var("APP_PORT", "8080");
std::env::set_var("APP_DEBUG", "true");
std::env::set_var("APP_PATH", "/app/bin");
config.set("prefix".to_string(), json!("APP_"));
config.set("single_input".to_string(), json!(false));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 3);
let port_input = inputs
.iter()
.find(|i| i.variables.get("env_key").unwrap().to_string() == "APP_PORT")
.expect("Should find APP_PORT input");
assert_eq!(
port_input.variables.get("env_value").unwrap().to_string(),
"8080"
);
assert_eq!(
port_input
.variables
.get("env_value_number")
.unwrap()
.as_number()
.unwrap(),
8080
);
assert_eq!(
port_input
.variables
.get("env_key_stripped")
.unwrap()
.to_string(),
"PORT"
);
let debug_input = inputs
.iter()
.find(|i| i.variables.get("env_key").unwrap().to_string() == "APP_DEBUG")
.expect("Should find APP_DEBUG input");
assert!(debug_input.variables.contains_key("env_value_bool"));
let debug_bool = debug_input.variables.get("env_value_bool").unwrap();
match debug_bool {
types::VariableValue::Boolean(b) => assert!(*b),
_ => panic!("Expected boolean value"),
}
let path_input = inputs
.iter()
.find(|i| i.variables.get("env_key").unwrap().to_string() == "APP_PATH")
.expect("Should find APP_PATH input");
assert!(path_input.variables.contains_key("env_value_path"));
std::env::remove_var("APP_PORT");
std::env::remove_var("APP_DEBUG");
std::env::remove_var("APP_PATH");
}
#[tokio::test]
#[serial_test::serial] async fn test_environment_provider_filter_empty() {
use environment::EnvironmentInputProvider;
let provider = EnvironmentInputProvider;
let mut config = provider::InputConfig::new();
std::env::set_var("TEST_FULL", "has_value");
std::env::set_var("TEST_EMPTY", "");
config.set("prefix".to_string(), json!("TEST_"));
config.set("filter_empty".to_string(), json!(true));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert!(!inputs.is_empty(), "Should filter out empty values");
let has_test_full = inputs
.iter()
.any(|i| i.variables.get("env_key").unwrap().to_string() == "TEST_FULL");
assert!(has_test_full, "Should include TEST_FULL");
config.set("filter_empty".to_string(), json!(false));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert!(inputs.len() >= 2, "Should include empty values");
let has_test_empty = inputs
.iter()
.any(|i| i.variables.get("env_key").unwrap().to_string() == "TEST_EMPTY");
assert!(
has_test_empty,
"Should include TEST_EMPTY when filter_empty=false"
);
std::env::remove_var("TEST_FULL");
std::env::remove_var("TEST_EMPTY");
}
#[tokio::test]
async fn test_environment_provider_supports() {
use environment::EnvironmentInputProvider;
let provider = EnvironmentInputProvider;
let mut config = provider::InputConfig::new();
config.set("input_type".to_string(), json!("environment"));
assert!(provider.supports(&config));
let mut config = provider::InputConfig::new();
config.set("env_prefix".to_string(), json!("TEST_"));
assert!(provider.supports(&config));
let mut config = provider::InputConfig::new();
config.set("use_environment".to_string(), json!(true));
assert!(provider.supports(&config));
let config = provider::InputConfig::new();
assert!(!provider.supports(&config));
}
#[tokio::test]
async fn test_generated_sequence() {
use generated::GeneratedInputProvider;
let provider = GeneratedInputProvider;
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("sequence"));
config.set("start".to_string(), json!("5"));
config.set("end".to_string(), json!("10"));
config.set("step".to_string(), json!("2"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 3);
assert_eq!(
inputs[0]
.variables
.get("value")
.unwrap()
.as_number()
.unwrap(),
5
);
assert_eq!(
inputs[1]
.variables
.get("value")
.unwrap()
.as_number()
.unwrap(),
7
);
assert_eq!(
inputs[2]
.variables
.get("value")
.unwrap()
.as_number()
.unwrap(),
9
);
}
#[tokio::test]
async fn test_generated_random() {
use generated::GeneratedInputProvider;
let provider = GeneratedInputProvider;
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("random"));
config.set("count".to_string(), json!("5"));
config.set("min".to_string(), json!("0"));
config.set("max".to_string(), json!("100"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 5);
for input in &inputs {
let value = input
.variables
.get("random_value")
.unwrap()
.as_number()
.unwrap();
assert!((0..=100).contains(&value));
}
}
#[tokio::test]
async fn test_generated_uuid() {
use generated::GeneratedInputProvider;
let provider = GeneratedInputProvider;
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("uuid"));
config.set("count".to_string(), json!("3"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 3);
for input in &inputs {
let uuid_str = input.variables.get("uuid").unwrap().to_string();
assert!(uuid::Uuid::parse_str(&uuid_str).is_ok());
}
}
#[tokio::test]
async fn test_generated_timestamp() {
use generated::GeneratedInputProvider;
let provider = GeneratedInputProvider;
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("timestamp"));
config.set("count".to_string(), json!("3"));
config.set("interval".to_string(), json!("60"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 3);
let ts0 = inputs[0]
.variables
.get("timestamp")
.unwrap()
.as_number()
.unwrap();
let ts1 = inputs[1]
.variables
.get("timestamp")
.unwrap()
.as_number()
.unwrap();
assert_eq!(ts1 - ts0, 60);
for input in &inputs {
let datetime_str = input.variables.get("datetime").unwrap().to_string();
assert!(chrono::DateTime::parse_from_rfc3339(&datetime_str).is_ok());
}
}
#[tokio::test]
async fn test_generated_range() {
use generated::GeneratedInputProvider;
let provider = GeneratedInputProvider;
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("range"));
config.set("start".to_string(), json!("0.0"));
config.set("end".to_string(), json!("1.0"));
config.set("steps".to_string(), json!("5"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 5);
let values: Vec<f64> = inputs
.iter()
.map(|i| match i.variables.get("value").unwrap() {
types::VariableValue::Float(f) => *f,
_ => panic!("Expected float value"),
})
.collect();
assert!((values[0] - 0.0).abs() < 0.001);
assert!((values[2] - 0.5).abs() < 0.001);
assert!((values[4] - 1.0).abs() < 0.001);
}
#[tokio::test]
async fn test_generated_grid() {
use generated::GeneratedInputProvider;
let provider = GeneratedInputProvider;
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("grid"));
config.set("width".to_string(), json!("3"));
config.set("height".to_string(), json!("2"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 6);
assert_eq!(
inputs[0].variables.get("x").unwrap().as_number().unwrap(),
0
);
assert_eq!(
inputs[0].variables.get("y").unwrap().as_number().unwrap(),
0
);
assert_eq!(
inputs[5].variables.get("x").unwrap().as_number().unwrap(),
2
);
assert_eq!(
inputs[5].variables.get("y").unwrap().as_number().unwrap(),
1
);
}
#[tokio::test]
async fn test_generated_fibonacci() {
use generated::GeneratedInputProvider;
let provider = GeneratedInputProvider;
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("fibonacci"));
config.set("count".to_string(), json!("8"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 8);
let expected = [0, 1, 1, 2, 3, 5, 8, 13];
for (i, expected_val) in expected.iter().enumerate() {
assert_eq!(
inputs[i]
.variables
.get("value")
.unwrap()
.as_number()
.unwrap(),
*expected_val
);
}
}
#[tokio::test]
async fn test_generated_factorial() {
use generated::GeneratedInputProvider;
let provider = GeneratedInputProvider;
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("factorial"));
config.set("count".to_string(), json!("6"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 6);
let expected = [1, 1, 2, 6, 24, 120]; for (i, expected_val) in expected.iter().enumerate() {
assert_eq!(
inputs[i]
.variables
.get("value")
.unwrap()
.as_number()
.unwrap(),
*expected_val
);
}
}
#[tokio::test]
async fn test_generated_prime() {
use generated::GeneratedInputProvider;
let provider = GeneratedInputProvider;
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("prime"));
config.set("count".to_string(), json!("10"));
let inputs = provider.generate_inputs(&config).await.unwrap();
assert_eq!(inputs.len(), 10);
let expected = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
for (i, expected_val) in expected.iter().enumerate() {
assert_eq!(
inputs[i]
.variables
.get("value")
.unwrap()
.as_number()
.unwrap(),
*expected_val
);
}
}
#[tokio::test]
async fn test_generated_validation() {
use generated::GeneratedInputProvider;
let provider = GeneratedInputProvider;
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("invalid_generator"));
let validation = provider.validate(&config).await.unwrap();
assert!(validation
.iter()
.any(|v| { v.field == "generator" && v.severity == provider::ValidationSeverity::Error }));
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("range"));
let validation = provider.validate(&config).await.unwrap();
assert!(validation
.iter()
.any(|v| { v.field == "config" && v.severity == provider::ValidationSeverity::Warning }));
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("random"));
let validation = provider.validate(&config).await.unwrap();
assert!(validation
.iter()
.any(|v| { v.field == "count" && v.severity == provider::ValidationSeverity::Warning }));
}
#[tokio::test]
async fn test_generated_available_variables() {
use generated::GeneratedInputProvider;
let provider = GeneratedInputProvider;
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("sequence"));
let vars = provider.available_variables(&config).unwrap();
assert!(vars.iter().any(|v| v.name == "value"));
assert!(vars.iter().any(|v| v.name == "index"));
assert!(vars.iter().any(|v| v.name == "generated_type"));
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("uuid"));
let vars = provider.available_variables(&config).unwrap();
assert!(vars.iter().any(|v| v.name == "uuid"));
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("timestamp"));
let vars = provider.available_variables(&config).unwrap();
assert!(vars.iter().any(|v| v.name == "timestamp"));
assert!(vars.iter().any(|v| v.name == "datetime"));
let mut config = provider::InputConfig::new();
config.set("generator".to_string(), json!("grid"));
let vars = provider.available_variables(&config).unwrap();
assert!(vars.iter().any(|v| v.name == "x"));
assert!(vars.iter().any(|v| v.name == "y"));
}
#[tokio::test]
async fn test_malformed_json_input() {
use structured_data::StructuredDataInputProvider;
let provider = StructuredDataInputProvider;
let mut config = provider::InputConfig::new();
let malformed_jsons = [
"{invalid json}", "{\"key\": }", "{\"key\": undefined}", "[1, 2, 3,]", "{\"key\": 'single quotes'}", "{'key': 42}", "{\"key\": NaN}", "{\"a\":1 \"b\":2}", ];
for (i, malformed) in malformed_jsons.iter().enumerate() {
config.set("data".to_string(), json!(malformed));
config.set("format".to_string(), json!("json"));
let result = provider.generate_inputs(&config).await;
assert!(
result.is_err(),
"Malformed JSON #{} should fail to parse: {}",
i,
malformed
);
}
}
#[tokio::test]
async fn test_malformed_yaml_input() {
use structured_data::StructuredDataInputProvider;
let provider = StructuredDataInputProvider;
let mut config = provider::InputConfig::new();
let malformed_yamls = [
"key:\n - item1\n - item2", "key: value\n invalid", "- item\nkey: value", "key: [unclosed", "key: {unclosed", "!!python/object:__main__.Test", ];
for (i, malformed) in malformed_yamls.iter().enumerate() {
config.set("data".to_string(), json!(malformed));
config.set("format".to_string(), json!("yaml"));
let result = provider.generate_inputs(&config).await;
if result.is_err() {
continue;
}
assert!(
result.is_ok(),
"YAML parsing #{} should at least not panic: {}",
i,
malformed
);
}
}
#[tokio::test]
async fn test_malformed_toml_input() {
use structured_data::StructuredDataInputProvider;
let provider = StructuredDataInputProvider;
let mut config = provider::InputConfig::new();
let malformed_tomls = [
"[section\nkey = value", "key = 'unclosed string", "key = value\nkey = other", "123key = value", "[.invalid]", "key = 01", "array = [1, 'mixed', types]", ];
for (i, malformed) in malformed_tomls.iter().enumerate() {
config.set("data".to_string(), json!(malformed));
config.set("format".to_string(), json!("toml"));
let result = provider.generate_inputs(&config).await;
assert!(
result.is_err(),
"Malformed TOML #{} should fail to parse: {}",
i,
malformed
);
}
}
#[tokio::test]
async fn test_malformed_csv_input() {
use structured_data::StructuredDataInputProvider;
let provider = StructuredDataInputProvider;
let mut config = provider::InputConfig::new();
let malformed_csvs = [
"col1,col2\n\"unclosed quote,value2", "col1,col2\nval1", "col1,col2\nval1,val2,val3", "col1,col2\n\n\n", "", ];
for (i, malformed) in malformed_csvs.iter().enumerate() {
config.set("data".to_string(), json!(malformed));
config.set("format".to_string(), json!("csv"));
let result = provider.generate_inputs(&config).await;
if !malformed.is_empty() {
assert!(
result.is_ok() || result.is_err(),
"CSV parsing #{} should be handled: {}",
i,
malformed
);
}
}
}
#[tokio::test]
async fn test_malformed_xml_input() {
use structured_data::StructuredDataInputProvider;
let provider = StructuredDataInputProvider;
let mut config = provider::InputConfig::new();
let malformed_xmls = [
"<root>unclosed", "<root><child></root>", "<<invalid>>", "<root attr=>content</root>", "<root>&invalid;</root>", "<?xml version='1.0'?><root></root", "<root><child attr='unclosed></root>", ];
for (i, malformed) in malformed_xmls.iter().enumerate() {
config.set("data".to_string(), json!(malformed));
config.set("format".to_string(), json!("xml"));
let result = provider.generate_inputs(&config).await;
if result.is_err() {
continue;
}
if let Ok(inputs) = result {
assert!(
!inputs.is_empty() || malformed.is_empty(),
"XML parsing #{} produced unexpected result: {}",
i,
malformed
);
}
}
}
#[tokio::test]
async fn test_format_auto_detection_edge_cases() {
use structured_data::StructuredDataInputProvider;
let provider = StructuredDataInputProvider;
let ambiguous_cases = vec![
("key: value", "yaml"), ("123", "text"), ("true", "text"), ("[1,2,3]", "json"), ("null", "text"), ];
for (content, _expected_format) in ambiguous_cases {
let mut config = provider::InputConfig::new();
config.set("data".to_string(), json!(content));
config.set("format".to_string(), json!("auto"));
let result = provider.generate_inputs(&config).await;
assert!(
result.is_ok() || result.is_err(),
"Auto-detection should handle: {}",
content
);
}
}
pub struct MockStdinProvider {
data: String,
format: String,
}
impl MockStdinProvider {
pub fn new(data: impl Into<String>, format: impl Into<String>) -> Self {
Self {
data: data.into(),
format: format.into(),
}
}
pub async fn generate_test_inputs(&self) -> Result<Vec<ExecutionInput>> {
use types::DataFormat;
let mut inputs = Vec::new();
match self.format.as_str() {
"json" => {
let parsed: serde_json::Value = serde_json::from_str(&self.data)?;
inputs.extend(self.process_json(parsed)?);
}
"lines" => {
for (i, line) in self.data.lines().enumerate() {
let mut input = ExecutionInput::new(
format!("line_{}", i),
InputType::StandardInput {
format: DataFormat::PlainText,
},
);
input.add_variable("line".to_string(), VariableValue::String(line.to_string()));
input.add_variable(
"line_number".to_string(),
VariableValue::Number(i as i64 + 1),
);
inputs.push(input);
}
}
"csv" => {
let mut reader = csv::Reader::from_reader(self.data.as_bytes());
let headers = reader.headers()?.clone();
for (i, result) in reader.records().enumerate() {
let record = result?;
let mut input = ExecutionInput::new(
format!("csv_row_{}", i),
InputType::StandardInput {
format: DataFormat::Csv,
},
);
let mut row_data = HashMap::new();
for (j, field) in record.iter().enumerate() {
if let Some(header) = headers.get(j) {
row_data.insert(
header.to_string(),
VariableValue::String(field.to_string()),
);
}
}
input.add_variable("row".to_string(), VariableValue::Object(row_data));
input.add_variable("row_index".to_string(), VariableValue::Number(i as i64));
inputs.push(input);
}
}
_ => {
let mut input = ExecutionInput::new(
"stdin_text".to_string(),
InputType::StandardInput {
format: DataFormat::PlainText,
},
);
input.add_variable("text".to_string(), VariableValue::String(self.data.clone()));
input.add_variable(
"length".to_string(),
VariableValue::Number(self.data.len() as i64),
);
inputs.push(input);
}
}
Ok(inputs)
}
fn process_json(&self, value: serde_json::Value) -> Result<Vec<ExecutionInput>> {
use types::DataFormat;
let mut inputs = Vec::new();
match value {
serde_json::Value::Array(arr) => {
for (i, item) in arr.into_iter().enumerate() {
let mut input = ExecutionInput::new(
format!("item_{}", i),
InputType::StandardInput {
format: DataFormat::Json,
},
);
input.add_variable("item".to_string(), self.json_to_variable_value(item));
input.add_variable("index".to_string(), VariableValue::Number(i as i64));
inputs.push(input);
}
}
_ => {
let mut input = ExecutionInput::new(
"stdin_json".to_string(),
InputType::StandardInput {
format: DataFormat::Json,
},
);
input.add_variable("data".to_string(), self.json_to_variable_value(value));
inputs.push(input);
}
}
Ok(inputs)
}
#[allow(clippy::only_used_in_recursion)]
fn json_to_variable_value(&self, value: serde_json::Value) -> VariableValue {
match value {
serde_json::Value::Null => VariableValue::Null,
serde_json::Value::Bool(b) => VariableValue::Boolean(b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
VariableValue::Number(i)
} else if let Some(f) = n.as_f64() {
VariableValue::Float(f)
} else {
VariableValue::String(n.to_string())
}
}
serde_json::Value::String(s) => VariableValue::String(s),
serde_json::Value::Array(arr) => VariableValue::Array(
arr.into_iter()
.map(|v| self.json_to_variable_value(v))
.collect(),
),
serde_json::Value::Object(obj) => {
let map = obj
.into_iter()
.map(|(k, v)| (k, self.json_to_variable_value(v)))
.collect();
VariableValue::Object(map)
}
}
}
}
#[tokio::test]
async fn test_mock_stdin_json() {
let mock = MockStdinProvider::new(
r#"[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]"#,
"json",
);
let inputs = mock.generate_test_inputs().await.unwrap();
assert_eq!(inputs.len(), 2);
let first = &inputs[0];
assert_eq!(first.id, "item_0");
let item = first.variables.get("item").unwrap();
match item {
VariableValue::Object(map) => {
assert_eq!(map.get("name").unwrap().to_string(), "Alice");
match map.get("age").unwrap() {
VariableValue::Number(n) => assert_eq!(*n, 30),
_ => panic!("Expected number for age"),
}
}
_ => panic!("Expected object for item"),
}
}
#[tokio::test]
async fn test_mock_stdin_lines() {
let mock = MockStdinProvider::new("line one\nline two\nline three", "lines");
let inputs = mock.generate_test_inputs().await.unwrap();
assert_eq!(inputs.len(), 3);
assert_eq!(
inputs[0].variables.get("line").unwrap().to_string(),
"line one"
);
assert_eq!(
inputs[0]
.variables
.get("line_number")
.unwrap()
.as_number()
.unwrap(),
1
);
assert_eq!(
inputs[2].variables.get("line").unwrap().to_string(),
"line three"
);
assert_eq!(
inputs[2]
.variables
.get("line_number")
.unwrap()
.as_number()
.unwrap(),
3
);
}
#[tokio::test]
async fn test_mock_stdin_csv() {
let mock = MockStdinProvider::new("name,age,city\nAlice,30,NYC\nBob,25,LA", "csv");
let inputs = mock.generate_test_inputs().await.unwrap();
assert_eq!(inputs.len(), 2);
let first_row = &inputs[0];
let row = first_row.variables.get("row").unwrap();
match row {
VariableValue::Object(map) => {
assert_eq!(map.get("name").unwrap().to_string(), "Alice");
assert_eq!(map.get("age").unwrap().to_string(), "30");
assert_eq!(map.get("city").unwrap().to_string(), "NYC");
}
_ => panic!("Expected object for row"),
}
}
#[tokio::test]
async fn test_mock_stdin_text() {
let test_text = "This is a test\nwith multiple lines\nof text.";
let mock = MockStdinProvider::new(test_text, "text");
let inputs = mock.generate_test_inputs().await.unwrap();
assert_eq!(inputs.len(), 1);
let input = &inputs[0];
assert_eq!(input.variables.get("text").unwrap().to_string(), test_text);
assert_eq!(
input.variables.get("length").unwrap().as_number().unwrap(),
test_text.len() as i64
);
}
#[tokio::test]
async fn test_mock_stdin_empty_input() {
let mock = MockStdinProvider::new("[]", "json");
let inputs = mock.generate_test_inputs().await.unwrap();
assert_eq!(inputs.len(), 0);
let mock = MockStdinProvider::new("", "lines");
let inputs = mock.generate_test_inputs().await.unwrap();
assert_eq!(inputs.len(), 0);
let mock = MockStdinProvider::new("", "text");
let inputs = mock.generate_test_inputs().await.unwrap();
assert_eq!(inputs.len(), 1);
assert_eq!(inputs[0].variables.get("text").unwrap().to_string(), "");
assert_eq!(
inputs[0]
.variables
.get("length")
.unwrap()
.as_number()
.unwrap(),
0
);
}
#[tokio::test]
async fn test_mock_stdin_complex_json() {
let complex_json = r#"{
"users": [
{"id": 1, "name": "Alice", "tags": ["admin", "user"]},
{"id": 2, "name": "Bob", "tags": ["user"]}
],
"metadata": {
"version": "1.0",
"count": 2
}
}"#;
let mock = MockStdinProvider::new(complex_json, "json");
let inputs = mock.generate_test_inputs().await.unwrap();
assert_eq!(inputs.len(), 1);
let data = inputs[0].variables.get("data").unwrap();
match data {
VariableValue::Object(map) => {
assert!(map.contains_key("users"));
if let Some(VariableValue::Object(metadata)) = map.get("metadata") {
assert_eq!(metadata.get("version").unwrap().to_string(), "1.0");
match metadata.get("count").unwrap() {
VariableValue::Number(n) => assert_eq!(*n, 2),
_ => panic!("Expected number for count"),
}
} else {
panic!("Expected metadata object");
}
}
_ => panic!("Expected object for data"),
}
}