octra-sqlite 0.6.3

Real SQLite inside an Octra Circle, with a Rust CLI and client library
Documentation
use serde_json::Value;
use std::fs;
use std::process::{Command, Stdio};

fn octra_sqlite() -> Command {
    Command::new(env!("CARGO_BIN_EXE_octra-sqlite"))
}

#[test]
fn limits_json_is_machine_readable_without_wallet() {
    let output = octra_sqlite()
        .args(["limits", "--json"])
        .output()
        .expect("run octra-sqlite limits --json");
    assert!(output.status.success());
    let value: Value = serde_json::from_slice(&output.stdout).unwrap();
    assert_eq!(value["ok"], true);
    assert_eq!(value["type"], "limits");
    assert_eq!(value["schema"], "octra-sqlite.cli.v1");
    assert_eq!(value["result"]["limit_error"], "result_limit_exceeded");
    assert!(
        value["trace"]["modes"]
            .as_array()
            .unwrap()
            .contains(&Value::String("summary".to_string()))
    );
}

#[test]
fn commands_json_is_machine_readable_without_wallet() {
    let output = octra_sqlite()
        .args(["commands", "--json"])
        .output()
        .expect("run octra-sqlite commands --json");
    assert!(output.status.success());
    let value: Value = serde_json::from_slice(&output.stdout).unwrap();
    assert_eq!(value["ok"], true);
    assert_eq!(value["type"], "commands");
    assert_eq!(value["schema"], "octra-sqlite.cli.v1");
    assert!(
        value["commands"]
            .as_array()
            .unwrap()
            .iter()
            .any(|command| command["command"] == "octra-sqlite DATABASE \"SQL\"")
    );
    assert!(
        value["json_envelopes"]
            .as_array()
            .unwrap()
            .contains(&Value::String("commands".to_string()))
    );
}

#[test]
fn json_errors_have_stable_shape_and_exit_code() {
    let output = octra_sqlite()
        .args(["check", "--json"])
        .stdin(Stdio::null())
        .output()
        .expect("run failing octra-sqlite check --json");
    assert!(!output.status.success());
    assert!(output.stdout.is_empty());
    let value: Value = serde_json::from_slice(&output.stderr).unwrap();
    assert_eq!(value["ok"], false);
    assert_eq!(value["type"], "error");
    assert_eq!(value["schema"], "octra-sqlite.cli.v1");
    assert_eq!(value["exit_code"], 1);
    assert!(value["error"]["code"].is_string());
    assert!(
        value["error"]["message"]
            .as_str()
            .unwrap()
            .contains("check requires")
    );
}

#[test]
fn json_errors_keep_source_owned_limit_codes() {
    let oversized = "x".repeat(8_192);
    let output = octra_sqlite()
        .args(["check", "--json", "--sql", &oversized])
        .output()
        .expect("run oversized octra-sqlite check --json");
    assert!(!output.status.success());
    let value: Value = serde_json::from_slice(&output.stderr).unwrap();
    assert_eq!(value["error"]["code"], "sql_too_large");

    let output = octra_sqlite()
        .args(["check", "--json", "--sql", "savepoint before_write;"])
        .output()
        .expect("run unsupported transaction check --json");
    assert!(!output.status.success());
    let value: Value = serde_json::from_slice(&output.stderr).unwrap();
    assert_eq!(value["error"]["code"], "transactions_not_supported");
}

#[test]
fn wallet_target_and_database_errors_keep_their_stable_codes() {
    let home =
        std::env::temp_dir().join(format!("octra-sqlite-json-errors-{}", std::process::id()));
    let _ = fs::remove_dir_all(&home);
    fs::create_dir_all(&home).unwrap();

    let output = octra_sqlite()
        .args(["wallet", "import", "--json"])
        .env("HOME", &home)
        .env("XDG_CONFIG_HOME", home.join(".config"))
        .output()
        .expect("run wallet import without a source");
    assert!(!output.status.success());
    let value: Value = serde_json::from_slice(&output.stderr).unwrap();
    assert_eq!(value["error"]["code"], "wallet_error");

    let output = octra_sqlite()
        .args(["database", "info", "not-a-database-target", "--json"])
        .env("HOME", &home)
        .env("XDG_CONFIG_HOME", home.join(".config"))
        .output()
        .expect("run database info with an invalid target");
    assert!(!output.status.success());
    let value: Value = serde_json::from_slice(&output.stderr).unwrap();
    assert_eq!(value["error"]["code"], "target_error");

    let output = octra_sqlite()
        .args(["database", "set", "demo", "oct://devnet/octABC"])
        .env("HOME", &home)
        .env("XDG_CONFIG_HOME", home.join(".config"))
        .output()
        .expect("save a database name");
    assert!(output.status.success());

    let output = octra_sqlite()
        .args(["new", "demo", "--json"])
        .env("HOME", &home)
        .env("XDG_CONFIG_HOME", home.join(".config"))
        .output()
        .expect("run new with an existing database name");
    assert!(!output.status.success());
    let value: Value = serde_json::from_slice(&output.stderr).unwrap();
    assert_eq!(value["error"]["code"], "target_error");

    let missing_wallet = home.join("missing-wallet.json");
    let output = octra_sqlite()
        .args(["new", "fresh", "--json", "--wallet"])
        .arg(&missing_wallet)
        .env("HOME", &home)
        .env("XDG_CONFIG_HOME", home.join(".config"))
        .output()
        .expect("run new with a missing wallet path");
    assert!(!output.status.success());
    let value: Value = serde_json::from_slice(&output.stderr).unwrap();
    assert_eq!(value["error"]["code"], "wallet_error");

    fs::remove_dir_all(home).unwrap();
}