#![allow(clippy::expect_used, clippy::unwrap_used)]
use std::path::Path;
use std::process::{Command, Output};
fn tuible(args: &[&str]) -> Output {
let state_home = tempfile::tempdir().unwrap();
tuible_with_state(args, state_home.path())
}
fn tuible_with_state(args: &[&str], state_home: &Path) -> Output {
Command::new(env!("CARGO_BIN_EXE_tuible"))
.args(args)
.env("XDG_STATE_HOME", state_home)
.env("XDG_DATA_HOME", state_home)
.output()
.unwrap()
}
#[test]
fn top_level_help_explains_safety_and_workflows() {
let output = tuible(&["--help"]);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(output.status.success());
assert!(stdout.contains("read-only"));
assert!(stdout.contains("tuible query"));
assert!(stdout.contains("execute"));
assert!(stdout.contains("dynamodb"));
}
#[test]
fn query_emits_bounded_typed_json() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("cli.sqlite");
std::fs::File::create(&path).unwrap();
let path = path.to_str().unwrap();
assert!(
tuible(&[
"execute",
path,
"CREATE TABLE values_table (id INTEGER, name TEXT)",
"--yes",
])
.status
.success()
);
assert!(
tuible(&[
"execute",
path,
"INSERT INTO values_table VALUES (1, 'one'), (2, 'two')",
"--yes",
])
.status
.success()
);
let output = tuible(&[
"query",
path,
"SELECT id, name FROM values_table ORDER BY id",
"--max-rows",
"1",
]);
let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
assert!(output.status.success());
assert_eq!(json["columns"], serde_json::json!(["id", "name"]));
assert_eq!(json["rows"], serde_json::json!([[1, "one"]]));
assert_eq!(json["truncated"], true);
}
#[test]
fn writes_require_execute_and_confirmation() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("readonly.sqlite");
std::fs::File::create(&path).unwrap();
let path = path.to_str().unwrap();
assert!(
tuible(&["execute", path, "CREATE TABLE items (id INTEGER)", "--yes"])
.status
.success()
);
let query_write = tuible(&["query", path, "INSERT INTO items VALUES (1)"]);
let unconfirmed = tuible(&["execute", path, "INSERT INTO items VALUES (1)"]);
assert!(!query_write.status.success());
assert!(String::from_utf8_lossy(&query_write.stderr).contains("readonly"));
assert!(!unconfirmed.status.success());
assert!(String::from_utf8_lossy(&unconfirmed.stderr).contains("--yes"));
}
#[test]
fn cli_statements_never_create_interactive_query_history() {
let dir = tempfile::tempdir().unwrap();
let state_home = dir.path().join("state");
let database = dir.path().join("cli-history.sqlite");
std::fs::File::create(&database).unwrap();
let database = database.to_str().unwrap();
assert!(
tuible_with_state(
&[
"execute",
database,
"CREATE TABLE private_values (value TEXT)",
"--yes",
],
&state_home,
)
.status
.success()
);
assert!(
tuible_with_state(
&["query", database, "SELECT 'sensitive-literal'"],
&state_home,
)
.status
.success()
);
assert!(!state_home.join("tuible/query-history.json").exists());
}
#[test]
fn dynamodb_help_exposes_job_workflows_and_pagination() {
let query = tuible(&["dynamodb", "query", "--help"]);
let put = tuible(&["dynamodb", "put", "--help"]);
let query_help = String::from_utf8(query.stdout).unwrap();
let put_help = String::from_utf8(put.stdout).unwrap();
assert!(query.status.success());
assert!(query_help.contains("--profile"));
assert!(query_help.contains("--index"));
assert!(query_help.contains("--start-key"));
assert!(query_help.contains("--typed-json"));
assert!(put.status.success());
assert!(put_help.contains("--condition"));
assert!(put_help.contains("--yes"));
}
#[test]
fn dynamodb_writes_fail_before_network_without_confirmation() {
let output = tuible(&["dynamodb", "put", "users", r#"{"pk":"USER#1"}"#, "--local"]);
assert!(!output.status.success());
assert!(String::from_utf8_lossy(&output.stderr).contains("--yes"));
}