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

166 lines
6.4 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 OperationProfileServiceTests
{
[Fact]
public async Task Dirty_git_does_not_enqueue()
{
await using var ctx = await ProfileHarness.CreateAsync();
ctx.Git.Status = new GitStatus { RepoRoot = ctx.Root, Branch = "main", ModifiedCount = 2 };
ctx.Profile.RequireGitClean = true;
var plan = await ctx.Profiles.EnqueueAsync(ctx.Profile);
Assert.False(plan.CanEnqueue);
Assert.Contains(plan.Issues, i => i.Message == "Working tree is not clean.");
Assert.Empty(ctx.Queue.Snapshot());
}
[Fact]
public async Task Auto_run_fires_only_after_the_destination_returns()
{
await using var ctx = await ProfileHarness.CreateAsync();
ctx.Profile.AutoRun = true;
await ctx.Profiles.SaveAsync(ctx.Profile);
await ctx.Profiles.TryAutoRunAsync();
Assert.Empty(ctx.Queue.Snapshot());
ctx.Volumes.Reachable = false;
await ctx.Profiles.TryAutoRunAsync();
Assert.Empty(ctx.Queue.Snapshot());
ctx.Volumes.Reachable = true;
await ctx.Queue.StartAsync(CancellationToken.None);
await ctx.Profiles.TryAutoRunAsync();
await WaitUntil(() => ctx.Queue.Snapshot().Any(j => j.Status == TransferStatus.Done));
Assert.True(File.Exists(Path.Combine(ctx.Dest, Path.GetFileName(ctx.Root), "a.txt")));
File.WriteAllText(Path.Combine(ctx.Root, "b.txt"), "b");
await ctx.Profiles.TryAutoRunAsync();
Assert.DoesNotContain(ctx.Queue.Snapshot(), j => j.SourcePath.EndsWith("b.txt", StringComparison.OrdinalIgnoreCase));
await ctx.Queue.StopAsync(CancellationToken.None);
}
[Fact]
public async Task Compress_auto_run_is_ignored()
{
await using var ctx = await ProfileHarness.CreateAsync();
ctx.Profile.DoCompress = true;
ctx.Profile.AutoRun = true;
await ctx.Profiles.SaveAsync(ctx.Profile);
Assert.False((await ctx.Store.OperationProfiles.GetAsync(ctx.Profile.Id))!.CanAutoRun);
ctx.Volumes.Reachable = false;
await ctx.Profiles.TryAutoRunAsync();
ctx.Volumes.Reachable = true;
await ctx.Profiles.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 ProfileHarness : IAsyncDisposable
{
public required OperationProfileService Profiles { get; init; }
public required TransferQueue Queue { get; init; }
public required ControlledVolumes Volumes { get; init; }
public required SqliteIndexStore Store { get; init; }
public required OperationProfile Profile { get; init; }
public required StubGit Git { get; init; }
public required string Root { get; init; }
public required string Dest { get; init; }
public static async Task<ProfileHarness> CreateAsync()
{
var work = Path.Combine(Path.GetTempPath(), "ew-op", Guid.NewGuid().ToString("N"));
var source = Path.Combine(work, "src");
var dest = Path.Combine(work, "dest");
Directory.CreateDirectory(source);
Directory.CreateDirectory(dest);
File.WriteAllText(Path.Combine(source, "a.txt"), "a");
var store = new SqliteIndexStore(Path.Combine(work, "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 git = new StubGit();
var planner = new FileOperationProfilePlanner(new RenamePlanner());
var renames = new RenameBatchService(new RenamePlanner(), store, ops);
var profiles = new OperationProfileService(
planner,
store,
ops,
renames,
volumes,
enumerator,
git,
new NeverHydrate(),
new FakeArchiveExecutor());
return new ProfileHarness
{
Profiles = profiles,
Queue = queue,
Volumes = volumes,
Store = store,
Git = git,
Profile = new OperationProfile
{
Name = "Copy",
SourcePath = source,
DestPath = dest,
DoCopy = true
},
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 StubGit : IGitStatusProvider
{
public bool IsAvailable { get; set; } = true;
public GitStatus? Status { get; set; }
public string? FindRepoRoot(string path) => Status?.RepoRoot;
public bool IsRepoRoot(string path) => Status is not null;
public Task<GitStatus?> GetStatusAsync(string path, CancellationToken cancellationToken = default)
=> Task.FromResult(Status);
}
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);
}
}