201 lines
7.7 KiB
C#
201 lines
7.7 KiB
C#
using Explorer.Application;
|
|
using Explorer.Domain;
|
|
using Explorer.Domain.Abstractions;
|
|
|
|
namespace Explorer.Application.Tests;
|
|
|
|
public class ReorganizePlannerTests
|
|
{
|
|
private static readonly DateTimeOffset T0 = DateTimeOffset.Parse("2024-06-01T12:00:00Z");
|
|
|
|
[Fact]
|
|
public void Proposes_moves_and_leaves_unknown_and_build_output()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\Downloads")
|
|
.File(@"C:\Downloads\setup.exe", 10, T0)
|
|
.File(@"C:\Downloads\photo.jpg", 20, T0)
|
|
.File(@"C:\Downloads\clip.mkv", 30, T0)
|
|
.File(@"C:\Downloads\notes.pdf", 8, T0)
|
|
.File(@"C:\Downloads\pack.zip", 4, T0)
|
|
.File(@"C:\Downloads\weird.bin", 1, T0)
|
|
.Dir(@"C:\Downloads\node_modules")
|
|
.File(@"C:\Downloads\node_modules\pkg.js", 1, T0)
|
|
.Dir(@"C:\Software")
|
|
.Dir(@"C:\Pictures")
|
|
.Dir(@"C:\Videos")
|
|
.Dir(@"C:\Docs")
|
|
.Dir(@"C:\Archive");
|
|
|
|
var plan = Build(fs);
|
|
Assert.True(plan.CanEnqueue);
|
|
Assert.Contains(plan.Operations, o => o.Op == TransferOp.Move && o.SourcePath.EndsWith("setup.exe") && o.DestinationPath == @"C:\Software\setup.exe");
|
|
Assert.Contains(plan.Operations, o => o.DestinationPath == @"C:\Pictures\photo.jpg");
|
|
Assert.Contains(plan.Operations, o => o.DestinationPath == @"C:\Videos\clip.mkv");
|
|
Assert.Contains(plan.Operations, o => o.DestinationPath == @"C:\Docs\notes.pdf");
|
|
Assert.Contains(plan.Operations, o => o.DestinationPath == @"C:\Archive\pack.zip");
|
|
Assert.DoesNotContain(plan.Operations, o => o.SourcePath.Contains("weird.bin", StringComparison.OrdinalIgnoreCase));
|
|
Assert.DoesNotContain(plan.Operations, o => o.SourcePath.Contains("node_modules", StringComparison.OrdinalIgnoreCase));
|
|
Assert.Contains(plan.OrganizePreview, r => r.Name == "weird.bin" && r.Action == "Skip");
|
|
Assert.Contains(plan.OrganizePreview, r => r.Name == "node_modules" && r.Action == "Skip");
|
|
}
|
|
|
|
[Fact]
|
|
public void Git_repo_moves_as_a_unit_to_development()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\Downloads")
|
|
.Dir(@"C:\Downloads\Explorer")
|
|
.File(@"C:\Downloads\Explorer\README.md", 1, T0)
|
|
.Dir(@"C:\Dev");
|
|
|
|
var plan = Build(fs, dest => dest.Development = @"C:\Dev", isRepoRoot: path => path.EndsWith(@"\Explorer", StringComparison.OrdinalIgnoreCase));
|
|
Assert.Contains(plan.Operations, o => o.SourcePath == @"C:\Downloads\Explorer" && o.DestinationPath == @"C:\Dev\Explorer");
|
|
Assert.DoesNotContain(plan.Operations, o => o.SourcePath.Contains("README", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
[Fact]
|
|
public void Missing_destination_skips_that_category()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\Downloads")
|
|
.File(@"C:\Downloads\setup.exe", 10, T0)
|
|
.File(@"C:\Downloads\photo.jpg", 20, T0)
|
|
.Dir(@"C:\Pictures");
|
|
|
|
var plan = Build(fs, dest => dest.Installers = "");
|
|
Assert.Contains(plan.Operations, o => o.DestinationPath == @"C:\Pictures\photo.jpg");
|
|
Assert.DoesNotContain(plan.Operations, o => o.SourcePath.EndsWith("setup.exe"));
|
|
Assert.Contains(plan.OrganizePreview, r => r.Name == "setup.exe" && r.Action == "Skip");
|
|
}
|
|
|
|
[Fact]
|
|
public void Old_installer_is_a_warning_still_proposed()
|
|
{
|
|
var old = T0.AddYears(-2);
|
|
var fs = Tree()
|
|
.Dir(@"C:\Downloads")
|
|
.File(@"C:\Downloads\old.msi", 10, old)
|
|
.Dir(@"C:\Software");
|
|
|
|
var plan = Build(fs, now: T0);
|
|
Assert.True(plan.CanEnqueue);
|
|
Assert.Contains(plan.Operations, o => o.SourcePath.EndsWith("old.msi"));
|
|
Assert.Contains(plan.Issues, i => i.Severity == PlanIssueSeverity.Warning && i.Message.Contains("Old", StringComparison.OrdinalIgnoreCase));
|
|
Assert.Contains(plan.OrganizePreview, r => r.Name == "old.msi" && r.Detail == "Older than 1 year.");
|
|
}
|
|
|
|
[Fact]
|
|
public void Offline_destination_is_an_error_and_does_not_enqueue()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\Downloads")
|
|
.File(@"C:\Downloads\photo.jpg", 20, T0)
|
|
.Dir(@"Z:\Pictures");
|
|
|
|
var plan = new ReorganizePlanner().Build(
|
|
@"C:\Downloads",
|
|
Map(d => d.Pictures = @"Z:\Pictures"),
|
|
fs,
|
|
path => path.StartsWith(@"C:\", StringComparison.OrdinalIgnoreCase),
|
|
_ => false);
|
|
Assert.False(plan.CanEnqueue);
|
|
Assert.Empty(plan.Operations);
|
|
Assert.Contains(plan.Issues, i => i.Message == "Destination is not available.");
|
|
}
|
|
|
|
[Fact]
|
|
public void Destination_inside_the_source_is_allowed()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\Downloads")
|
|
.File(@"C:\Downloads\setup.exe", 10, T0)
|
|
.Dir(@"C:\Downloads\Software");
|
|
|
|
var plan = Build(fs, dest => dest.Installers = @"C:\Downloads\Software");
|
|
Assert.True(plan.CanEnqueue);
|
|
Assert.Contains(plan.Operations, o => o.DestinationPath == @"C:\Downloads\Software\setup.exe");
|
|
Assert.DoesNotContain(plan.Operations, o => o.SourcePath == @"C:\Downloads\Software");
|
|
}
|
|
|
|
[Fact]
|
|
public void Planner_does_not_move_files_itself()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\Downloads")
|
|
.File(@"C:\Downloads\photo.jpg", 20, T0)
|
|
.Dir(@"C:\Pictures");
|
|
|
|
var plan = Build(fs);
|
|
Assert.NotEmpty(plan.Operations);
|
|
Assert.NotNull(fs.GetItem(@"C:\Downloads\photo.jpg"));
|
|
Assert.Null(fs.GetItem(@"C:\Pictures\photo.jpg"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Prefers_indexed_archive_contents_over_extension()
|
|
{
|
|
var fs = Tree()
|
|
.Dir(@"C:\Downloads")
|
|
.File(@"C:\Downloads\photos.zip", 4, T0)
|
|
.Dir(@"C:\Pictures")
|
|
.Dir(@"C:\Archive");
|
|
|
|
var indexed = new Dictionary<string, IndexEntry>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["photos.zip"] = new IndexEntry
|
|
{
|
|
Name = "photos.zip",
|
|
NameNorm = "photos.zip",
|
|
PathRel = "photos.zip",
|
|
Category = FileCategory.Photos,
|
|
CategorySource = CategorySources.ArchiveContents,
|
|
CategoryReason = "Archive contents are mostly photos."
|
|
}
|
|
};
|
|
var plan = new ReorganizePlanner().Build(
|
|
@"C:\Downloads",
|
|
Map(),
|
|
fs,
|
|
_ => true,
|
|
_ => false,
|
|
now: T0,
|
|
pathExists: _ => false,
|
|
indexedByName: indexed);
|
|
Assert.Contains(plan.Operations, o => o.SourcePath.EndsWith("photos.zip") && o.DestinationPath == @"C:\Pictures\photos.zip");
|
|
Assert.DoesNotContain(plan.Operations, o => o.DestinationPath == @"C:\Archive\photos.zip");
|
|
}
|
|
|
|
private static OperationPlan Build(
|
|
IFileSystemEnumerator fs,
|
|
Action<OrganizeDestinations>? configure = null,
|
|
Func<string, bool>? isRepoRoot = null,
|
|
DateTimeOffset? now = null)
|
|
=> new ReorganizePlanner().Build(
|
|
@"C:\Downloads",
|
|
Map(configure),
|
|
fs,
|
|
_ => true,
|
|
isRepoRoot ?? (_ => false),
|
|
now: now ?? T0,
|
|
pathExists: _ => false);
|
|
|
|
private static OrganizeDestinations Map(Action<OrganizeDestinations>? configure = null)
|
|
{
|
|
var dest = new OrganizeDestinations
|
|
{
|
|
Pictures = @"C:\Pictures",
|
|
Videos = @"C:\Videos",
|
|
Audio = @"C:\Music",
|
|
Documents = @"C:\Docs",
|
|
Installers = @"C:\Software",
|
|
Archives = @"C:\Archive",
|
|
Development = @"C:\Dev"
|
|
};
|
|
configure?.Invoke(dest);
|
|
return dest;
|
|
}
|
|
|
|
private static TreeEnumerator Tree() => new();
|
|
}
|