#![cfg(feature = "windows")]
#![allow(clippy::unwrap_used, clippy::expect_used)]
use chrono::{TimeZone, Utc};
use trash_core::{parse_index, scan_pairs, Error, IndexVersion};
const V2_DOCX: &[u8] = include_bytes!("../../tests/data/$IAB12CD.docx");
const V1_TXT: &[u8] = include_bytes!("../../tests/data/$IZZ99YY.txt");
const V2_NODATE: &[u8] = include_bytes!("../../tests/data/$INODATE.bin");
const V2_TRAVERSAL: &[u8] = include_bytes!("../../tests/data/$ITRAVER.dll");
#[test]
fn parses_version2_index() {
let idx = parse_index(V2_DOCX).expect("v2 fixture must parse");
assert_eq!(idx.version, IndexVersion::V2);
assert_eq!(idx.original_size, 1234);
assert_eq!(
idx.original_path,
"C:\\Users\\victim\\Documents\\secret plan.docx"
);
assert_eq!(
idx.deleted_at,
Some(Utc.with_ymd_and_hms(2024, 1, 15, 10, 30, 0).unwrap())
);
}
#[test]
fn parses_version1_index() {
let idx = parse_index(V1_TXT).expect("v1 fixture must parse");
assert_eq!(idx.version, IndexVersion::V1);
assert_eq!(idx.original_size, 999);
assert_eq!(idx.original_path, "C:\\temp\\oldfile.txt");
assert_eq!(
idx.deleted_at,
Some(Utc.with_ymd_and_hms(2015, 6, 1, 0, 0, 0).unwrap())
);
}
#[test]
fn zero_filetime_maps_to_none() {
let idx = parse_index(V2_NODATE).expect("zero-filetime fixture must parse");
assert_eq!(idx.original_size, 42);
assert_eq!(idx.original_path, "C:\\Data\\nodate.bin");
assert_eq!(idx.deleted_at, None);
}
#[test]
fn preserves_path_traversal_verbatim() {
let idx = parse_index(V2_TRAVERSAL).expect("traversal fixture must parse");
assert_eq!(idx.original_path, "..\\..\\..\\Windows\\System32\\evil.dll");
}
#[test]
fn truncated_header_is_error_not_panic() {
let err = parse_index(&[0u8; 10]).unwrap_err();
assert!(matches!(err, Error::TruncatedHeader { got: 10 }));
}
#[test]
fn unsupported_version_surfaces_raw_value() {
let mut data = vec![0u8; 24];
data[0] = 0x07; let err = parse_index(&data).unwrap_err();
match err {
Error::UnsupportedVersion { version, raw } => {
assert_eq!(version, 7);
assert_eq!(raw, 7);
}
other => panic!("expected UnsupportedVersion, got {other:?}"),
}
}
#[test]
fn version2_name_length_overflow_is_rejected() {
let mut data = vec![0u8; 28];
data[0] = 2; data[24] = 0xFF;
data[25] = 0xFF;
data[26] = 0xFF;
data[27] = 0xFF;
let err = parse_index(&data).unwrap_err();
assert!(matches!(err, Error::NameLengthTooLarge { .. }));
}
#[test]
fn version1_truncated_name_field_is_error() {
let mut data = vec![0u8; 100]; data[0] = 1;
let err = parse_index(&data).unwrap_err();
assert!(matches!(err, Error::TruncatedV1Name { .. }));
}
#[test]
fn scan_pairs_matches_i_and_r() {
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/data"));
let pairs = scan_pairs(dir).expect("scan must succeed");
let paired = pairs
.iter()
.find(|p| p.index_path.file_name().and_then(|n| n.to_str()) == Some("$IAB12CD.docx"))
.expect("v2 index present");
let content = paired
.content_path
.as_ref()
.expect("$RAB12CD.docx should pair");
assert_eq!(
content.file_name().and_then(|n| n.to_str()),
Some("$RAB12CD.docx")
);
let lone = pairs
.iter()
.find(|p| p.index_path.file_name().and_then(|n| n.to_str()) == Some("$IZZ99YY.txt"))
.expect("v1 index present");
assert!(lone.content_path.is_none());
}