aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Api.Tests/Controllers/SystemControllerTests.cs1
-rw-r--r--tests/Jellyfin.Extensions.Tests/FileHelperTests.cs23
-rw-r--r--tests/Jellyfin.LiveTv.Tests/Listings/XmlTvListingsProviderTests.cs4
-rw-r--r--tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/emptycategory.xml2
-rw-r--r--tests/Jellyfin.LiveTv.Tests/Test Data/LiveTv/Listings/XmlTv/notitle.xml2
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs11
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs2
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkParseTests.cs43
-rw-r--r--tests/Jellyfin.Providers.Tests/Lyrics/LrcLyricParserTests.cs16
-rw-r--r--tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs7
-rw-r--r--tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs184
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/CoreResolutionIgnoreRuleTest.cs129
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs11
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs66
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs3
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);
}
}