aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs3
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj10
-rw-r--r--tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs185
-rw-r--r--tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs40
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj2
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs1
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs1
-rw-r--r--tests/Jellyfin.Common.Tests/PasswordHashTests.cs31
-rw-r--r--tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs85
-rw-r--r--tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs200
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj3
-rw-r--r--tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs131
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj1
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs56
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/some_matadata.json74
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs19
-rw-r--r--tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs1
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj2
-rw-r--r--tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj2
-rw-r--r--tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs1
-rw-r--r--tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj2
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs63
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkParseTests.cs110
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs250
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs34
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj7
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs11
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs280
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs59
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs30
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs170
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj17
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs1
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs32
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/xunit.runner.json4
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj10
-rw-r--r--tests/Jellyfin.Server.Tests/ParseNetworkTests.cs1
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj2
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs21
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs1
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo5
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Radarr.nfo2
48 files changed, 1844 insertions, 124 deletions
diff --git a/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs
index 09ffa8468..5b3d784ff 100644
--- a/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Net;
using System.Threading.Tasks;
using AutoFixture;
using AutoFixture.AutoMoq;
@@ -41,7 +42,7 @@ namespace Jellyfin.Api.Tests.Auth.LocalAccessPolicy
public async Task LocalAccessOnly(bool isInLocalNetwork, bool shouldSucceed)
{
_networkManagerMock
- .Setup(n => n.IsInLocalNetwork(It.IsAny<string>()))
+ .Setup(n => n.IsInLocalNetwork(It.IsAny<IPAddress>()))
.Returns(isInLocalNetwork);
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 577b61d02..397b863b7 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -15,12 +15,12 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" />
+ <PackageReference Include="AutoFixture" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs
new file mode 100644
index 000000000..e6c325bac
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs
@@ -0,0 +1,185 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Common.Cryptography;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Cryptography
+{
+ public static class PasswordHashTests
+ {
+ [Fact]
+ public static void Ctor_Null_ThrowsArgumentNullException()
+ {
+ Assert.Throws<ArgumentNullException>(() => new PasswordHash(null!, Array.Empty<byte>()));
+ }
+
+ [Fact]
+ public static void Ctor_Empty_ThrowsArgumentException()
+ {
+ Assert.Throws<ArgumentException>(() => new PasswordHash(string.Empty, Array.Empty<byte>()));
+ }
+
+ public static IEnumerable<object[]> Parse_Valid_TestData()
+ {
+ // Id
+ yield return new object[]
+ {
+ "$PBKDF2",
+ new PasswordHash("PBKDF2", Array.Empty<byte>())
+ };
+
+ // Id + parameter
+ yield return new object[]
+ {
+ "$PBKDF2$iterations=1000",
+ new PasswordHash(
+ "PBKDF2",
+ Array.Empty<byte>(),
+ Array.Empty<byte>(),
+ new Dictionary<string, string>()
+ {
+ { "iterations", "1000" },
+ })
+ };
+
+ // Id + parameters
+ yield return new object[]
+ {
+ "$PBKDF2$iterations=1000,m=120",
+ new PasswordHash(
+ "PBKDF2",
+ Array.Empty<byte>(),
+ Array.Empty<byte>(),
+ new Dictionary<string, string>()
+ {
+ { "iterations", "1000" },
+ { "m", "120" }
+ })
+ };
+
+ // Id + hash
+ yield return new object[]
+ {
+ "$PBKDF2$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
+ new PasswordHash(
+ "PBKDF2",
+ Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
+ Array.Empty<byte>(),
+ new Dictionary<string, string>())
+ };
+
+ // Id + salt + hash
+ yield return new object[]
+ {
+ "$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
+ new PasswordHash(
+ "PBKDF2",
+ Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
+ Convert.FromHexString("69F420"),
+ new Dictionary<string, string>())
+ };
+
+ // Id + parameter + hash
+ yield return new object[]
+ {
+ "$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
+ new PasswordHash(
+ "PBKDF2",
+ Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
+ Array.Empty<byte>(),
+ new Dictionary<string, string>()
+ {
+ { "iterations", "1000" }
+ })
+ };
+
+ // Id + parameters + hash
+ yield return new object[]
+ {
+ "$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
+ new PasswordHash(
+ "PBKDF2",
+ Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
+ Array.Empty<byte>(),
+ new Dictionary<string, string>()
+ {
+ { "iterations", "1000" },
+ { "m", "120" }
+ })
+ };
+
+ // Id + parameters + salt + hash
+ yield return new object[]
+ {
+ "$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
+ new PasswordHash(
+ "PBKDF2",
+ Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
+ Convert.FromHexString("69F420"),
+ new Dictionary<string, string>()
+ {
+ { "iterations", "1000" },
+ { "m", "120" }
+ })
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(Parse_Valid_TestData))]
+ public static void Parse_Valid_Success(string passwordHashString, PasswordHash expected)
+ {
+ var passwordHash = PasswordHash.Parse(passwordHashString);
+ Assert.Equal(expected.Id, passwordHash.Id);
+ Assert.Equal(expected.Parameters, passwordHash.Parameters);
+ Assert.Equal(expected.Salt.ToArray(), passwordHash.Salt.ToArray());
+ Assert.Equal(expected.Hash.ToArray(), passwordHash.Hash.ToArray());
+ Assert.Equal(expected.ToString(), passwordHash.ToString());
+ }
+
+ [Theory]
+ [InlineData("$PBKDF2")]
+ [InlineData("$PBKDF2$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
+ [InlineData("$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
+ [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
+ [InlineData("$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
+ [InlineData("$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
+ [InlineData("$PBKDF2$iterations=1000,m=120")]
+ public static void ToString_Roundtrip_Success(string passwordHash)
+ {
+ Assert.Equal(passwordHash, PasswordHash.Parse(passwordHash).ToString());
+ }
+
+ [Fact]
+ public static void Parse_Null_ThrowsArgumentException()
+ {
+ Assert.Throws<ArgumentException>(() => PasswordHash.Parse(null));
+ }
+
+ [Fact]
+ public static void Parse_Empty_ThrowsArgumentException()
+ {
+ Assert.Throws<ArgumentException>(() => PasswordHash.Parse(string.Empty));
+ }
+
+ [Theory]
+ [InlineData("$")] // No id
+ [InlineData("$$")] // Empty segments
+ [InlineData("PBKDF2$")] // Doesn't start with $
+ [InlineData("$PBKDF2$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty segment
+ [InlineData("$PBKDF2$iterations=1000$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty salt segment
+ [InlineData("$PBKDF2$iterations=1000$69F420$")] // Empty hash segment
+ [InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
+ [InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
+ [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
+ [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $
+ [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment
+ [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment
+ [InlineData("$PBKDF2$iterations=$invalidstalt$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid salt
+ [InlineData("$PBKDF2$iterations=$69F420$invalid hash")] // Invalid hash
+ [InlineData("$PBKDF2$69F420$")] // Empty hash
+ public static void Parse_InvalidFormat_ThrowsFormatException(string passwordHash)
+ {
+ Assert.Throws<FormatException>(() => PasswordHash.Parse(passwordHash));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs b/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs
new file mode 100644
index 000000000..9903409fa
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Common.Extensions;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Extensions
+{
+ public static class CopyToExtensionsTests
+ {
+ public static IEnumerable<object[]> CopyTo_Valid_Correct_TestData()
+ {
+ yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 0, new[] { 0, 1, 2, 3, 4, 5 } };
+ yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } };
+ }
+
+ [Theory]
+ [MemberData(nameof(CopyTo_Valid_Correct_TestData))]
+ public static void CopyTo_Valid_Correct<T>(IReadOnlyList<T> source, IList<T> destination, int index, IList<T> expected)
+ {
+ source.CopyTo(destination, index);
+ Assert.Equal(expected, destination);
+ }
+
+ public static IEnumerable<object[]> CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData()
+ {
+ yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, -1 };
+ yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 6 };
+ yield return new object[] { new[] { 0, 1, 2 }, Array.Empty<int>(), 0 };
+ yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0 }, 0 };
+ yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 };
+ }
+
+ [Theory]
+ [MemberData(nameof(CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData))]
+ public static void CopyTo_Invalid_ThrowsArgumentOutOfRangeException<T>(IReadOnlyList<T> source, IList<T> destination, int index)
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => source.CopyTo(destination, index));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 017a67e9f..8018b2966 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -15,7 +15,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs
index 1e1cde957..dbfad3c2f 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs
+++ b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Globalization;
using System.Text.Json;
using MediaBrowser.Common.Json.Converters;
using Xunit;
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
index 22bc7afb9..cb3b66c4c 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
+++ b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Globalization;
using System.Text.Json;
using MediaBrowser.Common.Json.Converters;
using Xunit;
diff --git a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/PasswordHashTests.cs
deleted file mode 100644
index c4422bd10..000000000
--- a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using MediaBrowser.Common;
-using MediaBrowser.Common.Cryptography;
-using Xunit;
-
-namespace Jellyfin.Common.Tests
-{
- public class PasswordHashTests
- {
- [Theory]
- [InlineData(
- "$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
- "PBKDF2",
- "",
- "62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
- public void ParseTest(string passwordHash, string id, string salt, string hash)
- {
- var pass = PasswordHash.Parse(passwordHash);
- Assert.Equal(id, pass.Id);
- Assert.Equal(salt, Convert.ToHexString(pass.Salt));
- Assert.Equal(hash, Convert.ToHexString(pass.Hash));
- }
-
- [Theory]
- [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
- public void ToStringTest(string passwordHash)
- {
- Assert.Equal(passwordHash, PasswordHash.Parse(passwordHash).ToString());
- }
- }
-}
diff --git a/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs
new file mode 100644
index 000000000..ef9d31cc1
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Providers/ProviderIdParserTests.cs
@@ -0,0 +1,85 @@
+using System;
+using MediaBrowser.Common.Providers;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Providers
+{
+ public class ProviderIdParserTests
+ {
+ [Theory]
+ [InlineData("tt1234567", "tt1234567")]
+ [InlineData("tt12345678", "tt12345678")]
+ [InlineData("https://www.imdb.com/title/tt1234567", "tt1234567")]
+ [InlineData("https://www.imdb.com/title/tt12345678", "tt12345678")]
+ [InlineData(@"multiline\nhttps://www.imdb.com/title/tt1234567", "tt1234567")]
+ [InlineData(@"multiline\nhttps://www.imdb.com/title/tt12345678", "tt12345678")]
+ [InlineData("tt1234567tt7654321", "tt1234567")]
+ [InlineData("tt12345678tt7654321", "tt12345678")]
+ [InlineData("tt123456789", "tt12345678")]
+ public void FindImdbId_Valid_Success(string text, string expected)
+ {
+ Assert.True(ProviderIdParsers.TryFindImdbId(text, out ReadOnlySpan<char> parsedId));
+ Assert.Equal(expected, parsedId.ToString());
+ }
+
+ [Theory]
+ [InlineData("tt123456")]
+ [InlineData("https://www.imdb.com/title/tt123456")]
+ [InlineData("Jellyfin")]
+ public void FindImdbId_Invalid_Success(string text)
+ {
+ Assert.False(ProviderIdParsers.TryFindImdbId(text, out _));
+ }
+
+ [Theory]
+ [InlineData("https://www.themoviedb.org/movie/30287-fallo", "30287")]
+ [InlineData("themoviedb.org/movie/30287", "30287")]
+ public void FindTmdbMovieId_Valid_Success(string text, string expected)
+ {
+ Assert.True(ProviderIdParsers.TryFindTmdbMovieId(text, out ReadOnlySpan<char> parsedId));
+ Assert.Equal(expected, parsedId.ToString());
+ }
+
+ [Theory]
+ [InlineData("https://www.themoviedb.org/movie/fallo-30287")]
+ [InlineData("https://www.themoviedb.org/tv/1668-friends")]
+ public void FindTmdbMovieId_Invalid_Success(string text)
+ {
+ Assert.False(ProviderIdParsers.TryFindTmdbMovieId(text, out _));
+ }
+
+ [Theory]
+ [InlineData("https://www.themoviedb.org/tv/1668-friends", "1668")]
+ [InlineData("themoviedb.org/tv/1668", "1668")]
+ public void FindTmdbSeriesId_Valid_Success(string text, string expected)
+ {
+ Assert.True(ProviderIdParsers.TryFindTmdbSeriesId(text, out ReadOnlySpan<char> parsedId));
+ Assert.Equal(expected, parsedId.ToString());
+ }
+
+ [Theory]
+ [InlineData("https://www.themoviedb.org/tv/friends-1668")]
+ [InlineData("https://www.themoviedb.org/movie/30287-fallo")]
+ public void FindTmdbSeriesId_Invalid_Success(string text)
+ {
+ Assert.False(ProviderIdParsers.TryFindTmdbSeriesId(text, out _));
+ }
+
+ [Theory]
+ [InlineData("https://www.thetvdb.com/?tab=series&id=121361", "121361")]
+ [InlineData("thetvdb.com/?tab=series&id=121361", "121361")]
+ public void FindTvdbId_Valid_Success(string text, string expected)
+ {
+ Assert.True(ProviderIdParsers.TryFindTvdbId(text, out ReadOnlySpan<char> parsedId));
+ Assert.Equal(expected, parsedId.ToString());
+ }
+
+ [Theory]
+ [InlineData("thetvdb.com/?tab=series&id=Jellyfin121361")]
+ [InlineData("https://www.themoviedb.org/tv/1668-friends")]
+ public void FindTvdbId_Invalid_Success(string text)
+ {
+ Assert.False(ProviderIdParsers.TryFindTvdbId(text, out _));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs
new file mode 100644
index 000000000..feffb50e8
--- /dev/null
+++ b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs
@@ -0,0 +1,200 @@
+using System.Linq;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.IO;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Controller.Tests
+{
+ public class DirectoryServiceTests
+ {
+ private const string LowerCasePath = "/music/someartist";
+ private const string UpperCasePath = "/music/SOMEARTIST";
+
+ private static readonly FileSystemMetadata[] _lowerCaseFileSystemMetadata =
+ {
+ new ()
+ {
+ FullName = LowerCasePath + "/Artwork",
+ IsDirectory = true
+ },
+ new ()
+ {
+ FullName = LowerCasePath + "/Some Other Folder",
+ IsDirectory = true
+ },
+ new ()
+ {
+ FullName = LowerCasePath + "/Song 2.mp3",
+ IsDirectory = false
+ },
+ new ()
+ {
+ FullName = LowerCasePath + "/Song 3.mp3",
+ IsDirectory = false
+ }
+ };
+
+ private static readonly FileSystemMetadata[] _upperCaseFileSystemMetadata =
+ {
+ new ()
+ {
+ FullName = UpperCasePath + "/Lyrics",
+ IsDirectory = true
+ },
+ new ()
+ {
+ FullName = UpperCasePath + "/Song 1.mp3",
+ IsDirectory = false
+ }
+ };
+
+ [Fact]
+ public void GetFileSystemEntries_GivenPathsWithDifferentCasing_CachesAll()
+ {
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
+ fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var upperCaseResult = directoryService.GetFileSystemEntries(UpperCasePath);
+ var lowerCaseResult = directoryService.GetFileSystemEntries(LowerCasePath);
+
+ Assert.Equal(_upperCaseFileSystemMetadata, upperCaseResult);
+ Assert.Equal(_lowerCaseFileSystemMetadata, lowerCaseResult);
+ }
+
+ [Fact]
+ public void GetFiles_GivenPathsWithDifferentCasing_ReturnsCorrectFiles()
+ {
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
+ fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var upperCaseResult = directoryService.GetFiles(UpperCasePath);
+ var lowerCaseResult = directoryService.GetFiles(LowerCasePath);
+
+ Assert.Equal(_upperCaseFileSystemMetadata.Where(f => !f.IsDirectory), upperCaseResult);
+ Assert.Equal(_lowerCaseFileSystemMetadata.Where(f => !f.IsDirectory), lowerCaseResult);
+ }
+
+ [Fact]
+ public void GetFile_GivenFilePathsWithDifferentCasing_ReturnsCorrectFile()
+ {
+ const string lowerCasePath = "/music/someartist/song 1.mp3";
+ var lowerCaseFileSystemMetadata = new FileSystemMetadata
+ {
+ FullName = lowerCasePath,
+ Exists = true
+ };
+ const string upperCasePath = "/music/SOMEARTIST/SONG 1.mp3";
+ var upperCaseFileSystemMetadata = new FileSystemMetadata
+ {
+ FullName = upperCasePath,
+ Exists = false
+ };
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
+ fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var lowerCaseResult = directoryService.GetFile(lowerCasePath);
+ var upperCaseResult = directoryService.GetFile(upperCasePath);
+
+ Assert.Equal(lowerCaseFileSystemMetadata, lowerCaseResult);
+ Assert.Null(upperCaseResult);
+ }
+
+ [Fact]
+ public void GetFile_GivenCachedPath_ReturnsCachedFile()
+ {
+ const string path = "/music/someartist/song 1.mp3";
+ var cachedFileSystemMetadata = new FileSystemMetadata
+ {
+ FullName = path,
+ Exists = true
+ };
+ var newFileSystemMetadata = new FileSystemMetadata
+ {
+ FullName = "/music/SOMEARTIST/song 1.mp3",
+ Exists = true
+ };
+
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == path))).Returns(cachedFileSystemMetadata);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var result = directoryService.GetFile(path);
+ fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == path))).Returns(newFileSystemMetadata);
+ var secondResult = directoryService.GetFile(path);
+
+ Assert.Equal(cachedFileSystemMetadata, result);
+ Assert.Equal(cachedFileSystemMetadata, secondResult);
+ }
+
+ [Fact]
+ public void GetFilePaths_GivenCachedFilePathWithoutClear_ReturnsOnlyCachedPaths()
+ {
+ const string path = "/music/someartist";
+
+ var cachedPaths = new[]
+ {
+ "/music/someartist/song 1.mp3",
+ "/music/someartist/song 2.mp3",
+ "/music/someartist/song 3.mp3",
+ "/music/someartist/song 4.mp3",
+ };
+ var newPaths = new[]
+ {
+ "/music/someartist/song 5.mp3",
+ "/music/someartist/song 6.mp3",
+ "/music/someartist/song 7.mp3",
+ "/music/someartist/song 8.mp3",
+ };
+
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var result = directoryService.GetFilePaths(path);
+ fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
+ var secondResult = directoryService.GetFilePaths(path);
+
+ Assert.Equal(cachedPaths, result);
+ Assert.Equal(cachedPaths, secondResult);
+ }
+
+ [Fact]
+ public void GetFilePaths_GivenCachedFilePathWithClear_ReturnsNewPaths()
+ {
+ const string path = "/music/someartist";
+
+ var cachedPaths = new[]
+ {
+ "/music/someartist/song 1.mp3",
+ "/music/someartist/song 2.mp3",
+ "/music/someartist/song 3.mp3",
+ "/music/someartist/song 4.mp3",
+ };
+ var newPaths = new[]
+ {
+ "/music/someartist/song 5.mp3",
+ "/music/someartist/song 6.mp3",
+ "/music/someartist/song 7.mp3",
+ "/music/someartist/song 8.mp3",
+ };
+
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+
+ var result = directoryService.GetFilePaths(path);
+ fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
+ var secondResult = directoryService.GetFilePaths(path, true);
+
+ Assert.Equal(cachedPaths, result);
+ Assert.Equal(newPaths, secondResult);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 6dec25aa4..ad1627698 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -15,7 +15,8 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
+ <PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs
new file mode 100644
index 000000000..668bd8f87
--- /dev/null
+++ b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs
@@ -0,0 +1,131 @@
+using Emby.Dlna;
+using Emby.Dlna.PlayTo;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Serialization;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Dlna.Tests
+{
+ public class DlnaManagerTests
+ {
+ private DlnaManager GetManager()
+ {
+ var xmlSerializer = new Mock<IXmlSerializer>();
+ var fileSystem = new Mock<IFileSystem>();
+ var appPaths = new Mock<IApplicationPaths>();
+ var loggerFactory = new Mock<ILoggerFactory>();
+ var appHost = new Mock<IServerApplicationHost>();
+
+ return new DlnaManager(xmlSerializer.Object, fileSystem.Object, appPaths.Object, loggerFactory.Object, appHost.Object);
+ }
+
+ [Fact]
+ public void IsMatch_GivenMatchingName_ReturnsTrue()
+ {
+ var device = new DeviceInfo()
+ {
+ Name = "My Device",
+ Manufacturer = "LG Electronics",
+ ManufacturerUrl = "http://www.lge.com",
+ ModelDescription = "LG WebOSTV DMRplus",
+ ModelName = "LG TV",
+ ModelNumber = "1.0",
+ };
+
+ var profile = new DeviceProfile()
+ {
+ Name = "Test Profile",
+ FriendlyName = "My Device",
+ Manufacturer = "LG Electronics",
+ ManufacturerUrl = "http://www.lge.com",
+ ModelDescription = "LG WebOSTV DMRplus",
+ ModelName = "LG TV",
+ ModelNumber = "1.0",
+ Identification = new ()
+ {
+ FriendlyName = "My Device",
+ Manufacturer = "LG Electronics",
+ ManufacturerUrl = "http://www.lge.com",
+ ModelDescription = "LG WebOSTV DMRplus",
+ ModelName = "LG TV",
+ ModelNumber = "1.0",
+ }
+ };
+
+ var profile2 = new DeviceProfile()
+ {
+ Name = "Test Profile",
+ FriendlyName = "My Device",
+ Identification = new DeviceIdentification()
+ {
+ FriendlyName = "My Device",
+ }
+ };
+
+ var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile2.Identification);
+ var deviceMatch2 = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
+
+ Assert.True(deviceMatch);
+ Assert.True(deviceMatch2);
+ }
+
+ [Fact]
+ public void IsMatch_GivenNamesAndManufacturersDoNotMatch_ReturnsFalse()
+ {
+ var device = new DeviceInfo()
+ {
+ Name = "My Device",
+ Manufacturer = "JVC"
+ };
+
+ var profile = new DeviceProfile()
+ {
+ Name = "Test Profile",
+ FriendlyName = "My Device",
+ Manufacturer = "LG Electronics",
+ ManufacturerUrl = "http://www.lge.com",
+ ModelDescription = "LG WebOSTV DMRplus",
+ ModelName = "LG TV",
+ ModelNumber = "1.0",
+ Identification = new ()
+ {
+ FriendlyName = "My Device",
+ Manufacturer = "LG Electronics",
+ ManufacturerUrl = "http://www.lge.com",
+ ModelDescription = "LG WebOSTV DMRplus",
+ ModelName = "LG TV",
+ ModelNumber = "1.0",
+ }
+ };
+
+ var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
+
+ Assert.False(deviceMatch);
+ }
+
+ [Fact]
+ public void IsMatch_GivenNamesAndRegExMatch_ReturnsTrue()
+ {
+ var device = new DeviceInfo()
+ {
+ Name = "My Device"
+ };
+
+ var profile = new DeviceProfile()
+ {
+ Name = "Test Profile",
+ FriendlyName = "My .*",
+ Identification = new ()
+ };
+
+ var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
+
+ Assert.True(deviceMatch);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
index 5d52f94c0..f7c21f072 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index 4cc1d37ee..8321d0255 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -21,7 +21,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
new file mode 100644
index 000000000..69e2aa437
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
@@ -0,0 +1,56 @@
+using System.IO;
+using System.Text.Json;
+using MediaBrowser.Common.Json;
+using MediaBrowser.MediaEncoding.Probing;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.MediaInfo;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Jellyfin.MediaEncoding.Tests.Probing
+{
+ public class ProbeResultNormalizerTests
+ {
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
+ private readonly ProbeResultNormalizer _probeResultNormalizer = new ProbeResultNormalizer(new NullLogger<EncoderValidatorTests>(), null);
+
+ [Fact]
+ public void GetMediaInfo_MetaData_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/Probing/some_matadata.json");
+ var internalMediaInfoResult = JsonSerializer.Deserialize<InternalMediaInfoResult>(bytes, _jsonOptions);
+ MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/some_matadata.mkv", MediaProtocol.File);
+
+ Assert.Single(res.MediaStreams);
+
+ Assert.NotNull(res.VideoStream);
+ Assert.Equal("4:3", res.VideoStream.AspectRatio);
+ Assert.Equal(25f, res.VideoStream.AverageFrameRate);
+ Assert.Equal(8, res.VideoStream.BitDepth);
+ Assert.Equal(69432, res.VideoStream.BitRate);
+ Assert.Equal("h264", res.VideoStream.Codec);
+ Assert.Equal("1/50", res.VideoStream.CodecTimeBase);
+ Assert.Equal(240, res.VideoStream.Height);
+ Assert.Equal(320, res.VideoStream.Width);
+ Assert.Equal(0, res.VideoStream.Index);
+ Assert.False(res.VideoStream.IsAnamorphic);
+ Assert.True(res.VideoStream.IsAVC);
+ Assert.True(res.VideoStream.IsDefault);
+ Assert.False(res.VideoStream.IsExternal);
+ Assert.False(res.VideoStream.IsForced);
+ Assert.False(res.VideoStream.IsInterlaced);
+ Assert.False(res.VideoStream.IsTextSubtitleStream);
+ Assert.Equal(13d, res.VideoStream.Level);
+ Assert.Equal("4", res.VideoStream.NalLengthSize);
+ Assert.Equal("yuv444p", res.VideoStream.PixelFormat);
+ Assert.Equal("High 4:4:4 Predictive", res.VideoStream.Profile);
+ Assert.Equal(25f, res.VideoStream.RealFrameRate);
+ Assert.Equal(1, res.VideoStream.RefFrames);
+ Assert.Equal("1/1000", res.VideoStream.TimeBase);
+ Assert.Equal(MediaStreamType.Video, res.VideoStream.Type);
+
+ Assert.Empty(res.Chapters);
+ Assert.Equal("Just color bars", res.Overview);
+ }
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/some_matadata.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/some_matadata.json
new file mode 100644
index 000000000..720fc5c8f
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/some_matadata.json
@@ -0,0 +1,74 @@
+{
+ "streams": [
+ {
+ "index": 0,
+ "codec_name": "h264",
+ "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
+ "profile": "High 4:4:4 Predictive",
+ "codec_type": "video",
+ "codec_time_base": "1/50",
+ "codec_tag_string": "[0][0][0][0]",
+ "codec_tag": "0x0000",
+ "width": 320,
+ "height": 240,
+ "coded_width": 320,
+ "coded_height": 240,
+ "closed_captions": 0,
+ "has_b_frames": 2,
+ "sample_aspect_ratio": "1:1",
+ "display_aspect_ratio": "4:3",
+ "pix_fmt": "yuv444p",
+ "level": 13,
+ "chroma_location": "left",
+ "field_order": "progressive",
+ "refs": 1,
+ "is_avc": "true",
+ "nal_length_size": "4",
+ "r_frame_rate": "25/1",
+ "avg_frame_rate": "25/1",
+ "time_base": "1/1000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "bits_per_raw_sample": "8",
+ "disposition": {
+ "default": 1,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "ENCODER": "Lavc57.107.100 libx264",
+ "DURATION": "00:00:01.000000000"
+ }
+ }
+ ],
+ "chapters": [
+
+ ],
+ "format": {
+ "filename": "some_metadata.mkv",
+ "nb_streams": 1,
+ "nb_programs": 0,
+ "format_name": "matroska,webm",
+ "format_long_name": "Matroska / WebM",
+ "start_time": "0.000000",
+ "duration": "1.000000",
+ "size": "8679",
+ "bit_rate": "69432",
+ "probe_score": 100,
+ "tags": {
+ "DESCRIPTION": "Just color bars",
+ "ARCHIVAL": "yes",
+ "PRESERVE_THIS": "okay",
+ "ENCODER": "Lavf57.83.100"
+ }
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs b/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs
new file mode 100644
index 000000000..cca056c28
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Dlna/ContainerProfileTests.cs
@@ -0,0 +1,19 @@
+using MediaBrowser.Model.Dlna;
+using Xunit;
+
+namespace Jellyfin.Model.Tests.Dlna
+{
+ public class ContainerProfileTests
+ {
+ private readonly ContainerProfile _emptyContainerProfile = new ContainerProfile();
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("mp4")]
+ public void ContainsContainer_EmptyContainerProfile_True(string? containers)
+ {
+ Assert.True(_emptyContainerProfile.ContainsContainer(containers));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs
index 51633e157..5864a0509 100644
--- a/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs
+++ b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs
@@ -1,4 +1,3 @@
-using System;
using MediaBrowser.Model.Extensions;
using Xunit;
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index 0c7e262f5..c5b51ef76 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
index e5768b620..d9e77dd2e 100644
--- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
index ad63adadc..53b35c2d6 100644
--- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index cc12a99a6..ebb134fc3 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -15,7 +15,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
index f3abacb4f..2446660f3 100644
--- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
@@ -1,4 +1,3 @@
-using System;
using Emby.Naming.Common;
using Emby.Naming.Subtitles;
using Xunit;
diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
index d34f65409..2f173b0ce 100644
--- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
@@ -1,4 +1,3 @@
-using System;
using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Model.Entities;
diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
index a76c0e9a0..d5268facc 100644
--- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
@@ -15,7 +15,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs b/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs
new file mode 100644
index 000000000..1cad625b7
--- /dev/null
+++ b/tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs
@@ -0,0 +1,63 @@
+using System.Net;
+using Jellyfin.Networking.Configuration;
+using Jellyfin.Networking.Manager;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Jellyfin.Networking.Tests
+{
+ public class NetworkManagerTests
+ {
+ /// <summary>
+ /// Checks that the given IP address is in the specified network(s).
+ /// </summary>
+ /// <param name="network">Network address(es).</param>
+ /// <param name="value">The IP to check.</param>
+ [Theory]
+ [InlineData("192.168.2.1/24", "192.168.2.123")]
+ [InlineData("192.168.2.1/24, !192.168.2.122/32", "192.168.2.123")]
+ [InlineData("fd23:184f:2029:0::/56", "fd23:184f:2029:0:3139:7386:67d7:d517")]
+ [InlineData("fd23:184f:2029:0::/56, !fd23:184f:2029:0:3139:7386:67d7:d518/128", "fd23:184f:2029:0:3139:7386:67d7:d517")]
+ public void InNetwork_True_Success(string network, string value)
+ {
+ var ip = IPAddress.Parse(value);
+ var conf = new NetworkConfiguration()
+ {
+ EnableIPV6 = true,
+ EnableIPV4 = true,
+ LocalNetworkSubnets = network.Split(',')
+ };
+
+ using var networkManager = new NetworkManager(NetworkParseTests.GetMockConfig(conf), new NullLogger<NetworkManager>());
+
+ Assert.True(networkManager.IsInLocalNetwork(ip));
+ }
+
+ /// <summary>
+ /// Checks that thge given IP address is not in the network provided.
+ /// </summary>
+ /// <param name="network">Network address(es).</param>
+ /// <param name="value">The IP to check.</param>
+ [Theory]
+ [InlineData("192.168.10.0/24", "192.168.11.1")]
+ [InlineData("192.168.10.0/24, !192.168.10.60/32", "192.168.10.60")]
+ [InlineData("192.168.10.0/24", "fd23:184f:2029:0:3139:7386:67d7:d517")]
+ [InlineData("fd23:184f:2029:0::/56", "fd24:184f:2029:0:3139:7386:67d7:d517")]
+ [InlineData("fd23:184f:2029:0::/56, !fd23:184f:2029:0:3139:7386:67d7:d500/120", "fd23:184f:2029:0:3139:7386:67d7:d517")]
+ [InlineData("fd23:184f:2029:0::/56", "192.168.10.60")]
+ public void InNetwork_False_Success(string network, string value)
+ {
+ var ip = IPAddress.Parse(value);
+ var conf = new NetworkConfiguration()
+ {
+ EnableIPV6 = true,
+ EnableIPV4 = true,
+ LocalNetworkSubnets = network.Split(',')
+ };
+
+ using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), new NullLogger<NetworkManager>());
+
+ Assert.False(nm.IsInLocalNetwork(ip));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
index 28b5a4691..671b8598d 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
@@ -13,7 +13,7 @@ namespace Jellyfin.Networking.Tests
{
public class NetworkParseTests
{
- private static IConfigurationManager GetMockConfig(NetworkConfiguration conf)
+ internal static IConfigurationManager GetMockConfig(NetworkConfiguration conf)
{
var configManager = new Mock<IConfigurationManager>
{
@@ -34,10 +34,12 @@ namespace Jellyfin.Networking.Tests
[InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.0/24", "[192.168.1.208/24,200.200.200.200/24]")]
// eth16 only
[InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")]
- // All interfaces excluded.
- [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[]")]
+ // All interfaces excluded. (including loopbacks)
+ [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[127.0.0.1/8,::1/128]")]
// vEthernet1 and vEthernet212 should be excluded.
- [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24]")]
+ [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24,127.0.0.1/8,::1/128]")]
+ // Overlapping interface,
+ [InlineData("192.168.1.110/24,-20,br0|192.168.1.10/24,-16,br0|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.110/24,192.168.1.10/24]")]
public void IgnoreVirtualInterfaces(string interfaces, string lan, string value)
{
var conf = new NetworkConfiguration()
@@ -55,32 +57,6 @@ namespace Jellyfin.Networking.Tests
}
/// <summary>
- /// Check that the value given is in the network provided.
- /// </summary>
- /// <param name="network">Network address.</param>
- /// <param name="value">Value to check.</param>
- [Theory]
- [InlineData("192.168.10.0/24, !192.168.10.60/32", "192.168.10.60")]
- public void IsInNetwork(string network, string value)
- {
- if (network == null)
- {
- throw new ArgumentNullException(nameof(network));
- }
-
- var conf = new NetworkConfiguration()
- {
- EnableIPV6 = true,
- EnableIPV4 = true,
- LocalNetworkSubnets = network.Split(',')
- };
-
- using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
-
- Assert.False(nm.IsInLocalNetwork(value));
- }
-
- /// <summary>
/// Checks IP address formats.
/// </summary>
/// <param name="address">IP Address.</param>
@@ -201,29 +177,29 @@ namespace Jellyfin.Networking.Tests
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
// Test included.
- Collection<IPObject> nc = nm.CreateIPCollection(settings.Split(","), false);
+ Collection<IPObject> nc = nm.CreateIPCollection(settings.Split(','), false);
Assert.Equal(nc.AsString(), result1);
// Test excluded.
- nc = nm.CreateIPCollection(settings.Split(","), true);
+ nc = nm.CreateIPCollection(settings.Split(','), true);
Assert.Equal(nc.AsString(), result3);
conf.EnableIPV6 = false;
nm.UpdateSettings(conf);
// Test IP4 included.
- nc = nm.CreateIPCollection(settings.Split(","), false);
+ nc = nm.CreateIPCollection(settings.Split(','), false);
Assert.Equal(nc.AsString(), result2);
// Test IP4 excluded.
- nc = nm.CreateIPCollection(settings.Split(","), true);
+ nc = nm.CreateIPCollection(settings.Split(','), true);
Assert.Equal(nc.AsString(), result4);
conf.EnableIPV6 = true;
nm.UpdateSettings(conf);
// Test network addresses of collection.
- nc = nm.CreateIPCollection(settings.Split(","), false);
+ nc = nm.CreateIPCollection(settings.Split(','), false);
nc = nc.AsNetworks();
Assert.Equal(nc.AsString(), result5);
}
@@ -262,10 +238,10 @@ namespace Jellyfin.Networking.Tests
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
- Collection<IPObject> nc1 = nm.CreateIPCollection(settings.Split(","), false);
- Collection<IPObject> nc2 = nm.CreateIPCollection(compare.Split(","), false);
+ Collection<IPObject> nc1 = nm.CreateIPCollection(settings.Split(','), false);
+ Collection<IPObject> nc2 = nm.CreateIPCollection(compare.Split(','), false);
- Assert.Equal(nc1.Union(nc2).AsString(), result);
+ Assert.Equal(nc1.ThatAreContainedInNetworks(nc2).AsString(), result);
}
[Theory]
@@ -372,10 +348,10 @@ namespace Jellyfin.Networking.Tests
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
// Test included, IP6.
- Collection<IPObject> ncSource = nm.CreateIPCollection(source.Split(","));
- Collection<IPObject> ncDest = nm.CreateIPCollection(dest.Split(","));
- Collection<IPObject> ncResult = ncSource.Union(ncDest);
- Collection<IPObject> resultCollection = nm.CreateIPCollection(result.Split(","));
+ Collection<IPObject> ncSource = nm.CreateIPCollection(source.Split(','));
+ Collection<IPObject> ncDest = nm.CreateIPCollection(dest.Split(','));
+ Collection<IPObject> ncResult = ncSource.ThatAreContainedInNetworks(ncDest);
+ Collection<IPObject> resultCollection = nm.CreateIPCollection(result.Split(','));
Assert.True(ncResult.Compare(resultCollection));
}
@@ -408,6 +384,9 @@ namespace Jellyfin.Networking.Tests
[InlineData("jellyfin.org", "eth16", false, "eth16")]
// User on external network, no binding - so result is the 1st external.
[InlineData("jellyfin.org", "", false, "eth11")]
+ // Dns failure - should skip the test.
+ // https://en.wikipedia.org/wiki/.test
+ [InlineData("invalid.domain.test", "", false, "eth11")]
// User assumed to be internal, no binding - so result is the 1st internal.
[InlineData("", "", false, "eth16")]
public void TestBindInterfaces(string source, string bindAddresses, bool ipv6enabled, string result)
@@ -440,10 +419,13 @@ namespace Jellyfin.Networking.Tests
_ = nm.TryParseInterface(result, out Collection<IPObject>? resultObj);
- if (resultObj != null)
+ // Check to see if dns resolution is working. If not, skip test.
+ _ = IPHost.TryParse(source, out var host);
+
+ if (resultObj != null && host?.HasAddress == true)
{
result = ((IPNetAddress)resultObj[0]).ToString(true);
- var intf = nm.GetBindInterface(source, out int? _);
+ var intf = nm.GetBindInterface(source, out _);
Assert.Equal(intf, result);
}
@@ -514,5 +496,45 @@ namespace Jellyfin.Networking.Tests
Assert.Equal(intf, result);
}
+
+ [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)]
+
+ public void HasRemoteAccess_GivenWhitelist_AllowsOnlyIpsInWhitelist(string addresses, string remoteIp, bool denied)
+ {
+ // 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,
+ RemoteIPFilter = addresses.Split(','),
+ IsRemoteIPFilterBlacklist = false
+ };
+ using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+
+ Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIp)), denied);
+ }
+
+ [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)]
+ public void HasRemoteAccess_GivenBlacklist_BlacklistTheIps(string addresses, string remoteIp, bool denied)
+ {
+ // 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,
+ RemoteIPFilter = addresses.Split(','),
+ IsRemoteIPFilterBlacklist = true
+ };
+
+ using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+
+ Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIp)), denied);
+ }
}
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs
new file mode 100644
index 000000000..71f8c5181
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs
@@ -0,0 +1,250 @@
+using System;
+using System.Collections.Generic;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Emby.Server.Implementations.Data;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Data
+{
+ public class SqliteItemRepositoryTests
+ {
+ public const string VirtualMetaDataPath = "%MetadataPath%";
+ public const string MetaDataPath = "/meta/data/path";
+
+ private readonly IFixture _fixture;
+ private readonly SqliteItemRepository _sqliteItemRepository;
+
+ public SqliteItemRepositoryTests()
+ {
+ var appHost = new Mock<IServerApplicationHost>();
+ appHost.Setup(x => x.ExpandVirtualPath(It.IsAny<string>()))
+ .Returns((string x) => x.Replace(VirtualMetaDataPath, MetaDataPath, StringComparison.Ordinal));
+ appHost.Setup(x => x.ReverseVirtualPath(It.IsAny<string>()))
+ .Returns((string x) => x.Replace(MetaDataPath, VirtualMetaDataPath, StringComparison.Ordinal));
+
+ _fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
+ _fixture.Inject(appHost);
+ _sqliteItemRepository = _fixture.Create<SqliteItemRepository>();
+ }
+
+ public static IEnumerable<object[]> ItemImageInfoFromValueString_Valid_TestData()
+ {
+ yield return new object[]
+ {
+ "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN",
+ new ItemImageInfo
+ {
+ Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg",
+ Type = ImageType.Primary,
+ DateModified = new DateTime(637452096478512963, DateTimeKind.Utc),
+ Width = 1920,
+ Height = 1080,
+ BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN"
+ }
+ };
+
+ yield return new object[]
+ {
+ "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*0*0",
+ new ItemImageInfo
+ {
+ Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
+ Type = ImageType.Primary,
+ }
+ };
+
+ yield return new object[]
+ {
+ "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary",
+ new ItemImageInfo
+ {
+ Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
+ Type = ImageType.Primary,
+ }
+ };
+
+ yield return new object[]
+ {
+ "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*600",
+ new ItemImageInfo
+ {
+ Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
+ Type = ImageType.Primary,
+ }
+ };
+
+ yield return new object[]
+ {
+ "%MetadataPath%/library/68/68578562b96c80a7ebd530848801f645/poster.jpg*637264380567586027*Primary*600*336",
+ new ItemImageInfo
+ {
+ Path = "/meta/data/path/library/68/68578562b96c80a7ebd530848801f645/poster.jpg",
+ Type = ImageType.Primary,
+ DateModified = new DateTime(637264380567586027, DateTimeKind.Utc),
+ Width = 600,
+ Height = 336
+ }
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(ItemImageInfoFromValueString_Valid_TestData))]
+ public void ItemImageInfoFromValueString_Valid_Success(string value, ItemImageInfo expected)
+ {
+ var result = _sqliteItemRepository.ItemImageInfoFromValueString(value);
+ Assert.Equal(expected.Path, result.Path);
+ Assert.Equal(expected.Type, result.Type);
+ Assert.Equal(expected.DateModified, result.DateModified);
+ Assert.Equal(expected.Width, result.Width);
+ Assert.Equal(expected.Height, result.Height);
+ Assert.Equal(expected.BlurHash, result.BlurHash);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("*")]
+ [InlineData("https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0")]
+ public void ItemImageInfoFromValueString_Invalid_Null(string value)
+ {
+ Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value));
+ }
+
+ public static IEnumerable<object[]> DeserializeImages_Valid_TestData()
+ {
+ yield return new object[]
+ {
+ "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN",
+ new ItemImageInfo[]
+ {
+ new ItemImageInfo()
+ {
+ Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg",
+ Type = ImageType.Primary,
+ DateModified = new DateTime(637452096478512963, DateTimeKind.Utc),
+ Width = 1920,
+ Height = 1080,
+ BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN"
+ }
+ }
+ };
+
+ yield return new object[]
+ {
+ "%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/poster.jpg*637261226720645297*Primary*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/logo.png*637261226720805297*Logo*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/landscape.jpg*637261226721285297*Thumb*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/backdrop.jpg*637261226721685297*Backdrop*0*0",
+ new ItemImageInfo[]
+ {
+ new ItemImageInfo()
+ {
+ Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/poster.jpg",
+ Type = ImageType.Primary,
+ DateModified = new DateTime(637261226720645297, DateTimeKind.Utc),
+ },
+ new ItemImageInfo()
+ {
+ Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/logo.png",
+ Type = ImageType.Logo,
+ DateModified = new DateTime(637261226720805297, DateTimeKind.Utc),
+ },
+ new ItemImageInfo()
+ {
+ Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/landscape.jpg",
+ Type = ImageType.Thumb,
+ DateModified = new DateTime(637261226721285297, DateTimeKind.Utc),
+ },
+ new ItemImageInfo()
+ {
+ Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/backdrop.jpg",
+ Type = ImageType.Backdrop,
+ DateModified = new DateTime(637261226721685297, DateTimeKind.Utc),
+ }
+ }
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(DeserializeImages_Valid_TestData))]
+ public void DeserializeImages_Valid_Success(string value, ItemImageInfo[] expected)
+ {
+ var result = _sqliteItemRepository.DeserializeImages(value);
+ Assert.Equal(expected.Length, result.Length);
+ for (int i = 0; i < expected.Length; i++)
+ {
+ Assert.Equal(expected[i].Path, result[i].Path);
+ Assert.Equal(expected[i].Type, result[i].Type);
+ Assert.Equal(expected[i].DateModified, result[i].DateModified);
+ Assert.Equal(expected[i].Width, result[i].Width);
+ Assert.Equal(expected[i].Height, result[i].Height);
+ Assert.Equal(expected[i].BlurHash, result[i].BlurHash);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(DeserializeImages_Valid_TestData))]
+ public void SerializeImages_Valid_Success(string expected, ItemImageInfo[] value)
+ {
+ Assert.Equal(expected, _sqliteItemRepository.SerializeImages(value));
+ }
+
+ public static IEnumerable<object[]> DeserializeProviderIds_Valid_TestData()
+ {
+ yield return new object[]
+ {
+ "Imdb=tt0119567",
+ new Dictionary<string, string>()
+ {
+ { "Imdb", "tt0119567" },
+ }
+ };
+
+ yield return new object[]
+ {
+ "Imdb=tt0119567|Tmdb=330|TmdbCollection=328",
+ new Dictionary<string, string>()
+ {
+ { "Imdb", "tt0119567" },
+ { "Tmdb", "330" },
+ { "TmdbCollection", "328" },
+ }
+ };
+
+ yield return new object[]
+ {
+ "MusicBrainzAlbum=9d363e43-f24f-4b39-bc5a-7ef305c677c7|MusicBrainzReleaseGroup=63eba062-847c-3b73-8b0f-6baf27bba6fa|AudioDbArtist=111352|AudioDbAlbum=2116560|MusicBrainzAlbumArtist=20244d07-534f-4eff-b4d4-930878889970",
+ new Dictionary<string, string>()
+ {
+ { "MusicBrainzAlbum", "9d363e43-f24f-4b39-bc5a-7ef305c677c7" },
+ { "MusicBrainzReleaseGroup", "63eba062-847c-3b73-8b0f-6baf27bba6fa" },
+ { "AudioDbArtist", "111352" },
+ { "AudioDbAlbum", "2116560" },
+ { "MusicBrainzAlbumArtist", "20244d07-534f-4eff-b4d4-930878889970" },
+ }
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(DeserializeProviderIds_Valid_TestData))]
+ public void DeserializeProviderIds_Valid_Success(string value, Dictionary<string, string> expected)
+ {
+ var result = new ProviderIdsExtensionsTestsObject();
+ SqliteItemRepository.DeserializeProviderIds(value, result);
+ Assert.Equal(expected, result.ProviderIds);
+ }
+
+ [Theory]
+ [MemberData(nameof(DeserializeProviderIds_Valid_TestData))]
+ public void SerializeProviderIds_Valid_Success(string expected, Dictionary<string, string> values)
+ {
+ Assert.Equal(expected, SqliteItemRepository.SerializeProviderIds(values));
+ }
+
+ private class ProviderIdsExtensionsTestsObject : IHasProviderIds
+ {
+ public Dictionary<string, string> ProviderIds { get; set; } = new Dictionary<string, string>();
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
index 671c59b2e..30e6542f9 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
@@ -1,3 +1,6 @@
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Runtime.InteropServices;
using AutoFixture;
using AutoFixture.AutoMoq;
using Emby.Server.Implementations.IO;
@@ -38,5 +41,36 @@ namespace Jellyfin.Server.Implementations.Tests.IO
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(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+
+ 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/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index c3c258b68..27713d58a 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -22,12 +22,13 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="AutoFixture" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
</ItemGroup>
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
index 876519215..c393742eb 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
@@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
using Moq;
using Xunit;
@@ -28,7 +29,10 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
Parent = parent,
CollectionType = CollectionType.TvShows,
- Path = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv"
+ FileInfo = new FileSystemMetadata()
+ {
+ FullName = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv"
+ }
};
Assert.Null(episodeResolver.Resolve(itemResolveArgs));
@@ -48,7 +52,10 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
Parent = series,
CollectionType = CollectionType.TvShows,
- Path = "Extras/Extras S01E01.mkv"
+ FileInfo = new FileSystemMetadata()
+ {
+ FullName = "Extras/Extras S01E01.mkv"
+ }
};
Assert.NotNull(episodeResolver.Resolve(itemResolveArgs));
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
index e5508243f..c5cc056f5 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
@@ -33,6 +33,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library
[InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff", "/home/jeff/", "/home/jeff/myfile.mkv")]
[InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff/", "/home/jeff/", "/home/jeff/myfile.mkv")]
[InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff/", "/", "/myfile.mkv")]
+ [InlineData("/o", "/o", "/s", "/s")] // regression test for #5977
public void TryReplaceSubPath_ValidArgs_Correct(string path, string subPath, string newSubPath, string? expectedResult)
{
Assert.True(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result));
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
index 7e04a1ec1..fd499d9cf 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Text;
using Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun;
using Xunit;
@@ -17,8 +18,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
Span<byte> buffer = stackalloc byte[128];
int len = HdHomerunManager.WriteNullTerminatedString(buffer, string.Empty);
- Assert.Equal(expected.Length, len);
- Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
}
[Fact]
@@ -32,8 +34,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
Span<byte> buffer = stackalloc byte[128];
int len = HdHomerunManager.WriteNullTerminatedString(buffer, "The quick");
- Assert.Equal(expected.Length, len);
- Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
}
[Fact]
@@ -51,8 +54,273 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
Span<byte> buffer = stackalloc byte[128];
int len = HdHomerunManager.WriteGetMessage(buffer, 0, "N");
- Assert.Equal(expected.Length, len);
- Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
+ }
+
+ [Fact]
+ public void WriteSetMessage_NoLockKey_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 0, 4,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xa9, 0x49, 0xd0, 0x68
+ };
+
+ Span<byte> buffer = stackalloc byte[128];
+ int len = HdHomerunManager.WriteSetMessage(buffer, 0, "N", "value", null);
+
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
+ }
+
+ [Fact]
+ public void WriteSetMessage_LockKey_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 0, 4,
+ 0, 26,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 21,
+ 4, 0x00, 0x01, 0x38, 0xd5,
+ 0x8e, 0xb6, 0x06, 0x82
+ };
+
+ Span<byte> buffer = stackalloc byte[128];
+ int len = HdHomerunManager.WriteSetMessage(buffer, 0, "N", "value", 80085);
+
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_Valid_Success()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.True(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out var value));
+ Assert.Equal("value", Encoding.UTF8.GetString(value));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidCrc_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf4
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidPacketType_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 4,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xa9, 0x49, 0xd0, 0x68
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidPacket_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 0x7d, 0xa3, 0xa3
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooSmallMessageLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 19,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x25, 0x25, 0x44, 0x9a
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooLargeMessageLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 21,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xe3, 0x20, 0x79, 0x6c
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooLargeNameLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 20, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xe1, 0x8e, 0x9c, 0x74
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidGetSetNameTag_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 4,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xee, 0x05, 0xe7, 0x12
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidGetSetValueTag_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 3,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x64, 0xaa, 0x66, 0xf9
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooLargeValueLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 7, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xc9, 0xa8, 0xd4, 0x55
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void VerifyReturnValueOfGetSet_Valid_True()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.True(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "value"));
+ }
+
+ [Fact]
+ public void VerifyReturnValueOfGetSet_WrongValue_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.False(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "none"));
+ }
+
+ [Fact]
+ public void VerifyReturnValueOfGetSet_InvalidPacket_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 4,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.False(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "value"));
}
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
new file mode 100644
index 000000000..ea6838682
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net.Mime;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models.StartupDtos;
+using Jellyfin.Api.Models.UserDtos;
+using MediaBrowser.Common.Json;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ public static class AuthHelper
+ {
+ public const string AuthHeaderName = "X-Emby-Authorization";
+ public const string DummyAuthHeader = "MediaBrowser Client=\"Jellyfin.Server Integration Tests\", DeviceId=\"69420\", Device=\"Apple II\", Version=\"10.8.0\"";
+
+ public static async Task<string> CompleteStartupAsync(HttpClient client)
+ {
+ var jsonOptions = JsonDefaults.Options;
+ var userResponse = await client.GetByteArrayAsync("/Startup/User").ConfigureAwait(false);
+ var user = JsonSerializer.Deserialize<StartupUserDto>(userResponse, jsonOptions);
+
+ using var completeResponse = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty<byte>())).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, completeResponse.StatusCode);
+
+ using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(
+ new AuthenticateUserByName()
+ {
+ Username = user!.Name,
+ Pw = user.Password,
+ },
+ jsonOptions));
+ content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ content.Headers.Add("X-Emby-Authorization", DummyAuthHeader);
+
+ using var authResponse = await client.PostAsync("/Users/AuthenticateByName", content).ConfigureAwait(false);
+ var auth = await JsonSerializer.DeserializeAsync<AuthenticationResultDto>(
+ await authResponse.Content.ReadAsStreamAsync().ConfigureAwait(false),
+ jsonOptions).ConfigureAwait(false);
+
+ return auth!.AccessToken;
+ }
+
+ public static void AddAuthHeader(this HttpHeaders headers, string accessToken)
+ {
+ headers.Add(AuthHeaderName, DummyAuthHeader + $", Token={accessToken}");
+ }
+
+ private class AuthenticationResultDto
+ {
+ public string AccessToken { get; set; } = string.Empty;
+
+ public string ServerId { get; set; } = string.Empty;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs
new file mode 100644
index 000000000..be89fbc9a
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs
@@ -0,0 +1,30 @@
+using System.Net;
+using System.Net.Mime;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers
+{
+ public sealed class ActivityLogControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+ private static string? _accessToken;
+
+ public ActivityLogControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task ActivityLog_GetEntries_Ok()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var response = await client.GetAsync("System/ActivityLog/Entries").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
new file mode 100644
index 000000000..6584490de
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net.Mime;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models.UserDtos;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Model.Dto;
+using Xunit;
+using Xunit.Priority;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers
+{
+ [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+ public sealed class UserControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private const string TestUsername = "testUser01";
+
+ private readonly JellyfinApplicationFactory _factory;
+ private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.Options;
+ private static string? _accessToken;
+ private static Guid _testUserId = Guid.Empty;
+
+ public UserControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ private Task<HttpResponseMessage> CreateUserByName(HttpClient httpClient, CreateUserByName request)
+ {
+ using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions));
+ postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ return httpClient.PostAsync("Users/New", postContent);
+ }
+
+ private Task<HttpResponseMessage> UpdateUserPassword(HttpClient httpClient, Guid userId, UpdateUserPassword request)
+ {
+ using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions));
+ postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ return httpClient.PostAsync("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Password", postContent);
+ }
+
+ [Fact]
+ [Priority(-1)]
+ public async Task GetPublicUsers_Valid_Success()
+ {
+ var client = _factory.CreateClient();
+
+ using var response = await client.GetAsync("Users/Public").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
+ await response.Content.ReadAsStreamAsync().ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
+ // User are hidden by default
+ Assert.Empty(users);
+ }
+
+ [Fact]
+ [Priority(-1)]
+ public async Task GetUsers_Valid_Success()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ using var response = await client.GetAsync("Users").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
+ await response.Content.ReadAsStreamAsync().ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
+ Assert.Single(users);
+ Assert.False(users![0].HasConfiguredPassword);
+ }
+
+ [Fact]
+ [Priority(0)]
+ public async Task New_Valid_Success()
+ {
+ var client = _factory.CreateClient();
+
+ // access token can't be null here as the previous test populated it
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
+
+ var createRequest = new CreateUserByName()
+ {
+ Name = TestUsername
+ };
+
+ using var response = await CreateUserByName(client, createRequest).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var user = await JsonSerializer.DeserializeAsync<UserDto>(
+ await response.Content.ReadAsStreamAsync().ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
+ Assert.Equal(TestUsername, user!.Name);
+ Assert.False(user.HasPassword);
+ Assert.False(user.HasConfiguredPassword);
+
+ _testUserId = user.Id;
+
+ Console.WriteLine(user.Id.ToString("N", CultureInfo.InvariantCulture));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData("‼️")]
+ [Priority(0)]
+ public async Task New_Invalid_Fail(string? username)
+ {
+ var client = _factory.CreateClient();
+
+ // access token can't be null here as the previous test populated it
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
+
+ var createRequest = new CreateUserByName()
+ {
+ Name = username
+ };
+
+ using var response = await CreateUserByName(client, createRequest).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ }
+
+ [Fact]
+ [Priority(1)]
+ public async Task UpdateUserPassword_Valid_Success()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
+
+ var createRequest = new UpdateUserPassword()
+ {
+ NewPw = "4randomPa$$word"
+ };
+
+ using var response = await UpdateUserPassword(client, _testUserId, createRequest).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+
+ var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
+ await client.GetStreamAsync("Users").ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
+ var user = users!.First(x => x.Id == _testUserId);
+ Assert.True(user.HasPassword);
+ Assert.True(user.HasConfiguredPassword);
+ }
+
+ [Fact]
+ [Priority(2)]
+ public async Task UpdateUserPassword_Empty_RemoveSetPassword()
+ {
+ var client = _factory.CreateClient();
+
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
+
+ var createRequest = new UpdateUserPassword()
+ {
+ CurrentPw = "4randomPa$$word",
+ };
+
+ using var response = await UpdateUserPassword(client, _testUserId, createRequest).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+
+ var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
+ await client.GetStreamAsync("Users").ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
+ var user = users!.First(x => x.Id == _testUserId);
+ Assert.False(user.HasPassword);
+ Assert.False(user.HasConfiguredPassword);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
index 34cef9005..938385a2a 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -9,12 +9,12 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" />
+ <PackageReference Include="AutoFixture" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="Xunit.Priority" Version="1.1.6" />
@@ -22,6 +22,13 @@
<PackageReference Include="Moq" Version="4.16.0" />
</ItemGroup>
+ <ItemGroup>
+ <!-- Don't run tests in parallel -->
+ <None Update="xunit.runner.json">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
+
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
diff --git a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
index 3cbd638f9..0ade345a1 100644
--- a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
@@ -1,8 +1,6 @@
using System.IO;
using System.Reflection;
-using System.Text.Json;
using System.Threading.Tasks;
-using MediaBrowser.Model.Branding;
using Xunit;
using Xunit.Abstractions;
diff --git a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
index 4e5d0fcb6..0a463cfa3 100644
--- a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Reflection;
using Emby.Server.Implementations;
-using Jellyfin.Server;
using MediaBrowser.Controller;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Configuration;
diff --git a/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs b/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs
new file mode 100644
index 000000000..ffdc04eba
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ public sealed class WebSocketTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+
+ public WebSocketTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task WebSocket_Unauthenticated_ThrowsInvalidOperationException()
+ {
+ var server = _factory.Server;
+ var client = server.CreateWebSocketClient();
+
+ await Assert.ThrowsAsync<InvalidOperationException>(
+ () => client.ConnectAsync(
+ new UriBuilder(server.BaseAddress)
+ {
+ Scheme = "ws",
+ Path = "websocket"
+ }.Uri, CancellationToken.None));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json b/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json
new file mode 100644
index 000000000..809e880c7
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json
@@ -0,0 +1,4 @@
+{
+ "parallelizeAssembly": false,
+ "parallelizeTestCollections": false
+}
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index a310b0ea9..72e40ebcb 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -10,12 +10,12 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" />
+ <PackageReference Include="AutoFixture" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
index 0b714e80a..146b16cf9 100644
--- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
+++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
@@ -1,4 +1,3 @@
-using System;
using System.Globalization;
using System.Text;
using Jellyfin.Networking.Configuration;
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index 9380fe2af..4132205c3 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -16,7 +16,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
index d4ce74132..30a48857a 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
@@ -156,7 +156,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
Assert.Equal("Justice League Collection", item.CollectionName);
// Images
- Assert.Equal(6, result.RemoteImages.Count);
+ Assert.Equal(7, result.RemoteImages.Count);
var posters = result.RemoteImages.Where(x => x.type == ImageType.Primary).ToList();
Assert.Single(posters);
@@ -182,6 +182,10 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
Assert.Single(discArt);
Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].url);
+ var backdrop = result.RemoteImages.Where(x => x.type == ImageType.Backdrop).ToList();
+ Assert.Single(backdrop);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg", backdrop[0].url);
+
// Local Image - contains only one item depending on operating system
Assert.Single(result.Images);
Assert.Equal(_localImageFileMetadata.Name, result.Images[0].FileInfo.Name);
@@ -204,6 +208,21 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
+ public void Parse_RadarrUrlFile_Success()
+ {
+ var result = new MetadataResult<Video>()
+ {
+ Item = new Movie()
+ };
+
+ _parser.Fetch(result, "Test Data/Radarr.nfo", CancellationToken.None);
+ var item = (Movie)result.Item;
+
+ Assert.Equal("583689", item.ProviderIds[MetadataProvider.Tmdb.ToString()]);
+ Assert.Equal("tt4154796", item.ProviderIds[MetadataProvider.Imdb.ToString()]);
+ }
+
+ [Fact]
public void Fetch_WithNullItem_ThrowsArgumentException()
{
var result = new MetadataResult<Video>();
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
index 3d8e13e96..8ca3dd96e 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Linq;
using System.Threading;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities.Audio;
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
index c35d37b18..4e8c79dca 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
@@ -82,8 +82,8 @@
<thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a0b913c233be.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a0b913c233be.png</thumb>
<thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-5a87e0cdb1209.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a87e0cdb1209.png</thumb>
<thumb aspect="discart" preview="https://assets.fanart.tv/preview/movies/141052/moviedisc/justice-league-59dc595362ef1.png">https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-59dc595362ef1.png</thumb>
+ <thumb aspect="fanart">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg</thumb>
<fanart>
- <thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg</thumb>
<thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg</thumb>
<thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5a53cf2dac1c8.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a53cf2dac1c8.jpg</thumb>
<thumb preview="https://assets.fanart.tv/preview/movies/141052/moviebackground/justice-league-5976ba93eb5d3.jpg">https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5976ba93eb5d3.jpg</thumb>
@@ -93,7 +93,8 @@
</fanart>
<mpaa>Australia:M</mpaa>
<id>tt0974015</id>
- <uniqueid type="imdb" default="true">tt0974015</uniqueid>
+ <uniqueid type="imdb">tt0974015</uniqueid>
+ <uniqueid type="tmdb">141052</uniqueid>
<genre>Action</genre>
<genre>Adventure</genre>
<genre>Fantasy</genre>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Radarr.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Radarr.nfo
new file mode 100644
index 000000000..43da4881c
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Radarr.nfo
@@ -0,0 +1,2 @@
+https://www.themoviedb.org/movie/583689
+https://www.imdb.com/title/tt4154796