Files
Explorer-Workbench/tests/Explorer.FileOperations.Tests/FolderSyncServiceTests.cs

165 lines
6.3 KiB
C#

using Explorer.Application;
using Explorer.Domain;
using Explorer.Domain.Abstractions;
using Explorer.FileOperations;
using Explorer.Storage.Sqlite;
using Microsoft.Extensions.Logging.Abstractions;
namespace Explorer.FileOperations.Tests;
public class FolderSyncServiceTests
{
[Fact]
public async Task Enqueue_copy_waits_when_the_destination_is_offline()
{
await using var ctx = await SyncHarness.CreateAsync();
var plan = await ctx.Sync.PreviewAsync(ctx.Profile);
Assert.True(plan.CanEnqueue);
ctx.Volumes.Reachable = false;
await ctx.Queue.StartAsync(CancellationToken.None);
await ctx.Sync.EnqueueAsync(ctx.Profile, plan);
await WaitUntil(() => ctx.Queue.Snapshot().Any(j => j.Status == TransferStatus.Waiting));
Assert.Equal(0, ctx.Shell.CopyCount);
ctx.Volumes.Reachable = true;
ctx.Queue.NotifyAvailability();
await WaitUntil(() => ctx.Queue.Snapshot().All(j => j.Status == TransferStatus.Done));
Assert.True(File.Exists(Path.Combine(ctx.Dest, "a.txt")));
await ctx.Queue.StopAsync(CancellationToken.None);
}
[Fact]
public async Task Auto_run_fires_only_after_the_destination_returns()
{
await using var ctx = await SyncHarness.CreateAsync();
ctx.Profile.AutoRun = true;
await ctx.Sync.SaveAsync(ctx.Profile);
await ctx.Sync.TryAutoRunAsync();
Assert.Empty(ctx.Queue.Snapshot());
ctx.Volumes.Reachable = false;
await ctx.Sync.TryAutoRunAsync();
Assert.Empty(ctx.Queue.Snapshot());
ctx.Volumes.Reachable = true;
await ctx.Queue.StartAsync(CancellationToken.None);
await ctx.Sync.TryAutoRunAsync();
await WaitUntil(() => ctx.Queue.Snapshot().Any(j => j.Status == TransferStatus.Done));
Assert.True(File.Exists(Path.Combine(ctx.Dest, "a.txt")));
File.WriteAllText(Path.Combine(ctx.Root, "b.txt"), "b");
await ctx.Sync.TryAutoRunAsync();
Assert.DoesNotContain(ctx.Queue.Snapshot(), j => j.SourcePath.EndsWith("b.txt", StringComparison.OrdinalIgnoreCase));
await ctx.Queue.StopAsync(CancellationToken.None);
}
[Fact]
public async Task Mirror_auto_run_is_ignored()
{
await using var ctx = await SyncHarness.CreateAsync();
ctx.Profile.Mode = SyncMode.Mirror;
ctx.Profile.AutoRun = true;
await ctx.Sync.SaveAsync(ctx.Profile);
ctx.Volumes.Reachable = false;
await ctx.Sync.TryAutoRunAsync();
ctx.Volumes.Reachable = true;
await ctx.Sync.TryAutoRunAsync();
Assert.Empty(ctx.Queue.Snapshot());
}
private static async Task WaitUntil(Func<bool> condition)
{
var limit = DateTime.UtcNow + TimeSpan.FromSeconds(4);
while (!condition())
{
if (DateTime.UtcNow > limit)
{
throw new TimeoutException("Condition was not met.");
}
await Task.Delay(20);
}
}
private sealed class SyncHarness : IAsyncDisposable
{
public required FolderSyncService Sync { get; init; }
public required TransferQueue Queue { get; init; }
public required GateShell Shell { get; init; }
public required ControlledVolumes Volumes { get; init; }
public required SqliteIndexStore Store { get; init; }
public required SyncProfile Profile { get; init; }
public required string Root { get; init; }
public required string Dest { get; init; }
public static async Task<SyncHarness> CreateAsync()
{
var root = Path.Combine(Path.GetTempPath(), "ew-sync", Guid.NewGuid().ToString("N"));
var source = Path.Combine(root, "src");
var dest = Path.Combine(root, "dest");
Directory.CreateDirectory(source);
Directory.CreateDirectory(dest);
File.WriteAllText(Path.Combine(source, "a.txt"), "a");
var store = new SqliteIndexStore(Path.Combine(root, "index.db"), NullLogger<SqliteIndexStore>.Instance);
await store.OpenAsync();
var shell = new GateShell();
var volumes = new ControlledVolumes();
var enumerator = new DiskEnum();
var queue = new TransferQueue(
new NativeFileOperationExecutor(shell, enumerator),
store,
volumes,
NullLogger<TransferQueue>.Instance);
var ops = new FileOperationService(queue, shell, enumerator);
var env = new SyncEnv(root);
var sources = new SourceManager(store, volumes, env, new SystemClock(), NullLogger<SourceManager>.Instance);
var sync = new FolderSyncService(
new FolderSyncPlanner(),
store,
sources,
ops,
volumes,
enumerator,
new NeverHydrate());
return new SyncHarness
{
Sync = sync,
Queue = queue,
Shell = shell,
Volumes = volumes,
Store = store,
Profile = new SyncProfile
{
Name = "Test",
SourcePath = source,
DestPath = dest,
Mode = SyncMode.CopyUpdate
},
Root = source,
Dest = dest
};
}
public async ValueTask DisposeAsync()
{
await Store.DisposeAsync();
var work = Path.GetDirectoryName(Root);
try { if (work is not null) Directory.Delete(work, true); } catch { /* ignore */ }
}
}
private sealed class SyncEnv(string dir) : IAppEnvironment
{
public string DataDirectory { get; } = dir;
public string DatabasePath { get; } = Path.Combine(dir, "index.db");
public string LogDirectory { get; } = Path.Combine(dir, "logs");
}
private sealed class NeverHydrate : IHydrationGuard
{
public bool WouldHydrateOnRead(FileSystemItem item) => false;
public bool WouldHydrateOnRead(int attributes, CloudAvailability? availability) => false;
public Task<bool> WouldHydrateOnReadAsync(string path, CancellationToken cancellationToken = default)
=> Task.FromResult(false);
}
}