|
| 1 | +//! Tests for `get_listing_stats()` — total/visible counts, sizes, and selection sums. |
| 2 | +
|
| 3 | +use super::caching::{CachedListing, LISTING_CACHE}; |
| 4 | +use super::operations::{get_listing_stats, list_directory_end}; |
| 5 | +use super::sorting::DirectorySortMode; |
| 6 | +use super::{FileEntry, SortColumn, SortOrder}; |
| 7 | + |
| 8 | +/// Creates a test entry with configurable sizes. |
| 9 | +fn make_entry(name: &str, is_dir: bool, size: Option<u64>, physical_size: Option<u64>) -> FileEntry { |
| 10 | + let mut entry = FileEntry { |
| 11 | + size: if is_dir { None } else { size }, |
| 12 | + physical_size: if is_dir { None } else { physical_size }, |
| 13 | + recursive_size: if is_dir { size } else { None }, |
| 14 | + recursive_physical_size: if is_dir { physical_size } else { None }, |
| 15 | + modified_at: Some(1_700_000_000), |
| 16 | + created_at: Some(1_700_000_000), |
| 17 | + permissions: if is_dir { 0o755 } else { 0o644 }, |
| 18 | + owner: "testuser".to_string(), |
| 19 | + group: "staff".to_string(), |
| 20 | + extended_metadata_loaded: true, |
| 21 | + ..FileEntry::new(name.to_string(), format!("/{}", name), is_dir, false) |
| 22 | + }; |
| 23 | + // Ensure directories don't have file-level size and vice versa |
| 24 | + if is_dir { |
| 25 | + entry.size = None; |
| 26 | + entry.physical_size = None; |
| 27 | + } else { |
| 28 | + entry.recursive_size = None; |
| 29 | + entry.recursive_physical_size = None; |
| 30 | + } |
| 31 | + entry |
| 32 | +} |
| 33 | + |
| 34 | +/// Inserts entries into the listing cache and returns the listing ID. |
| 35 | +fn insert_test_listing(id: &str, entries: Vec<FileEntry>) -> String { |
| 36 | + let listing_id = id.to_string(); |
| 37 | + let mut cache = LISTING_CACHE.write().unwrap(); |
| 38 | + cache.insert( |
| 39 | + listing_id.clone(), |
| 40 | + CachedListing { |
| 41 | + volume_id: "test".to_string(), |
| 42 | + path: std::path::PathBuf::from("/"), |
| 43 | + entries, |
| 44 | + sort_by: SortColumn::Name, |
| 45 | + sort_order: SortOrder::Ascending, |
| 46 | + directory_sort_mode: DirectorySortMode::LikeFiles, |
| 47 | + sequence: std::sync::atomic::AtomicU64::new(0), |
| 48 | + }, |
| 49 | + ); |
| 50 | + listing_id |
| 51 | +} |
| 52 | + |
| 53 | +// ============================================================================ |
| 54 | +// Basic stats |
| 55 | +// ============================================================================ |
| 56 | + |
| 57 | +#[test] |
| 58 | +fn test_stats_mixed_files_and_dirs() { |
| 59 | + let entries = vec![ |
| 60 | + make_entry("Documents", true, Some(50_000), Some(52_000)), |
| 61 | + make_entry("Photos", true, Some(100_000), Some(104_000)), |
| 62 | + make_entry("notes.txt", false, Some(1_024), Some(4_096)), |
| 63 | + make_entry("report.pdf", false, Some(2_048), Some(4_096)), |
| 64 | + make_entry("tiny.log", false, Some(10), Some(4_096)), |
| 65 | + ]; |
| 66 | + let listing_id = insert_test_listing("test-stats-mixed", entries); |
| 67 | + |
| 68 | + let stats = get_listing_stats(&listing_id, true, None).unwrap(); |
| 69 | + |
| 70 | + list_directory_end(&listing_id); |
| 71 | + |
| 72 | + assert_eq!(stats.total_dirs, 2); |
| 73 | + assert_eq!(stats.total_files, 3); |
| 74 | + assert_eq!(stats.total_size, 50_000 + 100_000 + 1_024 + 2_048 + 10); |
| 75 | + assert_eq!(stats.total_physical_size, 52_000 + 104_000 + 4_096 + 4_096 + 4_096); |
| 76 | + assert!(stats.selected_files.is_none()); |
| 77 | + assert!(stats.selected_dirs.is_none()); |
| 78 | + assert!(stats.selected_size.is_none()); |
| 79 | + assert!(stats.selected_physical_size.is_none()); |
| 80 | +} |
| 81 | + |
| 82 | +// ============================================================================ |
| 83 | +// Hidden file filtering |
| 84 | +// ============================================================================ |
| 85 | + |
| 86 | +#[test] |
| 87 | +fn test_stats_hidden_files_excluded() { |
| 88 | + let entries = vec![ |
| 89 | + make_entry(".config", true, Some(5_000), Some(8_192)), |
| 90 | + make_entry(".bashrc", false, Some(512), Some(4_096)), |
| 91 | + make_entry("Documents", true, Some(50_000), Some(52_000)), |
| 92 | + make_entry("readme.md", false, Some(1_024), Some(4_096)), |
| 93 | + ]; |
| 94 | + let listing_id = insert_test_listing("test-stats-hidden-excluded", entries); |
| 95 | + |
| 96 | + let stats_all = get_listing_stats(&listing_id, true, None).unwrap(); |
| 97 | + let stats_visible = get_listing_stats(&listing_id, false, None).unwrap(); |
| 98 | + |
| 99 | + list_directory_end(&listing_id); |
| 100 | + |
| 101 | + // With hidden: all 4 entries |
| 102 | + assert_eq!(stats_all.total_dirs, 2); |
| 103 | + assert_eq!(stats_all.total_files, 2); |
| 104 | + assert_eq!(stats_all.total_size, 5_000 + 512 + 50_000 + 1_024); |
| 105 | + |
| 106 | + // Without hidden: only Documents + readme.md |
| 107 | + assert_eq!(stats_visible.total_dirs, 1); |
| 108 | + assert_eq!(stats_visible.total_files, 1); |
| 109 | + assert_eq!(stats_visible.total_size, 50_000 + 1_024); |
| 110 | + assert_eq!(stats_visible.total_physical_size, 52_000 + 4_096); |
| 111 | +} |
| 112 | + |
| 113 | +// ============================================================================ |
| 114 | +// Selection stats |
| 115 | +// ============================================================================ |
| 116 | + |
| 117 | +#[test] |
| 118 | +fn test_stats_with_selection() { |
| 119 | + let entries = vec![ |
| 120 | + make_entry("Documents", true, Some(50_000), Some(52_000)), |
| 121 | + make_entry("Photos", true, Some(100_000), Some(104_000)), |
| 122 | + make_entry("notes.txt", false, Some(1_024), Some(4_096)), |
| 123 | + make_entry("report.pdf", false, Some(2_048), Some(4_096)), |
| 124 | + ]; |
| 125 | + let listing_id = insert_test_listing("test-stats-selection", entries); |
| 126 | + |
| 127 | + // Select indices 0 (Documents) and 2 (notes.txt) |
| 128 | + let stats = get_listing_stats(&listing_id, true, Some(&[0, 2])).unwrap(); |
| 129 | + |
| 130 | + list_directory_end(&listing_id); |
| 131 | + |
| 132 | + // Totals cover all entries |
| 133 | + assert_eq!(stats.total_dirs, 2); |
| 134 | + assert_eq!(stats.total_files, 2); |
| 135 | + |
| 136 | + // Selection covers only the two selected entries |
| 137 | + assert_eq!(stats.selected_dirs, Some(1)); |
| 138 | + assert_eq!(stats.selected_files, Some(1)); |
| 139 | + assert_eq!(stats.selected_size, Some(50_000 + 1_024)); |
| 140 | + assert_eq!(stats.selected_physical_size, Some(52_000 + 4_096)); |
| 141 | +} |
| 142 | + |
| 143 | +// ============================================================================ |
| 144 | +// Edge cases |
| 145 | +// ============================================================================ |
| 146 | + |
| 147 | +#[test] |
| 148 | +fn test_stats_empty_directory() { |
| 149 | + let listing_id = insert_test_listing("test-stats-empty", vec![]); |
| 150 | + |
| 151 | + let stats = get_listing_stats(&listing_id, true, None).unwrap(); |
| 152 | + |
| 153 | + list_directory_end(&listing_id); |
| 154 | + |
| 155 | + assert_eq!(stats.total_dirs, 0); |
| 156 | + assert_eq!(stats.total_files, 0); |
| 157 | + assert_eq!(stats.total_size, 0); |
| 158 | + assert_eq!(stats.total_physical_size, 0); |
| 159 | + assert!(stats.selected_files.is_none()); |
| 160 | +} |
| 161 | + |
| 162 | +#[test] |
| 163 | +fn test_stats_all_hidden_without_hidden_flag() { |
| 164 | + let entries = vec![ |
| 165 | + make_entry(".git", true, Some(200_000), Some(204_800)), |
| 166 | + make_entry(".gitignore", false, Some(256), Some(4_096)), |
| 167 | + make_entry(".env", false, Some(128), Some(4_096)), |
| 168 | + ]; |
| 169 | + let listing_id = insert_test_listing("test-stats-all-hidden", entries); |
| 170 | + |
| 171 | + let stats = get_listing_stats(&listing_id, false, None).unwrap(); |
| 172 | + |
| 173 | + list_directory_end(&listing_id); |
| 174 | + |
| 175 | + // Everything is hidden, so visible stats are all zero |
| 176 | + assert_eq!(stats.total_dirs, 0); |
| 177 | + assert_eq!(stats.total_files, 0); |
| 178 | + assert_eq!(stats.total_size, 0); |
| 179 | + assert_eq!(stats.total_physical_size, 0); |
| 180 | +} |
| 181 | + |
| 182 | +#[test] |
| 183 | +fn test_stats_selection_with_out_of_bounds_index_is_ignored() { |
| 184 | + let entries = vec![make_entry("file.txt", false, Some(1_000), Some(4_096))]; |
| 185 | + let listing_id = insert_test_listing("test-stats-oob-selection", entries); |
| 186 | + |
| 187 | + // Index 0 is valid, index 99 is out of bounds |
| 188 | + let stats = get_listing_stats(&listing_id, true, Some(&[0, 99])).unwrap(); |
| 189 | + |
| 190 | + list_directory_end(&listing_id); |
| 191 | + |
| 192 | + // Only the valid index should be counted |
| 193 | + assert_eq!(stats.selected_files, Some(1)); |
| 194 | + assert_eq!(stats.selected_dirs, Some(0)); |
| 195 | + assert_eq!(stats.selected_size, Some(1_000)); |
| 196 | +} |
| 197 | + |
| 198 | +#[test] |
| 199 | +fn test_stats_entries_without_sizes() { |
| 200 | + // Entries where size is None (e.g., network volumes that don't report sizes) |
| 201 | + let entries = vec![ |
| 202 | + make_entry("remote_dir", true, None, None), |
| 203 | + make_entry("unknown.dat", false, None, None), |
| 204 | + ]; |
| 205 | + let listing_id = insert_test_listing("test-stats-no-sizes", entries); |
| 206 | + |
| 207 | + let stats = get_listing_stats(&listing_id, true, Some(&[0, 1])).unwrap(); |
| 208 | + |
| 209 | + list_directory_end(&listing_id); |
| 210 | + |
| 211 | + assert_eq!(stats.total_dirs, 1); |
| 212 | + assert_eq!(stats.total_files, 1); |
| 213 | + assert_eq!(stats.total_size, 0); |
| 214 | + assert_eq!(stats.total_physical_size, 0); |
| 215 | + assert_eq!(stats.selected_size, Some(0)); |
| 216 | + assert_eq!(stats.selected_physical_size, Some(0)); |
| 217 | +} |
0 commit comments