1use crate::{Error, Result};
7use rquickjs::{Context, Ctx, Function, Object, Runtime};
8use tracing::debug;
9
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use std::collections::HashMap;
13use std::sync::Arc;
14use tokio::sync::Semaphore;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct ScriptResult {
19 pub return_value: Option<Value>,
21 pub modified_variables: HashMap<String, Value>,
23 pub errors: Vec<String>,
25 pub execution_time_ms: u64,
27}
28
29#[derive(Debug, Clone)]
31pub struct ScriptContext {
32 pub request: Option<crate::request_chaining::ChainRequest>,
34 pub response: Option<crate::request_chaining::ChainResponse>,
36 pub chain_context: HashMap<String, Value>,
38 pub variables: HashMap<String, Value>,
40 pub env_vars: HashMap<String, String>,
42}
43
44pub struct ScriptEngine {
46 semaphore: Arc<Semaphore>,
47}
48
49impl std::fmt::Debug for ScriptEngine {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.debug_struct("ScriptEngine")
52 .field("semaphore", &format!("Semaphore({})", self.semaphore.available_permits()))
53 .finish()
54 }
55}
56
57impl ScriptEngine {
62 pub fn new() -> Self {
64 let semaphore = Arc::new(Semaphore::new(10)); Self { semaphore }
67 }
68
69 pub async fn execute_script(
71 &self,
72 script: &str,
73 script_context: &ScriptContext,
74 timeout_ms: u64,
75 ) -> Result<ScriptResult> {
76 let _permit =
77 self.semaphore.acquire().await.map_err(|e| {
78 Error::generic(format!("Failed to acquire execution permit: {}", e))
79 })?;
80
81 let script = script.to_string();
82 let script_context = script_context.clone();
83
84 let start_time = std::time::Instant::now();
85
86 let script_clone = script.clone();
89 let script_context_clone = script_context.clone();
90
91 let timeout_duration = std::time::Duration::from_millis(timeout_ms);
92 let timeout_result = tokio::time::timeout(
93 timeout_duration,
94 tokio::task::spawn_blocking(move || {
95 execute_script_in_runtime(&script_clone, &script_context_clone)
96 }),
97 )
98 .await;
99
100 let execution_time_ms = start_time.elapsed().as_millis() as u64;
101
102 match timeout_result {
103 Ok(join_result) => match join_result {
104 Ok(Ok(mut script_result)) => {
105 script_result.execution_time_ms = execution_time_ms;
106 Ok(script_result)
107 }
108 Ok(Err(e)) => Err(e),
109 Err(e) => Err(Error::generic(format!("Script execution task failed: {}", e))),
110 },
111 Err(_) => {
112 Err(Error::generic(format!("Script execution timed out after {}ms", timeout_ms)))
113 }
114 }
115 }
116}
117
118fn execute_script_in_runtime(script: &str, script_context: &ScriptContext) -> Result<ScriptResult> {
121 let runtime = Runtime::new()
123 .map_err(|e| Error::generic(format!("Failed to create JavaScript runtime: {:?}", e)))?;
124
125 let context = Context::full(&runtime)
126 .map_err(|e| Error::generic(format!("Failed to create JavaScript context: {:?}", e)))?;
127
128 context.with(|ctx| {
129 let global = ctx.globals();
131 let mockforge_obj = Object::new(ctx.clone())
132 .map_err(|e| Error::generic(format!("Failed to create mockforge object: {:?}", e)))?;
133
134 expose_script_context(ctx.clone(), &mockforge_obj, script_context)
136 .map_err(|e| Error::generic(format!("Failed to expose script context: {:?}", e)))?;
137
138 global.set("mockforge", mockforge_obj).map_err(|e| {
140 Error::generic(format!("Failed to set global mockforge object: {:?}", e))
141 })?;
142
143 add_global_functions(ctx.clone(), &global, script_context)
145 .map_err(|e| Error::generic(format!("Failed to add global functions: {:?}", e)))?;
146
147 let result = ctx
149 .eval(script)
150 .map_err(|e| Error::generic(format!("Script execution failed: {:?}", e)))?;
151
152 let modified_vars = extract_modified_variables(&ctx, script_context).map_err(|e| {
154 Error::generic(format!("Failed to extract modified variables: {:?}", e))
155 })?;
156
157 let return_value = extract_return_value(&ctx, &result)
158 .map_err(|e| Error::generic(format!("Failed to extract return value: {:?}", e)))?;
159
160 Ok(ScriptResult {
161 return_value,
162 modified_variables: modified_vars,
163 errors: vec![], execution_time_ms: 0, })
166 })
167}
168
169fn extract_return_value<'js>(
171 _ctx: &Ctx<'js>,
172 result: &rquickjs::Value<'js>,
173) -> Result<Option<Value>> {
174 match result.type_of() {
175 rquickjs::Type::String => {
176 if let Some(string_val) = result.as_string() {
178 Ok(Some(Value::String(string_val.to_string()?)))
179 } else {
180 Ok(None)
181 }
182 }
183 rquickjs::Type::Float => {
184 if let Some(num) = result.as_number() {
185 if let Some(f64_val) = serde_json::Number::from_f64(num) {
188 Ok(Some(Value::Number(f64_val)))
189 } else {
190 Ok(Some(Value::Number(serde_json::Number::from(result.as_int().unwrap_or(0)))))
192 }
193 } else {
194 Ok(Some(Value::Number(serde_json::Number::from(result.as_int().unwrap_or(0)))))
196 }
197 }
198 rquickjs::Type::Bool => {
199 if let Some(bool_val) = result.as_bool() {
201 Ok(Some(Value::Bool(bool_val)))
202 } else {
203 Ok(None)
204 }
205 }
206 rquickjs::Type::Object => {
207 if let Some(obj) = result.as_object() {
209 if let Some(string_val) = obj.as_string() {
210 let json_str = string_val.to_string()?;
211 Ok(Some(Value::String(json_str)))
212 } else {
213 Ok(None)
214 }
215 } else {
216 Ok(None)
217 }
218 }
219 _ => Ok(None),
220 }
221}
222
223fn extract_modified_variables<'js>(
225 ctx: &Ctx<'js>,
226 original_context: &ScriptContext,
227) -> Result<HashMap<String, Value>> {
228 let mut modified = HashMap::new();
229
230 let global = ctx.globals();
232 let mockforge_obj: Object = global.get("mockforge")?;
233
234 let vars_obj: Object = mockforge_obj.get("variables")?;
236
237 let keys = vars_obj.keys::<String>();
239
240 for key_result in keys {
241 let key = key_result?;
242 let js_value: rquickjs::Value = vars_obj.get(&key)?;
243
244 if let Some(value) = js_value_to_json_value(&js_value) {
246 let original_value = original_context.variables.get(&key);
248 if original_value != Some(&value) {
249 modified.insert(key, value);
250 }
251 }
252 }
253
254 Ok(modified)
255}
256
257fn js_value_to_json_value(js_value: &rquickjs::Value) -> Option<Value> {
259 match js_value.type_of() {
260 rquickjs::Type::String => {
261 js_value.as_string().and_then(|s| s.to_string().ok()).map(Value::String)
262 }
263 rquickjs::Type::Int => {
264 js_value.as_int().map(|i| Value::Number(serde_json::Number::from(i)))
265 }
266 rquickjs::Type::Float => {
267 js_value.as_number().and_then(serde_json::Number::from_f64).map(Value::Number)
268 }
269 rquickjs::Type::Bool => js_value.as_bool().map(Value::Bool),
270 rquickjs::Type::Object | rquickjs::Type::Array => {
271 if let Some(obj) = js_value.as_object() {
273 if let Some(str_val) = obj.as_string() {
274 str_val
275 .to_string()
276 .ok()
277 .and_then(|json_str| serde_json::from_str(&json_str).ok())
278 } else {
279 None
281 }
282 } else {
283 None
284 }
285 }
286 _ => None, }
288}
289
290impl Default for ScriptEngine {
291 fn default() -> Self {
292 Self::new()
293 }
294}
295
296fn expose_script_context<'js>(
298 ctx: Ctx<'js>,
299 mockforge_obj: &Object<'js>,
300 script_context: &ScriptContext,
301) -> Result<()> {
302 if let Some(request) = &script_context.request {
304 let request_obj = Object::new(ctx.clone())?;
305 request_obj.set("id", &request.id)?;
306 request_obj.set("method", &request.method)?;
307 request_obj.set("url", &request.url)?;
308
309 let headers_obj = Object::new(ctx.clone())?;
311 for (key, value) in &request.headers {
312 headers_obj.set(key.as_str(), value.as_str())?;
313 }
314 request_obj.set("headers", headers_obj)?;
315
316 if let Some(body) = &request.body {
318 let body_json = serde_json::to_string(body)
319 .map_err(|e| Error::generic(format!("Failed to serialize request body: {}", e)))?;
320 request_obj.set("body", body_json)?;
321 }
322
323 mockforge_obj.set("request", request_obj)?;
324 }
325
326 if let Some(response) = &script_context.response {
328 let response_obj = Object::new(ctx.clone())?;
329 response_obj.set("status", response.status as i32)?;
330 response_obj.set("duration_ms", response.duration_ms as i32)?;
331
332 let headers_obj = Object::new(ctx.clone())?;
334 for (key, value) in &response.headers {
335 headers_obj.set(key.as_str(), value.as_str())?;
336 }
337 response_obj.set("headers", headers_obj)?;
338
339 if let Some(body) = &response.body {
341 let body_json = serde_json::to_string(body)
342 .map_err(|e| Error::generic(format!("Failed to serialize response body: {}", e)))?;
343 response_obj.set("body", body_json)?;
344 }
345
346 mockforge_obj.set("response", response_obj)?;
347 }
348
349 let chain_obj = Object::new(ctx.clone())?;
351 for (key, value) in &script_context.chain_context {
352 match value {
353 Value::String(s) => chain_obj.set(key.as_str(), s.as_str())?,
354 Value::Number(n) => {
355 if let Some(i) = n.as_i64() {
356 chain_obj.set(key.as_str(), i as i32)?;
357 } else if let Some(f) = n.as_f64() {
358 chain_obj.set(key.as_str(), f)?;
359 }
360 }
361 Value::Bool(b) => chain_obj.set(key.as_str(), *b)?,
362 Value::Object(obj) => {
363 let json_str = serde_json::to_string(&obj)
364 .map_err(|e| Error::generic(format!("Failed to serialize object: {}", e)))?;
365 chain_obj.set(key.as_str(), json_str)?;
366 }
367 Value::Array(arr) => {
368 let json_str = serde_json::to_string(&arr)
369 .map_err(|e| Error::generic(format!("Failed to serialize array: {}", e)))?;
370 chain_obj.set(key.as_str(), json_str)?;
371 }
372 _ => {} }
374 }
375 mockforge_obj.set("chain", chain_obj)?;
376
377 let vars_obj = Object::new(ctx.clone())?;
379 for (key, value) in &script_context.variables {
380 match value {
381 Value::String(s) => vars_obj.set(key.as_str(), s.as_str())?,
382 Value::Number(n) => {
383 if let Some(i) = n.as_i64() {
384 vars_obj.set(key.as_str(), i as i32)?;
385 } else if let Some(f) = n.as_f64() {
386 vars_obj.set(key.as_str(), f)?;
387 }
388 }
389 Value::Bool(b) => vars_obj.set(key.as_str(), *b)?,
390 _ => {
391 let json_str = serde_json::to_string(&value).map_err(|e| {
392 Error::generic(format!("Failed to serialize variable {}: {}", key, e))
393 })?;
394 vars_obj.set(key.as_str(), json_str)?;
395 }
396 }
397 }
398 mockforge_obj.set("variables", vars_obj)?;
399
400 let env_obj = Object::new(ctx.clone())?;
402 for (key, value) in &script_context.env_vars {
403 env_obj.set(key.as_str(), value.as_str())?;
404 }
405 mockforge_obj.set("env", env_obj)?;
406
407 Ok(())
408}
409
410fn add_global_functions<'js>(
412 ctx: Ctx<'js>,
413 global: &Object<'js>,
414 _script_context: &ScriptContext,
415) -> Result<()> {
416 let console_obj = Object::new(ctx.clone())?;
418 let log_func = Function::new(ctx.clone(), || {
419 debug!("Script log called");
420 })?;
421 console_obj.set("log", log_func)?;
422 global.set("console", console_obj)?;
423
424 let log_func = Function::new(ctx.clone(), |msg: String| {
426 debug!("Script log: {}", msg);
427 })?;
428 global.set("log", log_func)?;
429
430 let stringify_func = Function::new(ctx.clone(), |value: rquickjs::Value| {
431 if let Some(obj) = value.as_object() {
432 if let Some(str_val) = obj.as_string() {
433 str_val.to_string().unwrap_or_else(|_| "undefined".to_string())
434 } else {
435 "object".to_string()
436 }
437 } else if value.is_string() {
438 value
439 .as_string()
440 .unwrap()
441 .to_string()
442 .unwrap_or_else(|_| "undefined".to_string())
443 } else {
444 format!("{:?}", value)
445 }
446 })?;
447 global.set("stringify", stringify_func)?;
448
449 let crypto_obj = Object::new(ctx.clone())?;
451
452 let base64_encode_func = Function::new(ctx.clone(), |input: String| -> String {
453 use base64::{engine::general_purpose, Engine as _};
454 general_purpose::STANDARD.encode(input)
455 })?;
456 crypto_obj.set("base64Encode", base64_encode_func)?;
457
458 let base64_decode_func = Function::new(ctx.clone(), |input: String| -> String {
459 use base64::{engine::general_purpose, Engine as _};
460 general_purpose::STANDARD
461 .decode(input)
462 .map(|bytes| String::from_utf8_lossy(&bytes).to_string())
463 .unwrap_or_else(|_| "".to_string())
464 })?;
465 crypto_obj.set("base64Decode", base64_decode_func)?;
466
467 let sha256_func = Function::new(ctx.clone(), |input: String| -> String {
468 use sha2::{Digest, Sha256};
469 let mut hasher = Sha256::new();
470 hasher.update(input);
471 hex::encode(hasher.finalize())
472 })?;
473 crypto_obj.set("sha256", sha256_func)?;
474
475 let random_bytes_func = Function::new(ctx.clone(), |length: usize| -> String {
476 use rand::Rng;
477 let mut rng = rand::thread_rng();
478 let bytes: Vec<u8> = (0..length).map(|_| rng.random()).collect();
479 hex::encode(bytes)
480 })?;
481 crypto_obj.set("randomBytes", random_bytes_func)?;
482
483 global.set("crypto", crypto_obj)?;
484
485 let date_obj = Object::new(ctx.clone())?;
487
488 let now_func = Function::new(ctx.clone(), || -> String { chrono::Utc::now().to_rfc3339() })?;
489 date_obj.set("now", now_func)?;
490
491 let format_func = Function::new(ctx.clone(), |timestamp: String, format: String| -> String {
492 if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(×tamp) {
493 dt.format(&format).to_string()
494 } else {
495 "".to_string()
496 }
497 })?;
498 date_obj.set("format", format_func)?;
499
500 let parse_func = Function::new(ctx.clone(), |date_str: String, format: String| -> String {
501 if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(&date_str, &format) {
502 dt.and_utc().to_rfc3339()
503 } else {
504 "".to_string()
505 }
506 })?;
507 date_obj.set("parse", parse_func)?;
508
509 let add_days_func = Function::new(ctx.clone(), |timestamp: String, days: i64| -> String {
510 if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(×tamp) {
511 (dt + chrono::Duration::days(days)).to_rfc3339()
512 } else {
513 "".to_string()
514 }
515 })?;
516 date_obj.set("addDays", add_days_func)?;
517
518 global.set("date", date_obj)?;
519
520 let validate_obj = Object::new(ctx.clone())?;
522
523 let email_func = Function::new(ctx.clone(), |email: String| -> bool {
524 regex::Regex::new(r"^[^@]+@[^@]+\.[^@]+$")
528 .map(|re| re.is_match(&email))
529 .unwrap_or_else(|_| {
530 email.contains('@') && email.contains('.') && email.len() > 5
532 })
533 })?;
534 validate_obj.set("email", email_func)?;
535
536 let url_func = Function::new(ctx.clone(), |url_str: String| -> bool {
537 url::Url::parse(&url_str).is_ok()
538 })?;
539 validate_obj.set("url", url_func)?;
540
541 let regex_func = Function::new(ctx.clone(), |pattern: String, text: String| -> bool {
542 regex::Regex::new(&pattern).map(|re| re.is_match(&text)).unwrap_or(false)
543 })?;
544 validate_obj.set("regex", regex_func)?;
545
546 global.set("validate", validate_obj)?;
547
548 let json_obj = Object::new(ctx.clone())?;
550
551 let json_parse_func = Function::new(ctx.clone(), |json_str: String| -> String {
552 match serde_json::from_str::<Value>(&json_str) {
553 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
554 Err(_) => "null".to_string(),
555 }
556 })?;
557 json_obj.set("parse", json_parse_func)?;
558
559 let json_stringify_func = Function::new(ctx.clone(), |value: String| -> String {
560 value
562 })?;
563 json_obj.set("stringify", json_stringify_func)?;
564
565 let json_validate_func = Function::new(ctx.clone(), |json_str: String| -> bool {
566 serde_json::from_str::<Value>(&json_str).is_ok()
567 })?;
568 json_obj.set("validate", json_validate_func)?;
569
570 global.set("JSON", json_obj)?;
571
572 let http_obj = Object::new(ctx.clone())?;
574
575 let http_get_func = Function::new(ctx.clone(), |url: String| -> String {
576 tokio::task::block_in_place(|| {
581 reqwest::blocking::get(&url)
582 .and_then(|resp| resp.text())
583 .unwrap_or_else(|_| "".to_string())
584 })
585 })?;
586 http_obj.set("get", http_get_func)?;
587
588 let http_post_func = Function::new(ctx.clone(), |url: String, body: String| -> String {
589 tokio::task::block_in_place(|| {
594 reqwest::blocking::Client::new()
595 .post(&url)
596 .body(body)
597 .send()
598 .and_then(|resp| resp.text())
599 .unwrap_or_else(|_| "".to_string())
600 })
601 })?;
602 http_obj.set("post", http_post_func)?;
603
604 let url_encode_func = Function::new(ctx.clone(), |input: String| -> String {
605 urlencoding::encode(&input).to_string()
606 })?;
607 http_obj.set("urlEncode", url_encode_func)?;
608
609 let url_decode_func = Function::new(ctx.clone(), |input: String| -> String {
610 urlencoding::decode(&input)
611 .unwrap_or(std::borrow::Cow::Borrowed(""))
612 .to_string()
613 })?;
614 http_obj.set("urlDecode", url_decode_func)?;
615
616 global.set("http", http_obj)?;
617
618 Ok(())
619}
620
621#[cfg(test)]
622mod tests {
623 use super::*;
624 use serde_json::json;
625
626 fn create_empty_script_context() -> ScriptContext {
627 ScriptContext {
628 request: None,
629 response: None,
630 chain_context: HashMap::new(),
631 variables: HashMap::new(),
632 env_vars: HashMap::new(),
633 }
634 }
635
636 fn create_full_script_context() -> ScriptContext {
637 ScriptContext {
638 request: Some(crate::request_chaining::ChainRequest {
639 id: "test-request".to_string(),
640 method: "GET".to_string(),
641 url: "https://api.example.com/test".to_string(),
642 headers: [("Content-Type".to_string(), "application/json".to_string())].into(),
643 body: Some(crate::request_chaining::RequestBody::Json(json!({"key": "value"}))),
644 depends_on: vec![],
645 timeout_secs: Some(30),
646 expected_status: Some(vec![200]),
647 scripting: None,
648 }),
649 response: Some(crate::request_chaining::ChainResponse {
650 status: 200,
651 headers: [("Content-Type".to_string(), "application/json".to_string())].into(),
652 body: Some(json!({"result": "success"})),
653 duration_ms: 150,
654 executed_at: chrono::Utc::now().to_rfc3339(),
655 error: None,
656 }),
657 chain_context: {
658 let mut ctx = HashMap::new();
659 ctx.insert("login_token".to_string(), json!("abc123"));
660 ctx.insert("user_id".to_string(), json!(42));
661 ctx.insert("is_admin".to_string(), json!(true));
662 ctx.insert("items".to_string(), json!(["a", "b", "c"]));
663 ctx.insert("config".to_string(), json!({"timeout": 30}));
664 ctx
665 },
666 variables: {
667 let mut vars = HashMap::new();
668 vars.insert("counter".to_string(), json!(0));
669 vars.insert("name".to_string(), json!("test"));
670 vars
671 },
672 env_vars: [
673 ("NODE_ENV".to_string(), "test".to_string()),
674 ("API_KEY".to_string(), "secret123".to_string()),
675 ]
676 .into(),
677 }
678 }
679
680 #[test]
682 fn test_script_result_clone() {
683 let result = ScriptResult {
684 return_value: Some(json!("test")),
685 modified_variables: {
686 let mut vars = HashMap::new();
687 vars.insert("key".to_string(), json!("value"));
688 vars
689 },
690 errors: vec!["error1".to_string()],
691 execution_time_ms: 100,
692 };
693
694 let cloned = result.clone();
695 assert_eq!(cloned.return_value, result.return_value);
696 assert_eq!(cloned.modified_variables, result.modified_variables);
697 assert_eq!(cloned.errors, result.errors);
698 assert_eq!(cloned.execution_time_ms, result.execution_time_ms);
699 }
700
701 #[test]
702 fn test_script_result_debug() {
703 let result = ScriptResult {
704 return_value: Some(json!("test")),
705 modified_variables: HashMap::new(),
706 errors: vec![],
707 execution_time_ms: 50,
708 };
709
710 let debug = format!("{:?}", result);
711 assert!(debug.contains("ScriptResult"));
712 assert!(debug.contains("return_value"));
713 }
714
715 #[test]
716 fn test_script_result_serialize() {
717 let result = ScriptResult {
718 return_value: Some(json!("test")),
719 modified_variables: HashMap::new(),
720 errors: vec![],
721 execution_time_ms: 50,
722 };
723
724 let json = serde_json::to_string(&result).unwrap();
725 assert!(json.contains("return_value"));
726 assert!(json.contains("execution_time_ms"));
727 }
728
729 #[test]
730 fn test_script_result_deserialize() {
731 let json =
732 r#"{"return_value":"test","modified_variables":{},"errors":[],"execution_time_ms":50}"#;
733 let result: ScriptResult = serde_json::from_str(json).unwrap();
734 assert_eq!(result.return_value, Some(json!("test")));
735 assert_eq!(result.execution_time_ms, 50);
736 }
737
738 #[test]
740 fn test_script_context_clone() {
741 let ctx = create_full_script_context();
742 let cloned = ctx.clone();
743
744 assert_eq!(cloned.request.is_some(), ctx.request.is_some());
745 assert_eq!(cloned.response.is_some(), ctx.response.is_some());
746 assert_eq!(cloned.chain_context.len(), ctx.chain_context.len());
747 assert_eq!(cloned.variables.len(), ctx.variables.len());
748 assert_eq!(cloned.env_vars.len(), ctx.env_vars.len());
749 }
750
751 #[test]
752 fn test_script_context_debug() {
753 let ctx = create_empty_script_context();
754 let debug = format!("{:?}", ctx);
755 assert!(debug.contains("ScriptContext"));
756 }
757
758 #[test]
760 fn test_script_engine_new() {
761 let engine = ScriptEngine::new();
762 let debug = format!("{:?}", engine);
764 assert!(debug.contains("ScriptEngine"));
765 assert!(debug.contains("Semaphore"));
766 }
767
768 #[test]
769 fn test_script_engine_default() {
770 let engine = ScriptEngine::default();
771 let debug = format!("{:?}", engine);
772 assert!(debug.contains("ScriptEngine"));
773 }
774
775 #[test]
776 fn test_script_engine_debug() {
777 let engine = ScriptEngine::new();
778 let debug = format!("{:?}", engine);
779 assert!(debug.contains("ScriptEngine"));
780 assert!(debug.contains("10")); }
783
784 #[tokio::test]
785 async fn test_script_execution() {
786 let engine = ScriptEngine::new();
787
788 let script_context = ScriptContext {
789 request: Some(crate::request_chaining::ChainRequest {
790 id: "test-request".to_string(),
791 method: "GET".to_string(),
792 url: "https://api.example.com/test".to_string(),
793 headers: [("Content-Type".to_string(), "application/json".to_string())].into(),
794 body: None,
795 depends_on: vec![],
796 timeout_secs: None,
797 expected_status: None,
798 scripting: None,
799 }),
800 response: None,
801 chain_context: {
802 let mut ctx = HashMap::new();
803 ctx.insert("login_token".to_string(), json!("abc123"));
804 ctx
805 },
806 variables: HashMap::new(),
807 env_vars: [("NODE_ENV".to_string(), "test".to_string())].into(),
808 };
809
810 let script = r#"
811 for (let i = 0; i < 1000000; i++) {
812 // Loop to ensure measurable execution time
813 }
814 "script executed successfully";
815 "#;
816
817 let result = engine.execute_script(script, &script_context, 5000).await;
818 assert!(result.is_ok(), "Script execution should succeed");
819
820 let script_result = result.unwrap();
821 assert_eq!(script_result.return_value, Some(json!("script executed successfully")));
822 assert!(script_result.execution_time_ms > 0);
823 assert!(script_result.errors.is_empty());
824 }
825
826 #[tokio::test]
827 async fn test_script_with_error() {
828 let engine = ScriptEngine::new();
829
830 let script_context = ScriptContext {
831 request: None,
832 response: None,
833 chain_context: HashMap::new(),
834 variables: HashMap::new(),
835 env_vars: HashMap::new(),
836 };
837
838 let script = r#"throw new Error("Intentional test error");"#;
839
840 let result = engine.execute_script(script, &script_context, 1000).await;
841 assert!(result.is_err() || result.is_ok()); }
845
846 #[tokio::test]
847 async fn test_simple_script_string_return() {
848 let engine = ScriptEngine::new();
849 let ctx = create_empty_script_context();
850
851 let result = engine.execute_script(r#""hello world""#, &ctx, 1000).await;
852 assert!(result.is_ok());
853 assert_eq!(result.unwrap().return_value, Some(json!("hello world")));
854 }
855
856 #[tokio::test]
857 async fn test_simple_script_number_return() {
858 let engine = ScriptEngine::new();
859 let ctx = create_empty_script_context();
860
861 let result = engine.execute_script("42", &ctx, 1000).await;
862 assert!(result.is_ok());
863 }
866
867 #[tokio::test]
868 async fn test_simple_script_boolean_return() {
869 let engine = ScriptEngine::new();
870 let ctx = create_empty_script_context();
871
872 let result = engine.execute_script("true", &ctx, 1000).await;
873 assert!(result.is_ok());
874 assert_eq!(result.unwrap().return_value, Some(json!(true)));
875 }
876
877 #[tokio::test]
878 async fn test_script_timeout() {
879 let engine = ScriptEngine::new();
880 let ctx = create_empty_script_context();
881
882 let script = r#"
884 let count = 0;
885 while (count < 100000000) {
886 count++;
887 }
888 count;
889 "#;
890
891 let result = engine.execute_script(script, &ctx, 10).await;
892 assert!(result.is_ok() || result.is_err());
895 }
896
897 #[tokio::test]
898 async fn test_script_with_request_context() {
899 let engine = ScriptEngine::new();
900 let ctx = create_full_script_context();
901
902 let script = r#"
904 mockforge.request.method;
905 "#;
906
907 let result = engine.execute_script(script, &ctx, 1000).await;
908 assert!(result.is_ok());
909 assert_eq!(result.unwrap().return_value, Some(json!("GET")));
910 }
911
912 #[tokio::test]
913 async fn test_script_with_response_context() {
914 let engine = ScriptEngine::new();
915 let ctx = create_full_script_context();
916
917 let script = r#"
919 mockforge.response.status;
920 "#;
921
922 let result = engine.execute_script(script, &ctx, 1000).await;
923 assert!(result.is_ok());
924 }
925
926 #[tokio::test]
927 async fn test_script_with_chain_context() {
928 let engine = ScriptEngine::new();
929 let ctx = create_full_script_context();
930
931 let script = r#"
933 mockforge.chain.login_token;
934 "#;
935
936 let result = engine.execute_script(script, &ctx, 1000).await;
937 assert!(result.is_ok());
938 assert_eq!(result.unwrap().return_value, Some(json!("abc123")));
939 }
940
941 #[tokio::test]
942 async fn test_script_with_env_vars() {
943 let engine = ScriptEngine::new();
944 let ctx = create_full_script_context();
945
946 let script = r#"
948 mockforge.env.NODE_ENV;
949 "#;
950
951 let result = engine.execute_script(script, &ctx, 1000).await;
952 assert!(result.is_ok());
953 assert_eq!(result.unwrap().return_value, Some(json!("test")));
954 }
955
956 #[tokio::test]
957 async fn test_script_modify_variables() {
958 let engine = ScriptEngine::new();
959 let mut ctx = create_empty_script_context();
960 ctx.variables.insert("counter".to_string(), json!(0));
961
962 let script = r#"
964 mockforge.variables.counter = 10;
965 mockforge.variables.new_var = "created";
966 mockforge.variables.counter;
967 "#;
968
969 let result = engine.execute_script(script, &ctx, 1000).await;
970 assert!(result.is_ok());
971 let script_result = result.unwrap();
972 assert!(
974 script_result.modified_variables.contains_key("counter")
975 || script_result.modified_variables.contains_key("new_var")
976 );
977 }
978
979 #[tokio::test]
980 async fn test_script_console_log() {
981 let engine = ScriptEngine::new();
982 let ctx = create_empty_script_context();
983
984 let script = r#"
986 console.log("test message");
987 "logged";
988 "#;
989
990 let result = engine.execute_script(script, &ctx, 1000).await;
991 assert!(result.is_ok());
992 }
993
994 #[tokio::test]
995 async fn test_script_log_function() {
996 let engine = ScriptEngine::new();
997 let ctx = create_empty_script_context();
998
999 let script = r#"
1001 log("test log");
1002 "logged";
1003 "#;
1004
1005 let result = engine.execute_script(script, &ctx, 1000).await;
1006 assert!(result.is_ok());
1007 }
1008
1009 #[tokio::test]
1010 async fn test_script_crypto_base64() {
1011 let engine = ScriptEngine::new();
1012 let ctx = create_empty_script_context();
1013
1014 let script = r#"
1016 crypto.base64Encode("hello");
1017 "#;
1018
1019 let result = engine.execute_script(script, &ctx, 1000).await;
1020 assert!(result.is_ok());
1021 assert_eq!(result.unwrap().return_value, Some(json!("aGVsbG8=")));
1023 }
1024
1025 #[tokio::test]
1026 async fn test_script_crypto_base64_decode() {
1027 let engine = ScriptEngine::new();
1028 let ctx = create_empty_script_context();
1029
1030 let script = r#"
1032 crypto.base64Decode("aGVsbG8=");
1033 "#;
1034
1035 let result = engine.execute_script(script, &ctx, 1000).await;
1036 assert!(result.is_ok());
1037 assert_eq!(result.unwrap().return_value, Some(json!("hello")));
1038 }
1039
1040 #[tokio::test]
1041 async fn test_script_crypto_sha256() {
1042 let engine = ScriptEngine::new();
1043 let ctx = create_empty_script_context();
1044
1045 let script = r#"
1047 crypto.sha256("hello");
1048 "#;
1049
1050 let result = engine.execute_script(script, &ctx, 1000).await;
1051 assert!(result.is_ok());
1052 let return_val = result.unwrap().return_value;
1054 assert!(return_val.is_some());
1055 let hash = return_val.unwrap();
1056 assert!(hash.as_str().unwrap().len() == 64); }
1058
1059 #[tokio::test]
1060 async fn test_script_crypto_random_bytes() {
1061 let engine = ScriptEngine::new();
1062 let ctx = create_empty_script_context();
1063
1064 let script = r#"
1066 crypto.randomBytes(16);
1067 "#;
1068
1069 let result = engine.execute_script(script, &ctx, 1000).await;
1070 assert!(result.is_ok());
1071 let return_val = result.unwrap().return_value;
1072 assert!(return_val.is_some());
1073 let hex = return_val.unwrap();
1074 assert!(hex.as_str().unwrap().len() == 32); }
1076
1077 #[tokio::test]
1078 async fn test_script_date_now() {
1079 let engine = ScriptEngine::new();
1080 let ctx = create_empty_script_context();
1081
1082 let script = r#"
1084 date.now();
1085 "#;
1086
1087 let result = engine.execute_script(script, &ctx, 1000).await;
1088 assert!(result.is_ok());
1089 let return_val = result.unwrap().return_value;
1090 assert!(return_val.is_some());
1091 let timestamp = return_val.unwrap();
1093 assert!(timestamp.as_str().unwrap().contains("T"));
1094 }
1095
1096 #[tokio::test]
1097 async fn test_script_date_add_days() {
1098 let engine = ScriptEngine::new();
1099 let ctx = create_empty_script_context();
1100
1101 let script = r#"
1103 date.addDays("2024-01-01T00:00:00+00:00", 1);
1104 "#;
1105
1106 let result = engine.execute_script(script, &ctx, 1000).await;
1107 assert!(result.is_ok());
1108 let return_val = result.unwrap().return_value;
1109 assert!(return_val.is_some());
1110 let new_date = return_val.unwrap();
1111 assert!(new_date.as_str().unwrap().contains("2024-01-02"));
1112 }
1113
1114 #[tokio::test]
1115 async fn test_script_validate_email() {
1116 let engine = ScriptEngine::new();
1117 let ctx = create_empty_script_context();
1118
1119 let script = r#"validate.email("test@example.com");"#;
1121 let result = engine.execute_script(script, &ctx, 1000).await;
1122 assert!(result.is_ok());
1123 assert_eq!(result.unwrap().return_value, Some(json!(true)));
1124
1125 let script = r#"validate.email("not-an-email");"#;
1127 let result = engine.execute_script(script, &ctx, 1000).await;
1128 assert!(result.is_ok());
1129 assert_eq!(result.unwrap().return_value, Some(json!(false)));
1130 }
1131
1132 #[tokio::test]
1133 async fn test_script_validate_url() {
1134 let engine = ScriptEngine::new();
1135 let ctx = create_empty_script_context();
1136
1137 let script = r#"validate.url("https://example.com");"#;
1139 let result = engine.execute_script(script, &ctx, 1000).await;
1140 assert!(result.is_ok());
1141 assert_eq!(result.unwrap().return_value, Some(json!(true)));
1142
1143 let script = r#"validate.url("not-a-url");"#;
1145 let result = engine.execute_script(script, &ctx, 1000).await;
1146 assert!(result.is_ok());
1147 assert_eq!(result.unwrap().return_value, Some(json!(false)));
1148 }
1149
1150 #[tokio::test]
1151 async fn test_script_validate_regex() {
1152 let engine = ScriptEngine::new();
1153 let ctx = create_empty_script_context();
1154
1155 let script = r#"validate.regex("^hello", "hello world");"#;
1157 let result = engine.execute_script(script, &ctx, 1000).await;
1158 assert!(result.is_ok());
1159 assert_eq!(result.unwrap().return_value, Some(json!(true)));
1160
1161 let script = r#"validate.regex("^world", "hello world");"#;
1163 let result = engine.execute_script(script, &ctx, 1000).await;
1164 assert!(result.is_ok());
1165 assert_eq!(result.unwrap().return_value, Some(json!(false)));
1166 }
1167
1168 #[tokio::test]
1169 async fn test_script_json_parse() {
1170 let engine = ScriptEngine::new();
1171 let ctx = create_empty_script_context();
1172
1173 let script = r#"JSON.parse('{"key": "value"}');"#;
1174 let result = engine.execute_script(script, &ctx, 1000).await;
1175 assert!(result.is_ok());
1176 }
1177
1178 #[tokio::test]
1179 async fn test_script_json_validate() {
1180 let engine = ScriptEngine::new();
1181 let ctx = create_empty_script_context();
1182
1183 let script = r#"JSON.validate('{"key": "value"}');"#;
1185 let result = engine.execute_script(script, &ctx, 1000).await;
1186 assert!(result.is_ok());
1187 assert_eq!(result.unwrap().return_value, Some(json!(true)));
1188
1189 let script = r#"JSON.validate('not json');"#;
1191 let result = engine.execute_script(script, &ctx, 1000).await;
1192 assert!(result.is_ok());
1193 assert_eq!(result.unwrap().return_value, Some(json!(false)));
1194 }
1195
1196 #[tokio::test]
1197 async fn test_script_http_url_encode() {
1198 let engine = ScriptEngine::new();
1199 let ctx = create_empty_script_context();
1200
1201 let script = r#"http.urlEncode("hello world");"#;
1202 let result = engine.execute_script(script, &ctx, 1000).await;
1203 assert!(result.is_ok());
1204 assert_eq!(result.unwrap().return_value, Some(json!("hello%20world")));
1205 }
1206
1207 #[tokio::test]
1208 async fn test_script_http_url_decode() {
1209 let engine = ScriptEngine::new();
1210 let ctx = create_empty_script_context();
1211
1212 let script = r#"http.urlDecode("hello%20world");"#;
1213 let result = engine.execute_script(script, &ctx, 1000).await;
1214 assert!(result.is_ok());
1215 assert_eq!(result.unwrap().return_value, Some(json!("hello world")));
1216 }
1217
1218 #[tokio::test]
1219 async fn test_script_with_syntax_error() {
1220 let engine = ScriptEngine::new();
1221 let ctx = create_empty_script_context();
1222
1223 let script = r#"function { broken"#;
1225 let result = engine.execute_script(script, &ctx, 1000).await;
1226 assert!(result.is_err());
1227 }
1228
1229 #[tokio::test]
1230 async fn test_execute_simple_string() {
1231 let engine = ScriptEngine::new();
1232 let ctx = create_empty_script_context();
1233
1234 let result = engine.execute_script(r#""test""#, &ctx, 1000).await;
1235 assert!(result.is_ok());
1236 assert_eq!(result.unwrap().return_value, Some(json!("test")));
1237 }
1238
1239 #[tokio::test]
1240 async fn test_script_with_no_request() {
1241 let engine = ScriptEngine::new();
1242 let ctx = create_empty_script_context();
1243
1244 let script = r#""no request needed""#;
1246 let result = engine.execute_script(script, &ctx, 1000).await;
1247 assert!(result.is_ok());
1248 }
1249
1250 #[tokio::test]
1251 async fn test_script_with_no_response() {
1252 let engine = ScriptEngine::new();
1253 let mut ctx = create_empty_script_context();
1254 ctx.request = Some(crate::request_chaining::ChainRequest {
1255 id: "test".to_string(),
1256 method: "GET".to_string(),
1257 url: "http://example.com".to_string(),
1258 headers: HashMap::new(),
1259 body: None,
1260 depends_on: vec![],
1261 timeout_secs: None,
1262 expected_status: None,
1263 scripting: None,
1264 });
1265
1266 let script = r#"mockforge.request.method"#;
1268 let result = engine.execute_script(script, &ctx, 1000).await;
1269 assert!(result.is_ok());
1270 }
1271
1272 #[tokio::test]
1273 async fn test_concurrent_script_execution() {
1274 let engine = Arc::new(ScriptEngine::new());
1275 let ctx = create_empty_script_context();
1276
1277 let mut handles = vec![];
1279 for i in 0..5 {
1280 let engine = engine.clone();
1281 let ctx = ctx.clone();
1282 let handle = tokio::spawn(async move {
1283 let script = format!("{}", i);
1284 engine.execute_script(&script, &ctx, 1000).await
1285 });
1286 handles.push(handle);
1287 }
1288
1289 for handle in handles {
1290 let result = handle.await.unwrap();
1291 assert!(result.is_ok());
1292 }
1293 }
1294
1295 #[test]
1297 fn test_execute_script_in_runtime_success() {
1298 let ctx = create_empty_script_context();
1299 let result = execute_script_in_runtime(r#""hello""#, &ctx);
1300 assert!(result.is_ok());
1301 assert_eq!(result.unwrap().return_value, Some(json!("hello")));
1302 }
1303
1304 #[test]
1305 fn test_execute_script_in_runtime_with_context() {
1306 let ctx = create_full_script_context();
1307 let result = execute_script_in_runtime(r#"mockforge.request.method"#, &ctx);
1308 assert!(result.is_ok());
1309 }
1310
1311 #[test]
1312 fn test_execute_script_in_runtime_error() {
1313 let ctx = create_empty_script_context();
1314 let result = execute_script_in_runtime(r#"throw new Error("test");"#, &ctx);
1315 assert!(result.is_err());
1316 }
1317
1318 #[tokio::test]
1320 async fn test_script_chain_context_number() {
1321 let engine = ScriptEngine::new();
1322 let ctx = create_full_script_context();
1323
1324 let script = r#"mockforge.chain.user_id;"#;
1325 let result = engine.execute_script(script, &ctx, 1000).await;
1326 assert!(result.is_ok());
1327 }
1328
1329 #[tokio::test]
1330 async fn test_script_chain_context_boolean() {
1331 let engine = ScriptEngine::new();
1332 let ctx = create_full_script_context();
1333
1334 let script = r#"mockforge.chain.is_admin;"#;
1335 let result = engine.execute_script(script, &ctx, 1000).await;
1336 assert!(result.is_ok());
1337 assert_eq!(result.unwrap().return_value, Some(json!(true)));
1338 }
1339
1340 #[tokio::test]
1342 async fn test_script_variables_number() {
1343 let engine = ScriptEngine::new();
1344 let ctx = create_full_script_context();
1345
1346 let script = r#"mockforge.variables.counter;"#;
1347 let result = engine.execute_script(script, &ctx, 1000).await;
1348 assert!(result.is_ok());
1349 }
1350
1351 #[tokio::test]
1352 async fn test_script_variables_string() {
1353 let engine = ScriptEngine::new();
1354 let ctx = create_full_script_context();
1355
1356 let script = r#"mockforge.variables.name;"#;
1357 let result = engine.execute_script(script, &ctx, 1000).await;
1358 assert!(result.is_ok());
1359 assert_eq!(result.unwrap().return_value, Some(json!("test")));
1360 }
1361
1362 #[tokio::test]
1363 async fn test_script_arithmetic() {
1364 let engine = ScriptEngine::new();
1365 let ctx = create_empty_script_context();
1366
1367 let script = r#"1 + 2 + 3"#;
1368 let result = engine.execute_script(script, &ctx, 1000).await;
1369 assert!(result.is_ok());
1370 }
1371
1372 #[tokio::test]
1373 async fn test_script_string_concatenation() {
1374 let engine = ScriptEngine::new();
1375 let ctx = create_empty_script_context();
1376
1377 let script = r#""hello" + " " + "world""#;
1378 let result = engine.execute_script(script, &ctx, 1000).await;
1379 assert!(result.is_ok());
1380 assert_eq!(result.unwrap().return_value, Some(json!("hello world")));
1381 }
1382
1383 #[tokio::test]
1384 async fn test_script_conditional() {
1385 let engine = ScriptEngine::new();
1386 let ctx = create_empty_script_context();
1387
1388 let script = r#"true ? "yes" : "no""#;
1389 let result = engine.execute_script(script, &ctx, 1000).await;
1390 assert!(result.is_ok());
1391 assert_eq!(result.unwrap().return_value, Some(json!("yes")));
1392 }
1393
1394 #[tokio::test]
1395 async fn test_script_function_definition_and_call() {
1396 let engine = ScriptEngine::new();
1397 let ctx = create_empty_script_context();
1398
1399 let script = r#"
1400 function add(a, b) {
1401 return a + b;
1402 }
1403 add(1, 2);
1404 "#;
1405 let result = engine.execute_script(script, &ctx, 1000).await;
1406 assert!(result.is_ok());
1407 }
1408
1409 #[tokio::test]
1410 async fn test_script_arrow_function() {
1411 let engine = ScriptEngine::new();
1412 let ctx = create_empty_script_context();
1413
1414 let script = r#"
1415 const multiply = (a, b) => a * b;
1416 multiply(3, 4);
1417 "#;
1418 let result = engine.execute_script(script, &ctx, 1000).await;
1419 assert!(result.is_ok());
1420 }
1421
1422 #[tokio::test]
1423 async fn test_script_array_operations() {
1424 let engine = ScriptEngine::new();
1425 let ctx = create_empty_script_context();
1426
1427 let script = r#"
1428 const arr = [1, 2, 3];
1429 arr.length;
1430 "#;
1431 let result = engine.execute_script(script, &ctx, 1000).await;
1432 assert!(result.is_ok());
1433 }
1434
1435 #[tokio::test]
1436 async fn test_script_object_access() {
1437 let engine = ScriptEngine::new();
1438 let ctx = create_empty_script_context();
1439
1440 let script = r#"
1441 const obj = {key: "value"};
1442 obj.key;
1443 "#;
1444 let result = engine.execute_script(script, &ctx, 1000).await;
1445 assert!(result.is_ok());
1446 assert_eq!(result.unwrap().return_value, Some(json!("value")));
1447 }
1448
1449 #[tokio::test]
1450 async fn test_date_format() {
1451 let engine = ScriptEngine::new();
1452 let ctx = create_empty_script_context();
1453
1454 let script = r#"date.format("2024-01-15T10:30:00+00:00", "%Y-%m-%d");"#;
1455 let result = engine.execute_script(script, &ctx, 1000).await;
1456 assert!(result.is_ok());
1457 assert_eq!(result.unwrap().return_value, Some(json!("2024-01-15")));
1458 }
1459
1460 #[tokio::test]
1461 async fn test_date_parse() {
1462 let engine = ScriptEngine::new();
1463 let ctx = create_empty_script_context();
1464
1465 let script = r#"date.parse("2024-01-15 10:30:00", "%Y-%m-%d %H:%M:%S");"#;
1466 let result = engine.execute_script(script, &ctx, 1000).await;
1467 assert!(result.is_ok());
1468 let return_val = result.unwrap().return_value;
1469 assert!(return_val.is_some());
1470 assert!(return_val.unwrap().as_str().unwrap().contains("2024-01-15"));
1472 }
1473
1474 #[tokio::test]
1475 async fn test_date_parse_invalid() {
1476 let engine = ScriptEngine::new();
1477 let ctx = create_empty_script_context();
1478
1479 let script = r#"date.parse("invalid", "%Y-%m-%d");"#;
1480 let result = engine.execute_script(script, &ctx, 1000).await;
1481 assert!(result.is_ok());
1482 assert_eq!(result.unwrap().return_value, Some(json!("")));
1484 }
1485
1486 #[tokio::test]
1487 async fn test_validate_regex_invalid_pattern() {
1488 let engine = ScriptEngine::new();
1489 let ctx = create_empty_script_context();
1490
1491 let script = r#"validate.regex("[invalid", "test");"#;
1493 let result = engine.execute_script(script, &ctx, 1000).await;
1494 assert!(result.is_ok());
1495 assert_eq!(result.unwrap().return_value, Some(json!(false)));
1497 }
1498
1499 #[tokio::test]
1500 async fn test_script_stringify_function() {
1501 let engine = ScriptEngine::new();
1502 let ctx = create_empty_script_context();
1503
1504 let script = r#"stringify("test");"#;
1505 let result = engine.execute_script(script, &ctx, 1000).await;
1506 assert!(result.is_ok());
1507 }
1508
1509 #[tokio::test]
1510 async fn test_crypto_base64_decode_invalid() {
1511 let engine = ScriptEngine::new();
1512 let ctx = create_empty_script_context();
1513
1514 let script = r#"crypto.base64Decode("!!invalid!!");"#;
1516 let result = engine.execute_script(script, &ctx, 1000).await;
1517 assert!(result.is_ok());
1518 assert_eq!(result.unwrap().return_value, Some(json!("")));
1520 }
1521
1522 #[tokio::test]
1523 async fn test_date_add_days_invalid() {
1524 let engine = ScriptEngine::new();
1525 let ctx = create_empty_script_context();
1526
1527 let script = r#"date.addDays("invalid", 1);"#;
1529 let result = engine.execute_script(script, &ctx, 1000).await;
1530 assert!(result.is_ok());
1531 assert_eq!(result.unwrap().return_value, Some(json!("")));
1533 }
1534
1535 #[tokio::test]
1536 async fn test_date_format_invalid() {
1537 let engine = ScriptEngine::new();
1538 let ctx = create_empty_script_context();
1539
1540 let script = r#"date.format("invalid", "%Y-%m-%d");"#;
1542 let result = engine.execute_script(script, &ctx, 1000).await;
1543 assert!(result.is_ok());
1544 assert_eq!(result.unwrap().return_value, Some(json!("")));
1546 }
1547
1548 #[tokio::test]
1549 async fn test_http_url_encode_special_chars() {
1550 let engine = ScriptEngine::new();
1551 let ctx = create_empty_script_context();
1552
1553 let script = r#"http.urlEncode("a=b&c=d");"#;
1554 let result = engine.execute_script(script, &ctx, 1000).await;
1555 assert!(result.is_ok());
1556 let encoded = result.unwrap().return_value.unwrap();
1557 assert!(encoded.as_str().unwrap().contains("%3D")); assert!(encoded.as_str().unwrap().contains("%26")); }
1560
1561 #[tokio::test]
1562 async fn test_json_parse_invalid() {
1563 let engine = ScriptEngine::new();
1564 let ctx = create_empty_script_context();
1565
1566 let script = r#"JSON.parse("invalid json");"#;
1567 let result = engine.execute_script(script, &ctx, 1000).await;
1568 assert!(result.is_ok());
1569 assert_eq!(result.unwrap().return_value, Some(json!("null")));
1571 }
1572
1573 #[tokio::test]
1574 async fn test_script_with_complex_chain_context() {
1575 let engine = ScriptEngine::new();
1576 let mut ctx = create_empty_script_context();
1577 ctx.chain_context.insert("float_val".to_string(), json!(3.125));
1578 ctx.chain_context.insert("bool_val".to_string(), json!(false));
1579
1580 let script = r#"mockforge.chain.bool_val;"#;
1581 let result = engine.execute_script(script, &ctx, 1000).await;
1582 assert!(result.is_ok());
1583 assert_eq!(result.unwrap().return_value, Some(json!(false)));
1584 }
1585
1586 #[tokio::test]
1587 async fn test_script_with_complex_variables() {
1588 let engine = ScriptEngine::new();
1589 let mut ctx = create_empty_script_context();
1590 ctx.variables.insert("obj".to_string(), json!({"nested": "value"}));
1591
1592 let script = r#""executed";"#;
1593 let result = engine.execute_script(script, &ctx, 1000).await;
1594 assert!(result.is_ok());
1595 }
1596}