using Explorer.Application; using Explorer.Domain; using Explorer.Domain.Abstractions; using Explorer.Storage.Sqlite; using Microsoft.Extensions.Logging.Abstractions; namespace Explorer.Application.Tests; public class EntryClassificationServiceTests { [Fact] public async Task Archive_children_override_zip_as_photos() { await using var store = await OpenStore(); var source = await AddSource(store, @"C:\media"); var zip = await Upsert(store, new IndexEntry { SourceId = source.Id, Name = "photos.zip", NameNorm = "photos.zip", Extension = "zip", PathRel = "photos.zip", LastSeenUtc = DateTimeOffset.UtcNow }); await Upsert(store, Child(source.Id, zip.Id, "a.jpg", "jpg")); await Upsert(store, Child(source.Id, zip.Id, "b.jpg", "jpg")); await Upsert(store, Child(source.Id, zip.Id, "c.jpg", "jpg")); var service = Create(store, new MemoryEnumerator()); service.Resume(); Assert.True(await service.ProcessPendingAsync()); var loaded = await store.Entries.GetAsync(zip.Id); Assert.Equal(FileCategory.Photos, loaded!.Category); Assert.Equal(CategorySources.ArchiveContents, loaded.CategorySource); Assert.False(await service.ProcessPendingAsync()); } [Fact] public async Task Signature_classifies_jpeg_without_extension() { var root = Path.Combine(Path.GetTempPath(), "ew-classify", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(root); var file = Path.Combine(root, "pic.bin"); File.WriteAllBytes(file, [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01]); await using var store = await OpenStore(); var source = await AddSource(store, root); var entry = await Upsert(store, new IndexEntry { SourceId = source.Id, Name = "pic.bin", NameNorm = "pic.bin", Extension = "bin", PathRel = "pic.bin", LastSeenUtc = DateTimeOffset.UtcNow }); Assert.Equal(FileCategory.Unknown, entry.Category); var enumerator = new MemoryEnumerator(); enumerator.Add(new FileSystemItem { FullPath = file, Name = "pic.bin" }); var service = Create(store, enumerator); service.Resume(); Assert.True(await service.ProcessPendingAsync()); var loaded = await store.Entries.GetAsync(entry.Id); Assert.Equal(FileCategory.Photos, loaded!.Category); Assert.Equal(CategorySources.Mime, loaded.CategorySource); } [Fact] public async Task Pause_skips_work() { await using var store = await OpenStore(); var source = await AddSource(store, @"C:\media"); var zip = await Upsert(store, new IndexEntry { SourceId = source.Id, Name = "photos.zip", NameNorm = "photos.zip", Extension = "zip", PathRel = "photos.zip", LastSeenUtc = DateTimeOffset.UtcNow }); await Upsert(store, Child(source.Id, zip.Id, "a.jpg", "jpg")); var service = Create(store, new MemoryEnumerator()); Assert.True(service.IsPaused); Assert.False(await service.ProcessPendingAsync()); var loaded = await store.Entries.GetAsync(zip.Id); Assert.Equal(FileCategory.Archive, loaded!.Category); } private static EntryClassificationService Create(IIndexStore store, IFileSystemEnumerator enumerator) => new( store, enumerator, new NeverHydrate(), NullLogger.Instance); private static async Task OpenStore() { var db = Path.Combine(Path.GetTempPath(), "ew-classify", Guid.NewGuid().ToString("N"), "index.db"); var store = new SqliteIndexStore(db, NullLogger.Instance); await store.OpenAsync(); return store; } private static async Task AddSource(IIndexStore store, string root) { var source = new Source { StableKey = Guid.NewGuid().ToString("N"), Kind = SourceKind.NtfsLocal, DisplayName = "Test", LastRootPath = root, Status = SourceStatus.Online }; source.Id = await store.Sources.UpsertAsync(source); return source; } private static async Task Upsert(IIndexStore store, IndexEntry entry) { entry.Id = await store.Entries.UpsertAsync(entry); return (await store.Entries.GetAsync(entry.Id))!; } private static IndexEntry Child(long sourceId, long parentId, string name, string ext) => new() { SourceId = sourceId, ParentId = parentId, Name = name, NameNorm = name, Extension = ext, PathRel = name, LastSeenUtc = DateTimeOffset.UtcNow }; private sealed class NeverHydrate : IHydrationGuard { public bool WouldHydrateOnRead(FileSystemItem item) => false; public bool WouldHydrateOnRead(int attributes, CloudAvailability? availability) => false; public Task WouldHydrateOnReadAsync(string path, CancellationToken cancellationToken = default) => Task.FromResult(false); } private sealed class MemoryEnumerator : IFileSystemEnumerator { private readonly Dictionary _items = new(StringComparer.OrdinalIgnoreCase); public void Add(FileSystemItem item) => _items[item.FullPath] = item; public IEnumerable EnumerateChildren(string directoryPath) => []; public FileSystemItem? GetItem(string path) => _items.TryGetValue(path, out var item) ? item : null; public IReadOnlyList EnumerateChildrenSafe(string directoryPath, out string? error) { error = null; return []; } } }