use recoco::base::schema::{TableKind, TableSchema, ValueType};
use recoco::base::value::{BasicValue, FieldValues, ScopeValue, Value};
use recoco::ops::factory_bases::SimpleFunctionFactoryBase;
use recoco::ops::interface::{FlowInstanceContext, SimpleFunctionFactory};
use recoco::setup::AuthRegistry;
use std::sync::Arc;
use thread_flow::functions::calls::ExtractCallsFactory;
use thread_flow::functions::imports::ExtractImportsFactory;
use thread_flow::functions::parse::ThreadParseFactory;
use thread_flow::functions::symbols::ExtractSymbolsFactory;
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!({})
}
fn create_mock_parsed_doc(symbols_count: usize, imports_count: usize, calls_count: usize) -> Value {
let symbols: Vec<ScopeValue> = (0..symbols_count)
.map(|i| {
ScopeValue(FieldValues {
fields: vec![
Value::Basic(BasicValue::Str(format!("symbol_{}", i).into())),
Value::Basic(BasicValue::Str("Function".to_string().into())),
Value::Basic(BasicValue::Str("global".to_string().into())),
],
})
})
.collect();
let imports: Vec<ScopeValue> = (0..imports_count)
.map(|i| {
ScopeValue(FieldValues {
fields: vec![
Value::Basic(BasicValue::Str(format!("import_{}", i).into())),
Value::Basic(BasicValue::Str("module/path".to_string().into())),
Value::Basic(BasicValue::Str("Named".to_string().into())),
],
})
})
.collect();
let calls: Vec<ScopeValue> = (0..calls_count)
.map(|i| {
ScopeValue(FieldValues {
fields: vec![
Value::Basic(BasicValue::Str(format!("call_{}", i).into())),
Value::Basic(BasicValue::Int64(i as i64)),
],
})
})
.collect();
let fingerprint = Value::Basic(BasicValue::Bytes(bytes::Bytes::from(vec![1, 2, 3, 4])));
Value::Struct(FieldValues {
fields: vec![
Value::LTable(symbols),
Value::LTable(imports),
Value::LTable(calls),
fingerprint,
],
})
}
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
}
#[tokio::test]
async fn test_extract_symbols_factory_name() {
let factory = ExtractSymbolsFactory;
assert_eq!(factory.name(), "extract_symbols");
}
#[tokio::test]
async fn test_extract_symbols_factory_build() {
let factory = Arc::new(ExtractSymbolsFactory);
let context = create_mock_context();
let result = factory.build(empty_spec(), vec![], context).await;
assert!(result.is_ok(), "Build should succeed");
let build_output = result.unwrap();
assert_eq!(
build_output.behavior_version,
Some(1),
"Behavior version should be 1"
);
}
#[tokio::test]
async fn test_extract_symbols_schema() {
let factory = Arc::new(ExtractSymbolsFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context)
.await
.expect("Build should succeed");
let schema = build_output.output_type;
assert!(!schema.nullable, "Schema should not be nullable");
match schema.typ {
ValueType::Table(TableSchema { kind, row }) => {
assert_eq!(kind, TableKind::LTable, "Should be LTable");
let fields = row.fields.as_ref();
assert_eq!(fields.len(), 3, "Symbol should have 3 fields");
assert_eq!(fields[0].name, "name");
assert_eq!(fields[1].name, "kind");
assert_eq!(fields[2].name, "scope");
}
_ => panic!("Expected Table type"),
}
}
#[tokio::test]
async fn test_extract_symbols_executor_creation() {
let factory = Arc::new(ExtractSymbolsFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Build should succeed");
let executor = build_output.executor.await;
assert!(executor.is_ok(), "Executor creation should succeed");
}
#[tokio::test]
async fn test_extract_symbols_executor_evaluate() {
let factory = Arc::new(ExtractSymbolsFactory);
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 should build");
let mock_doc = create_mock_parsed_doc(3, 2, 1);
let result = executor.evaluate(vec![mock_doc]).await;
assert!(result.is_ok(), "Evaluation should succeed");
match result.unwrap() {
Value::LTable(symbols) => {
assert_eq!(symbols.len(), 3, "Should have 3 symbols");
match &symbols[0].0.fields[0] {
Value::Basic(BasicValue::Str(name)) => {
assert_eq!(name.as_ref(), "symbol_0");
}
_ => panic!("Expected string for symbol name"),
}
}
_ => panic!("Expected LTable"),
}
}
#[tokio::test]
async fn test_extract_symbols_empty_input() {
let factory = Arc::new(ExtractSymbolsFactory);
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 should build");
let result = executor.evaluate(vec![]).await;
assert!(result.is_err(), "Should error on empty input");
assert!(result.unwrap_err().to_string().contains("Missing"));
}
#[tokio::test]
async fn test_extract_symbols_invalid_type() {
let factory = Arc::new(ExtractSymbolsFactory);
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 should build");
let invalid_input = Value::Basic(BasicValue::Str("not a struct".to_string().into()));
let result = executor.evaluate(vec![invalid_input]).await;
assert!(result.is_err(), "Should error on invalid type");
assert!(result.unwrap_err().to_string().contains("Expected Struct"));
}
#[tokio::test]
async fn test_extract_symbols_missing_field() {
let factory = Arc::new(ExtractSymbolsFactory);
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 should build");
let invalid_struct = Value::Struct(FieldValues { fields: vec![] });
let result = executor.evaluate(vec![invalid_struct]).await;
assert!(result.is_err(), "Should error on missing symbols field");
assert!(
result
.unwrap_err()
.to_string()
.contains("Missing symbols field"),
"Error should mention missing symbols field"
);
}
#[tokio::test]
async fn test_extract_symbols_cache_enabled() {
let factory = Arc::new(ExtractSymbolsFactory);
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 should build");
assert!(executor.enable_cache(), "Cache should be enabled");
}
#[tokio::test]
async fn test_extract_symbols_timeout() {
let factory = Arc::new(ExtractSymbolsFactory);
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 should build");
let timeout = executor.timeout();
assert!(
timeout.is_none() || timeout.is_some(),
"Timeout method should be callable"
);
}
#[tokio::test]
async fn test_extract_symbols_from_real_parse() {
let content = "fn test() {}";
let parsed = execute_parse(content, "rs", "test.rs")
.await
.expect("Parse should succeed");
let factory = Arc::new(ExtractSymbolsFactory);
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 should build");
let result = executor.evaluate(vec![parsed]).await;
assert!(result.is_ok(), "Extraction should succeed");
match result.unwrap() {
Value::LTable(symbols) => {
println!("Extracted {} symbols from real parse", symbols.len());
}
_ => panic!("Expected LTable"),
}
}
#[tokio::test]
async fn test_extract_imports_factory_name() {
let factory = ExtractImportsFactory;
assert_eq!(factory.name(), "extract_imports");
}
#[tokio::test]
async fn test_extract_imports_factory_build() {
let factory = Arc::new(ExtractImportsFactory);
let context = create_mock_context();
let result = factory.build(empty_spec(), vec![], context).await;
assert!(result.is_ok(), "Build should succeed");
let build_output = result.unwrap();
assert_eq!(
build_output.behavior_version,
Some(1),
"Behavior version should be 1"
);
}
#[tokio::test]
async fn test_extract_imports_schema() {
let factory = Arc::new(ExtractImportsFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context)
.await
.expect("Build should succeed");
let schema = build_output.output_type;
assert!(!schema.nullable, "Schema should not be nullable");
match schema.typ {
ValueType::Table(TableSchema { kind, row }) => {
assert_eq!(kind, TableKind::LTable, "Should be LTable");
let fields = row.fields.as_ref();
assert_eq!(fields.len(), 3, "Import should have 3 fields");
assert_eq!(fields[0].name, "symbol_name");
assert_eq!(fields[1].name, "source_path");
assert_eq!(fields[2].name, "kind");
}
_ => panic!("Expected Table type"),
}
}
#[tokio::test]
async fn test_extract_imports_executor_creation() {
let factory = Arc::new(ExtractImportsFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Build should succeed");
let executor = build_output.executor.await;
assert!(executor.is_ok(), "Executor creation should succeed");
}
#[tokio::test]
async fn test_extract_imports_executor_evaluate() {
let factory = Arc::new(ExtractImportsFactory);
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 should build");
let mock_doc = create_mock_parsed_doc(3, 5, 1);
let result = executor.evaluate(vec![mock_doc]).await;
assert!(result.is_ok(), "Evaluation should succeed");
match result.unwrap() {
Value::LTable(imports) => {
assert_eq!(imports.len(), 5, "Should have 5 imports");
match &imports[0].0.fields[0] {
Value::Basic(BasicValue::Str(name)) => {
assert_eq!(name.as_ref(), "import_0");
}
_ => panic!("Expected string for import name"),
}
}
_ => panic!("Expected LTable"),
}
}
#[tokio::test]
async fn test_extract_imports_empty_input() {
let factory = Arc::new(ExtractImportsFactory);
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 should build");
let result = executor.evaluate(vec![]).await;
assert!(result.is_err(), "Should error on empty input");
assert!(result.unwrap_err().to_string().contains("Missing"));
}
#[tokio::test]
async fn test_extract_imports_invalid_type() {
let factory = Arc::new(ExtractImportsFactory);
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 should build");
let invalid_input = Value::Basic(BasicValue::Int64(42));
let result = executor.evaluate(vec![invalid_input]).await;
assert!(result.is_err(), "Should error on invalid type");
assert!(result.unwrap_err().to_string().contains("Expected Struct"));
}
#[tokio::test]
async fn test_extract_imports_missing_field() {
let factory = Arc::new(ExtractImportsFactory);
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 should build");
let invalid_struct = Value::Struct(FieldValues {
fields: vec![Value::LTable(vec![])],
});
let result = executor.evaluate(vec![invalid_struct]).await;
assert!(result.is_err(), "Should error on missing field");
}
#[tokio::test]
async fn test_extract_imports_cache_enabled() {
let factory = Arc::new(ExtractImportsFactory);
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 should build");
assert!(executor.enable_cache(), "Cache should be enabled");
}
#[tokio::test]
async fn test_extract_imports_timeout() {
let factory = Arc::new(ExtractImportsFactory);
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 should build");
let timeout = executor.timeout();
assert!(
timeout.is_none() || timeout.is_some(),
"Timeout method should be callable"
);
}
#[tokio::test]
async fn test_extract_imports_from_real_parse() {
let content = "import os\nfrom sys import argv";
let parsed = execute_parse(content, "py", "test.py")
.await
.expect("Parse should succeed");
let factory = Arc::new(ExtractImportsFactory);
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 should build");
let result = executor.evaluate(vec![parsed]).await;
assert!(result.is_ok(), "Extraction should succeed");
match result.unwrap() {
Value::LTable(imports) => {
println!("Extracted {} imports from real parse", imports.len());
}
_ => panic!("Expected LTable"),
}
}
#[tokio::test]
async fn test_extract_calls_factory_name() {
let factory = ExtractCallsFactory;
assert_eq!(factory.name(), "extract_calls");
}
#[tokio::test]
async fn test_extract_calls_factory_build() {
let factory = Arc::new(ExtractCallsFactory);
let context = create_mock_context();
let result = factory.build(empty_spec(), vec![], context).await;
assert!(result.is_ok(), "Build should succeed");
let build_output = result.unwrap();
assert_eq!(
build_output.behavior_version,
Some(1),
"Behavior version should be 1"
);
}
#[tokio::test]
async fn test_extract_calls_schema() {
let factory = Arc::new(ExtractCallsFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context)
.await
.expect("Build should succeed");
let schema = build_output.output_type;
assert!(!schema.nullable, "Schema should not be nullable");
match schema.typ {
ValueType::Table(TableSchema { kind, row }) => {
assert_eq!(kind, TableKind::LTable, "Should be LTable");
let fields = row.fields.as_ref();
assert_eq!(fields.len(), 2, "Call should have 2 fields");
assert_eq!(fields[0].name, "function_name");
assert_eq!(fields[1].name, "arguments_count");
}
_ => panic!("Expected Table type"),
}
}
#[tokio::test]
async fn test_extract_calls_executor_creation() {
let factory = Arc::new(ExtractCallsFactory);
let context = create_mock_context();
let build_output = factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Build should succeed");
let executor = build_output.executor.await;
assert!(executor.is_ok(), "Executor creation should succeed");
}
#[tokio::test]
async fn test_extract_calls_executor_evaluate() {
let factory = Arc::new(ExtractCallsFactory);
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 should build");
let mock_doc = create_mock_parsed_doc(3, 2, 7);
let result = executor.evaluate(vec![mock_doc]).await;
assert!(result.is_ok(), "Evaluation should succeed");
match result.unwrap() {
Value::LTable(calls) => {
assert_eq!(calls.len(), 7, "Should have 7 calls");
match &calls[0].0.fields[0] {
Value::Basic(BasicValue::Str(name)) => {
assert_eq!(name.as_ref(), "call_0");
}
_ => panic!("Expected string for call name"),
}
match &calls[0].0.fields[1] {
Value::Basic(BasicValue::Int64(count)) => {
assert_eq!(*count, 0);
}
_ => panic!("Expected Int64 for argument count"),
}
}
_ => panic!("Expected LTable"),
}
}
#[tokio::test]
async fn test_extract_calls_empty_input() {
let factory = Arc::new(ExtractCallsFactory);
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 should build");
let result = executor.evaluate(vec![]).await;
assert!(result.is_err(), "Should error on empty input");
assert!(result.unwrap_err().to_string().contains("Missing"));
}
#[tokio::test]
async fn test_extract_calls_invalid_type() {
let factory = Arc::new(ExtractCallsFactory);
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 should build");
let invalid_input = Value::LTable(vec![]);
let result = executor.evaluate(vec![invalid_input]).await;
assert!(result.is_err(), "Should error on invalid type");
assert!(result.unwrap_err().to_string().contains("Expected Struct"));
}
#[tokio::test]
async fn test_extract_calls_missing_field() {
let factory = Arc::new(ExtractCallsFactory);
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 should build");
let invalid_struct = Value::Struct(FieldValues {
fields: vec![Value::LTable(vec![]), Value::LTable(vec![])],
});
let result = executor.evaluate(vec![invalid_struct]).await;
assert!(result.is_err(), "Should error on missing calls field");
assert!(
result
.unwrap_err()
.to_string()
.contains("Missing calls field"),
"Error should mention missing calls field"
);
}
#[tokio::test]
async fn test_extract_calls_cache_enabled() {
let factory = Arc::new(ExtractCallsFactory);
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 should build");
assert!(executor.enable_cache(), "Cache should be enabled");
}
#[tokio::test]
async fn test_extract_calls_timeout() {
let factory = Arc::new(ExtractCallsFactory);
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 should build");
let timeout = executor.timeout();
assert!(
timeout.is_none() || timeout.is_some(),
"Timeout method should be callable"
);
}
#[tokio::test]
async fn test_extract_calls_from_real_parse() {
let content = "console.log('hello');\nsetTimeout(fn, 100);";
let parsed = execute_parse(content, "ts", "test.ts")
.await
.expect("Parse should succeed");
let factory = Arc::new(ExtractCallsFactory);
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 should build");
let result = executor.evaluate(vec![parsed]).await;
assert!(result.is_ok(), "Extraction should succeed");
match result.unwrap() {
Value::LTable(calls) => {
println!("Extracted {} calls from real parse", calls.len());
}
_ => panic!("Expected LTable"),
}
}
#[tokio::test]
async fn test_all_extractors_on_same_document() {
let mock_doc = create_mock_parsed_doc(3, 2, 5);
let context = create_mock_context();
let symbols_factory = Arc::new(ExtractSymbolsFactory);
let symbols_output = symbols_factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Build should succeed");
let symbols_executor = symbols_output
.executor
.await
.expect("Executor should build");
let symbols_result = symbols_executor.evaluate(vec![mock_doc.clone()]).await;
assert!(symbols_result.is_ok(), "Symbols extraction should succeed");
if let Value::LTable(symbols) = symbols_result.unwrap() {
assert_eq!(symbols.len(), 3, "Should extract 3 symbols");
}
let imports_factory = Arc::new(ExtractImportsFactory);
let imports_output = imports_factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Build should succeed");
let imports_executor = imports_output
.executor
.await
.expect("Executor should build");
let imports_result = imports_executor.evaluate(vec![mock_doc.clone()]).await;
assert!(imports_result.is_ok(), "Imports extraction should succeed");
if let Value::LTable(imports) = imports_result.unwrap() {
assert_eq!(imports.len(), 2, "Should extract 2 imports");
}
let calls_factory = Arc::new(ExtractCallsFactory);
let calls_output = calls_factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Build should succeed");
let calls_executor = calls_output.executor.await.expect("Executor should build");
let calls_result = calls_executor.evaluate(vec![mock_doc.clone()]).await;
assert!(calls_result.is_ok(), "Calls extraction should succeed");
if let Value::LTable(calls) = calls_result.unwrap() {
assert_eq!(calls.len(), 5, "Should extract 5 calls");
}
}
#[tokio::test]
async fn test_extractors_with_empty_tables() {
let mock_doc = create_mock_parsed_doc(0, 0, 0);
let context = create_mock_context();
let symbols_factory = Arc::new(ExtractSymbolsFactory);
let symbols_output = symbols_factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Build should succeed");
let symbols_executor = symbols_output
.executor
.await
.expect("Executor should build");
let symbols_result = symbols_executor.evaluate(vec![mock_doc.clone()]).await;
if let Ok(Value::LTable(symbols)) = symbols_result {
assert_eq!(symbols.len(), 0, "Empty document should have no symbols");
}
let imports_factory = Arc::new(ExtractImportsFactory);
let imports_output = imports_factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Build should succeed");
let imports_executor = imports_output
.executor
.await
.expect("Executor should build");
let imports_result = imports_executor.evaluate(vec![mock_doc.clone()]).await;
if let Ok(Value::LTable(imports)) = imports_result {
assert_eq!(imports.len(), 0, "Empty document should have no imports");
}
let calls_factory = Arc::new(ExtractCallsFactory);
let calls_output = calls_factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Build should succeed");
let calls_executor = calls_output.executor.await.expect("Executor should build");
let calls_result = calls_executor.evaluate(vec![mock_doc.clone()]).await;
if let Ok(Value::LTable(calls)) = calls_result {
assert_eq!(calls.len(), 0, "Empty document should have no calls");
}
}
#[tokio::test]
async fn test_extractors_behavior_versions_match() {
let context = create_mock_context();
let symbols_factory = Arc::new(ExtractSymbolsFactory);
let imports_factory = Arc::new(ExtractImportsFactory);
let calls_factory = Arc::new(ExtractCallsFactory);
let symbols_output = symbols_factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Symbols build should succeed");
let imports_output = imports_factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Imports build should succeed");
let calls_output = calls_factory
.build(empty_spec(), vec![], context.clone())
.await
.expect("Calls build should succeed");
assert_eq!(
symbols_output.behavior_version, imports_output.behavior_version,
"Symbols and Imports should have same behavior version"
);
assert_eq!(
imports_output.behavior_version, calls_output.behavior_version,
"Imports and Calls should have same behavior version"
);
}