mobius 0.15.49

A small, modular Rust framework for building coding agents
Documentation
use super::*;

#[test]
fn only_launching_a_command_requires_approval() {
    let mut catalog = Catalog::default();
    catalog.register(Arc::new(Bash)).expect("bash");
    catalog
        .register(Arc::new(ManageCommand))
        .expect("manage command");

    assert!(catalog.requires_approval("bash"));
    assert!(!catalog.requires_approval("manage_command"));
}

#[test]
fn background_output_remains_valid_json_at_its_limit() {
    let rendered = background_output(BackgroundCommandPoll {
        command_id: Some(uuid::Uuid::nil().to_string()),
        status: crate::backend::sandbox::BackgroundCommandStatus::Running,
        exit_code: None,
        stdout: "\0".repeat(6_000),
        stderr: String::new(),
        truncated: false,
        error: Some("\0".repeat(512)),
    });

    assert!(rendered.len() <= MAX_TOOL_OUTPUT_BYTES);
    let value: Value = serde_json::from_str(&rendered).expect("valid JSON");
    assert_eq!(value["status"], "running");
    assert_eq!(value["command_id"], uuid::Uuid::nil().to_string());
}

fn command_context(sandbox: &Arc<Sandbox>, owner: &str, authorized: bool) -> ToolContext {
    ToolContext::new(
        Arc::clone(sandbox),
        SandboxPermissions::restore(
            owner,
            crate::backend::sandbox::SandboxMode::WorkspaceWrite,
            crate::backend::sandbox::NetworkAccess::Denied,
            authorized.then(|| "command".into()),
        )
        .for_call("command"),
        "turn",
    )
}

#[tokio::test]
async fn bash_returns_completed_output_and_rejects_unauthorized_launches() {
    let sandbox = test_sandbox();
    let command = serde_json::json!({"command": "printf done; printf warning >&2; exit 7"});
    assert!(
        Bash.call(command_context(&sandbox, "owner", false), command.clone())
            .await
            .is_err()
    );
    assert!(
        !sandbox
            .has_background_commands("owner")
            .expect("no unauthorized command")
    );

    let response = Bash
        .call(command_context(&sandbox, "owner", true), command)
        .await
        .expect("bash");
    let output: Value = serde_json::from_str(&response.content.text()).expect("command output");
    assert_eq!(output["status"], "exited");
    assert_eq!(output["stdout"], "done");
    assert_eq!(output["stderr"], "warning");
    assert_eq!(output["exit_code"], 7);
    assert!(output["command_id"].is_null());
    assert!(
        !sandbox
            .has_background_commands("owner")
            .expect("completed command removed")
    );
}

#[tokio::test]
async fn bash_yields_a_command_that_only_its_owner_can_poll_and_stop() {
    let sandbox = test_sandbox();
    let response = Bash
        .call(
            command_context(&sandbox, "owner", true),
            serde_json::json!({"command": "printf first; sleep 60"}),
        )
        .await
        .expect("bash");
    let output: Value = serde_json::from_str(&response.content.text()).expect("running command");
    assert_eq!(output["status"], "running");
    assert_eq!(output["stdout"], "first");
    let id = output["command_id"].as_str().expect("running command ID");

    for action in ["poll", "stop"] {
        assert!(
            ManageCommand
                .call(
                    command_context(&sandbox, "other", false),
                    serde_json::json!({"command_id": id, "action": action}),
                )
                .await
                .is_err()
        );
    }
    assert!(
        ManageCommand
            .call(
                command_context(&sandbox, "owner", false),
                serde_json::json!({"command_id": id, "action": "restart"}),
            )
            .await
            .is_err()
    );

    for (action, expected) in [("poll", "running"), ("stop", "stopped")] {
        let response = ManageCommand
            .call(
                command_context(&sandbox, "owner", false),
                serde_json::json!({"command_id": id, "action": action}),
            )
            .await
            .expect("manage owned command without new approval");
        let output: Value = serde_json::from_str(&response.content.text()).expect("command output");
        assert_eq!(output["status"], expected);
        assert_eq!(output["stdout"], "");
    }
    assert!(
        !sandbox
            .has_background_commands("owner")
            .expect("stopped command removed")
    );
    assert!(
        ManageCommand
            .call(
                command_context(&sandbox, "owner", false),
                serde_json::json!({"command_id": id, "action": "poll"}),
            )
            .await
            .is_err()
    );
}