pctx_executor 0.2.0

TypeScript execution environment orchestration
use std::sync::Arc;

use pctx_code_execution_runtime::PctxRegistry;
use serde::Deserialize;
use serde_json::json;

use super::serial;
use crate::{ExecuteOptions, execute};

#[serial]
#[tokio::test]
async fn test_execute_with_callbacks() {
    let registry = PctxRegistry::default();
    registry
        .add_callback(
            "my_math.add",
            Arc::new(move |args: Option<serde_json::Value>| {
                Box::pin(async move {
                    #[derive(Deserialize)]
                    struct AddArgs {
                        a: isize,
                        b: isize,
                    }

                    let args = args.ok_or_else(|| "Missing arguments".to_string())?;
                    let parsed: AddArgs =
                        serde_json::from_value(args).map_err(|e| format!("Invalid args: {e}"))?;

                    Ok(json!(parsed.a + parsed.b))
                })
            }),
        )
        .expect("callback registration should succeed");

    let code = r#"
async function test() {
    try {
        const val = await invokeInternal({ name: "my_math.add", arguments: { a: 12, b: 4 } });
        return { error: false, value: val };
    } catch (e) {
        return { error: true, message: e instanceof Error ? e.message : String(e) };
    }
}

export default await test();
"#;

    let result = execute(code, ExecuteOptions::new().with_registry(registry))
        .await
        .expect("execution should succeed");

    assert_eq!(result.output, Some(json!({"error": false, "value": 16})));
    assert!(
        result.success,
        "Code with callbacks should execute successfully"
    );
    assert!(
        result.runtime_error.is_none(),
        "Should have no runtime errors"
    );
    assert!(result.diagnostics.is_empty(), "Should have no type errors");
}

#[serial]
#[tokio::test]
async fn test_execute_with_async_callbacks() {
    let registry = PctxRegistry::default();
    registry
        .add_callback(
            "my_async__wait",
            Arc::new(move |args: Option<serde_json::Value>| {
                Box::pin(async move {
                    #[derive(Deserialize)]
                    struct WaitArgs {
                        ms: u64,
                    }

                    let args = args.ok_or_else(|| "Missing arguments".to_string())?;
                    let parsed: WaitArgs =
                        serde_json::from_value(args).map_err(|e| format!("Invalid args: {e}"))?;
                    tokio::time::sleep(tokio::time::Duration::from_millis(parsed.ms)).await;

                    Ok(json!(format!("Waited for {}ms", parsed.ms)))
                })
            }),
        )
        .expect("callback registration should succeed");

    let code = r#"
async function test() {
    try {
        const val = await invokeInternal({ name: "my_async__wait", arguments: { ms: 50 } });
        return { error: false, value: val };
    } catch (e) {
        return { error: true, message: e instanceof Error ? e.message : String(e) };
    }
}

export default await test();
"#;

    let result = execute(code, ExecuteOptions::new().with_registry(registry))
        .await
        .expect("execution should succeed");

    assert_eq!(
        result.output,
        Some(json!({"error": false, "value": "Waited for 50ms"}))
    );
    assert!(
        result.success,
        "Code with callbacks should execute successfully"
    );
    assert!(
        result.runtime_error.is_none(),
        "Should have no runtime errors"
    );
    assert!(result.diagnostics.is_empty(), "Should have no type errors");
}