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