#![allow(clippy::unwrap_used, clippy::expect_used)]
mod common;
use common::hive_builder::TestHiveBuilder;
use winreg_artifacts::shellbags::{parse, ShellbagEntry};
use winreg_core::hive::Hive;
const BAGMRU_PATH: &str = "Software\\Microsoft\\Windows\\Shell\\BagMRU";
const REG_BINARY: u32 = 3;
fn mrulistex(indices: &[u32]) -> Vec<u8> {
let mut data: Vec<u8> = indices.iter().flat_map(|&i| i.to_le_bytes()).collect();
data.extend_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
data
}
fn shell_item_blob(tag: u8) -> Vec<u8> {
vec![tag, 0x00, 0x1F, 0x00, 0xAA, 0xBB, 0xCC, 0xDD]
}
#[test]
fn parse_empty_hive_returns_empty() {
let data = TestHiveBuilder::new().build();
let hive = Hive::from_bytes(data).unwrap();
let entries = parse(&hive);
assert!(
entries.is_empty(),
"empty hive (no BagMRU key) should return empty Vec"
);
}
#[test]
fn parse_bagmru_key_returns_entry() {
let blob = shell_item_blob(0xAA);
let data = TestHiveBuilder::new()
.add_key(BAGMRU_PATH)
.add_value(BAGMRU_PATH, "0", REG_BINARY, &blob)
.build();
let hive = Hive::from_bytes(data).unwrap();
let entries = parse(&hive);
assert!(
!entries.is_empty(),
"BagMRU key with slot values should produce at least one entry"
);
}
#[test]
fn parse_entry_key_path_is_correct() {
let data = TestHiveBuilder::new().add_key(BAGMRU_PATH).build();
let hive = Hive::from_bytes(data).unwrap();
let entries = parse(&hive);
assert_eq!(entries.len(), 1, "should have one entry for the BagMRU key");
assert!(
entries[0].key_path.contains("BagMRU"),
"key_path should contain 'BagMRU', got: {}",
entries[0].key_path
);
}
#[test]
fn parse_last_written_populated() {
let data = TestHiveBuilder::new().add_key(BAGMRU_PATH).build();
let hive = Hive::from_bytes(data).unwrap();
let entries = parse(&hive);
assert_eq!(entries.len(), 1);
let _lw: Option<&str> = entries[0].last_written.as_deref();
}
#[test]
fn parse_mru_order_decoded() {
let mru = mrulistex(&[2, 0, 1]);
let data = TestHiveBuilder::new()
.add_key(BAGMRU_PATH)
.add_value(BAGMRU_PATH, "MRUListEx", REG_BINARY, &mru)
.build();
let hive = Hive::from_bytes(data).unwrap();
let entries = parse(&hive);
assert_eq!(entries.len(), 1);
let order = &entries[0].mru_order;
assert_eq!(
order.len(),
3,
"MRUListEx [2,0,1,term] should decode to 3 items"
);
assert_eq!(order[0], "2", "first MRU slot should be '2'");
assert_eq!(order[1], "0", "second MRU slot should be '0'");
assert_eq!(order[2], "1", "third MRU slot should be '1'");
}
#[test]
fn parse_subkey_creates_separate_entry() {
let subkey = format!("{BAGMRU_PATH}\\0");
let data = TestHiveBuilder::new()
.add_key(BAGMRU_PATH)
.add_key(&subkey)
.build();
let hive = Hive::from_bytes(data).unwrap();
let entries = parse(&hive);
assert_eq!(
entries.len(),
2,
"BagMRU key + one subkey should produce 2 entries (recursive walk)"
);
}
#[test]
fn parse_missing_mrulistex_gives_empty_order() {
let data = TestHiveBuilder::new().add_key(BAGMRU_PATH).build();
let hive = Hive::from_bytes(data).unwrap();
let entries = parse(&hive);
assert_eq!(entries.len(), 1);
assert!(
entries[0].mru_order.is_empty(),
"mru_order should be empty when MRUListEx is absent"
);
}
#[test]
fn parse_path_field_contains_slot_preview() {
let blob = shell_item_blob(0x1F);
let data = TestHiveBuilder::new()
.add_key(BAGMRU_PATH)
.add_value(BAGMRU_PATH, "0", REG_BINARY, &blob)
.build();
let hive = Hive::from_bytes(data).unwrap();
let entries = parse(&hive);
assert_eq!(entries.len(), 1);
assert!(
!entries[0].path.is_empty(),
"path should not be empty when slot values are present"
);
}
fn volume_2f_blob(name: &str) -> Vec<u8> {
let mut field = [0u8; 20];
for (i, b) in name.bytes().enumerate().take(19) {
field[i] = b;
}
let mut v = Vec::new();
v.extend_from_slice(&(3u16 + field.len() as u16).to_le_bytes()); v.push(0x2F); v.extend_from_slice(&field);
v
}
#[test]
fn parse_slot_decodes_real_folder_name() {
let blob = volume_2f_blob("C:\\");
let data = TestHiveBuilder::new()
.add_key(BAGMRU_PATH)
.add_value(BAGMRU_PATH, "0", REG_BINARY, &blob)
.build();
let hive = Hive::from_bytes(data).unwrap();
let entries = parse(&hive);
assert_eq!(entries.len(), 1);
assert!(
entries[0].path.contains("C:\\"),
"decoded shellbag path should contain the real folder name 'C:\\', got: {}",
entries[0].path
);
}
#[test]
fn shellbag_entry_struct_fields_accessible() {
let entry = ShellbagEntry {
path: "BagMRU[slot=0, size=8 bytes]".to_string(),
key_path: "Software\\Microsoft\\Windows\\Shell\\BagMRU".to_string(),
last_written: None,
mru_order: vec!["2".to_string(), "0".to_string()],
};
assert_eq!(entry.path, "BagMRU[slot=0, size=8 bytes]");
assert_eq!(
entry.key_path,
"Software\\Microsoft\\Windows\\Shell\\BagMRU"
);
assert!(entry.last_written.is_none());
assert_eq!(entry.mru_order.len(), 2);
}