#![cfg(all(test, feature = "attachment_scan"))]
#[cfg(test)]
mod cached_scanner_coverage_tests {
use crate::config::config_file::YaraSection;
use crate::utils::cached_scanner::CachedYaraScanner;
use std::fs;
use tempfile::tempdir;
fn create_test_yara_section(
rules_source: &str,
rules_dir: &str,
cache_file: &str,
cache_version_file: &str,
) -> YaraSection {
YaraSection::new(
rules_source.to_string(),
rules_dir.to_string(),
cache_file.to_string(),
cache_version_file.to_string(),
)
}
#[tokio::test]
async fn test_scanner_from_config_with_zip_source() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version marker");
fs::write(
rules_dir.join("test_rule.yar"),
r#"
rule TestRule {
strings:
$a = "test-pattern"
condition:
$a
}
"#,
)
.expect("write yara rule");
let section = create_test_yara_section(
"https://example.com/rules.zip",
"rules",
"yara_rules.cache",
"yara_rules.cache.version",
);
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir.clone())
.await
.expect("scanner builds");
let matches = scanner
.scan_buffer(b"test-pattern")
.expect("scan buffer succeeds");
assert!(matches.contains(&"TestRule".to_string()));
}
#[tokio::test]
async fn test_scanner_from_config_with_git_source() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(
rules_dir.join("git_rule.yar"),
r#"
rule GitRule {
strings:
$b = "git-pattern"
condition:
$b
}
"#,
)
.expect("write yara rule");
let section = create_test_yara_section(
"https://example.com/rules.git",
"rules",
"cache.bin",
"cache.version",
);
let result = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir.clone()).await;
assert!(result.is_ok() || result.is_err()); }
#[tokio::test]
async fn test_scanner_scan_file_not_found() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("minimal.yar"),
r#"
rule Minimal {
condition:
false
}
"#,
)
.expect("write rule");
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir)
.await
.expect("scanner builds");
let result = scanner.scan_file("/nonexistent/path/file.txt");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("not found"));
}
#[tokio::test]
async fn test_scanner_scan_file_success() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("file_rule.yar"),
r#"
rule FileRule {
strings:
$magic = "MAGIC"
condition:
$magic
}
"#,
)
.expect("write rule");
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir.clone())
.await
.expect("scanner builds");
let test_file = temp_home.path().join("test_file.bin");
fs::write(&test_file, b"MAGIC data here").expect("write test file");
let matches = scanner.scan_file(&test_file).expect("scan succeeds");
assert!(matches.contains(&"FileRule".to_string()));
}
#[tokio::test]
async fn test_scanner_scan_buffer_no_matches() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("nomatch.yar"),
r#"
rule NoMatch {
strings:
$x = "WILL_NOT_MATCH"
condition:
$x
}
"#,
)
.expect("write rule");
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir)
.await
.expect("scanner builds");
let matches = scanner
.scan_buffer(b"different data")
.expect("scan succeeds");
assert_eq!(matches.len(), 0);
}
#[tokio::test]
async fn test_scanner_multiple_rules_match() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("multi.yar"),
r#"
rule Rule1 {
strings:
$a = "FOO"
condition:
$a
}
rule Rule2 {
strings:
$b = "BAR"
condition:
$b
}
rule Rule3 {
strings:
$c = "BAZ"
condition:
$c
}
"#,
)
.expect("write rules");
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir)
.await
.expect("scanner builds");
let matches = scanner
.scan_buffer(b"FOO and BAR data")
.expect("scan succeeds");
assert!(matches.contains(&"Rule1".to_string()));
assert!(matches.contains(&"Rule2".to_string()));
assert!(!matches.contains(&"Rule3".to_string()));
assert_eq!(matches.len(), 2);
}
#[tokio::test]
async fn test_scanner_cache_reuse() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("cache_test.yar"),
r#"
rule CacheTest {
strings:
$s = "cached"
condition:
$s
}
"#,
)
.expect("write rule");
let section = create_test_yara_section("test.zip", "rules", "cache.bin", "cache.ver");
let scanner1 = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir.clone())
.await
.expect("scanner 1 builds");
let matches1 = scanner1.scan_buffer(b"cached").expect("scan 1 succeeds");
assert!(matches1.contains(&"CacheTest".to_string()));
assert!(base_dir.join("cache.bin").exists());
assert!(base_dir.join("cache.ver").exists());
drop(scanner1);
let scanner2 = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir.clone())
.await
.expect("scanner 2 builds from cache");
let matches2 = scanner2.scan_buffer(b"cached").expect("scan 2 succeeds");
assert!(matches2.contains(&"CacheTest".to_string()));
}
#[tokio::test]
async fn test_scanner_with_custom_config_paths() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("custom-rules-dir");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "abcdef1234567890").expect("write version");
fs::write(
rules_dir.join("custom.yar"),
r#"
rule CustomPath {
condition:
true
}
"#,
)
.expect("write rule");
let section = create_test_yara_section(
"custom.zip",
"custom-rules-dir",
"custom.cache",
"custom.version",
);
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir.clone())
.await
.expect("scanner with custom paths builds");
let matches = scanner.scan_buffer(b"anything").expect("scan succeeds");
assert!(matches.contains(&"CustomPath".to_string()));
assert!(base_dir.join("custom.cache").exists());
assert!(base_dir.join("custom.version").exists());
}
#[tokio::test]
async fn test_scanner_with_empty_rules_directory() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("empty");
fs::create_dir_all(&rules_dir).expect("create rules dir");
let section = create_test_yara_section("test.zip", "empty", "cache", "cache.ver");
let result = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_scanner_skips_invalid_rules() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("valid.yar"),
r#"
rule Valid {
condition:
true
}
"#,
)
.expect("write valid rule");
fs::write(
rules_dir.join("invalid.yar"),
r#"
rule Invalid {
this is not valid yara syntax
}
"#,
)
.expect("write invalid rule");
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir)
.await
.expect("scanner builds despite invalid rule");
let matches = scanner.scan_buffer(b"test").expect("scan succeeds");
assert!(matches.contains(&"Valid".to_string()));
}
#[tokio::test]
async fn test_scanner_with_yara_files_only() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("test1.yara"),
r#"
rule YaraExt {
condition:
true
}
"#,
)
.expect("write .yara file");
fs::write(
rules_dir.join("test2.yar"),
r#"
rule YarExt {
condition:
true
}
"#,
)
.expect("write .yar file");
fs::write(rules_dir.join("readme.txt"), "This is not a YARA file")
.expect("write txt file");
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir)
.await
.expect("scanner builds");
let matches = scanner.scan_buffer(b"test").expect("scan succeeds");
assert_eq!(matches.len(), 2);
assert!(matches.contains(&"YaraExt".to_string()));
assert!(matches.contains(&"YarExt".to_string()));
}
#[tokio::test]
async fn test_scanner_force_recompile() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("test.yar"),
r#"
rule Test {
condition:
true
}
"#,
)
.expect("write rule");
let cache_file = base_dir.join("test.cache");
let version_file = base_dir.join("test.version");
let section = create_test_yara_section("test.zip", "rules", "test.cache", "test.version");
let _scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir.clone())
.await
.expect("scanner builds");
assert!(cache_file.exists());
assert!(version_file.exists());
}
#[tokio::test]
async fn test_scanner_with_subdirectories() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
let subdir = rules_dir.join("malware");
fs::create_dir_all(&subdir).expect("create subdirectory");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("root.yar"),
r#"
rule RootRule {
strings:
$a = "root"
condition:
$a
}
"#,
)
.expect("write root rule");
fs::write(
subdir.join("sub.yar"),
r#"
rule SubRule {
strings:
$b = "sub"
condition:
$b
}
"#,
)
.expect("write sub rule");
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir)
.await
.expect("scanner builds");
let matches1 = scanner.scan_buffer(b"root").expect("scan root");
assert!(matches1.contains(&"RootRule".to_string()));
let matches2 = scanner.scan_buffer(b"sub").expect("scan sub");
assert!(matches2.contains(&"SubRule".to_string()));
let matches3 = scanner.scan_buffer(b"root and sub").expect("scan both");
assert_eq!(matches3.len(), 2);
}
#[tokio::test]
async fn test_scanner_large_file() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("size.yar"),
r#"
rule LargeFile {
strings:
$marker = "SECRET"
condition:
$marker
}
"#,
)
.expect("write rule");
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir.clone())
.await
.expect("scanner builds");
let test_file = temp_home.path().join("large_file.bin");
let mut large_data = vec![0u8; 1024 * 1024];
large_data[512 * 1024..512 * 1024 + 6].copy_from_slice(b"SECRET");
fs::write(&test_file, large_data).expect("write large file");
let matches = scanner.scan_file(&test_file).expect("scan large file");
assert!(matches.contains(&"LargeFile".to_string()));
}
#[tokio::test]
async fn test_scanner_binary_data() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("binary.yar"),
r#"
rule BinaryPattern {
strings:
$hex = { 4D 5A 90 00 }
condition:
$hex
}
"#,
)
.expect("write rule");
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir)
.await
.expect("scanner builds");
let binary_data = vec![0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00];
let matches = scanner.scan_buffer(&binary_data).expect("scan binary");
assert!(matches.contains(&"BinaryPattern".to_string()));
}
#[tokio::test]
async fn test_scanner_update_yara_rules_entrypoint() {
use crate::utils::cached_scanner::update_yara_rules;
let result = update_yara_rules().await;
assert!(result.is_ok() || result.is_err());
}
#[tokio::test]
async fn test_scanner_scan_file_entrypoint() {
use crate::utils::cached_scanner::scan_file;
let temp_dir = tempdir().expect("temp dir");
let test_file = temp_dir.path().join("test.bin");
fs::write(&test_file, b"test data").expect("write test file");
let result = scan_file(&test_file).await;
assert!(result.is_ok() || result.is_err());
}
#[tokio::test]
async fn test_scanner_cache_corruption_recovery() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("test.yar"),
r#"
rule CacheRecovery {
condition:
true
}
"#,
)
.expect("write rule");
let cache_file = base_dir.join("cache.bin");
let version_file = base_dir.join("cache.ver");
let section = create_test_yara_section("test.zip", "rules", "cache.bin", "cache.ver");
let _scanner1 = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir.clone())
.await
.expect("scanner 1 builds");
assert!(cache_file.exists());
assert!(version_file.exists());
fs::write(&cache_file, b"corrupted data").expect("corrupt cache");
let scanner2 = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir)
.await
.expect("scanner 2 rebuilds after corruption");
let matches = scanner2.scan_buffer(b"test").expect("scan works");
assert!(matches.contains(&"CacheRecovery".to_string()));
}
#[tokio::test]
async fn test_scanner_version_file_missing() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(
rules_dir.join("test.yar"),
r#"
rule NoVersion {
condition:
true
}
"#,
)
.expect("write rule");
let section = create_test_yara_section("test.zip", "rules", "cache.bin", "cache.ver");
let result = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_scanner_rules_with_imports() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("with_import.yar"),
r#"
import "pe"
rule PERule {
condition:
false
}
"#,
)
.expect("write rule");
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let result = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir).await;
assert!(result.is_ok() || result.is_err());
}
#[tokio::test]
async fn test_scanner_many_rules() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
for i in 0..150 {
fs::write(
rules_dir.join(format!("rule_{}.yar", i)),
format!(
r#"
rule Rule{} {{
strings:
$s = "pattern{}"
condition:
$s
}}
"#,
i, i
),
)
.expect("write rule");
}
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir)
.await
.expect("scanner with many rules builds");
let matches = scanner.scan_buffer(b"pattern42").expect("scan");
assert!(matches.contains(&"Rule42".to_string()));
}
#[tokio::test]
async fn test_scanner_unreadable_yar_file() {
let temp_home = tempdir().expect("temp HOME");
let base_dir = temp_home.path().join(".jirust-cli");
let rules_dir = base_dir.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(rules_dir.join(".version"), "12345678abcdef").expect("write version");
fs::write(
rules_dir.join("good.yar"),
r#"
rule Good {
condition:
true
}
"#,
)
.expect("write good rule");
let bad_file = rules_dir.join("bad.yar");
fs::write(&bad_file, vec![0xFF, 0xFE, 0xFD]).expect("write bad bytes");
let section = create_test_yara_section("test.zip", "rules", "cache", "cache.ver");
let scanner = CachedYaraScanner::from_config_with_base_dir(§ion, base_dir)
.await
.expect("scanner builds despite unreadable file");
let matches = scanner.scan_buffer(b"test").expect("scan");
assert!(matches.contains(&"Good".to_string()));
}
}