use libmagic_rs::parser::load_magic_directory;
use std::fs;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
fn create_test_magic_file(dir: &Path, name: &str, content: &str) -> PathBuf {
let file_path = dir.join(name);
fs::write(&file_path, content).expect("Failed to write test magic file");
file_path
}
fn create_magdir_structure(dir: &Path) -> Vec<PathBuf> {
vec![
create_test_magic_file(
dir,
"01-elf",
"# ELF executables\n\
0 string \\x7fELF ELF executable\n\
>4 byte 1 32-bit\n\
>4 byte 2 64-bit\n",
),
create_test_magic_file(
dir,
"02-archive",
"# Archive formats\n\
0 string \\x21\\x3c ar archive\n\
0 string \\x50\\x4b\\x03\\x04 ZIP archive\n",
),
create_test_magic_file(
dir,
"03-text",
"# Text files\n\
0 string \\x23\\x21 shell script\n\
0 string \\x23\\x21 bash script\n",
),
]
}
#[test]
fn test_load_empty_directory() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load empty directory");
assert_eq!(rules.len(), 0, "Empty directory should return no rules");
}
#[test]
fn test_load_directory_single_file() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_magic_file(
temp_dir.path(),
"test.magic",
"0 string \\x7fELF ELF executable\n\
>4 byte 1 32-bit\n\
>4 byte 2 64-bit\n",
);
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load directory");
assert_eq!(rules.len(), 1, "Should load one top-level rule");
assert_eq!(rules[0].message, "ELF executable");
assert_eq!(rules[0].children.len(), 2, "Should have 2 child rules");
}
#[test]
fn test_load_directory_multiple_files() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_magic_file(
temp_dir.path(),
"elf.magic",
"0 string \\x7fELF ELF executable\n",
);
create_test_magic_file(
temp_dir.path(),
"archive.magic",
"0 string \\x21\\x3c ar archive\n\
0 string \\x50\\x4b\\x03\\x04 ZIP archive\n",
);
create_test_magic_file(
temp_dir.path(),
"script.magic",
"0 string \\x23\\x21 shell script\n",
);
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load directory");
assert_eq!(rules.len(), 4, "Should load all rules from all files");
let messages: Vec<&str> = rules.iter().map(|r| r.message.as_str()).collect();
assert!(messages.contains(&"ar archive"));
assert!(messages.contains(&"ZIP archive"));
assert!(messages.contains(&"ELF executable"));
assert!(messages.contains(&"shell script"));
}
#[test]
fn test_load_directory_preserves_order() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_magic_file(
temp_dir.path(),
"01-first.magic",
"0 string \\x01\\x02\\x03 first file\n",
);
create_test_magic_file(
temp_dir.path(),
"02-second.magic",
"0 string \\x04\\x05\\x06 second file\n",
);
create_test_magic_file(
temp_dir.path(),
"03-third.magic",
"0 string \\x07\\x08\\x09 third file\n",
);
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load directory");
assert_eq!(rules.len(), 3);
assert_eq!(rules[0].message, "first file");
assert_eq!(rules[1].message, "second file");
assert_eq!(rules[2].message, "third file");
}
#[test]
fn test_load_directory_skips_subdirectories() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_magic_file(
temp_dir.path(),
"main.magic",
"0 string \\x01\\x02 main file\n",
);
let subdir = temp_dir.path().join("subdir");
fs::create_dir(&subdir).expect("Failed to create subdirectory");
create_test_magic_file(&subdir, "sub.magic", "0 string \\x03\\x04 sub file\n");
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load directory");
assert_eq!(rules.len(), 1);
assert_eq!(rules[0].message, "main file");
}
#[test]
#[cfg(unix)] fn test_load_directory_skips_symlinks() {
use std::os::unix::fs::symlink;
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let _regular_file = create_test_magic_file(
temp_dir.path(),
"regular.magic",
"0 string \\x01\\x02 regular file\n",
);
let external_dir = TempDir::new().expect("Failed to create external temp dir");
let external_file = create_test_magic_file(
external_dir.path(),
"external.magic",
"0 string \\x03\\x04 external file\n",
);
let symlink_path = temp_dir.path().join("symlink.magic");
symlink(&external_file, &symlink_path).expect("Failed to create symlink");
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load directory");
assert_eq!(rules.len(), 1, "Should skip symlinks");
assert_eq!(rules[0].message, "regular file");
}
#[test]
fn test_load_directory_with_parse_errors() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_magic_file(
temp_dir.path(),
"01-valid.magic",
"0 string \\x01\\x02 valid file\n",
);
create_test_magic_file(
temp_dir.path(),
"02-invalid.magic",
"this is not valid magic file syntax\n\
completely broken\n",
);
create_test_magic_file(
temp_dir.path(),
"03-valid.magic",
"0 string \\x03\\x04 another valid file\n",
);
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load directory");
assert_eq!(
rules.len(),
2,
"Should load only valid files, skipping invalid ones"
);
assert_eq!(rules[0].message, "valid file");
assert_eq!(rules[1].message, "another valid file");
}
#[test]
fn test_load_directory_io_error() {
let non_existent_path = Path::new("/this/path/should/not/exist/anywhere");
let result = load_magic_directory(non_existent_path);
assert!(
result.is_err(),
"Should return error for non-existent directory"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Failed to read directory"),
"Error should mention directory read failure"
);
}
#[test]
fn test_load_directory_with_comments() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_magic_file(
temp_dir.path(),
"commented.magic",
"# This is a comment\n\
# Another comment\n\
0 string \\x01\\x02 test file\n\
# Inline comment\n\
>4 byte 1 version 1\n\
\n\
# Empty lines above\n",
);
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load directory");
assert_eq!(rules.len(), 1);
assert_eq!(rules[0].message, "test file");
assert_eq!(rules[0].children.len(), 1);
}
#[test]
fn test_load_directory_with_nested_rules() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_magic_file(
temp_dir.path(),
"nested.magic",
"0 string \\x7fELF ELF executable\n\
>4 byte 1 32-bit\n\
>>16 short 2 executable\n\
>>16 short 3 shared object\n\
>4 byte 2 64-bit\n",
);
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load directory");
assert_eq!(rules.len(), 1, "Should have one top-level rule");
assert_eq!(rules[0].children.len(), 2, "Should have two child rules");
assert_eq!(
rules[0].children[0].children.len(),
2,
"First child should have 2 nested children"
);
}
#[test]
fn test_load_directory_rule_count() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_magdir_structure(temp_dir.path());
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load directory");
assert_eq!(
rules.len(),
5,
"Should have 5 top-level rules from Magdir structure"
);
}
#[test]
fn test_load_directory_empty_files() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_magic_file(temp_dir.path(), "empty.magic", "");
create_test_magic_file(temp_dir.path(), "whitespace.magic", " \n\n \n");
create_test_magic_file(
temp_dir.path(),
"valid.magic",
"0 string \\x01\\x02 valid file\n",
);
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load directory");
assert_eq!(
rules.len(),
1,
"Should load only the valid file, empty files contribute no rules"
);
assert_eq!(rules[0].message, "valid file");
}
#[test]
fn test_load_directory_mixed_extensions() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_magic_file(
temp_dir.path(),
"file.magic",
"0 string \\x01\\x02 magic ext\n",
);
create_test_magic_file(temp_dir.path(), "file.txt", "0 string \\x03\\x04 txt ext\n");
create_test_magic_file(temp_dir.path(), "noext", "0 string \\x05\\x06 no ext\n");
let rules = load_magic_directory(temp_dir.path()).expect("Failed to load directory");
assert_eq!(
rules.len(),
3,
"Should process all files regardless of extension"
);
let messages: Vec<&str> = rules.iter().map(|r| r.message.as_str()).collect();
assert!(messages.contains(&"magic ext"));
assert!(messages.contains(&"txt ext"));
assert!(messages.contains(&"no ext"));
}
#[test]
fn test_load_directory_all_files_fail_to_parse() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_magic_file(
temp_dir.path(),
"bad1",
"this is not valid magic file syntax at all",
);
create_test_magic_file(temp_dir.path(), "bad2", "also invalid\nno proper format\n");
let result = load_magic_directory(temp_dir.path());
assert!(
result.is_err(),
"Should return error when all files fail to parse"
);
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("All") && err_msg.contains("failed to parse"),
"Error message should indicate all files failed: {err_msg}"
);
}
#[test]
fn test_load_directory_partial_failure_succeeds() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_magic_file(temp_dir.path(), "good", "0 string \\x00 valid rule\n");
create_test_magic_file(temp_dir.path(), "bad", "not valid magic syntax");
let rules = load_magic_directory(temp_dir.path()).expect("Should succeed with partial failure");
assert_eq!(rules.len(), 1, "Should have one rule from the valid file");
assert_eq!(rules[0].message, "valid rule");
}