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