everruns-integrations-e2b 0.17.3

E2B cloud sandbox integration for Everruns
Documentation
//! Live E2B API integration tests.
//!
//! Gated behind:
//! - Feature flag: `e2b-live-tests`
//! - Environment variable: `E2B_API_KEY` (required; missing ⇒ panic)
//!
//! Missing-credential policy: these tests fail closed. If the feature flag is
//! set but the credential is missing, the tests panic rather than silently
//! passing, so CI live jobs cannot report false-green. See `specs/integrations.md`.

#![cfg(feature = "e2b-live-tests")]

use everruns_integrations_e2b::client::E2BClient;
use everruns_integrations_e2b::state::SandboxState;
use everruns_integrations_e2b::{E2B_DEFAULT_TIMEOUT_SECS, E2B_DEFAULT_WORKSPACE_PATH};
use serde_json::json;

fn get_api_key() -> Option<String> {
    std::env::var("E2B_API_KEY")
        .ok()
        .filter(|value| !value.trim().is_empty())
}

/// Require `E2B_API_KEY` or panic. Live tests fail closed so CI cannot
/// silently pass when the credential is missing. See `specs/integrations.md`.
macro_rules! require_api_key {
    () => {
        match get_api_key() {
            Some(key) => key,
            None => panic!(
                "E2B_API_KEY not set — live tests require real credentials (fail-closed policy)"
            ),
        }
    };
}

struct SandboxGuard {
    api_key: String,
    sandbox_id: String,
}

impl Drop for SandboxGuard {
    fn drop(&mut self) {
        let client = E2BClient::new(self.api_key.clone());
        let sandbox_id = self.sandbox_id.clone();
        let handle =
            tokio::runtime::Handle::try_current().expect("tokio runtime required for cleanup");
        // Use block_in_place to allow blocking inside an async context (requires
        // multi-thread runtime, which #[tokio::test] uses by default).
        tokio::task::block_in_place(|| {
            handle.block_on(async move {
                let _ = client.delete_sandbox(&sandbox_id).await;
            });
        });
    }
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn smoke_live_sandbox_exec_and_files() {
    // Test binary doesn't go through init_telemetry() or CLI main(),
    // so install the rustls CryptoProvider explicitly (rustls 0.23 requirement).
    let _ = rustls::crypto::ring::default_provider().install_default();

    let api_key = require_api_key!();
    let client = E2BClient::new(api_key.clone());
    let created = client
        .create_sandbox(
            "base",
            E2B_DEFAULT_TIMEOUT_SECS,
            json!({"everruns": "true", "test": "smoke_live_sandbox_exec_and_files"}),
            json!({"HELLO": "world"}),
        )
        .await
        .expect("create sandbox");
    let _guard = SandboxGuard {
        api_key,
        sandbox_id: created.sandbox_id.clone(),
    };

    let detail = client
        .get_sandbox(&created.sandbox_id)
        .await
        .expect("get sandbox detail");
    let state = SandboxState {
        sandbox_id: detail.sandbox_id.clone(),
        sandbox_domain: detail
            .domain
            .clone()
            .or_else(|| created.domain.clone())
            .unwrap_or_else(|| "e2b.app".to_string()),
        envd_version: detail.envd_version.clone(),
        envd_access_token: detail
            .envd_access_token
            .clone()
            .or_else(|| created.envd_access_token.clone()),
        workspace_path: E2B_DEFAULT_WORKSPACE_PATH.to_string(),
        started_at: detail.started_at.clone(),
        timeout_seconds: E2B_DEFAULT_TIMEOUT_SECS,
    };

    client
        .write_file(&state, "/home/user/hello.txt", "hello from everruns\n")
        .await
        .expect("write file");
    let content = client
        .read_file(&state, "/home/user/hello.txt")
        .await
        .expect("read file");
    assert_eq!(content, "hello from everruns\n");

    let result = client
        .exec(
            &state,
            "pwd && cat /home/user/hello.txt && echo $HELLO",
            Some("/home/user"),
            Some(60_000),
        )
        .await
        .expect("exec command");
    assert_eq!(result.exit_code, 0);
    assert!(
        result.stdout.contains("/home/user"),
        "stdout: {}",
        result.stdout
    );
    assert!(
        result.stdout.contains("hello from everruns"),
        "stdout: {}",
        result.stdout
    );
    assert!(result.stdout.contains("world"), "stdout: {}", result.stdout);
}