diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-05-04 21:26:26 +0200 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-05-04 21:26:26 +0200 |
| commit | 57c0fcd674c659c658369f0aebfd5d9d6787a9d4 (patch) | |
| tree | 7aff23d6f54e913a6a34cb5a2568a07298582444 /tests | |
| parent | 68ab58589444091925c15ad20d36f935b7bc2e21 (diff) | |
| parent | ec04313317bed62728b059108cd232e9744f6354 (diff) | |
Merge remote-tracking branch 'upstream/master' into epg-fixes
Diffstat (limited to 'tests')
8 files changed, 544 insertions, 13 deletions
diff --git a/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs b/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs index 6171f12e47..5c187da413 100644 --- a/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs +++ b/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs @@ -1,3 +1,4 @@ +using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.MediaInfo; @@ -23,11 +24,6 @@ public class BaseItemTests [InlineData("/Movies/Deadpool 2 (2018)/Deadpool 2 (2018).mkv", "/Movies/Deadpool 2 (2018)/Deadpool 2 (2018) - Super Duper Cut.mkv", "Deadpool 2 (2018)", "Super Duper Cut")] public void GetMediaSourceName_Valid(string primaryPath, string altPath, string name, string altName) { - var mediaSourceManager = new Mock<IMediaSourceManager>(); - mediaSourceManager.Setup(x => x.GetPathProtocol(It.IsAny<string>())) - .Returns((string x) => MediaProtocol.File); - BaseItem.MediaSourceManager = mediaSourceManager.Object; - var video = new Video() { Path = primaryPath @@ -38,7 +34,14 @@ public class BaseItemTests Path = altPath, }; - video.LocalAlternateVersions = [videoAlt.Path]; + var mediaSourceManager = new Mock<IMediaSourceManager>(); + mediaSourceManager.Setup(x => x.GetPathProtocol(It.IsAny<string>())) + .Returns((string x) => MediaProtocol.File); + var libraryManager = new Mock<ILibraryManager>(); + libraryManager.Setup(x => x.GetLocalAlternateVersionIds(It.IsAny<Video>())) + .Returns([Guid.Empty]); + BaseItem.MediaSourceManager = mediaSourceManager.Object; + BaseItem.LibraryManager = libraryManager.Object; Assert.Equal(name, video.GetMediaSourceName(video)); Assert.Equal(altName, video.GetMediaSourceName(videoAlt)); diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs b/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs index fc969527e8..1406c8ee91 100644 --- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs +++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs @@ -15,10 +15,17 @@ namespace Jellyfin.MediaEncoding.Hls.Tests.Playlist } [Fact] - public void ComputeSegments_InvalidDuration_ThrowsArgumentException() + public void ComputeSegments_ZeroDurationOvershoot_ClampsToDuration() { var keyframeData = new KeyframeData(0, new[] { MsToTicks(10000) }); - Assert.Throws<ArgumentException>(() => DynamicHlsPlaylistGenerator.ComputeSegments(keyframeData, 6000)); + Assert.Equal(new[] { 10.0 }, DynamicHlsPlaylistGenerator.ComputeSegments(keyframeData, 6000)); + } + + [Fact] + public void ComputeSegments_MinorDurationOvershoot_ClampsToDuration() + { + var keyframeData = new KeyframeData(MsToTicks(9900), new[] { 0L, MsToTicks(5000), MsToTicks(10000) }); + Assert.Equal(new[] { 10.0 }, DynamicHlsPlaylistGenerator.ComputeSegments(keyframeData, 6000)); } [Theory] diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs index 7bfab570b7..abdade8f6d 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs @@ -80,7 +80,9 @@ namespace Jellyfin.Naming.Tests.TV [InlineData("[VCB-Studio] Re Zero kara Hajimeru Isekai Seikatsu [21][Ma10p_1080p][x265_flac].mkv", 21)] [InlineData("[CASO&Sumisora][Oda_Nobuna_no_Yabou][04][BDRIP][1920x1080][x264_AAC][7620E503].mp4", 4)] - // [InlineData("Case Closed (1996-2007)/Case Closed - 317.mkv", 317)] // triple digit episode number + [InlineData("Case Closed (1996-2007)/Case Closed - 317.mkv", 317)] // triple digit episode number + [InlineData("Season 2/Hunter X Hunter - 101.mkv", 101)] // triple digit episode number without brackets + [InlineData("Season 1/Show Name - 1234 [720p].mkv", 1234)] // four digit episode number with quality tag // TODO: [InlineData("Season 2/16 12 Some Title.avi", 16)] // TODO: [InlineData("Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", 3)] // TODO: [InlineData("Season 2/7 12 Angry Men.avi", 7)] diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs index 7f2188a3eb..9fef6c517a 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs @@ -18,7 +18,7 @@ namespace Jellyfin.Naming.Tests.TV [InlineData(2, "The Simpsons/The Simpsons - 02.avi")] [InlineData(2, "The Simpsons/The Simpsons - 02 Ep Name.avi")] [InlineData(7, "GJ Club (2013)/GJ Club - 07.mkv")] - [InlineData(17, "Case Closed (1996-2007)/Case Closed - 317.mkv")] + [InlineData(317, "Case Closed (1996-2007)/Case Closed - 317.mkv")] // TODO: [InlineData(2, @"The Simpsons/The Simpsons 5 - 02 - Ep Name.avi")] // TODO: [InlineData(2, @"The Simpsons/The Simpsons 5 - 02 Ep Name.avi")] // TODO: [InlineData(7, @"Seinfeld/Seinfeld 0807 The Checks.avi")] diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs index ab825c9fa7..09bae2adab 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs @@ -52,7 +52,7 @@ namespace Jellyfin.Naming.Tests.TV [InlineData("Season 2009/S2009E23-E24-E26 - The Woman.mp4", 2009)] [InlineData("Series/1-12 - The Woman.mp4", 1)] [InlineData("Running Man/Running Man S2017E368.mkv", 2017)] - [InlineData("Case Closed (1996-2007)/Case Closed - 317.mkv", 3)] + [InlineData("Case Closed (1996-2007)/Case Closed - 317.mkv", null)] // TODO: [InlineData(@"Seinfeld/Seinfeld 0807 The Checks.avi", 8)] public void GetSeasonNumberFromEpisodeFileTest(string path, int? expected) { diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs index 76922af8d5..a7491f42e9 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs @@ -45,8 +45,9 @@ public class FFProbeVideoInfoTests [Theory] [InlineData(null, 0)] [InlineData(0L, 0)] - [InlineData(1L, 0)] - [InlineData(TimeSpan.TicksPerMinute * 5, 0)] + [InlineData(1L, 1)] + [InlineData(TimeSpan.TicksPerMinute * 3, 1)] + [InlineData(TimeSpan.TicksPerMinute * 5, 1)] [InlineData((TimeSpan.TicksPerMinute * 5) + 1, 1)] [InlineData(TimeSpan.TicksPerMinute * 50, 10)] public void CreateDummyChapters_ValidRuntime_CorrectChaptersCount(long? runtime, int chaptersCount) @@ -58,4 +59,20 @@ public class FFProbeVideoInfoTests Assert.Equal(chaptersCount, chapters.Length); } + + [Theory] + [InlineData(1L)] + [InlineData(TimeSpan.TicksPerMinute * 3)] + [InlineData(TimeSpan.TicksPerMinute * 5)] + [InlineData((TimeSpan.TicksPerMinute * 5) + 1)] + [InlineData((TimeSpan.TicksPerMinute * 50) + 1)] + public void CreateDummyChapters_PositiveRuntime_NoChapterBeyondRuntime(long runtime) + { + var chapters = _fFProbeVideoInfo.CreateDummyChapters(new Video() + { + RunTimeTicks = runtime + }); + + Assert.All(chapters, chapter => Assert.True(chapter.StartPositionTicks < runtime)); + } } diff --git a/tests/Jellyfin.Providers.Tests/TV/EpisodeMetadataServiceTests.cs b/tests/Jellyfin.Providers.Tests/TV/EpisodeMetadataServiceTests.cs new file mode 100644 index 0000000000..8f5b1b3c48 --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/TV/EpisodeMetadataServiceTests.cs @@ -0,0 +1,110 @@ +using System; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Providers.Manager; +using MediaBrowser.Providers.TV; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.TV; + +public class EpisodeMetadataServiceTests +{ + private readonly TestEpisodeMetadataService _service = new(); + + [Fact] + public void MergeData_ProviderSeasonOverridesPathDerivedSeason() + { + var source = new MetadataResult<Episode> + { + Item = new Episode + { + ParentIndexNumber = 2 + } + }; + + var target = new MetadataResult<Episode> + { + Item = new Episode + { + ParentIndexNumber = 1 + } + }; + + _service.Merge(source, target, replaceData: false, mergeMetadataSettings: true); + + Assert.Equal(2, target.Item.ParentIndexNumber); + } + + [Fact] + public void MergeData_BackfillExistingMetadata_DoesNotOverrideProviderSeason() + { + var existingMetadata = new MetadataResult<Episode> + { + Item = new Episode + { + ParentIndexNumber = 1 + } + }; + + var temp = new MetadataResult<Episode> + { + Item = new Episode + { + ParentIndexNumber = 2 + } + }; + + _service.Merge(existingMetadata, temp, replaceData: false, mergeMetadataSettings: false); + + Assert.Equal(2, temp.Item.ParentIndexNumber); + } + + [Fact] + public void MergeData_MissingProviderSeasonKeepsExistingSeason() + { + var source = new MetadataResult<Episode> + { + Item = new Episode() + }; + + var target = new MetadataResult<Episode> + { + Item = new Episode + { + ParentIndexNumber = 1 + } + }; + + _service.Merge(source, target, replaceData: false, mergeMetadataSettings: true); + + Assert.Equal(1, target.Item.ParentIndexNumber); + } + + private sealed class TestEpisodeMetadataService : EpisodeMetadataService + { + public TestEpisodeMetadataService() + : base( + Mock.Of<IServerConfigurationManager>(), + NullLogger<EpisodeMetadataService>.Instance, + Mock.Of<IProviderManager>(), + Mock.Of<IFileSystem>(), + Mock.Of<ILibraryManager>(), + Mock.Of<IExternalDataManager>(), + Mock.Of<IItemRepository>()) + { + } + + public void Merge(MetadataResult<Episode> source, MetadataResult<Episode> target, bool replaceData, bool mergeMetadataSettings) + { + MergeData(source, target, Array.Empty<MetadataField>(), replaceData, mergeMetadataSettings); + } + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/DotIgnoreIgnoreRuleTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/DotIgnoreIgnoreRuleTest.cs index a7bbef7ed4..03c0b4af39 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/DotIgnoreIgnoreRuleTest.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/DotIgnoreIgnoreRuleTest.cs @@ -1,4 +1,9 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; using Emby.Server.Implementations.Library; +using MediaBrowser.Model.IO; using Xunit; namespace Jellyfin.Server.Implementations.Tests.Library; @@ -78,4 +83,391 @@ public class DotIgnoreIgnoreRuleTest // Without normalization, Windows paths with backslashes won't match patterns expecting forward slashes Assert.False(DotIgnoreIgnoreRule.CheckIgnoreRules(path, _rule1, isDirectory: false, normalizePath: false)); } + + [Fact] + public void CacheHit_RepeatedCallsDoNotRereadFiles() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + var subDir = Path.Combine(tempDir, "subdir"); + Directory.CreateDirectory(subDir); + + try + { + var ignoreFilePath = Path.Combine(tempDir, ".ignore"); + File.WriteAllText(ignoreFilePath, "*.tmp"); + + var rule = new DotIgnoreIgnoreRule(); + var fileInfo = new FileSystemMetadata + { + FullName = Path.Combine(subDir, "test.tmp"), + IsDirectory = false + }; + + // First call - should cache + var result1 = rule.ShouldIgnore(fileInfo, null); + Assert.True(result1); + + // Second call - should use cache + var result2 = rule.ShouldIgnore(fileInfo, null); + Assert.True(result2); + + // Third call with different file in same directory - should use cache + var fileInfo2 = new FileSystemMetadata + { + FullName = Path.Combine(subDir, "other.tmp"), + IsDirectory = false + }; + var result3 = rule.ShouldIgnore(fileInfo2, null); + Assert.True(result3); + + // Call with file that doesn't match pattern + var fileInfo3 = new FileSystemMetadata + { + FullName = Path.Combine(subDir, "other.txt"), + IsDirectory = false + }; + var result4 = rule.ShouldIgnore(fileInfo3, null); + Assert.False(result4); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void CacheInvalidation_ModifyIgnoreFile_Reparses() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + var ignoreFilePath = Path.Combine(tempDir, ".ignore"); + File.WriteAllText(ignoreFilePath, "*.tmp"); + + var rule = new DotIgnoreIgnoreRule(); + var fileInfo = new FileSystemMetadata + { + FullName = Path.Combine(tempDir, "test.tmp"), + IsDirectory = false + }; + + // First call - should ignore .tmp files + var result1 = rule.ShouldIgnore(fileInfo, null); + Assert.True(result1); + + // Modify the .ignore file to ignore .txt instead + // Wait a bit to ensure the file modification time changes + Thread.Sleep(50); + File.WriteAllText(ignoreFilePath, "*.txt"); + + // Now .tmp files should NOT be ignored + var result2 = rule.ShouldIgnore(fileInfo, null); + Assert.False(result2); + + // And .txt files SHOULD be ignored + var txtFileInfo = new FileSystemMetadata + { + FullName = Path.Combine(tempDir, "test.txt"), + IsDirectory = false + }; + var result3 = rule.ShouldIgnore(txtFileInfo, null); + Assert.True(result3); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void EmptyIgnoreFile_IgnoresEverything() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + var ignoreFilePath = Path.Combine(tempDir, ".ignore"); + File.WriteAllText(ignoreFilePath, string.Empty); + + var rule = new DotIgnoreIgnoreRule(); + + var fileInfo = new FileSystemMetadata + { + FullName = Path.Combine(tempDir, "anyfile.mkv"), + IsDirectory = false + }; + + // Empty .ignore file should ignore everything + var result = rule.ShouldIgnore(fileInfo, null); + Assert.True(result); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void WhitespaceOnlyIgnoreFile_IgnoresEverything() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + var ignoreFilePath = Path.Combine(tempDir, ".ignore"); + File.WriteAllText(ignoreFilePath, " \n\t\n "); + + var rule = new DotIgnoreIgnoreRule(); + + var fileInfo = new FileSystemMetadata + { + FullName = Path.Combine(tempDir, "anyfile.mkv"), + IsDirectory = false + }; + + // Whitespace-only .ignore file should ignore everything + var result = rule.ShouldIgnore(fileInfo, null); + Assert.True(result); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void NoIgnoreFile_DoesNotIgnore() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + var rule = new DotIgnoreIgnoreRule(); + + var fileInfo = new FileSystemMetadata + { + FullName = Path.Combine(tempDir, "anyfile.mkv"), + IsDirectory = false + }; + + // No .ignore file means don't ignore + var result = rule.ShouldIgnore(fileInfo, null); + Assert.False(result); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void ConcurrentAccess_ThreadSafe() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + var ignoreFilePath = Path.Combine(tempDir, ".ignore"); + File.WriteAllText(ignoreFilePath, "*.tmp"); + + var rule = new DotIgnoreIgnoreRule(); + + // Run multiple parallel checks + Parallel.For(0, 100, i => + { + var fileInfo = new FileSystemMetadata + { + FullName = Path.Combine(tempDir, $"test{i}.tmp"), + IsDirectory = false + }; + + var result = rule.ShouldIgnore(fileInfo, null); + Assert.True(result); + }); + + // Also test with non-matching files + Parallel.For(0, 100, i => + { + var fileInfo = new FileSystemMetadata + { + FullName = Path.Combine(tempDir, $"test{i}.txt"), + IsDirectory = false + }; + + var result = rule.ShouldIgnore(fileInfo, null); + Assert.False(result); + }); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void ClearCache_ClearsAllCachedData() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + var ignoreFilePath = Path.Combine(tempDir, ".ignore"); + File.WriteAllText(ignoreFilePath, "*.tmp"); + + var rule = new DotIgnoreIgnoreRule(); + var fileInfo = new FileSystemMetadata + { + FullName = Path.Combine(tempDir, "test.tmp"), + IsDirectory = false + }; + + // First call to populate cache + var result1 = rule.ShouldIgnore(fileInfo, null); + Assert.True(result1); + + // Clear cache + rule.ClearDirectoryCache(); + + // Should still work (will re-populate cache) + var result2 = rule.ShouldIgnore(fileInfo, null); + Assert.True(result2); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void IgnoreFileDeleted_HandlesGracefully() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + var ignoreFilePath = Path.Combine(tempDir, ".ignore"); + File.WriteAllText(ignoreFilePath, "*.tmp"); + + var rule = new DotIgnoreIgnoreRule(); + var fileInfo = new FileSystemMetadata + { + FullName = Path.Combine(tempDir, "test.tmp"), + IsDirectory = false + }; + + // First call - should ignore + var result1 = rule.ShouldIgnore(fileInfo, null); + Assert.True(result1); + + // Delete the .ignore file + File.Delete(ignoreFilePath); + + // Should not ignore anymore (file deleted) + var result2 = rule.ShouldIgnore(fileInfo, null); + Assert.False(result2); + } + finally + { + if (Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, true); + } + } + } + + [Fact] + public void ParentDirectoryIgnoreFile_AppliesToSubdirectories() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + var subDir1 = Path.Combine(tempDir, "sub1"); + var subDir2 = Path.Combine(tempDir, "sub1", "sub2"); + Directory.CreateDirectory(subDir1); + Directory.CreateDirectory(subDir2); + + try + { + // Put .ignore in root + var ignoreFilePath = Path.Combine(tempDir, ".ignore"); + File.WriteAllText(ignoreFilePath, "*.tmp"); + + var rule = new DotIgnoreIgnoreRule(); + + // Check file in sub2 - should find .ignore in parent + var fileInfo = new FileSystemMetadata + { + FullName = Path.Combine(subDir2, "test.tmp"), + IsDirectory = false + }; + + var result = rule.ShouldIgnore(fileInfo, null); + Assert.True(result); + + // Check file in sub1 + var fileInfo2 = new FileSystemMetadata + { + FullName = Path.Combine(subDir1, "test.tmp"), + IsDirectory = false + }; + + var result2 = rule.ShouldIgnore(fileInfo2, null); + Assert.True(result2); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void DirectoryMatching_TrailingSlashPattern() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + var subDir = Path.Combine(tempDir, "videos"); + Directory.CreateDirectory(subDir); + + try + { + var ignoreFilePath = Path.Combine(tempDir, ".ignore"); + File.WriteAllText(ignoreFilePath, "videos/"); + + var rule = new DotIgnoreIgnoreRule(); + + // Directory should be ignored + var dirInfo = new FileSystemMetadata + { + FullName = subDir, + IsDirectory = true + }; + + var result = rule.ShouldIgnore(dirInfo, null); + Assert.True(result); + + // File named "videos" should NOT be ignored (pattern has trailing slash) + var fileInfo = new FileSystemMetadata + { + FullName = Path.Combine(tempDir, "videos"), + IsDirectory = false + }; + + // Note: The Ignore library behavior may vary here, this tests the actual behavior + var resultFile = rule.ShouldIgnore(fileInfo, null); + // The file named "videos" without trailing slash might or might not match depending on the library + // This test documents the actual behavior + } + finally + { + Directory.Delete(tempDir, true); + } + } } |
