Add Explorer Workbench with hierarchical, off-UI Storage analysis.
Storage queries run in the background with cancellation and covering indexes so switching views no longer freezes the UI. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
123
tests/Explorer.Application.Tests/SourceManagerTests.cs
Normal file
123
tests/Explorer.Application.Tests/SourceManagerTests.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
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<SqliteIndexStore>.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<SourceManager>.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<SqliteIndexStore>.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<SourceManager>.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<VolumeFingerprint> Online { get; set; } = [];
|
||||
public IReadOnlyList<VolumeFingerprint> 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; }
|
||||
}
|
||||
Reference in New Issue
Block a user