diff options
Diffstat (limited to 'tests')
17 files changed, 399 insertions, 108 deletions
diff --git a/tests/Jellyfin.Api.Tests/Controllers/SystemControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/SystemControllerTests.cs index dd84c1a18..8cb3cde2b 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/SystemControllerTests.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/SystemControllerTests.cs @@ -1,4 +1,5 @@ using Jellyfin.Api.Controllers; +using Jellyfin.Server.Implementations.SystemBackupService; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Model.IO; diff --git a/tests/Jellyfin.Extensions.Tests/FileHelperTests.cs b/tests/Jellyfin.Extensions.Tests/FileHelperTests.cs new file mode 100644 index 000000000..fb6a5dd0a --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/FileHelperTests.cs @@ -0,0 +1,23 @@ +using System.IO; +using Xunit; + +namespace Jellyfin.Extensions.Tests; + +public static class FileHelperTests +{ + [Fact] + public static void CreateEmpty_Valid_Correct() + { + var path = Path.Join(Path.GetTempPath(), Path.GetRandomFileName()); + var fileInfo = new FileInfo(path); + + Assert.False(fileInfo.Exists); + + FileHelper.CreateEmpty(path); + + fileInfo.Refresh(); + Assert.True(fileInfo.Exists); + + File.Delete(path); + } +} diff --git a/tests/Jellyfin.LiveTv.Tests/Listings/XmlTvListingsProviderTests.cs b/tests/Jellyfin.LiveTv.Tests/Listings/XmlTvListingsProviderTests.cs index 0fb7894e5..b71dc1520 100644 --- a/tests/Jellyfin.LiveTv.Tests/Listings/XmlTvListingsProviderTests.cs +++ b/tests/Jellyfin.LiveTv.Tests/Listings/XmlTvListingsProviderTests.cs @@ -54,7 +54,7 @@ public class XmlTvListingsProviderTests Path = path }; - var startDate = new DateTime(2022, 11, 4); + var startDate = new DateTime(2022, 11, 4, 0, 0, 0, DateTimeKind.Utc); var programs = await _xmlTvListingsProvider.GetProgramsAsync(info, "3297", startDate, startDate.AddDays(1), CancellationToken.None); var programsList = programs.ToList(); Assert.Single(programsList); @@ -78,7 +78,7 @@ public class XmlTvListingsProviderTests Path = path }; - var startDate = new DateTime(2022, 11, 4); + var startDate = new DateTime(2022, 11, 4, 0, 0, 0, DateTimeKind.Utc); var programs = await _xmlTvListingsProvider.GetProgramsAsync(info, "3297", startDate, startDate.AddDays(1), CancellationToken.None); var programsList = programs.ToList(); Assert.Single(programsList); diff --git a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/emptycategory.xml b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/emptycategory.xml index dd4aa8977..487b02893 100644 --- a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/emptycategory.xml +++ b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/emptycategory.xml @@ -1,5 +1,5 @@ <tv date="20221104"> - <programme channel="3297" start="20221104130000 -0400" stop="20221105235959 -0400"> + <programme channel="3297" start="20221104130000 +0000" stop="20221105235959 +0000"> <category lang="en" /> <category lang="en">sports</category> </programme> diff --git a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/notitle.xml b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/notitle.xml index 5a5be7997..b3cef41f3 100644 --- a/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/notitle.xml +++ b/tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/notitle.xml @@ -1,5 +1,5 @@ <tv date="20221104"> - <programme channel="3297" start="20221104130000 -0400" stop="20221105235959 -0400"> + <programme channel="3297" start="20221104130000 +0000" stop="20221105235959 +0000"> <category lang="en">sports</category> <episode-num system="original-air-date">2022-11-04 13:00:00</episode-num> <icon height="" src="https://domain.tld/image.png" width=""/> diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs index 51eb99f49..6d887c577 100644 --- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs @@ -23,7 +23,7 @@ namespace Jellyfin.Naming.Tests.Video Test("300-trailer.mp4", ExtraType.Trailer); Test("300.trailer.mp4", ExtraType.Trailer); Test("300_trailer.mp4", ExtraType.Trailer); - Test("300 trailer.mp4", ExtraType.Trailer); + Test("300 - trailer.mp4", ExtraType.Trailer); Test("theme.mp3", ExtraType.ThemeSong); } @@ -132,7 +132,14 @@ namespace Jellyfin.Naming.Tests.Video Test("300-sample.mp4", ExtraType.Sample); Test("300.sample.mp4", ExtraType.Sample); Test("300_sample.mp4", ExtraType.Sample); - Test("300 sample.mp4", ExtraType.Sample); + Test("300 - sample.mp4", ExtraType.Sample); + } + + [Fact] + public void TestSuffixPartOfTitle() + { + Test("I Live In A Trailer.mp4", null); + Test("The DNA Sample.mp4", null); } private void Test(string input, ExtraType? expectedType) diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index 377f82eac..d3164ba9c 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -87,7 +87,7 @@ namespace Jellyfin.Naming.Tests.Video var files = new[] { "300.mkv", - "300 trailer.mkv" + "300 - trailer.mkv" }; var result = VideoListResolver.Resolve( diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index ef87e46a7..38208476f 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -281,11 +281,11 @@ namespace Jellyfin.Networking.Tests } [Theory] - [InlineData("185.10.10.10,200.200.200.200", "79.2.3.4", true)] - [InlineData("185.10.10.10", "185.10.10.10", false)] - [InlineData("", "100.100.100.100", false)] + [InlineData("185.10.10.10,200.200.200.200", "79.2.3.4", RemoteAccessPolicyResult.RejectDueToNotAllowlistedRemoteIP)] + [InlineData("185.10.10.10", "185.10.10.10", RemoteAccessPolicyResult.Allow)] + [InlineData("", "100.100.100.100", RemoteAccessPolicyResult.Allow)] - public void HasRemoteAccess_GivenWhitelist_AllowsOnlyIPsInWhitelist(string addresses, string remoteIP, bool denied) + public void HasRemoteAccess_GivenWhitelist_AllowsOnlyIPsInWhitelist(string addresses, string remoteIP, RemoteAccessPolicyResult expectedResult) { // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. // If left blank, all remote addresses will be allowed. @@ -299,15 +299,38 @@ namespace Jellyfin.Networking.Tests var startupConf = new Mock<IConfiguration>(); using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>()); - Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIP)), denied); + Assert.Equal(expectedResult, nm.ShouldAllowServerAccess(IPAddress.Parse(remoteIP))); } [Theory] - [InlineData("185.10.10.10", "79.2.3.4", false)] - [InlineData("185.10.10.10", "185.10.10.10", true)] - [InlineData("", "100.100.100.100", false)] + [InlineData("185.10.10.10,200.200.200.200", "79.2.3.4", RemoteAccessPolicyResult.RejectDueToRemoteAccessDisabled)] + [InlineData("185.10.10.10", "127.0.0.1", RemoteAccessPolicyResult.Allow)] + [InlineData("", "100.100.100.100", RemoteAccessPolicyResult.RejectDueToRemoteAccessDisabled)] - public void HasRemoteAccess_GivenBlacklist_BlacklistTheIPs(string addresses, string remoteIP, bool denied) + public void HasRemoteAccess_GivenRemoteAccessDisabled_IgnoresAllowlist(string addresses, string remoteIP, RemoteAccessPolicyResult expectedResult) + { + // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. + // If left blank, all remote addresses will be allowed. + var conf = new NetworkConfiguration() + { + EnableIPv4 = true, + EnableRemoteAccess = false, + RemoteIPFilter = addresses.Split(','), + IsRemoteIPFilterBlacklist = false + }; + + var startupConf = new Mock<IConfiguration>(); + using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>()); + + Assert.Equal(expectedResult, nm.ShouldAllowServerAccess(IPAddress.Parse(remoteIP))); + } + + [Theory] + [InlineData("185.10.10.10", "79.2.3.4", RemoteAccessPolicyResult.Allow)] + [InlineData("185.10.10.10", "185.10.10.10", RemoteAccessPolicyResult.RejectDueToIPBlocklist)] + [InlineData("", "100.100.100.100", RemoteAccessPolicyResult.Allow)] + + public void HasRemoteAccess_GivenBlacklist_BlacklistTheIPs(string addresses, string remoteIP, RemoteAccessPolicyResult expectedResult) { // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. // If left blank, all remote addresses will be allowed. @@ -321,7 +344,7 @@ namespace Jellyfin.Networking.Tests var startupConf = new Mock<IConfiguration>(); using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>()); - Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIP)), denied); + Assert.Equal(expectedResult, nm.ShouldAllowServerAccess(IPAddress.Parse(remoteIP))); } [Theory] diff --git a/tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs b/tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs index 756a688ab..a1fc067cc 100644 --- a/tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs +++ b/tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs @@ -20,22 +20,28 @@ public static class LrcLyricParserTests var line1 = parsed.Lyrics[0]; Assert.Equal("Every night that goes between", line1.Text); Assert.NotNull(line1.Cues); - Assert.Equal(9, line1.Cues.Count); + Assert.Equal(5, line1.Cues.Count); Assert.Equal(68400000, line1.Cues[0].Start); Assert.Equal(72000000, line1.Cues[0].End); + Assert.Equal(0, line1.Cues[0].Position); + Assert.Equal(5, line1.Cues[0].EndPosition); + Assert.Equal(6, line1.Cues[1].Position); + Assert.Equal(11, line1.Cues[1].EndPosition); + Assert.Equal(12, line1.Cues[2].Position); var line5 = parsed.Lyrics[4]; Assert.Equal("Every night you do not come", line5.Text); Assert.NotNull(line5.Cues); - Assert.Equal(11, line5.Cues.Count); - Assert.Equal(377300000, line5.Cues[5].Start); - Assert.Equal(380000000, line5.Cues[5].End); + Assert.Equal(6, line5.Cues.Count); + Assert.Equal(375200000, line5.Cues[2].Start); + Assert.Equal(377300000, line5.Cues[2].End); var lastLine = parsed.Lyrics[^1]; Assert.Equal("I have always been a storm", lastLine.Text); Assert.NotNull(lastLine.Cues); - Assert.Equal(11, lastLine.Cues.Count); + Assert.Equal(6, lastLine.Cues.Count); Assert.Equal(2358000000, lastLine.Cues[^1].Start); + Assert.Equal(26, lastLine.Cues[^1].EndPosition); Assert.Null(lastLine.Cues[^1].End); } } diff --git a/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs b/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs index b32ecf6ec..b5585f4fd 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs @@ -22,7 +22,7 @@ namespace Jellyfin.Providers.Tests.Manager { var newLocked = new[] { MetadataField.Genres, MetadataField.Cast }; var newString = "new"; - var newDate = DateTime.Now; + var newDate = DateTime.UtcNow; var oldLocked = new[] { MetadataField.Genres }; var oldString = "old"; @@ -39,6 +39,7 @@ namespace Jellyfin.Providers.Tests.Manager DateCreated = newDate } }; + if (defaultDate) { source.Item.DateCreated = default; @@ -141,8 +142,8 @@ namespace Jellyfin.Providers.Tests.Manager { "ProductionYear", 1, 2 }, { "CommunityRating", 1.0f, 2.0f }, { "CriticRating", 1.0f, 2.0f }, - { "EndDate", DateTime.UnixEpoch, DateTime.Now }, - { "PremiereDate", DateTime.UnixEpoch, DateTime.Now }, + { "EndDate", DateTime.UnixEpoch, DateTime.UtcNow }, + { "PremiereDate", DateTime.UnixEpoch, DateTime.UtcNow }, { "Video3DFormat", Video3DFormat.HalfSideBySide, Video3DFormat.FullSideBySide } }; diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index c227883b5..87e7a4b56 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -12,6 +12,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Lyrics; +using MediaBrowser.Controller.MediaSegments; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Configuration; diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs index 95a5b8179..6997b51ac 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs @@ -5,83 +5,119 @@ using System.Runtime.InteropServices; using AutoFixture; using AutoFixture.AutoMoq; using Emby.Server.Implementations.IO; +using Jellyfin.Extensions; using Xunit; -namespace Jellyfin.Server.Implementations.Tests.IO +namespace Jellyfin.Server.Implementations.Tests.IO; + +public class ManagedFileSystemTests { - public class ManagedFileSystemTests + private readonly IFixture _fixture; + private readonly ManagedFileSystem _sut; + + public ManagedFileSystemTests() + { + _fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true }); + _sut = _fixture.Create<ManagedFileSystem>(); + } + + [Fact] + public void MoveDirectory_SameFileSystem_Correct() + => MoveDirectoryInternal(); + + [SkippableFact] + public void MoveDirectory_DifferentFileSystem_Correct() + { + const string DestinationParent = "/dev/shm"; + + Skip.IfNot(Directory.Exists(DestinationParent)); + + MoveDirectoryInternal(DestinationParent); + } + + internal void MoveDirectoryInternal(string? destinationParent = null) { - private readonly IFixture _fixture; - private readonly ManagedFileSystem _sut; - - public ManagedFileSystemTests() - { - _fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true }); - _sut = _fixture.Create<ManagedFileSystem>(); - } - - [SkippableTheory] - [InlineData("/Volumes/Library/Sample/Music/Playlists/", "../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Beethoven/Misc/Moonlight Sonata.mp3")] - [InlineData("/Volumes/Library/Sample/Music/Playlists/", "../../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Beethoven/Misc/Moonlight Sonata.mp3")] - [InlineData("/Volumes/Library/Sample/Music/Playlists/", "Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Playlists/Beethoven/Misc/Moonlight Sonata.mp3")] - [InlineData("/Volumes/Library/Sample/Music/Playlists/", "/mnt/Beethoven/Misc/Moonlight Sonata.mp3", "/mnt/Beethoven/Misc/Moonlight Sonata.mp3")] - public void MakeAbsolutePathCorrectlyHandlesRelativeFilePathsOnUnixLike( - string folderPath, - string filePath, - string expectedAbsolutePath) - { - Skip.If(OperatingSystem.IsWindows()); - - var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath); - Assert.Equal(expectedAbsolutePath, generatedPath); - } - - [SkippableTheory] - [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Beethoven\Misc\Moonlight Sonata.mp3")] - [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Beethoven\Misc\Moonlight Sonata.mp3")] - [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Playlists\Beethoven\Misc\Moonlight Sonata.mp3")] - [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"D:\\Beethoven\Misc\Moonlight Sonata.mp3", @"D:\\Beethoven\Misc\Moonlight Sonata.mp3")] - public void MakeAbsolutePathCorrectlyHandlesRelativeFilePathsOnWindows( - string folderPath, - string filePath, - string expectedAbsolutePath) - { - Skip.If(!OperatingSystem.IsWindows()); - - var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath); - - Assert.Equal(expectedAbsolutePath, generatedPath); - } - - [Theory] - [InlineData("ValidFileName", "ValidFileName")] - [InlineData("AC/DC", "AC DC")] - [InlineData("Invalid\0", "Invalid ")] - [InlineData("AC/DC\0KD/A", "AC DC KD A")] - public void GetValidFilename_ReturnsValidFilename(string filename, string expectedFileName) - { - Assert.Equal(expectedFileName, _sut.GetValidFilename(filename)); - } - - [SkippableFact] - public void GetFileInfo_DanglingSymlink_ExistsFalse() - { - Skip.If(OperatingSystem.IsWindows()); - - string testFileDir = Path.Combine(Path.GetTempPath(), "jellyfin-test-data"); - string testFileName = Path.Combine(testFileDir, Path.GetRandomFileName() + "-danglingsym.link"); - - Directory.CreateDirectory(testFileDir); - Assert.Equal(0, symlink("thispathdoesntexist", testFileName)); - Assert.True(File.Exists(testFileName)); - - var metadata = _sut.GetFileInfo(testFileName); - Assert.False(metadata.Exists); - } - - [SuppressMessage("Naming Rules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Have to")] - [DllImport("libc", SetLastError = true, CharSet = CharSet.Ansi)] - [DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)] - private static extern int symlink(string target, string linkpath); + const string TempFile0 = "tempfile0"; + const string TempFile1 = "tempfile1"; + + destinationParent ??= Path.GetTempPath(); + + var sourceDir = Directory.CreateTempSubdirectory(); + var destinationDir = Path.Join(destinationParent, Path.GetRandomFileName()); + FileHelper.CreateEmpty(Path.Join(sourceDir.FullName, TempFile0)); + FileHelper.CreateEmpty(Path.Join(sourceDir.FullName, TempFile1)); + + _sut.MoveDirectory(sourceDir.FullName, destinationDir); + + Assert.True(Directory.Exists(destinationDir)); + Assert.True(File.Exists(Path.Join(destinationDir, TempFile0))); + Assert.True(File.Exists(Path.Join(destinationDir, TempFile1))); + Assert.False(Directory.Exists(sourceDir.FullName)); + + Directory.Delete(destinationDir, true); } + + [SkippableTheory] + [InlineData("/Volumes/Library/Sample/Music/Playlists/", "../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Beethoven/Misc/Moonlight Sonata.mp3")] + [InlineData("/Volumes/Library/Sample/Music/Playlists/", "../../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Beethoven/Misc/Moonlight Sonata.mp3")] + [InlineData("/Volumes/Library/Sample/Music/Playlists/", "Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Playlists/Beethoven/Misc/Moonlight Sonata.mp3")] + [InlineData("/Volumes/Library/Sample/Music/Playlists/", "/mnt/Beethoven/Misc/Moonlight Sonata.mp3", "/mnt/Beethoven/Misc/Moonlight Sonata.mp3")] + public void MakeAbsolutePathCorrectlyHandlesRelativeFilePathsOnUnixLike( + string folderPath, + string filePath, + string expectedAbsolutePath) + { + Skip.If(OperatingSystem.IsWindows()); + + var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath); + Assert.Equal(expectedAbsolutePath, generatedPath); + } + + [SkippableTheory] + [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Beethoven\Misc\Moonlight Sonata.mp3")] + [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Beethoven\Misc\Moonlight Sonata.mp3")] + [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Playlists\Beethoven\Misc\Moonlight Sonata.mp3")] + [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"D:\\Beethoven\Misc\Moonlight Sonata.mp3", @"D:\\Beethoven\Misc\Moonlight Sonata.mp3")] + public void MakeAbsolutePathCorrectlyHandlesRelativeFilePathsOnWindows( + string folderPath, + string filePath, + string expectedAbsolutePath) + { + Skip.IfNot(OperatingSystem.IsWindows()); + + var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath); + + Assert.Equal(expectedAbsolutePath, generatedPath); + } + + [Theory] + [InlineData("ValidFileName", "ValidFileName")] + [InlineData("AC/DC", "AC DC")] + [InlineData("Invalid\0", "Invalid ")] + [InlineData("AC/DC\0KD/A", "AC DC KD A")] + public void GetValidFilename_ReturnsValidFilename(string filename, string expectedFileName) + { + Assert.Equal(expectedFileName, _sut.GetValidFilename(filename)); + } + + [SkippableFact] + public void GetFileInfo_DanglingSymlink_ExistsFalse() + { + Skip.If(OperatingSystem.IsWindows()); + + string testFileDir = Path.Combine(Path.GetTempPath(), "jellyfin-test-data"); + string testFileName = Path.Combine(testFileDir, Path.GetRandomFileName() + "-danglingsym.link"); + + Directory.CreateDirectory(testFileDir); + Assert.Equal(0, symlink("thispathdoesntexist", testFileName)); + Assert.True(File.Exists(testFileName)); + + var metadata = _sut.GetFileInfo(testFileName); + Assert.False(metadata.Exists); + } + + [SuppressMessage("Naming Rules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Have to")] + [DllImport("libc", SetLastError = true, CharSet = CharSet.Ansi)] + [DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)] + private static extern int symlink(string target, string linkpath); } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/CoreResolutionIgnoreRuleTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/CoreResolutionIgnoreRuleTest.cs new file mode 100644 index 000000000..0495c209d --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/CoreResolutionIgnoreRuleTest.cs @@ -0,0 +1,129 @@ +using System; +using System.IO; +using Emby.Naming.Common; +using Emby.Naming.Video; +using Emby.Server.Implementations.Library; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using Moq; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Library; + +public class CoreResolutionIgnoreRuleTest +{ + private readonly CoreResolutionIgnoreRule _rule; + private readonly NamingOptions _namingOptions; + private readonly Mock<IServerApplicationPaths> _appPathsMock; + + public CoreResolutionIgnoreRuleTest() + { + _namingOptions = new NamingOptions(); + + _namingOptions.AllExtrasTypesFolderNames.TryAdd("extras", ExtraType.Trailer); + + _appPathsMock = new Mock<IServerApplicationPaths>(); + _appPathsMock.SetupGet(x => x.RootFolderPath).Returns("/server/root"); + + _rule = new CoreResolutionIgnoreRule(_namingOptions, _appPathsMock.Object); + } + + private FileSystemMetadata MakeFileSystemMetadata(string fullName, bool isDirectory = false) + => new FileSystemMetadata { FullName = fullName, Name = Path.GetFileName(fullName), IsDirectory = isDirectory }; + + private BaseItem MakeParent(string name = "Parent", bool isTopParent = false, Type? type = null) + { + return type switch + { + Type t when t == typeof(Folder) => CreateMock<Folder>(name, isTopParent).Object, + Type t when t == typeof(AggregateFolder) => CreateMock<AggregateFolder>(name, isTopParent).Object, + Type t when t == typeof(UserRootFolder) => CreateMock<UserRootFolder>(name, isTopParent).Object, + _ => CreateMock<BaseItem>(name, isTopParent).Object + }; + } + + private static Mock<T> CreateMock<T>(string name, bool isTopParent) + where T : BaseItem + { + var mock = new Mock<T>(); + mock.SetupGet(p => p.Name).Returns(name); + mock.SetupGet(p => p.IsTopParent).Returns(isTopParent); + return mock; + } + + [Fact] + public void TestApplicationFolder() + { + Assert.False(_rule.ShouldIgnore( + MakeFileSystemMetadata("/server/root/extras", isDirectory: true), + null)); + + Assert.False(_rule.ShouldIgnore( + MakeFileSystemMetadata("/server/root/small.jpg"), + null)); + } + + [Fact] + public void TestTopLevelDirectory() + { + Assert.False(_rule.ShouldIgnore( + MakeFileSystemMetadata("Series/Extras", true), + MakeParent(type: typeof(AggregateFolder)))); + + Assert.False(_rule.ShouldIgnore( + MakeFileSystemMetadata("Series/Extras/Extras", true), + MakeParent(isTopParent: true))); + } + + [Fact] + public void TestIgnorePatterns() + { + Assert.False(_rule.ShouldIgnore( + MakeFileSystemMetadata("/Media/big.jpg"), + MakeParent())); + + Assert.True(_rule.ShouldIgnore( + MakeFileSystemMetadata("/Media/small.jpg"), + MakeParent())); + } + + [Fact] + public void TestExtrasTypesFolderNames() + { + FileSystemMetadata fileSystemMetadata = MakeFileSystemMetadata("/Movies/Up/extras", true); + + Assert.False(_rule.ShouldIgnore( + fileSystemMetadata, + MakeParent(type: typeof(AggregateFolder)))); + + Assert.False(_rule.ShouldIgnore( + fileSystemMetadata, + MakeParent(type: typeof(UserRootFolder)))); + + Assert.False(_rule.ShouldIgnore( + fileSystemMetadata, + null)); + + Assert.True(_rule.ShouldIgnore( + fileSystemMetadata, + MakeParent())); + + Assert.True(_rule.ShouldIgnore( + fileSystemMetadata, + MakeParent(type: typeof(Folder)))); + } + + [Fact] + public void TestThemeSong() + { + Assert.False(_rule.ShouldIgnore( + MakeFileSystemMetadata("/Movies/Up/intro.mp3"), + MakeParent())); + + Assert.True(_rule.ShouldIgnore( + MakeFileSystemMetadata("/Movies/Up/theme.mp3"), + MakeParent())); + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs index a7a1e5e81..6d6bba4fc 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs @@ -41,7 +41,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization await localizationManager.LoadAll(); var cultures = localizationManager.GetCultures().ToList(); - Assert.Equal(191, cultures.Count); + Assert.Equal(194, cultures.Count); var germany = cultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals("de", StringComparison.Ordinal)); Assert.NotNull(germany); diff --git a/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs index 934024826..3d8ea15a3 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using AutoFixture; using Emby.Server.Implementations.Library; using Emby.Server.Implementations.Plugins; +using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Common.Plugins; @@ -85,7 +86,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins var dllPath = Path.GetDirectoryName(Path.Combine(_pluginPath, dllFile))!; Directory.CreateDirectory(dllPath); - File.Create(Path.Combine(dllPath, filename)); + FileHelper.CreateEmpty(Path.Combine(dllPath, filename)); var metafilePath = Path.Combine(_pluginPath, "meta.json"); File.WriteAllText(metafilePath, JsonSerializer.Serialize(manifest, _options)); @@ -141,7 +142,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins foreach (var file in files) { - File.Create(Path.Combine(_pluginPath, file)); + FileHelper.CreateEmpty(Path.Combine(_pluginPath, file)); } var metafilePath = Path.Combine(_pluginPath, "meta.json"); @@ -184,7 +185,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins Description = packageInfo.Description, Overview = packageInfo.Overview, TargetAbi = packageInfo.Versions[0].TargetAbi!, - Timestamp = DateTime.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture), + Timestamp = DateTimeOffset.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture).UtcDateTime, Changelog = packageInfo.Versions[0].Changelog!, Version = new Version(1, 0).ToString(), ImagePath = string.Empty @@ -220,7 +221,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins Description = packageInfo.Description, Overview = packageInfo.Overview, TargetAbi = packageInfo.Versions[0].TargetAbi!, - Timestamp = DateTime.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture), + Timestamp = DateTimeOffset.Parse(packageInfo.Versions[0].Timestamp!, CultureInfo.InvariantCulture).UtcDateTime, Changelog = packageInfo.Versions[0].Changelog!, Version = packageInfo.Versions[0].Version, ImagePath = string.Empty @@ -300,7 +301,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins var versionInfo = fixture.Create<VersionInfo>(); versionInfo.Version = new Version(1, 0).ToString(); - versionInfo.Timestamp = DateTime.UtcNow.ToString(CultureInfo.InvariantCulture); + versionInfo.Timestamp = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture); var packageInfo = fixture.Create<PackageInfo>(); packageInfo.Versions = new[] { versionInfo }; diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index c09bce52d..0952fb8b6 100644 --- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -5,6 +5,7 @@ using System.IO; using Emby.Server.Implementations; using Jellyfin.Server.Extensions; using Jellyfin.Server.Helpers; +using Jellyfin.Server.ServerSetupApp; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using Microsoft.AspNetCore.Hosting; @@ -16,6 +17,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Serilog; +using Serilog.Core; using Serilog.Extensions.Logging; namespace Jellyfin.Server.Integration.Tests @@ -95,7 +97,11 @@ namespace Jellyfin.Server.Integration.Tests .AddInMemoryCollection(ConfigurationOptions.DefaultConfiguration) .AddEnvironmentVariables("JELLYFIN_") .AddInMemoryCollection(commandLineOpts.ConvertToConfig()); - }); + }) + .ConfigureServices(e => e + .AddSingleton<IStartupLogger, NullStartupLogger<object>>() + .AddTransient(typeof(IStartupLogger<>), typeof(NullStartupLogger<>)) + .AddSingleton(e)); } /// <inheritdoc/> @@ -106,7 +112,7 @@ namespace Jellyfin.Server.Integration.Tests appHost.ServiceProvider = host.Services; var applicationPaths = appHost.ServiceProvider.GetRequiredService<IApplicationPaths>(); Program.ApplyStartupMigrationAsync((ServerApplicationPaths)applicationPaths, appHost.ServiceProvider.GetRequiredService<IConfiguration>()).GetAwaiter().GetResult(); - Program.ApplyCoreMigrationsAsync(appHost.ServiceProvider, Migrations.Stages.JellyfinMigrationStageTypes.CoreInitialisaition).GetAwaiter().GetResult(); + Program.ApplyCoreMigrationsAsync(appHost.ServiceProvider, Migrations.Stages.JellyfinMigrationStageTypes.CoreInitialisation).GetAwaiter().GetResult(); appHost.InitializeServices(Mock.Of<IConfiguration>()).GetAwaiter().GetResult(); Program.ApplyCoreMigrationsAsync(appHost.ServiceProvider, Migrations.Stages.JellyfinMigrationStageTypes.AppInitialisation).GetAwaiter().GetResult(); host.Start(); @@ -128,5 +134,61 @@ namespace Jellyfin.Server.Integration.Tests base.Dispose(disposing); } + + private sealed class NullStartupLogger<TCategory> : IStartupLogger<TCategory> + { + public StartupLogTopic? Topic => throw new NotImplementedException(); + + public IStartupLogger BeginGroup(FormattableString logEntry) + { + return this; + } + + public IStartupLogger<TCategory1> BeginGroup<TCategory1>(FormattableString logEntry) + { + return new NullStartupLogger<TCategory1>(); + } + + public IDisposable? BeginScope<TState>(TState state) + where TState : notnull + { + return NullLogger.Instance.BeginScope(state); + } + + public bool IsEnabled(LogLevel logLevel) + { + return NullLogger.Instance.IsEnabled(logLevel); + } + + public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) + { + NullLogger.Instance.Log(logLevel, eventId, state, exception, formatter); + } + + public Microsoft.Extensions.Logging.ILogger With(Microsoft.Extensions.Logging.ILogger logger) + { + return this; + } + + public IStartupLogger<TCategory1> With<TCategory1>(Microsoft.Extensions.Logging.ILogger logger) + { + return new NullStartupLogger<TCategory1>(); + } + + IStartupLogger<TCategory> IStartupLogger<TCategory>.BeginGroup(FormattableString logEntry) + { + return new NullStartupLogger<TCategory>(); + } + + IStartupLogger IStartupLogger.With(Microsoft.Extensions.Logging.ILogger logger) + { + return this; + } + + IStartupLogger<TCategory> IStartupLogger<TCategory>.With(Microsoft.Extensions.Logging.ILogger logger) + { + return this; + } + } } } diff --git a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs index 98195a294..62cdd25ae 100644 --- a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs @@ -1,6 +1,7 @@ using System.IO; using System.Reflection; using System.Threading.Tasks; +using MediaBrowser.Model.IO; using Xunit; using Xunit.Abstractions; @@ -33,7 +34,7 @@ namespace Jellyfin.Server.Integration.Tests // Write out for publishing string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json")); _outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath); - await using var fs = File.Create(outputPath); + await using var fs = AsyncFile.Create(outputPath); await response.Content.CopyToAsync(fs); } } |
