Files
qiming/qiming-run_code_rmcp/src/tests/js_tests.rs
2026-06-01 13:43:09 +08:00

741 lines
25 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#[cfg(test)]
mod js_tests {
use anyhow::Result;
use log::info;
use serde_json::json;
use crate::model::{CodeExecutor, LanguageScript};
use crate::tests::test_utils::setup;
#[tokio::test]
async fn test_js_basic_execution() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_js.js")?;
info!("读取测试脚本: test_js.js");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, None).await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
Ok(())
}
#[tokio::test]
async fn test_js_with_params() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_js_params.js")?;
info!("读取测试脚本: test_js_params.js");
// 准备参数
let params = json!({
"a": 10,
"b": 20,
"name": "测试用户"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("sum"), "结果应包含 sum 字段");
assert!(json_str.contains("greeting"), "结果应包含 greeting 字段");
assert!(json_str.contains("message"), "结果应包含 message 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_object_result() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/rfunction_test1.js")?;
info!("读取测试脚本: rfunction_test1.js");
// 准备参数
let params = json!({
"a": 1,
"b": 2
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("3"), "message 字段应为 3");
}
Ok(())
}
#[tokio::test]
async fn test_js_with_timeout() -> Result<()> {
// 初始化日志
setup();
// 创建一个会超时的JavaScript脚本
let code = r#"
// 创建一个长时间运行的脚本
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function handler(input) {
console.log("开始执行耗时操作");
// 这个操作会运行10秒钟
for (let i = 0; i < 10; i++) {
console.log(`已经执行了 ${i+1} 秒`);
await sleep(1000); // 等待1秒
}
console.log("操作完成");
return { result: "完成" };
}
"#;
info!("创建了一个会运行10秒的测试脚本");
// 准备参数
let params = json!({
"test": "超时测试"
});
info!("准备测试参数: {params:?}");
// 设置2秒超时并执行脚本
info!("开始执行JavaScript脚本设置2秒超时...");
let start_time = std::time::Instant::now();
let result =
CodeExecutor::execute_with_params(code, LanguageScript::Js, Some(params), Some(2))
.await;
let elapsed = start_time.elapsed();
// 检查是否在2-3秒内超时给一点缓冲时间
info!("脚本执行耗时: {elapsed:?}");
assert!(elapsed.as_secs() >= 2, "脚本应该至少运行2秒");
assert!(elapsed.as_secs() < 4, "脚本应该在4秒内超时");
// 检查是否返回超时错误
match result {
Ok(exec_result) => {
if let Some(error) = exec_result.error {
info!("正确捕获到超时错误: {error}");
assert!(
error.contains("timed out") ||
error.contains("TimedOut") ||
error.contains("executor await error"),
"错误信息应该包含超时相关信息"
);
} else {
panic!("应该捕获到超时错误,但脚本执行成功了");
}
}
Err(e) => {
// 使用特殊格式化获取完整错误链
let full_error = format!("{e:#}");
info!("捕获到错误: {full_error}");
assert!(
full_error.contains("timed out"),
"完整错误链中应该包含'timed out'超时信息"
);
}
}
Ok(())
}
#[tokio::test]
async fn test_js_cow_say_hello() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/cow_say_hello.js")?;
info!("读取测试脚本: cow_say_hello.js");
// 准备参数
let params = json!({
"a": 5,
"b": 7
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params(&code, LanguageScript::Js, Some(params), None)
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
// 检查计算结果是否正确 (5 + 7 = 12)
if let Some(obj) = result_val.as_object() {
if let Some(message) = obj.get("message") {
assert_eq!(message, 12, "message 字段的值应为 12");
}
}
}
Ok(())
}
#[tokio::test]
async fn test_js_import_deno_std() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_deno_std_example.js")?;
info!("读取测试脚本: import_deno_std_example.js");
// 准备参数
let params = json!({
"path": "test.txt"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("pathInfo"), "结果应包含 pathInfo 字段");
assert!(
json_str.contains("fileContent"),
"结果应包含 fileContent 字段"
);
}
Ok(())
}
#[tokio::test]
async fn test_js_import_jsr() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_jsr_example.js")?;
info!("读取测试脚本: import_jsr_example.js");
// 准备参数
let params = json!({
"a": 10,
"b": 5,
"expected": 15
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("results"), "结果应包含 results 字段");
assert!(json_str.contains("summary"), "结果应包含 summary 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_local_module() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_local_module_example.js")?;
info!("读取测试脚本: import_local_module_example.js");
// 准备参数
let params = json!({
"order": {
"items": [
{ "name": "测试产品", "price": 200, "quantity": 2 }
],
"taxRate": 0.05
}
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("order"), "结果应包含 order 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_import_axios() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_axios_example.js")?;
info!("读取测试脚本: import_axios_example.js");
// 准备参数
let params = json!({
"url": "https://jsonplaceholder.typicode.com/todos/1"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("data"), "结果应包含 data 字段");
assert!(json_str.contains("timestamp"), "结果应包含 timestamp 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_import_esm() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_esm_module_example.js")?;
info!("读取测试脚本: import_esm_module_example.js");
// 准备参数
let params = json!({
"data": "测试数据",
"salt": "test-salt-123"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("result"), "结果应包含 result 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_import_lodash() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_lodash_example.js")?;
info!("读取测试脚本: import_lodash_example.js");
// 准备参数
let params = json!({
"data": [1, 2, 3, 4, 5, 6, 7, 8, 9],
"chunkSize": 3
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(
json_str.contains("originalData"),
"结果应包含 originalData 字段"
);
assert!(
json_str.contains("processedData"),
"结果应包含 processedData 字段"
);
assert!(json_str.contains("timestamp"), "结果应包含 timestamp 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_main_function() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/rfunction_js_test3.js")?;
info!("读取测试脚本: rfunction_js_test3.js");
// 准备参数
let params = json!({
"testKey": "测试值"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("key"), "结果应包含 key 字段");
assert!(json_str.contains("value"), "key 字段的值应为 value");
// 检查JSON结构
if let Some(obj) = result_val.as_object() {
if let Some(key_value) = obj.get("key") {
assert_eq!(key_value, "value", "key 字段的值应为 value");
}
}
}
Ok(())
}
#[tokio::test]
async fn test_js_large_parameters() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_js_large_params.js")?;
info!("读取测试脚本: test_js_large_params.js");
// 生成大于2MB的文本参数
fn generate_large_text(size_in_mb: usize) -> String {
let chunk = "This is a test chunk of text designed to generate large parameters for testing the Argument list too long fix. ".repeat(100);
let iterations = (size_in_mb * 1024 * 1024) / chunk.len();
let mut result = String::new();
for i in 0..iterations {
result.push_str(&chunk);
result.push_str(&format!("Iteration {}\n", i));
}
result
}
let large_text = generate_large_text(3); // 3MB
info!("生成了3MB大小的文本参数实际大小: {:.2} MB", large_text.len() as f64 / 1024.0 / 1024.0);
// 准备参数
let params = json!({
"largeText": large_text
});
info!("准备测试参数JSON大小: {:.2} MB", serde_json::to_string(&params).unwrap().len() as f64 / 1024.0 / 1024.0);
// 执行脚本
info!("开始执行JavaScript脚本测试大参数处理...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误,这表明大参数处理成功");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值包含成功处理大参数的信息
if let Some(result_val) = result.result {
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("success"), "结果应包含 success 字段");
assert!(json_str.contains("true"), "success 应为 true");
assert!(json_str.contains("Successfully processed large text parameter"), "结果应包含成功消息");
assert!(json_str.contains("sizeMB"), "结果应包含 sizeMB 字段");
// 验证返回值是对象格式
if let Some(obj) = result_val.as_object() {
if let Some(success) = obj.get("success") {
assert_eq!(success, true, "success 字段的值应为 true");
}
if let Some(text_size) = obj.get("textSize") {
assert!(text_size.is_number(), "textSize 应为数字");
let size = text_size.as_u64().unwrap();
assert!(size > 2 * 1024 * 1024, "文本大小应大于2MB");
}
}
}
info!("大参数测试通过!临时文件解决方案有效。");
Ok(())
}
}