use recoco::base::schema::ValueType;
use recoco::base::value::{BasicValue, FieldValues, ScopeValue, Value};
use recoco::ops::interface::{FlowInstanceContext, SimpleFunctionFactory};
use recoco::setup::AuthRegistry;
use std::sync::Arc;
use thread_flow::functions::parse::ThreadParseFactory;
fn read_test_file(filename: &str) -> String {
let path = format!("tests/test_data/{}", filename);
std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("Failed to read test file {}: {}", path, e))
}
fn create_mock_context() -> Arc<FlowInstanceContext> {
Arc::new(FlowInstanceContext {
flow_instance_name: "test_flow".to_string(),
auth_registry: Arc::new(AuthRegistry::new()),
})
}
fn empty_spec() -> serde_json::Value {
serde_json::json!({})
}
async fn execute_parse(
content: &str,
language: &str,
file_path: &str,
) -> Result<Value, recoco::prelude::Error> {
let factory = Arc::new(ThreadParseFactory);
let context = create_mock_context();
let build_output = factory.build(empty_spec(), vec![], context).await?;
let executor = build_output.executor.await?;
let inputs = vec![
Value::Basic(BasicValue::Str(content.to_string().into())),
Value::Basic(BasicValue::Str(language.to_string().into())),
Value::Basic(BasicValue::Str(file_path.to_string().into())),
];
executor.evaluate(inputs).await
}
fn extract_symbols(output: &Value) -> Vec<ScopeValue> {
match output {
Value::Struct(FieldValues { fields }) => match &fields[0] {
Value::LTable(symbols) => symbols.clone(),
_ => panic!("Expected LTable for symbols"),
},
_ => panic!("Expected Struct output"),
}
}
fn extract_imports(output: &Value) -> Vec<ScopeValue> {
match output {
Value::Struct(FieldValues { fields }) => match &fields[1] {
Value::LTable(imports) => imports.clone(),
_ => panic!("Expected LTable for imports"),
},
_ => panic!("Expected Struct output"),
}
}
fn extract_calls(output: &Value) -> Vec<ScopeValue> {
match output {
Value::Struct(FieldValues { fields }) => match &fields[2] {
Value::LTable(calls) => calls.clone(),
_ => panic!("Expected LTable for calls"),
},
_ => panic!("Expected Struct output"),
}
}
#[tokio::test]
async fn test_factory_build_succeeds() {
let factory = Arc::new(ThreadParseFactory);
let context = create_mock_context();
let result = factory.build(empty_spec(), vec![], context).await;
assert!(result.is_ok(), "Factory build should succeed");
}
#[tokio::test]
async fn test_executor_creation() {
let factory = Arc::new(ThreadParseFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context)
.await
.expect("Build should succeed");
let executor_result = build_output.executor.await;
assert!(executor_result.is_ok(), "Executor creation should succeed");
}
#[tokio::test]
async fn test_schema_output_type() {
let factory = Arc::new(ThreadParseFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context)
.await
.expect("Build should succeed");
let output_type = build_output.output_type;
assert!(!output_type.nullable, "Output should not be nullable");
match output_type.typ {
ValueType::Struct(schema) => {
assert_eq!(
schema.fields.len(),
4,
"Should have 4 fields in schema (symbols, imports, calls, content_fingerprint)"
);
let field_names: Vec<&str> = schema.fields.iter().map(|f| f.name.as_str()).collect();
assert!(
field_names.contains(&"symbols"),
"Should have symbols field"
);
assert!(
field_names.contains(&"imports"),
"Should have imports field"
);
assert!(field_names.contains(&"calls"), "Should have calls field");
assert!(
field_names.contains(&"content_fingerprint"),
"Should have content_fingerprint field"
);
}
_ => panic!("Output type should be Struct"),
}
}
#[tokio::test]
async fn test_behavior_version() {
let factory = Arc::new(ThreadParseFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context)
.await
.expect("Build should succeed");
assert_eq!(
build_output.behavior_version,
Some(1),
"Behavior version should be 1"
);
}
#[tokio::test]
async fn test_executor_cache_enabled() {
let factory = Arc::new(ThreadParseFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context)
.await
.expect("Build should succeed");
let executor = build_output
.executor
.await
.expect("Executor build should succeed");
assert!(
executor.enable_cache(),
"ThreadParseExecutor should enable cache"
);
}
#[tokio::test]
async fn test_executor_timeout() {
let factory = Arc::new(ThreadParseFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context)
.await
.expect("Build should succeed");
let executor = build_output
.executor
.await
.expect("Executor build should succeed");
let timeout = executor.timeout();
assert!(
timeout.is_none() || timeout.is_some(),
"Timeout method should be callable"
);
}
#[tokio::test]
async fn test_unsupported_language() {
let content = "print('hello')";
let result = execute_parse(content, "unsupported_lang", "test.unsupported").await;
assert!(result.is_err(), "Should error on unsupported language");
if let Err(e) = result {
let error_msg = e.to_string();
assert!(
error_msg.contains("Unsupported language") || error_msg.contains("client"),
"Error message should indicate unsupported language, got: {}",
error_msg
);
}
}
#[tokio::test]
async fn test_missing_content() {
let factory = Arc::new(ThreadParseFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context)
.await
.expect("Build should succeed");
let executor = build_output
.executor
.await
.expect("Executor build should succeed");
let result = executor.evaluate(vec![]).await;
assert!(result.is_err(), "Should error on missing content");
if let Err(e) = result {
assert!(
e.to_string().contains("Missing content"),
"Error should mention missing content"
);
}
}
#[tokio::test]
async fn test_invalid_input_type() {
let factory = Arc::new(ThreadParseFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context)
.await
.expect("Build should succeed");
let executor = build_output
.executor
.await
.expect("Executor build should succeed");
let inputs = vec![
Value::Basic(BasicValue::Int64(42)),
Value::Basic(BasicValue::Str("rs".to_string().into())),
];
let result = executor.evaluate(inputs).await;
assert!(result.is_err(), "Should error on invalid input type");
}
#[tokio::test]
async fn test_missing_language() {
let factory = Arc::new(ThreadParseFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context)
.await
.expect("Build should succeed");
let executor = build_output
.executor
.await
.expect("Executor build should succeed");
let inputs = vec![Value::Basic(BasicValue::Str("content".to_string().into()))];
let result = executor.evaluate(inputs).await;
assert!(result.is_err(), "Should error on missing language");
}
#[tokio::test]
async fn test_output_structure_basic() {
let minimal_rust = "// Simple comment\n";
let result = execute_parse(minimal_rust, "rs", "minimal.rs")
.await
.expect("Parse should succeed for minimal code");
match &result {
Value::Struct(FieldValues { fields }) => {
assert_eq!(fields.len(), 4, "Should have 4 fields");
assert!(
matches!(&fields[0], Value::LTable(_)),
"Field 0 should be LTable (symbols)"
);
assert!(
matches!(&fields[1], Value::LTable(_)),
"Field 1 should be LTable (imports)"
);
assert!(
matches!(&fields[2], Value::LTable(_)),
"Field 2 should be LTable (calls)"
);
assert!(
matches!(&fields[3], Value::Basic(_)),
"Field 3 should be Basic (content_fingerprint)"
);
}
_ => panic!("Expected Struct output"),
}
}
#[tokio::test]
async fn test_empty_tables_structure() {
let empty_content = "";
let result = execute_parse(empty_content, "rs", "empty.rs")
.await
.expect("Empty file should parse");
let symbols = extract_symbols(&result);
let imports = extract_imports(&result);
let calls = extract_calls(&result);
assert!(
symbols.is_empty() || symbols.len() <= 1,
"Empty file should have minimal symbols"
);
assert!(imports.is_empty(), "Empty file should have no imports");
assert!(calls.is_empty(), "Empty file should have no calls");
}
#[tokio::test]
async fn test_parse_rust_code() {
let content = read_test_file("sample.rs");
let result = execute_parse(&content, "rs", "sample.rs").await;
assert!(result.is_ok(), "Parse should succeed for valid Rust code");
let output = result.unwrap();
let symbols = extract_symbols(&output);
if !symbols.is_empty() {
let symbol_names: Vec<String> = symbols
.iter()
.filter_map(|s| match &s.0.fields[0] {
Value::Basic(BasicValue::Str(name)) => Some(name.to_string()),
_ => None,
})
.collect();
let found_function = symbol_names.iter().any(|name| {
name.contains("main")
|| name.contains("process_user")
|| name.contains("calculate_total")
});
assert!(
found_function,
"Should find at least one function (main, process_user, or calculate_total). Found: {:?}",
symbol_names
);
} else {
println!("Warning: No symbols extracted - pattern matching may need improvement");
}
}
#[tokio::test]
async fn test_parse_python_code() {
let content = read_test_file("sample.py");
let result = execute_parse(&content, "py", "sample.py").await;
assert!(result.is_ok(), "Parse should succeed for valid Python code");
let output = result.unwrap();
let symbols = extract_symbols(&output);
println!("Python symbols extracted: {}", symbols.len());
}
#[tokio::test]
async fn test_parse_typescript_code() {
let content = read_test_file("sample.ts");
let result = execute_parse(&content, "ts", "sample.ts").await;
assert!(
result.is_ok(),
"Parse should succeed for valid TypeScript code"
);
let output = result.unwrap();
let symbols = extract_symbols(&output);
println!("TypeScript symbols extracted: {}", symbols.len());
}
#[tokio::test]
async fn test_parse_go_code() {
let content = read_test_file("sample.go");
let result = execute_parse(&content, "go", "sample.go").await;
assert!(result.is_ok(), "Parse should succeed for valid Go code");
let output = result.unwrap();
let symbols = extract_symbols(&output);
println!("Go symbols extracted: {}", symbols.len());
}
#[tokio::test]
async fn test_multi_language_support() {
let languages = vec![
("rs", "sample.rs"),
("py", "sample.py"),
("ts", "sample.ts"),
("go", "sample.go"),
];
for (lang, file) in languages {
let content = read_test_file(file);
let result = execute_parse(&content, lang, file).await;
assert!(
result.is_ok(),
"Parse should succeed for {} ({})",
lang,
file
);
let output = result.unwrap();
let symbols = extract_symbols(&output);
println!("{} symbols extracted: {}", lang, symbols.len());
}
}
#[tokio::test]
#[ignore = "Performance test - run manually"]
async fn test_parse_performance() {
let content = read_test_file("large.rs");
let start = std::time::Instant::now();
let result = execute_parse(&content, "rs", "large.rs").await;
let duration = start.elapsed();
assert!(result.is_ok(), "Large file should parse successfully");
assert!(
duration.as_millis() < 1000,
"Parsing should complete within 1 second (took {}ms)",
duration.as_millis()
);
}
#[tokio::test]
async fn test_minimal_parse_performance() {
let minimal_code = "// Comment\nconst X: i32 = 42;\n";
let start = std::time::Instant::now();
let result = execute_parse(minimal_code, "rs", "perf.rs").await;
let duration = start.elapsed();
assert!(result.is_ok(), "Minimal parse should succeed");
assert!(
duration.as_millis() < 100,
"Minimal parse should be fast (took {}ms)",
duration.as_millis()
);
}