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 SourceManagerTests { [Fact] public async Task Removable_drive_keeps_stable_key_when_letter_changes() { var db = Path.Combine(Path.GetTempPath(), "ew-app", Guid.NewGuid().ToString("N"), "index.db"); await using var store = new SqliteIndexStore(db, NullLogger.Instance); await store.OpenAsync(); var volumes = new FakeVolumes(); volumes.Online = [ new VolumeFingerprint { Kind = SourceKind.Removable, VolumeGuid = @"\\?\Volume{same}\", RootPath = @"E:\", DisplayName = "Stick E", VolumeSerial = 9, CapacityBytes = 64 } ]; var env = new FakeEnv(Path.GetDirectoryName(db)!); var mgr = new SourceManager(store, volumes, env, new SystemClock(), NullLogger.Instance); await mgr.InitializeAsync(); var first = (await store.Sources.GetAllAsync()).Single(); var key = first.StableKey; volumes.Online = [ new VolumeFingerprint { Kind = SourceKind.Removable, VolumeGuid = @"\\?\Volume{same}\", RootPath = @"G:\", DisplayName = "Stick G", VolumeSerial = 9, CapacityBytes = 64 } ]; await mgr.RefreshOnlineStateAsync(); var again = (await store.Sources.GetAllAsync()).Single(); Assert.Equal(key, again.StableKey); Assert.Equal(@"G:\", again.LastRootPath); } [Fact] public async Task Ensure_for_mapped_letter_creates_source() { var db = Path.Combine(Path.GetTempPath(), "ew-app", Guid.NewGuid().ToString("N"), "index.db"); await using var store = new SqliteIndexStore(db, NullLogger.Instance); await store.OpenAsync(); var volumes = new FakeVolumes { Online = [ new VolumeFingerprint { Kind = SourceKind.Smb, RootPath = @"Z:\", DisplayName = "Z: (Network)", Filesystem = "SMB" } ] }; var env = new FakeEnv(Path.GetDirectoryName(db)!); var mgr = new SourceManager(store, volumes, env, new SystemClock(), NullLogger.Instance); volumes.Online = []; await mgr.InitializeAsync(); volumes.Online = [ new VolumeFingerprint { Kind = SourceKind.Smb, RootPath = @"Z:\", DisplayName = "Z: (Network)", Filesystem = "SMB" } ]; var source = await mgr.EnsureForPathAsync(@"Z:\Photos"); Assert.NotNull(source); Assert.Equal(@"Z:\", source!.LastRootPath); var found = await mgr.FindByPathAsync(@"Z:\"); Assert.Equal(source.Id, found!.Id); } } file sealed class FakeVolumes : IVolumeService { public List Online { get; set; } = []; public IReadOnlyList EnumerateOnlineVolumes() => Online; public VolumeFingerprint? Probe(string path) { var root = Path.GetPathRoot(path)?.TrimEnd('\\'); return Online.FirstOrDefault(v => v.RootPath.TrimEnd('\\').Equals(root, StringComparison.OrdinalIgnoreCase) || v.RootPath.Equals(path, StringComparison.OrdinalIgnoreCase)); } public bool IsPathReachable(string path) => Online.Any(v => path.StartsWith(v.RootPath.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase)); } file sealed class FakeEnv : IAppEnvironment { public FakeEnv(string dir) { DataDirectory = dir; Directory.CreateDirectory(dir); DatabasePath = Path.Combine(dir, "index.db"); LogDirectory = Path.Combine(dir, "logs"); Directory.CreateDirectory(LogDirectory); } public string DataDirectory { get; } public string DatabasePath { get; } public string LogDirectory { get; } }