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