forked from mirrors/kingfisher
1039 lines
31 KiB
Rust
1039 lines
31 KiB
Rust
// tests/cli_validate_revoke.rs
|
|
//
|
|
// CLI tests for the `kingfisher validate` and `kingfisher revoke` commands.
|
|
// These tests validate CLI argument parsing, error messages, and basic functionality
|
|
// without requiring actual network connections or valid credentials.
|
|
|
|
use assert_cmd::Command;
|
|
use predicates::{prelude::PredicateBooleanExt, str::contains};
|
|
use std::fs;
|
|
use tempfile::TempDir;
|
|
|
|
// =============================================================================
|
|
// Validate Command Tests
|
|
// =============================================================================
|
|
|
|
mod validate {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn validate_help() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["validate", "--help"])
|
|
.assert()
|
|
.success()
|
|
.stdout(
|
|
contains("Directly validate a known secret")
|
|
.and(contains("--rule"))
|
|
.and(contains("SECRET")),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn validate_help_shows_all_options() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["validate", "--help"])
|
|
.assert()
|
|
.success()
|
|
.stdout(
|
|
contains("--rule")
|
|
.and(contains("--arg"))
|
|
.and(contains("--var"))
|
|
.and(contains("--timeout"))
|
|
.and(contains("--retries"))
|
|
.and(contains("--rules-path"))
|
|
.and(contains("--no-builtins"))
|
|
.and(contains("--format")),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn validate_requires_rule_flag() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["validate", "test-secret", "--no-update-check"])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("--rule"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_requires_secret() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["validate", "--rule", "opsgenie", "--no-update-check"])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("No secret provided"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_rejects_empty_secret() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["validate", "--rule", "opsgenie", "", "--no-update-check"])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("Secret cannot be empty"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_rejects_unknown_rule() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"nonexistent.rule.xyz",
|
|
"test-secret",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("No rule found matching"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_accepts_rule_prefix() {
|
|
// Should find rules matching a prefix like "opsgenie"
|
|
// The actual validation will fail but the rule should be found
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["validate", "--rule", "opsgenie", "fake-api-key-12345", "--no-update-check"])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| {
|
|
// Exit 1 means validation failed (expected with fake key)
|
|
// Exit 0 would mean valid (unexpected but possible)
|
|
*code == 0 || *code == 1
|
|
}))
|
|
.stdout(contains("OpsGenie").or(contains("opsgenie")));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_accepts_full_rule_id() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"kingfisher.opsgenie.1",
|
|
"fake-api-key-12345",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_json_output() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"opsgenie",
|
|
"fake-api-key-12345",
|
|
"--format",
|
|
"json",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1))
|
|
.stdout(
|
|
contains("rule_id")
|
|
.and(contains("rule_name"))
|
|
.and(contains("is_valid"))
|
|
.and(contains("message")),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn validate_text_output() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"opsgenie",
|
|
"fake-api-key-12345",
|
|
"--format",
|
|
"text",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1))
|
|
.stdout(contains("Rule:").and(contains("Result:")));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_with_timeout() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"opsgenie",
|
|
"fake-api-key",
|
|
"--timeout",
|
|
"5",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_rejects_invalid_timeout() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"opsgenie",
|
|
"fake-api-key",
|
|
"--timeout",
|
|
"100",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("100").or(contains("invalid")).or(contains("range")));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_with_retries() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"opsgenie",
|
|
"fake-api-key",
|
|
"--retries",
|
|
"3",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_rejects_invalid_retries() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"opsgenie",
|
|
"fake-api-key",
|
|
"--retries",
|
|
"10",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("10").or(contains("invalid")).or(contains("range")));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_with_var_flag() {
|
|
// AWS validation requires AKID variable
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"aws",
|
|
"--var",
|
|
"AKID=AKIAIOSFODNN7EXAMPLE",
|
|
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_with_arg_flag() {
|
|
// AWS validation with --arg (auto-assigns to AKID)
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"aws",
|
|
"--arg",
|
|
"AKIAIOSFODNN7EXAMPLE",
|
|
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_rejects_invalid_var_format() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"aws",
|
|
"--var",
|
|
"INVALID_FORMAT_NO_EQUALS",
|
|
"fake-secret",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("Invalid variable format").or(contains("NAME=VALUE")));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_rule_without_validation() {
|
|
// Create a temporary rule without validation
|
|
let tmp = TempDir::new().unwrap();
|
|
fs::write(
|
|
tmp.path().join("no_validation.yml"),
|
|
r#"
|
|
rules:
|
|
- name: No Validation Rule
|
|
id: test.no.validation
|
|
pattern: "test_pattern_[a-z0-9]{4}"
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"test.no.validation",
|
|
"test_pattern_abcd",
|
|
"--rules-path",
|
|
tmp.path().to_str().unwrap(),
|
|
"--no-builtins",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("No rules with validation found"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_no_builtins_with_custom_rule() {
|
|
let tmp = TempDir::new().unwrap();
|
|
fs::write(
|
|
tmp.path().join("custom_rule.yml"),
|
|
r#"
|
|
rules:
|
|
- name: Custom HTTP Rule
|
|
id: test.custom.http
|
|
pattern: "custom_[a-z0-9]{8}"
|
|
validation:
|
|
type: Http
|
|
content:
|
|
request:
|
|
method: GET
|
|
url: "https://httpbin.org/status/401"
|
|
response_matcher:
|
|
- status:
|
|
- 200
|
|
type: StatusMatch
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"test.custom.http",
|
|
"custom_12345678",
|
|
"--rules-path",
|
|
tmp.path().to_str().unwrap(),
|
|
"--no-builtins",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1))
|
|
.stdout(contains("Custom HTTP Rule"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_missing_required_variable() {
|
|
// AWS validation requires AKID - should fail if not provided
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"aws",
|
|
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("AKID").or(contains("variable")));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_too_many_args() {
|
|
// OpsGenie only needs TOKEN, no additional variables
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"opsgenie",
|
|
"--arg",
|
|
"extra1",
|
|
"--arg",
|
|
"extra2",
|
|
"fake-api-key",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("Too many --arg"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_mongodb_with_connection_uri() {
|
|
// MongoDB validation expects a connection URI
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"mongodb",
|
|
"mongodb://user:pass@localhost:27017/test",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_postgres_with_connection_url() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"postgres",
|
|
"postgres://user:pass@localhost:5432/test",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_mysql_with_connection_url() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"mysql",
|
|
"mysql://user:pass@localhost:3306/test",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_jdbc_with_connection_string() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"jdbc",
|
|
"jdbc:postgresql://localhost:5432/test?user=admin&password=secret",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_jwt_token() {
|
|
// A fake JWT token (will fail validation but tests the flow)
|
|
let fake_jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U";
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["validate", "--rule", "jwt", fake_jwt, "--no-update-check"])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_format_invalid_value() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"opsgenie",
|
|
"fake-key",
|
|
"--format",
|
|
"yaml",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("yaml").or(contains("invalid")));
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Revoke Command Tests
|
|
// =============================================================================
|
|
|
|
mod revoke {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn revoke_help() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["revoke", "--help"])
|
|
.assert()
|
|
.success()
|
|
.stdout(
|
|
contains("Directly revoke a known secret")
|
|
.and(contains("--rule"))
|
|
.and(contains("SECRET")),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_help_shows_all_options() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["revoke", "--help"])
|
|
.assert()
|
|
.success()
|
|
.stdout(
|
|
contains("--rule")
|
|
.and(contains("--arg"))
|
|
.and(contains("--var"))
|
|
.and(contains("--timeout"))
|
|
.and(contains("--retries"))
|
|
.and(contains("--rules-path"))
|
|
.and(contains("--no-builtins"))
|
|
.and(contains("--format")),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_requires_rule_flag() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["revoke", "test-secret", "--no-update-check"])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("--rule"));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_requires_secret() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["revoke", "--rule", "slack", "--no-update-check"])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("No secret provided"));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_rejects_empty_secret() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["revoke", "--rule", "slack", "", "--no-update-check"])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("Secret cannot be empty"));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_rejects_unknown_rule() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["revoke", "--rule", "nonexistent.rule.xyz", "test-secret", "--no-update-check"])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("No rule found matching"));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_accepts_rule_prefix() {
|
|
// Slack has revocation support
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["revoke", "--rule", "slack", "xoxb-fake-token-12345", "--no-update-check"])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1))
|
|
.stdout(contains("Slack").or(contains("slack")));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_accepts_full_rule_id() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"kingfisher.slack.1",
|
|
"xoxb-fake-token",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_json_output() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"slack",
|
|
"xoxb-fake-token",
|
|
"--format",
|
|
"json",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1))
|
|
.stdout(
|
|
contains("rule_id")
|
|
.and(contains("rule_name"))
|
|
.and(contains("revoked"))
|
|
.and(contains("message")),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_text_output() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"slack",
|
|
"xoxb-fake-token",
|
|
"--format",
|
|
"text",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1))
|
|
.stdout(contains("Rule:").and(contains("Result:")));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_with_timeout() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"slack",
|
|
"xoxb-fake-token",
|
|
"--timeout",
|
|
"5",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_rejects_invalid_timeout() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"slack",
|
|
"xoxb-fake-token",
|
|
"--timeout",
|
|
"100",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("100").or(contains("invalid")).or(contains("range")));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_with_retries() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"slack",
|
|
"xoxb-fake-token",
|
|
"--retries",
|
|
"3",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_rejects_invalid_retries() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"slack",
|
|
"xoxb-fake-token",
|
|
"--retries",
|
|
"10",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("10").or(contains("invalid")).or(contains("range")));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_with_var_flag() {
|
|
// AWS revocation requires AKID variable
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"aws",
|
|
"--var",
|
|
"AKID=AKIAIOSFODNN7EXAMPLE",
|
|
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_with_arg_flag() {
|
|
// AWS revocation with --arg (auto-assigns to AKID)
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"aws",
|
|
"--arg",
|
|
"AKIAIOSFODNN7EXAMPLE",
|
|
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_rejects_invalid_var_format() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"aws",
|
|
"--var",
|
|
"INVALID_FORMAT_NO_EQUALS",
|
|
"fake-secret",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("Invalid variable format").or(contains("NAME=VALUE")));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_rule_without_revocation() {
|
|
// Create a temporary rule without revocation
|
|
let tmp = TempDir::new().unwrap();
|
|
fs::write(
|
|
tmp.path().join("no_revocation.yml"),
|
|
r#"
|
|
rules:
|
|
- name: No Revocation Rule
|
|
id: test.no.revocation
|
|
pattern: "test_pattern_[a-z0-9]{4}"
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"test.no.revocation",
|
|
"test_pattern_abcd",
|
|
"--rules-path",
|
|
tmp.path().to_str().unwrap(),
|
|
"--no-builtins",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("No rules with revocation found"));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_gcp_with_service_account_json() {
|
|
// GCP revocation expects service account JSON
|
|
let fake_sa_json =
|
|
r#"{"type":"service_account","project_id":"test","private_key_id":"key123"}"#;
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["revoke", "--rule", "gcp", fake_sa_json, "--no-update-check"])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_github_token() {
|
|
// GitHub has revocation support
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"github",
|
|
"ghp_fake1234567890abcdefghijklmnopqrstuvw",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_gitlab_token() {
|
|
// GitLab has revocation support
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"gitlab",
|
|
"glpat-fake1234567890abcdefgh",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_missing_required_variable() {
|
|
// AWS revocation requires AKID - should fail if not provided
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"aws",
|
|
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("AKID").or(contains("variable")));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_format_invalid_value() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"revoke",
|
|
"--rule",
|
|
"slack",
|
|
"xoxb-fake-token",
|
|
"--format",
|
|
"xml",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(contains("xml").or(contains("invalid")));
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Shared/Cross-Command Tests
|
|
// =============================================================================
|
|
|
|
mod shared {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn validate_and_revoke_exist_as_subcommands() {
|
|
// Verify both commands show up in main help
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.arg("--help")
|
|
.assert()
|
|
.success()
|
|
.stdout(contains("validate").and(contains("revoke")));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_accepts_stdin_marker() {
|
|
// Test that '-' is accepted as stdin marker (the actual read will fail in test)
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["validate", "--help"])
|
|
.assert()
|
|
.success()
|
|
.stdout(contains("stdin").or(contains("-")));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_accepts_stdin_marker() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["revoke", "--help"])
|
|
.assert()
|
|
.success()
|
|
.stdout(contains("stdin").or(contains("-")));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_with_custom_rules_path() {
|
|
let tmp = TempDir::new().unwrap();
|
|
fs::write(
|
|
tmp.path().join("custom.yml"),
|
|
r#"
|
|
rules:
|
|
- name: Custom Validate Rule
|
|
id: custom.validate.test
|
|
pattern: "customval_[a-z0-9]{4}"
|
|
validation:
|
|
type: Http
|
|
content:
|
|
request:
|
|
method: GET
|
|
url: "https://httpbin.org/status/200"
|
|
response_matcher:
|
|
- status:
|
|
- 200
|
|
type: StatusMatch
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"custom.validate.test",
|
|
"customval_abcd",
|
|
"--rules-path",
|
|
tmp.path().to_str().unwrap(),
|
|
"--no-builtins",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1))
|
|
.stdout(contains("Custom Validate Rule"));
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_rules_path_flags() {
|
|
let tmp1 = TempDir::new().unwrap();
|
|
let tmp2 = TempDir::new().unwrap();
|
|
|
|
fs::write(
|
|
tmp1.path().join("rule1.yml"),
|
|
r#"
|
|
rules:
|
|
- name: Rule One
|
|
id: multi.path.one
|
|
pattern: "ruleone_[a-z]{4}"
|
|
validation:
|
|
type: Http
|
|
content:
|
|
request:
|
|
method: GET
|
|
url: "https://httpbin.org/status/200"
|
|
response_matcher:
|
|
- status:
|
|
- 200
|
|
type: StatusMatch
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
fs::write(
|
|
tmp2.path().join("rule2.yml"),
|
|
r#"
|
|
rules:
|
|
- name: Rule Two
|
|
id: multi.path.two
|
|
pattern: "ruletwo_[a-z]{4}"
|
|
validation:
|
|
type: Http
|
|
content:
|
|
request:
|
|
method: GET
|
|
url: "https://httpbin.org/status/200"
|
|
response_matcher:
|
|
- status:
|
|
- 200
|
|
type: StatusMatch
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
// Should be able to find rules from both paths
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"validate",
|
|
"--rule",
|
|
"multi.path.one",
|
|
"ruleone_abcd",
|
|
"--rules-path",
|
|
tmp1.path().to_str().unwrap(),
|
|
"--rules-path",
|
|
tmp2.path().to_str().unwrap(),
|
|
"--no-builtins",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1))
|
|
.stdout(contains("Rule One"));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_with_verbose_flag() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"--verbose",
|
|
"validate",
|
|
"--rule",
|
|
"opsgenie",
|
|
"fake-api-key",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_with_verbose_flag() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"--verbose",
|
|
"revoke",
|
|
"--rule",
|
|
"slack",
|
|
"xoxb-fake-token",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_with_quiet_flag() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"--quiet",
|
|
"validate",
|
|
"--rule",
|
|
"opsgenie",
|
|
"fake-api-key",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn revoke_with_quiet_flag() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args(["--quiet", "revoke", "--rule", "slack", "xoxb-fake-token", "--no-update-check"])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
|
|
#[test]
|
|
fn validate_with_tls_lax_flag() {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("kingfisher"))
|
|
.args([
|
|
"--tls-mode",
|
|
"lax",
|
|
"validate",
|
|
"--rule",
|
|
"opsgenie",
|
|
"fake-api-key",
|
|
"--no-update-check",
|
|
])
|
|
.assert()
|
|
.code(predicates::function::function(|code: &i32| *code == 0 || *code == 1));
|
|
}
|
|
}
|