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")); } private static OperationPlan Build( IFileSystemEnumerator fs, Action? configure = null, Func? 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? 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(); }