decapod 0.59.3

Decapod is a Rust-built governance runtime for AI agents: repo-native state, enforced workflow, proof gates, safe coordination.
Documentation
use std::process::Command;
use tempfile::TempDir;

#[test]
fn test_cloud_opt_in_keeps_local_state_usable() {
    let tmp = TempDir::new().expect("tempdir");
    let dir = tmp.path().to_path_buf();

    let init_out = Command::new(env!("CARGO_BIN_EXE_decapod"))
        .args(["init", "--mode", "cloud", "--force", "--proof"])
        .current_dir(&dir)
        .output()
        .expect("decapod init");

    assert!(
        init_out.status.success(),
        "decapod init failed: {}",
        String::from_utf8_lossy(&init_out.stderr)
    );

    let title = format!(
        "Test local task with cloud opt-in {}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis()
    );

    let add_out = Command::new(env!("CARGO_BIN_EXE_decapod"))
        .args(["todo", "add", &title, "--format", "json"])
        .current_dir(&dir)
        .output()
        .expect("todo add");

    assert!(
        add_out.status.success(),
        "todo add failed: {}",
        String::from_utf8_lossy(&add_out.stderr)
    );

    let add_json: serde_json::Value =
        serde_json::from_slice(&add_out.stdout).expect("parse todo add json");
    let task_id = add_json["id"].as_str().expect("task id").to_string();

    let list_out = Command::new(env!("CARGO_BIN_EXE_decapod"))
        .args(["todo", "list", "--format", "json"])
        .current_dir(&dir)
        .output()
        .expect("todo list");

    assert!(
        list_out.status.success(),
        "todo list failed: {}",
        String::from_utf8_lossy(&list_out.stderr)
    );

    let list_stdout = String::from_utf8_lossy(&list_out.stdout);
    assert!(
        list_stdout.contains(&title),
        "Task '{}' not found in list output after cloud opt-in: {}",
        title,
        list_stdout
    );
    assert!(
        list_stdout.contains(&task_id),
        "Cloud task ID '{}' not found in list output",
        task_id
    );

    let get_out = Command::new(env!("CARGO_BIN_EXE_decapod"))
        .args(["todo", "get", "--id", &task_id, "--format", "json"])
        .current_dir(&dir)
        .output()
        .expect("todo get");

    assert!(
        get_out.status.success(),
        "todo get failed: {}",
        String::from_utf8_lossy(&get_out.stderr)
    );

    let get_stdout = String::from_utf8_lossy(&get_out.stdout);
    assert!(
        get_stdout.contains(&title),
        "Task get did not return expected title"
    );
}

#[test]
fn test_cloud_init_records_opt_in_without_auth_or_repo_credentials() {
    let tmp = TempDir::new().expect("tempdir");
    let dir = tmp.path().to_path_buf();

    let init_out = Command::new(env!("CARGO_BIN_EXE_decapod"))
        .args(["init", "--mode", "cloud", "--force", "--proof"])
        .env_remove("SUPABASE_URL")
        .env_remove("SUPABASE_KEY")
        .current_dir(&dir)
        .output()
        .expect("decapod init");

    assert!(
        init_out.status.success(),
        "cloud opt-in init should not require auth/backend calls: {}",
        String::from_utf8_lossy(&init_out.stderr)
    );

    let config_path = dir.join(".decapod/config.toml");
    let config = std::fs::read_to_string(config_path).unwrap();
    assert!(config.contains("[cloud]"));
    assert!(config.contains("enabled = true"));
    assert!(config.contains("experimental = true"));
    assert!(config.contains("mode = \"local\""));
    assert!(!config.contains("SUPABASE"));
    assert!(!config.contains("supabase"));
    assert!(!config.contains("token"));
    assert!(!dir.join(".decapod/session_token").exists());
}