195 lines
7.0 KiB
C#
195 lines
7.0 KiB
C#
using Explorer.Application;
|
|
using Explorer.Domain;
|
|
using Explorer.Domain.Abstractions;
|
|
|
|
namespace Explorer.Application.Tests;
|
|
|
|
public class FileOperationProfilePlannerTests
|
|
{
|
|
private static readonly DateTimeOffset T0 = DateTimeOffset.Parse("2024-06-01T12:00:00Z");
|
|
|
|
[Fact]
|
|
public void Copy_lists_copy_operations_only()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\src")
|
|
.File(@"C:\src\a.txt", 1, T0)
|
|
.File(@"C:\src\b.txt", 2, T0)
|
|
.Dir(@"C:\dst");
|
|
|
|
var plan = Build(CopyProfile(), fs, [@"C:\src"]);
|
|
Assert.True(plan.CanEnqueue);
|
|
Assert.All(plan.Operations, o => Assert.Equal(TransferOp.Copy, o.Op));
|
|
Assert.Contains(plan.Operations, o => o.SourcePath == @"C:\src\a.txt" && o.DestinationPath == @"C:\dst\src\a.txt");
|
|
Assert.Contains(plan.Operations, o => o.SourcePath == @"C:\src\b.txt" && o.DestinationPath == @"C:\dst\src\b.txt");
|
|
Assert.Contains(plan.ProfilePreview, r => r.Action == "Copy" && r.Path == @"C:\dst\src\a.txt");
|
|
}
|
|
|
|
[Fact]
|
|
public void Compress_lists_one_archive_job()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\src")
|
|
.File(@"C:\src\a.txt", 1, T0)
|
|
.File(@"C:\src\b.txt", 2, T0)
|
|
.Dir(@"C:\dst");
|
|
|
|
var plan = Build(new OperationProfile
|
|
{
|
|
Name = "Archive",
|
|
DestPath = @"C:\dst",
|
|
DoCompress = true
|
|
}, fs, [@"C:\src"]);
|
|
Assert.True(plan.CanEnqueue);
|
|
var compress = Assert.Single(plan.Operations);
|
|
Assert.Equal(TransferOp.Compress, compress.Op);
|
|
Assert.Equal(@"C:\src\a.txt|C:\src\b.txt", compress.SourcePath);
|
|
Assert.Equal(@"C:\dst\src.7z", compress.DestinationPath);
|
|
Assert.Contains(plan.ProfilePreview, r => r.Action == "Compress");
|
|
}
|
|
|
|
[Fact]
|
|
public void Dirty_git_stops_before_operations()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\src")
|
|
.File(@"C:\src\a.txt", 1, T0)
|
|
.Dir(@"C:\dst");
|
|
var git = new GitStatus { RepoRoot = @"C:\src", Branch = "main", ModifiedCount = 1 };
|
|
|
|
var plan = Build(CopyProfile(git: true), fs, [@"C:\src"], git);
|
|
Assert.False(plan.CanEnqueue);
|
|
Assert.Empty(plan.Operations);
|
|
Assert.Contains(plan.Issues, i => i.Message == "Working tree is not clean.");
|
|
}
|
|
|
|
[Fact]
|
|
public void Missing_git_and_missing_repo_are_errors()
|
|
{
|
|
var fs = Tree().Dir(@"C:\src").File(@"C:\src\a.txt", 1, T0).Dir(@"C:\dst");
|
|
var noGit = Build(CopyProfile(git: true), fs, [@"C:\src"], gitAvailable: false);
|
|
Assert.Contains(noGit.Issues, i => i.Message == "git.exe is not available.");
|
|
|
|
var noRepo = Build(CopyProfile(git: true), fs, [@"C:\src"], git: null);
|
|
Assert.Contains(noRepo.Issues, i => i.Message == "Source is not a Git repository.");
|
|
}
|
|
|
|
[Fact]
|
|
public void Offline_destination_is_an_error()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\src")
|
|
.File(@"C:\src\a.txt", 1, T0)
|
|
.Dir(@"Z:\backup");
|
|
|
|
var plan = Build(
|
|
CopyProfile(@"Z:\backup"),
|
|
fs,
|
|
[@"C:\src"],
|
|
reachable: path => path.StartsWith(@"C:\", StringComparison.OrdinalIgnoreCase));
|
|
Assert.False(plan.CanEnqueue);
|
|
Assert.Empty(plan.Operations);
|
|
Assert.Contains(plan.Issues, i => i.Message == "Destination is not available.");
|
|
}
|
|
|
|
[Fact]
|
|
public void Rename_then_copy_uses_new_names()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\src")
|
|
.File(@"C:\src\keep.txt", 1, T0)
|
|
.File(@"C:\src\a.txt", 1, T0)
|
|
.Dir(@"C:\dst");
|
|
|
|
var plan = Build(new OperationProfile
|
|
{
|
|
Name = "Rename copy",
|
|
DestPath = @"C:\dst",
|
|
DoCopy = true,
|
|
DoRename = true,
|
|
RenamePrefix = "x_"
|
|
}, fs, [@"C:\src"]);
|
|
Assert.True(plan.CanEnqueue);
|
|
Assert.Contains(plan.Operations, o => o.Op == TransferOp.Rename && o.NewName == "x_keep.txt");
|
|
Assert.Contains(plan.Operations, o =>
|
|
o.Op == TransferOp.Copy
|
|
&& o.SourcePath == @"C:\src\x_a.txt"
|
|
&& o.DestinationPath == @"C:\dst\src\x_a.txt");
|
|
Assert.Contains(plan.Operations, o =>
|
|
o.Op == TransferOp.Copy
|
|
&& o.SourcePath == @"C:\src\x_keep.txt"
|
|
&& o.DestinationPath == @"C:\dst\src\x_keep.txt");
|
|
}
|
|
|
|
[Fact]
|
|
public void Excludes_and_online_only_files_are_skipped()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\src")
|
|
.File(@"C:\src\keep.txt", 1, T0)
|
|
.File(@"C:\src\skip.tmp", 1, T0)
|
|
.Dir(@"C:\src\bin")
|
|
.File(@"C:\src\cloud.bin", 1, T0)
|
|
.Dir(@"C:\dst");
|
|
|
|
var plan = Build(new OperationProfile
|
|
{
|
|
Name = "Copy",
|
|
DestPath = @"C:\dst",
|
|
DoCopy = true,
|
|
Excludes = "*.tmp\nbin"
|
|
}, fs, [@"C:\src"], wouldHydrate: item => item.Name == "cloud.bin");
|
|
Assert.Contains(plan.Operations, o => o.SourcePath.EndsWith("keep.txt", StringComparison.OrdinalIgnoreCase));
|
|
Assert.DoesNotContain(plan.Operations, o => o.SourcePath.EndsWith("skip.tmp", StringComparison.OrdinalIgnoreCase));
|
|
Assert.DoesNotContain(plan.Operations, o => o.SourcePath.Contains(@"\bin", StringComparison.OrdinalIgnoreCase));
|
|
Assert.DoesNotContain(plan.Operations, o => o.SourcePath.EndsWith("cloud.bin", StringComparison.OrdinalIgnoreCase));
|
|
Assert.Contains(plan.Issues, i => i.Message.Contains("Online-only", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
[Fact]
|
|
public void Missing_7zip_is_an_error()
|
|
{
|
|
var fs = Tree().Dir(@"C:\src").File(@"C:\src\a.txt", 1, T0).Dir(@"C:\dst");
|
|
var plan = Build(new OperationProfile
|
|
{
|
|
Name = "Archive",
|
|
DestPath = @"C:\dst",
|
|
DoCompress = true
|
|
}, fs, [@"C:\src"], compressAvailable: false);
|
|
Assert.False(plan.CanEnqueue);
|
|
Assert.Contains(plan.Issues, i => i.Message.Contains("7-Zip", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
private static OperationProfile CopyProfile(string dest = @"C:\dst", bool git = false)
|
|
=> new()
|
|
{
|
|
Name = "Copy",
|
|
DestPath = dest,
|
|
DoCopy = true,
|
|
RequireGitClean = git
|
|
};
|
|
|
|
private static OperationPlan Build(
|
|
OperationProfile profile,
|
|
IFileSystemEnumerator fs,
|
|
string[] sources,
|
|
GitStatus? git = null,
|
|
bool gitAvailable = true,
|
|
bool compressAvailable = true,
|
|
Func<string, bool>? reachable = null,
|
|
Func<FileSystemItem, bool>? wouldHydrate = null)
|
|
=> new FileOperationProfilePlanner(new RenamePlanner()).Build(
|
|
profile,
|
|
sources,
|
|
fs,
|
|
reachable ?? (_ => true),
|
|
git,
|
|
gitAvailable,
|
|
compressAvailable,
|
|
SevenZipLocator.MissingHint,
|
|
pathExists: _ => false,
|
|
wouldHydrate: wouldHydrate);
|
|
|
|
private static TreeEnumerator Tree() => new();
|
|
}
|