using Explorer.Analysis; using Explorer.Application; using Explorer.Contracts; using Explorer.Domain; using Explorer.Domain.Abstractions; using Explorer.Hosting; using Explorer.Indexing; using Explorer.Plugin.Abstractions; using Explorer.Search; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace Explorer.Hosting.Tests; public class CoreRegistrationTests { [Fact] public async Task AddExplorerCore_registers_workbench_without_clipboard() { var dir = Path.Combine(Path.GetTempPath(), "ew-hosting", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir); try { var services = new ServiceCollection(); services.AddLogging(); services.AddSingleton(new TempEnv(dir)); services.AddExplorerCore(); await using var sp = services.BuildServiceProvider(); Assert.NotNull(sp.GetService()); Assert.NotNull(sp.GetService()); Assert.NotNull(sp.GetService()); Assert.NotNull(sp.GetService()); Assert.Null(sp.GetService()); Assert.NotNull(sp.GetService()); Assert.NotEmpty(sp.GetServices()); Assert.NotNull(sp.GetService()); Assert.NotNull(sp.GetService()); Assert.NotNull(sp.GetService()); Assert.NotNull(sp.GetService()); Assert.IsType(sp.GetService()); } finally { try { Directory.Delete(dir, true); } catch { /* ignore */ } } } [Fact] public async Task AddExplorerClient_uses_read_only_store_without_indexing_workers() { var dir = Path.Combine(Path.GetTempPath(), "ew-hosting", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir); try { var workbench = new StubWorkbench(); var services = new ServiceCollection(); services.AddLogging(); services.AddSingleton(new TempEnv(dir)); services.AddExplorerClient(workbench); await using var sp = services.BuildServiceProvider(); Assert.Same(workbench, sp.GetService()); Assert.Same(workbench.Indexing, sp.GetService()); Assert.Same(workbench.Transfers, sp.GetService()); Assert.Same(workbench.Sources, sp.GetService()); Assert.Same(workbench.Mutations, sp.GetService()); Assert.False(sp.GetRequiredService().CanWrite); Assert.NotNull(sp.GetService()); Assert.NotNull(sp.GetService()); Assert.NotNull(sp.GetService()); Assert.NotNull(sp.GetService()); Assert.NotNull(sp.GetService()); Assert.Same(NullCloudOverlay.Instance, sp.GetService()); Assert.Null(sp.GetService()); Assert.Null(sp.GetService()); Assert.Null(sp.GetService()); Assert.Null(sp.GetService()); Assert.Null(sp.GetService()); Assert.Null(sp.GetService()); Assert.Empty(sp.GetServices()); var hosted = sp.GetServices().ToList(); Assert.Contains(hosted, s => s is IndexStoreLifetime); Assert.DoesNotContain(hosted, s => s.GetType().Name is "IndexingCoordinator" or "TransferQueue" or "WatcherHostedService" or "DuplicateHashWorker" or "HistoryRollupService" or "BackgroundMaintenanceCoordinator"); } finally { try { Directory.Delete(dir, true); } catch { /* ignore */ } } } private sealed class TempEnv : IAppEnvironment { public TempEnv(string dir) { DataDirectory = 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; } } private sealed class StubWorkbench : IWorkbenchHost { public IIndexingHost Indexing { get; } = new StubIndexing(); public ITransferHost Transfers { get; } = new StubTransfers(); public ISourceHost Sources { get; } = new StubSources(); public IIndexMutations Mutations { get; } = new StubMutations(); } private sealed class StubIndexing : IIndexingHost { public event EventHandler? ProgressChanged = delegate { }; public void EnqueueFullScan(long sourceId) { } public void EnqueueFolderScan(long sourceId, string pathRel) { } public void EnqueueReconcile(long sourceId, string pathRel) { } public void Cancel(long sourceId) { } } private sealed class StubTransfers : ITransferHost { public event EventHandler? Changed = delegate { }; public event EventHandler? JobFinished = delegate { }; public bool IsPaused => false; public IReadOnlyList Snapshot() => []; public void PauseAll() { } public void ResumeAll() { } public void Pause(long jobId) { } public void Resume(long jobId) { } public void Retry(long jobId) { } public void Cancel(long jobId) { } public void Dismiss(long jobId) { } public void ClearFinished() { } public bool MoveUp(long jobId) => false; public bool MoveDown(long jobId) => false; } private sealed class StubSources : ISourceHost { public Task RefreshAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task AddUncAsync(string path, CancellationToken cancellationToken = default) => Task.FromResult(new Source { StableKey = "x", DisplayName = path }); public Task EnsureForPathAsync(string path, CancellationToken cancellationToken = default) => Task.FromResult(null); public Task ForgetAsync(string path, CancellationToken cancellationToken = default) => Task.FromResult(false); } private sealed class StubMutations : IIndexMutations { public Task UpsertSyncProfileAsync(SyncProfile profile, CancellationToken cancellationToken = default) => Task.FromResult(0L); public Task DeleteSyncProfileAsync(long id, CancellationToken cancellationToken = default) => Task.CompletedTask; public Task UpsertOperationProfileAsync(OperationProfile profile, CancellationToken cancellationToken = default) => Task.FromResult(0L); public Task DeleteOperationProfileAsync(long id, CancellationToken cancellationToken = default) => Task.CompletedTask; public Task CreateRenameBatchAsync(IReadOnlyList items, CancellationToken cancellationToken = default) => Task.FromResult(0L); public Task MarkRenameBatchUndoneAsync(long id, CancellationToken cancellationToken = default) => Task.CompletedTask; public Task EnqueueHashCollisionsAsync(long? sourceId, CancellationToken cancellationToken = default) => Task.CompletedTask; public Task UpsertRelationAsync(FileRelation relation, CancellationToken cancellationToken = default) => Task.CompletedTask; } }