#![allow(unused_crate_dependencies)]
use assert_cmd::Command;
use clap::CommandFactory;
use influxdb3_plugin_cli::PluginConfig;
const ENV_PREFIX: &str = "INFLUXDB3_PLUGIN_";
fn collect_offending_envs(cmd: &clap::Command, into: &mut Vec<String>) {
for arg in cmd.get_arguments() {
if let Some(env) = arg.get_env() {
let name = env.to_string_lossy().into_owned();
if !name.starts_with(ENV_PREFIX) {
into.push(format!("{} (arg `{}`)", name, arg.get_id().as_str()));
}
}
}
for sub in cmd.get_subcommands() {
collect_offending_envs(sub, into);
}
}
#[test]
fn every_env_var_binding_uses_influxdb3_plugin_prefix() {
let mut offenders = Vec::new();
collect_offending_envs(&PluginConfig::command(), &mut offenders);
assert!(
offenders.is_empty(),
"every clap `env = ...` binding must start with `{ENV_PREFIX}`. \
Offenders: {offenders:?}"
);
}
#[test]
fn clap_workspace_pin_meets_floor() {
const FLOOR: (u64, u64, u64) = (4, 5, 47);
let workspace_toml = include_str!("../../Cargo.toml");
let parsed: toml::Value = toml::from_str(workspace_toml).expect("workspace Cargo.toml is TOML");
let clap_dep = parsed
.get("workspace")
.and_then(|w| w.get("dependencies"))
.and_then(|d| d.get("clap"))
.expect("workspace.dependencies.clap entry must exist");
let version_str = match clap_dep {
toml::Value::String(s) => s.clone(),
toml::Value::Table(t) => t
.get("version")
.and_then(|v| v.as_str())
.expect("clap dep table must carry version")
.to_owned(),
other => panic!("unexpected clap dep shape: {other:?}"),
};
let parts: Vec<u64> = version_str
.trim_start_matches([' ', '=', '>', '^', '~'])
.split('.')
.map(|p| p.parse::<u64>().expect("version components are numeric"))
.collect();
assert!(
parts.len() >= 3,
"clap version {version_str:?} needs major.minor.patch"
);
let actual = (parts[0], parts[1], parts[2]);
assert!(
actual >= FLOOR,
"workspace clap pin {version_str:?} ({actual:?}) is below floor {FLOOR:?}"
);
}
#[test]
fn json_stdout_emits_no_ansi_under_force_color() {
let td = tempfile::tempdir().unwrap();
let dir = td.path().join("p");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(
dir.join("manifest.toml"),
"manifest_schema_version = \"1.0\"\n\n\
[plugin]\nname = \"p\"\nversion = \"1.0.0\"\n\
description = \"x\"\ntriggers = [\"process_writes\"]\n\n\
[dependencies]\ndatabase_version = \">=3.0.0\"\n",
)
.unwrap();
std::fs::write(
dir.join("__init__.py"),
"def process_writes(a, b, c):\n pass\n",
)
.unwrap();
let assert = Command::cargo_bin("influxdb3-plugin")
.unwrap()
.env("FORCE_COLOR", "1")
.args(["validate", "--output", "json"])
.arg(&dir)
.assert()
.success();
let stdout = assert.get_output().stdout.clone();
assert!(
!stdout.windows(2).any(|w| w == [0x1b, b'[']),
"stdout must not contain ANSI escape sequences in --output json mode \
(absolute rule), got: {:?}",
String::from_utf8_lossy(&stdout)
);
}
#[test]
fn ci_env_plus_pipe_yields_json_stdout() {
let td = tempfile::tempdir().unwrap();
let target = td.path().join("p");
let assert = Command::cargo_bin("influxdb3-plugin")
.unwrap()
.env("CI", "true")
.args(["new", "process_writes"])
.arg(&target)
.assert()
.success();
let stdout = String::from_utf8_lossy(&assert.get_output().stdout).into_owned();
let _: serde_json::Value = serde_json::from_str(&stdout)
.unwrap_or_else(|e| panic!("stdout failed to parse as JSON: {e}\n{stdout}"));
}
#[test]
fn observed_exit_codes_are_in_documented_set() {
let td = tempfile::tempdir().unwrap();
let zero = Command::cargo_bin("influxdb3-plugin")
.unwrap()
.arg("--version")
.assert()
.success();
assert_eq!(zero.get_output().status.code(), Some(0));
let two = Command::cargo_bin("influxdb3-plugin")
.unwrap()
.arg("garbage_subcommand")
.assert()
.failure();
assert_eq!(two.get_output().status.code(), Some(2));
let one = Command::cargo_bin("influxdb3-plugin")
.unwrap()
.args(["yank", "p@1.0.0"])
.arg("--index")
.arg(td.path().join("nonexistent.json"))
.arg("--out")
.arg(td.path().join("out"))
.assert()
.failure();
assert_eq!(one.get_output().status.code(), Some(1));
}
#[test]
fn help_text_snapshots() {
for (name, args) in [
("top", &["--help"][..]),
("new", &["new", "--help"]),
("validate", &["validate", "--help"]),
("package", &["package", "--help"]),
("yank", &["yank", "--help"]),
("search", &["search", "--help"]),
("info", &["info", "--help"]),
] {
let assert = Command::cargo_bin("influxdb3-plugin")
.unwrap()
.args(args)
.assert()
.success();
let stdout = String::from_utf8_lossy(&assert.get_output().stdout).into_owned();
insta::assert_snapshot!(format!("help_{name}"), stdout);
}
}
fn assert_json_error_envelope(output: &std::process::Output) {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.is_empty(),
"stderr must be empty in JSON-mode envelope dispatch; got:\n{stderr}"
);
let doc: serde_json::Value = serde_json::from_str(&stdout)
.unwrap_or_else(|e| panic!("stdout must be valid JSON: {e}\n{stdout}"));
assert_eq!(
doc.get("status").and_then(|v| v.as_str()),
Some("error"),
"envelope status must be \"error\"; got:\n{stdout}"
);
assert!(
doc.get("error").is_some(),
"envelope must carry an \"error\" field; got:\n{stdout}"
);
}
#[test]
fn json_mode_usage_error_emits_envelope_for_new() {
let assert = Command::cargo_bin("influxdb3-plugin")
.unwrap()
.args(["new", "not_a_template", "--output", "json"])
.assert()
.failure();
assert_eq!(assert.get_output().status.code(), Some(2));
assert_json_error_envelope(assert.get_output());
}
#[test]
fn ci_env_triggers_json_envelope_for_usage_errors() {
let assert = Command::cargo_bin("influxdb3-plugin")
.unwrap()
.env("CI", "true")
.args(["new", "not_a_template"])
.assert()
.failure();
assert_eq!(assert.get_output().status.code(), Some(2));
assert_json_error_envelope(assert.get_output());
}
#[test]
fn json_mode_validate_unknown_flag_emits_envelope() {
let assert = Command::cargo_bin("influxdb3-plugin")
.unwrap()
.args(["validate", "--nope", "--output", "json"])
.assert()
.failure();
assert_json_error_envelope(assert.get_output());
}
#[test]
fn json_mode_package_missing_required_emits_envelope() {
let assert = Command::cargo_bin("influxdb3-plugin")
.unwrap()
.args(["package", "--output", "json"])
.assert()
.failure();
assert_json_error_envelope(assert.get_output());
}
#[test]
fn explicit_human_mode_preserves_multi_line_clap_output() {
let assert = Command::cargo_bin("influxdb3-plugin")
.unwrap()
.args(["new", "not_a_template", "--output", "human"])
.assert()
.failure();
let stderr = String::from_utf8_lossy(&assert.get_output().stderr);
assert!(
stderr.contains("For more information"),
"human mode must preserve clap's full diagnostic (with help footer); got:\n{stderr}"
);
}
#[test]
fn no_production_path_emits_cli_unknown() {
let cases: &[&[&str]] = &[
&["package"],
&["yank"],
&["validate", "/path/that/definitely/does/not/exist/plugin"],
&["new", "list", "--output", "json"],
];
for argv in cases {
let mut cmd = Command::cargo_bin("influxdb3-plugin").unwrap();
cmd.args(*argv).env("CI", "true");
let out = cmd.output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
!stdout.contains(r#""cli::unknown""#),
"argv {argv:?} produced a cli::unknown envelope; \
a typed CliError is missing somewhere. stdout:\n{stdout}"
);
}
}
#[test]
fn sdk_error_mapping_must_not_fallback_to_cli_unknown() {
let source = include_str!("../src/output/error_mapping.rs");
let body = source
.split_once("pub(crate) fn json_error_from_sdk")
.and_then(|(_, rest)| rest.split_once("\n}\n\n#[cfg(test)]"))
.map(|(body, _)| body)
.expect("json_error_from_sdk body should be locatable");
assert!(
!body.contains("\"cli::unknown\""),
"json_error_from_sdk still contains a cli::unknown fallback; \
future SDK variants can silently lose typed JSON error codes"
);
}
#[test]
fn validate_smoke_tests_must_not_allow_cli_namespace_error_codes() {
let source = include_str!("validate_smoke.rs");
let forbidden_prefix = concat!("cli", "::");
let forbidden = format!("starts_with({forbidden_prefix:?})");
assert!(
!source.contains(&forbidden),
"validate_smoke.rs still permits cli:: error codes; tighten those \
assertions before fixing the mapper"
);
}