use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;
fn matchy_cmd() -> Command {
Command::new(assert_cmd::cargo::cargo_bin!("matchy"))
}
#[test]
fn test_build_threatdb_valid_json() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{"key": "evil.com", "data": {"threat_level": "high", "category": "malware", "source": "test-feed"}},
{"key": "192.168.1.0/24", "data": {"threat_level": "medium", "category": "c2", "source": "internal"}},
{"key": "*.bad-domain.org", "data": {"threat_level": "critical", "category": "phishing", "source": "abuse.ch"}}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.arg("--database-type")
.arg("threatdb")
.arg("-v")
.assert()
.success()
.stdout(predicate::str::contains(
"Schema validation: enabled (ThreatDB-v1)",
))
.stdout(predicate::str::contains("Database built"));
}
#[test]
fn test_build_threatdb_with_optional_fields() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{
"key": "evil.com",
"data": {
"threat_level": "high",
"category": "malware",
"source": "test-feed",
"confidence": 85,
"tlp": "AMBER",
"description": "Known malware C2 server",
"tags": ["emotet", "banking-trojan"]
}
}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.arg("--database-type")
.arg("threatdb")
.assert()
.success();
}
#[test]
fn test_build_threatdb_invalid_threat_level() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{"key": "evil.com", "data": {"threat_level": "super-critical", "category": "malware", "source": "test"}}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.arg("--database-type")
.arg("threatdb")
.assert()
.failure()
.stderr(predicate::str::contains("Schema validation failed"))
.stderr(predicate::str::contains("evil.com"));
}
#[test]
fn test_build_threatdb_missing_required_field() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{"key": "evil.com", "data": {"threat_level": "high"}}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.arg("--database-type")
.arg("threatdb")
.assert()
.failure()
.stderr(predicate::str::contains("Schema validation failed"))
.stderr(predicate::str::contains("required"));
}
#[test]
fn test_build_threatdb_invalid_confidence_range() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{"key": "evil.com", "data": {"threat_level": "high", "category": "malware", "source": "test", "confidence": 150}}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.arg("--database-type")
.arg("threatdb")
.assert()
.failure()
.stderr(predicate::str::contains("Schema validation failed"));
}
#[test]
fn test_build_threatdb_invalid_tlp() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{"key": "evil.com", "data": {"threat_level": "high", "category": "malware", "source": "test", "tlp": "purple"}}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.arg("--database-type")
.arg("threatdb")
.assert()
.failure()
.stderr(predicate::str::contains("Schema validation failed"));
}
#[test]
fn test_build_threatdb_valid_csv() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.csv");
let output_file = temp_dir.path().join("test.mxy");
let csv = "key,threat_level,category,source\n\
evil.com,high,malware,test-feed\n\
192.168.1.0/24,medium,c2,internal\n";
fs::write(&input_file, csv).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("csv")
.arg("--database-type")
.arg("threatdb")
.arg("-v")
.assert()
.success()
.stdout(predicate::str::contains("Schema validation: enabled"));
}
#[test]
fn test_build_threatdb_invalid_csv() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.csv");
let output_file = temp_dir.path().join("test.mxy");
let csv = "key,threat_level,category,source\n\
evil.com,super-bad,malware,test-feed\n";
fs::write(&input_file, csv).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("csv")
.arg("--database-type")
.arg("threatdb")
.assert()
.failure()
.stderr(predicate::str::contains("Schema validation failed"));
}
#[test]
fn test_build_custom_type_no_validation() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{"key": "evil.com", "data": {"arbitrary_field": "any_value", "number": 12345}}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.arg("--database-type")
.arg("MyCustomType")
.assert()
.success();
}
#[test]
fn test_build_no_type_no_validation() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{"key": "evil.com", "data": {"anything": "goes"}}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.assert()
.success();
}
#[test]
fn test_inspect_shows_canonical_database_type() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{"key": "evil.com", "data": {"threat_level": "high", "category": "malware", "source": "test"}}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.arg("--database-type")
.arg("threatdb")
.assert()
.success();
matchy_cmd()
.arg("inspect")
.arg(&output_file)
.assert()
.success()
.stdout(predicate::str::contains("ThreatDB-v1"));
}
#[test]
fn test_build_threatdb_additional_properties_allowed() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{
"key": "evil.com",
"data": {
"threat_level": "high",
"category": "malware",
"source": "test",
"custom_field": "custom_value",
"internal_id": 12345
}
}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.arg("--database-type")
.arg("threatdb")
.assert()
.success();
}
#[test]
fn test_build_threatdb_text_format_fails() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("patterns.txt");
let output_file = temp_dir.path().join("test.mxy");
fs::write(&input_file, "evil.com\n192.168.1.1\n").unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("text")
.arg("--database-type")
.arg("threatdb")
.assert()
.failure()
.stderr(predicate::str::contains("Schema validation failed"))
.stderr(predicate::str::contains("required"));
}
#[test]
fn test_validate_threatdb_valid_entries() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{"key": "evil.com", "data": {"threat_level": "high", "category": "malware", "source": "test"}},
{"key": "192.168.1.0/24", "data": {"threat_level": "medium", "category": "c2", "source": "internal"}}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.arg("--database-type")
.arg("threatdb")
.assert()
.success();
matchy_cmd()
.arg("validate")
.arg(&output_file)
.arg("--level")
.arg("strict")
.arg("-v")
.assert()
.success()
.stdout(predicate::str::contains("ThreatDB-v1"))
.stdout(predicate::str::contains("schema"));
}
#[test]
fn test_validate_json_output_includes_schema_stats() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{"key": "evil.com", "data": {"threat_level": "high", "category": "malware", "source": "test"}}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.arg("--database-type")
.arg("threatdb")
.assert()
.success();
matchy_cmd()
.arg("validate")
.arg(&output_file)
.arg("--json")
.assert()
.success()
.stdout(predicate::str::contains(
"\"database_type\": \"ThreatDB-v1\"",
))
.stdout(predicate::str::contains("\"schema_validated\":"))
.stdout(predicate::str::contains("\"schema_entries_checked\":"))
.stdout(predicate::str::contains("\"schema_validation_failures\":"));
}
#[test]
fn test_validate_non_schema_database_no_schema_validation() {
let temp_dir = TempDir::new().unwrap();
let input_file = temp_dir.path().join("data.json");
let output_file = temp_dir.path().join("test.mxy");
let json = r#"[
{"key": "evil.com", "data": {"custom": "data"}}
]"#;
fs::write(&input_file, json).unwrap();
matchy_cmd()
.arg("build")
.arg(&input_file)
.arg("-o")
.arg(&output_file)
.arg("-f")
.arg("json")
.assert()
.success();
matchy_cmd()
.arg("validate")
.arg(&output_file)
.arg("--json")
.assert()
.success()
.stdout(predicate::str::contains("\"schema_validated\": false"));
}