Keep official clients in charge of sync while Explorer can group locations, persist UI prefs, and list zip/rar/7z members without extracting them. Co-authored-by: Cursor <cursoragent@cursor.com>
185 lines
6.2 KiB
C#
185 lines
6.2 KiB
C#
using Explorer.Domain;
|
|
|
|
namespace Explorer.Domain.Tests;
|
|
|
|
public class NameNormalizerTests
|
|
{
|
|
[Fact]
|
|
public void Normalize_folds_case_and_keeps_unicode()
|
|
{
|
|
Assert.Equal("straße", NameNormalizer.Normalize("Straße"));
|
|
Assert.Equal("abc", NameNormalizer.Normalize("ABC"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Extension_skips_leading_dot_names()
|
|
{
|
|
Assert.Equal("mkv", NameNormalizer.Extension("film.mkv"));
|
|
Assert.Null(NameNormalizer.Extension(".gitignore"));
|
|
Assert.Null(NameNormalizer.Extension("Makefile"));
|
|
}
|
|
}
|
|
|
|
public class PathRulesTests
|
|
{
|
|
[Fact]
|
|
public void Extended_roundtrip_local_and_unc()
|
|
{
|
|
Assert.Equal(@"\\?\C:\Temp", PathRules.ToExtended(@"C:\Temp"));
|
|
Assert.Equal(@"\\?\UNC\server\share", PathRules.ToExtended(@"\\server\share"));
|
|
Assert.Equal(@"C:\Temp", PathRules.FromExtended(@"\\?\C:\Temp"));
|
|
Assert.Equal(@"\\server\share", PathRules.FromExtended(@"\\?\UNC\server\share"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Canonical_unc_lowercases_server()
|
|
{
|
|
Assert.Equal(@"\\media\Movies", PathRules.CanonicalUncRoot(@"\\Media\Movies\Action"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Relative_and_parent()
|
|
{
|
|
Assert.Equal(@"Windows\System32", PathRules.MakeRelative(@"C:\", @"C:\Windows\System32"));
|
|
Assert.Equal(@"C:\Windows", PathRules.Parent(@"C:\Windows\System32"));
|
|
Assert.Equal(@"C:\", PathRules.Parent(@"C:\Windows"));
|
|
Assert.Equal(@"pack.zip\docs", PathRules.RelativeParent(@"pack.zip\docs\a.txt"));
|
|
Assert.Equal("pack.zip", PathRules.RelativeParent(@"pack.zip\docs"));
|
|
Assert.Equal("", PathRules.RelativeParent("pack.zip"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Join_display_combines_root_and_relative()
|
|
{
|
|
Assert.Equal(@"C:\Users\Docs", PathRules.JoinDisplay(@"C:\Users", "Docs"));
|
|
Assert.Equal(@"C:\", PathRules.JoinDisplay(@"C:\", ""));
|
|
Assert.Equal("Docs", PathRules.JoinDisplay(null, "Docs"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Shorten_keeps_root_and_leaf()
|
|
{
|
|
Assert.Equal(@"C:\Temp", PathRules.ShortenDisplay(@"C:\Temp"));
|
|
Assert.Equal(@"C:\Users\…\Documents\Budget", PathRules.ShortenDisplay(@"C:\Users\Dominique\Documents\Budget", 28));
|
|
Assert.Equal(@"\\media\movies\…\Action\Dune", PathRules.ShortenDisplay(@"\\media\movies\New\Action\Dune", 28));
|
|
}
|
|
}
|
|
|
|
public class ArchiveFormatsTests
|
|
{
|
|
[Fact]
|
|
public void Detects_common_archive_extensions()
|
|
{
|
|
Assert.True(ArchiveFormats.IsArchive("pack.zip"));
|
|
Assert.True(ArchiveFormats.IsArchive("film.7z"));
|
|
Assert.True(ArchiveFormats.IsArchive("backup.rar"));
|
|
Assert.True(ArchiveFormats.IsArchive("src.tar.gz"));
|
|
Assert.True(ArchiveFormats.IsZipFamily("photos.cbz"));
|
|
Assert.False(ArchiveFormats.IsArchive("notes.txt"));
|
|
Assert.False(ArchiveFormats.IsArchive("report.docx"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Rejects_unsafe_archive_entry_paths()
|
|
{
|
|
Assert.True(ArchiveFormats.TryNormalizeEntryPath("docs/a.txt", out var ok));
|
|
Assert.Equal(@"docs\a.txt", ok);
|
|
Assert.False(ArchiveFormats.TryNormalizeEntryPath(@"..\escape.txt", out _));
|
|
Assert.False(ArchiveFormats.TryNormalizeEntryPath(@"C:\abs.txt", out _));
|
|
Assert.False(ArchiveFormats.TryNormalizeEntryPath("/", out _));
|
|
}
|
|
}
|
|
|
|
public class VolumeIdentityTests
|
|
{
|
|
[Fact]
|
|
public void Matches_volume_guid_across_drive_letters()
|
|
{
|
|
var known = new List<Source>
|
|
{
|
|
new()
|
|
{
|
|
Id = 1,
|
|
StableKey = "a",
|
|
DisplayName = "SanDisk Ultra 64 GB",
|
|
VolumeGuid = @"\\?\Volume{abc}\",
|
|
LastRootPath = @"E:\",
|
|
Kind = SourceKind.Removable
|
|
}
|
|
};
|
|
var fp = new VolumeFingerprint
|
|
{
|
|
Kind = SourceKind.Removable,
|
|
VolumeGuid = @"\\?\Volume{abc}\",
|
|
RootPath = @"G:\",
|
|
DisplayName = "SanDisk"
|
|
};
|
|
var match = VolumeIdentityMatcher.Match(fp, known);
|
|
Assert.NotNull(match.Source);
|
|
Assert.Equal(1, match.Source!.Id);
|
|
Assert.Equal(VolumeIdentityMatcher.MatchStrength.Guid, match.Strength);
|
|
Assert.False(match.Ambiguous);
|
|
}
|
|
|
|
[Fact]
|
|
public void Matches_unc_share()
|
|
{
|
|
var known = new List<Source>
|
|
{
|
|
new()
|
|
{
|
|
Id = 7,
|
|
StableKey = "s",
|
|
DisplayName = @"\\media\movies",
|
|
Kind = SourceKind.Smb,
|
|
LastRootPath = @"\\Media\Movies"
|
|
}
|
|
};
|
|
var fp = new VolumeFingerprint { Kind = SourceKind.Smb, RootPath = @"\\media\movies", DisplayName = "x" };
|
|
Assert.Equal(7, VolumeIdentityMatcher.Match(fp, known).Source?.Id);
|
|
}
|
|
}
|
|
|
|
public class ExcludeEvaluatorTests
|
|
{
|
|
[Fact]
|
|
public void Excludes_path_glob_and_extension()
|
|
{
|
|
var ev = new ExcludeEvaluator(
|
|
[
|
|
new() { Kind = ExcludeKind.PathPrefix, Pattern = @"C:\Windows" },
|
|
new() { Kind = ExcludeKind.Glob, Pattern = "node_modules" },
|
|
new() { Kind = ExcludeKind.Extension, Pattern = "tmp" }
|
|
], skipHidden: false, skipSystem: false);
|
|
|
|
Assert.True(ev.ShouldExclude(@"C:\Windows\System32", "System32", true, 0, null));
|
|
Assert.True(ev.ShouldExclude(@"D:\proj\node_modules", "node_modules", true, 0, null));
|
|
Assert.True(ev.ShouldExclude(@"D:\a.tmp", "a.tmp", false, 0, null));
|
|
Assert.False(ev.ShouldExclude(@"D:\a.txt", "a.txt", false, 0, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Hidden_policy()
|
|
{
|
|
var ev = new ExcludeEvaluator([], skipHidden: true, skipSystem: true);
|
|
Assert.True(ev.ShouldExclude(@"C:\x", "x", false, AttributeFlags.Hidden, null));
|
|
}
|
|
}
|
|
|
|
public class ReparsePolicyTests
|
|
{
|
|
[Fact]
|
|
public void Does_not_recurse_reparse_directories()
|
|
{
|
|
var item = new FileSystemItem
|
|
{
|
|
FullPath = @"C:\link",
|
|
Name = "link",
|
|
IsDirectory = true,
|
|
Attributes = AttributeFlags.Directory | AttributeFlags.ReparsePoint,
|
|
ReparseTag = ReparsePolicy.IoReparseTagSymlink
|
|
};
|
|
Assert.False(ReparsePolicy.ShouldRecurseIntoDirectory(item));
|
|
}
|
|
}
|