mod common;
use common::{rsigma, temp_file};
use predicates::prelude::*;
const RULE: &str = "title: Test\nlogsource:\n category: test\ndetection:\n sel:\n foo: bar\n condition: sel\n";
#[test]
fn init_writes_template_that_validates_clean() {
let dir = tempfile::tempdir().unwrap();
let cfg = dir.path().join("rsigma.yaml");
rsigma()
.args(["config", "init", "-o", cfg.to_str().unwrap()])
.assert()
.success();
assert!(cfg.exists(), "init should write the template");
rsigma()
.args(["config", "validate", "-c", cfg.to_str().unwrap()])
.assert()
.success()
.stderr(predicate::str::contains("Config is valid."));
let output = rsigma()
.args([
"config",
"validate",
"-c",
cfg.to_str().unwrap(),
"--format",
"json",
])
.assert()
.success()
.get_output()
.stdout
.clone();
let v: serde_json::Value = serde_json::from_slice(&output).unwrap();
assert_eq!(v["ok"], serde_json::json!(true));
assert!(
v["unknown_keys"].as_array().unwrap().is_empty(),
"the committed template must not carry unknown keys"
);
}
#[test]
fn init_refuses_overwrite_without_force() {
let dir = tempfile::tempdir().unwrap();
let cfg = dir.path().join("rsigma.yaml");
std::fs::write(&cfg, "version: 1\n").unwrap();
rsigma()
.args(["config", "init", "-o", cfg.to_str().unwrap()])
.assert()
.failure()
.code(3)
.stderr(predicate::str::contains("refusing to overwrite"));
rsigma()
.args(["config", "init", "-o", cfg.to_str().unwrap(), "--force"])
.assert()
.success();
}
#[test]
fn validate_warns_unknown_keys_and_strict_fails() {
let cfg = temp_file(".yaml", "version: 1\nbogus_key: true\n");
let path = cfg.path().to_str().unwrap();
rsigma()
.args(["config", "validate", "-c", path])
.assert()
.success()
.stderr(predicate::str::contains("unknown key 'bogus_key'"));
rsigma()
.args(["config", "validate", "-c", path, "--strict"])
.assert()
.failure()
.code(3);
}
#[test]
fn validate_missing_explicit_file_errors() {
rsigma()
.args([
"config",
"validate",
"-c",
"/tmp/no_such_rsigma_config.yaml",
])
.assert()
.failure()
.code(3)
.stderr(predicate::str::contains("could not read config"));
}
#[test]
fn schema_emits_json_schema() {
rsigma()
.args(["config", "schema"])
.assert()
.success()
.stdout(predicate::str::contains("\"$schema\""))
.stdout(predicate::str::contains("RsigmaConfigPartial"));
}
#[test]
fn show_json_reports_value_sources() {
let cfg = temp_file(".yaml", "daemon:\n api:\n addr: \"9.9.9.9:1\"\n");
let output = rsigma()
.args([
"config",
"show",
"-c",
cfg.path().to_str().unwrap(),
"--for",
"daemon",
"--format",
"json",
])
.assert()
.success()
.get_output()
.stdout
.clone();
let v: serde_json::Value = serde_json::from_slice(&output).unwrap();
assert_eq!(
v["config"]["daemon"]["api"]["addr"],
serde_json::json!("9.9.9.9:1")
);
assert_eq!(v["sources"]["daemon.api.addr"], serde_json::json!("file"));
assert_eq!(
v["sources"]["daemon.input.batch_size"],
serde_json::json!("default")
);
}
#[test]
fn path_prints_explicit_config() {
let cfg = temp_file(".yaml", "version: 1\n");
rsigma()
.args(["config", "path", "-c", cfg.path().to_str().unwrap()])
.assert()
.success()
.stdout(predicate::str::contains(cfg.path().to_str().unwrap()));
}
#[test]
fn eval_reads_rules_from_config() {
let dir = tempfile::tempdir().unwrap();
let rule = dir.path().join("rule.yml");
std::fs::write(&rule, RULE).unwrap();
let cfg = dir.path().join("rsigma.yaml");
std::fs::write(&cfg, format!("eval:\n rules: {}\n", rule.display())).unwrap();
rsigma()
.args(["engine", "eval", "--config", cfg.to_str().unwrap()])
.write_stdin("{\"foo\":\"bar\"}\n")
.assert()
.success()
.stdout(predicate::str::contains("\"rule_title\":\"Test\""));
}
#[test]
fn eval_flag_overrides_config_rules() {
let dir = tempfile::tempdir().unwrap();
let rule = dir.path().join("rule.yml");
std::fs::write(&rule, RULE).unwrap();
let cfg = dir.path().join("rsigma.yaml");
std::fs::write(&cfg, "eval:\n rules: /nonexistent/bogus/path\n").unwrap();
rsigma()
.args([
"engine",
"eval",
"--config",
cfg.to_str().unwrap(),
"--rules",
rule.to_str().unwrap(),
])
.write_stdin("{\"foo\":\"bar\"}\n")
.assert()
.success()
.stdout(predicate::str::contains("\"rule_title\":\"Test\""));
}
#[cfg(feature = "daemon")]
#[test]
fn daemon_dry_run_uses_config_values() {
let cfg = temp_file(
".yaml",
"daemon:\n rules: /tmp/myrules\n api:\n addr: \"1.2.3.4:5\"\n",
);
rsigma()
.args([
"engine",
"daemon",
"--config",
cfg.path().to_str().unwrap(),
"--dry-run",
])
.assert()
.success()
.stdout(predicate::str::contains("rules: /tmp/myrules"))
.stdout(predicate::str::contains("addr: 1.2.3.4:5"));
}
#[cfg(feature = "daemon")]
#[test]
fn daemon_egress_policy_reads_from_config() {
let cfg = temp_file(
".yaml",
"daemon:\n rules: /tmp/myrules\n engine:\n egress_policy: strict\n",
);
rsigma()
.args([
"engine",
"daemon",
"--config",
cfg.path().to_str().unwrap(),
"--dry-run",
])
.assert()
.success()
.stdout(predicate::str::contains("egress_policy: strict"));
}
#[cfg(feature = "daemon")]
#[test]
fn daemon_rejects_invalid_egress_policy_value() {
rsigma()
.args([
"engine",
"daemon",
"--rules",
"/tmp/nope",
"--egress-policy",
"yolo",
])
.assert()
.failure()
.stderr(predicate::str::contains("egress-policy"));
}
#[cfg(feature = "daemon")]
#[test]
fn invalid_global_output_format_in_config_warns_then_falls_back() {
let cfg = temp_file(
".yaml",
"global:\n output_format: xml\ndaemon:\n rules: /tmp/myrules\n",
);
rsigma()
.args([
"engine",
"daemon",
"--config",
cfg.path().to_str().unwrap(),
"--dry-run",
])
.assert()
.success()
.stderr(
predicate::str::contains("invalid global.output_format 'xml'")
.and(predicate::str::contains("falling back to default")),
);
}
#[cfg(feature = "daemon")]
#[test]
fn invalid_global_color_in_config_warns_then_falls_back() {
let cfg = temp_file(
".yaml",
"global:\n color: rainbow\ndaemon:\n rules: /tmp/myrules\n",
);
rsigma()
.args([
"engine",
"daemon",
"--config",
cfg.path().to_str().unwrap(),
"--dry-run",
])
.assert()
.success()
.stderr(predicate::str::contains("invalid global.color 'rainbow'"));
}
#[cfg(feature = "daemon")]
#[test]
fn valid_global_output_in_config_emits_no_warning() {
let cfg = temp_file(
".yaml",
"global:\n output_format: ndjson\n color: never\ndaemon:\n rules: /tmp/myrules\n",
);
rsigma()
.args([
"engine",
"daemon",
"--config",
cfg.path().to_str().unwrap(),
"--dry-run",
])
.assert()
.success()
.stderr(
predicate::str::contains("invalid global.output_format")
.not()
.and(predicate::str::contains("invalid global.color").not()),
);
}