aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs2
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj16
-rw-r--r--tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs6
-rw-r--r--tests/Jellyfin.Api.Tests/TestHelpers.cs7
-rw-r--r--tests/Jellyfin.Common.Tests/Crc32Tests.cs33
-rw-r--r--tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs43
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj11
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs52
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs27
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs39
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs36
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj11
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj11
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj11
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/SsaParserTests.cs96
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs15
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs88
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ssa20
-rw-r--r--tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs70
-rw-r--r--tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs196
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj11
-rw-r--r--tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs4
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj13
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs35
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs45
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs4
-rw-r--r--tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj (renamed from tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj)22
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkParseTests.cs (renamed from tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs)103
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj13
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs65
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs30
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs58
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json684
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs57
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs (renamed from tests/Jellyfin.Api.Tests/BrandingServiceTests.cs)19
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs86
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs119
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj40
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs (renamed from tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs)18
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs (renamed from tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs)2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs55
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/TestPage.html9
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs43
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs27
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj36
-rw-r--r--tests/Jellyfin.Server.Tests/ParseNetworkTests.cs (renamed from tests/Jellyfin.Api.Tests/ParseNetworkTests.cs)18
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj13
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs22
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs2
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs2
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs2
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs2
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs2
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Rising.nfo20
56 files changed, 2113 insertions, 362 deletions
diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
index ee20cc573..de03aa5f5 100644
--- a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
@@ -128,6 +128,8 @@ namespace Jellyfin.Api.Tests.Auth
{
var authorizationInfo = _fixture.Create<AuthorizationInfo>();
authorizationInfo.User = _fixture.Create<User>();
+ authorizationInfo.User.AddDefaultPermissions();
+ authorizationInfo.User.AddDefaultPreferences();
authorizationInfo.User.SetPermission(PermissionKind.IsAdministrator, isAdmin);
authorizationInfo.IsApiKey = false;
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index eca3df79b..577b61d02 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -10,6 +10,8 @@
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
@@ -18,27 +20,23 @@
<PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" />
<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.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.2" />
- <PackageReference Include="Moq" Version="4.16.0" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
+ <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\..\Jellyfin.Server\Jellyfin.Server.csproj" />
+ <ProjectReference Include="../../Jellyfin.Api/Jellyfin.Api.csproj" />
+ <ProjectReference Include="../../Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs b/tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs
index 544a74637..92c534eae 100644
--- a/tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs
+++ b/tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs
@@ -1,17 +1,11 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
namespace Jellyfin.Api.Tests.ModelBinders
{
public enum TestType
{
-#pragma warning disable SA1602 // Enumeration items should be documented
How,
Much,
Is,
The,
Fish
-#pragma warning restore SA1602 // Enumeration items should be documented
}
}
diff --git a/tests/Jellyfin.Api.Tests/TestHelpers.cs b/tests/Jellyfin.Api.Tests/TestHelpers.cs
index f27cdf7b6..f9bca4146 100644
--- a/tests/Jellyfin.Api.Tests/TestHelpers.cs
+++ b/tests/Jellyfin.Api.Tests/TestHelpers.cs
@@ -26,8 +26,11 @@ namespace Jellyfin.Api.Tests
{
var user = new User(
"jellyfin",
- typeof(DefaultAuthenticationProvider).FullName,
- typeof(DefaultPasswordResetProvider).FullName);
+ typeof(DefaultAuthenticationProvider).FullName!,
+ typeof(DefaultPasswordResetProvider).FullName!);
+
+ user.AddDefaultPermissions();
+ user.AddDefaultPreferences();
// Set administrator flag.
user.SetPermission(PermissionKind.IsAdministrator, role.Equals(UserRoles.Administrator, StringComparison.OrdinalIgnoreCase));
diff --git a/tests/Jellyfin.Common.Tests/Crc32Tests.cs b/tests/Jellyfin.Common.Tests/Crc32Tests.cs
new file mode 100644
index 000000000..e95a2867f
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Crc32Tests.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Text;
+using MediaBrowser.Common;
+using Xunit;
+
+namespace Jellyfin.Common.Tests
+{
+ public static class Crc32Tests
+ {
+ [Fact]
+ public static void Compute_Empty_Zero()
+ {
+ Assert.Equal<uint>(0, Crc32.Compute(Array.Empty<byte>()));
+ }
+
+ [Theory]
+ [InlineData(0x414fa339, "The quick brown fox jumps over the lazy dog")]
+ public static void Compute_Valid_Success(uint expected, string data)
+ {
+ Assert.Equal(expected, Crc32.Compute(Encoding.UTF8.GetBytes(data)));
+ }
+
+ [Theory]
+ [InlineData(0x414fa339, "54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F67")]
+ [InlineData(0x190a55ad, "0000000000000000000000000000000000000000000000000000000000000000")]
+ [InlineData(0xff6cab0b, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")]
+ [InlineData(0x91267e8a, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")]
+ public static void Compute_ValidHex_Success(uint expected, string data)
+ {
+ Assert.Equal(expected, Crc32.Compute(Convert.FromHexString(data)));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs b/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs
deleted file mode 100644
index 8bf613f05..000000000
--- a/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using MediaBrowser.Common.Extensions;
-using Xunit;
-
-namespace Jellyfin.Common.Tests.Extensions
-{
- public class StringExtensionsTests
- {
- [Theory]
- [InlineData("", 'q', "")]
- [InlineData("Banana split", ' ', "Banana")]
- [InlineData("Banana split", 'q', "Banana split")]
- public void LeftPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult)
- {
- var result = str.AsSpan().LeftPart(needle).ToString();
- Assert.Equal(expectedResult, result);
- }
-
- [Theory]
- [InlineData("", "", "")]
- [InlineData("", "q", "")]
- [InlineData("Banana split", "", "")]
- [InlineData("Banana split", " ", "Banana")]
- [InlineData("Banana split test", " split", "Banana")]
- public void LeftPart_ValidArgsWithoutStringComparison_Correct(string str, string needle, string expectedResult)
- {
- var result = str.AsSpan().LeftPart(needle).ToString();
- Assert.Equal(expectedResult, result);
- }
-
- [Theory]
- [InlineData("", "", StringComparison.Ordinal, "")]
- [InlineData("Banana split", " ", StringComparison.Ordinal, "Banana")]
- [InlineData("Banana split test", " split", StringComparison.Ordinal, "Banana")]
- [InlineData("Banana split test", " Split", StringComparison.Ordinal, "Banana split test")]
- [InlineData("Banana split test", " Splït", StringComparison.InvariantCultureIgnoreCase, "Banana split test")]
- public void LeftPart_ValidArgs_Correct(string str, string needle, StringComparison stringComparison, string expectedResult)
- {
- var result = str.AsSpan().LeftPart(needle, stringComparison).ToString();
- Assert.Equal(expectedResult, result);
- }
- }
-}
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 57edbf902..017a67e9f 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -10,18 +10,19 @@
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.2" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
</ItemGroup>
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
@@ -32,8 +33,4 @@
<ProjectReference Include="../../MediaBrowser.Providers/MediaBrowser.Providers.csproj" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs
index 0d2bdd1af..ca300401d 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs
+++ b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs
@@ -1,4 +1,5 @@
-using System.Text.Json;
+using System;
+using System.Text.Json;
using System.Text.Json.Serialization;
using Jellyfin.Common.Tests.Models;
using MediaBrowser.Model.Session;
@@ -9,6 +10,27 @@ namespace Jellyfin.Common.Tests.Json
public static class JsonCommaDelimitedArrayTests
{
[Fact]
+ public static void Deserialize_String_Null_Success()
+ {
+ var options = new JsonSerializerOptions();
+ var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": null }", options);
+ Assert.Null(value?.Value);
+ }
+
+ [Fact]
+ public static void Deserialize_Empty_Success()
+ {
+ var desiredValue = new GenericBodyArrayModel<string>
+ {
+ Value = Array.Empty<string>()
+ };
+
+ var options = new JsonSerializerOptions();
+ var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": """" }", options);
+ Assert.Equal(desiredValue.Value, value?.Value);
+ }
+
+ [Fact]
public static void Deserialize_String_Valid_Success()
{
var desiredValue = new GenericBodyArrayModel<string>
@@ -49,6 +71,34 @@ namespace Jellyfin.Common.Tests.Json
}
[Fact]
+ public static void Deserialize_GenericCommandType_EmptyEntry_Success()
+ {
+ var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
+ {
+ Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
+ };
+
+ var options = new JsonSerializerOptions();
+ options.Converters.Add(new JsonStringEnumConverter());
+ var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", options);
+ Assert.Equal(desiredValue.Value, value?.Value);
+ }
+
+ [Fact]
+ public static void Deserialize_GenericCommandType_Invalid_Success()
+ {
+ var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
+ {
+ Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
+ };
+
+ var options = new JsonSerializerOptions();
+ options.Converters.Add(new JsonStringEnumConverter());
+ var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,TotallyNotAVallidCommand,MoveDown"" }", options);
+ Assert.Equal(desiredValue.Value, value?.Value);
+ }
+
+ [Fact]
public static void Deserialize_GenericCommandType_Space_Valid_Success()
{
var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs
index faed086a1..efe8063a0 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs
+++ b/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs
@@ -39,6 +39,15 @@ namespace Jellyfin.Common.Tests.Json
}
[Theory]
+ [InlineData("\"8\"", 8)]
+ [InlineData("8", 8)]
+ public void Deserialize_NullableInt_Success(string input, int? expected)
+ {
+ var result = JsonSerializer.Deserialize<int?>(input, _options);
+ Assert.Equal(result, expected);
+ }
+
+ [Theory]
[InlineData("\"N/A\"")]
[InlineData("null")]
public void Deserialization_To_Nullable_String_Shoud_Be_Null(string input)
@@ -48,21 +57,11 @@ namespace Jellyfin.Common.Tests.Json
}
[Theory]
- [InlineData("\"8\"", 8)]
- [InlineData("8", 8)]
- public void Deserialize_Int_Success(string input, int expected)
- {
- var result = JsonSerializer.Deserialize<int>(input, _options);
- Assert.Equal(result, expected);
- }
-
- [Fact]
- public void Deserialize_Normal_String_Success()
+ [InlineData("\"Jellyfin\"", "Jellyfin")]
+ public void Deserialize_Normal_String_Success(string input, string expected)
{
- const string Input = "\"Jellyfin\"";
- const string Expected = "Jellyfin";
- var result = JsonSerializer.Deserialize<string>(Input, _options);
- Assert.Equal(Expected, result);
+ var result = JsonSerializer.Deserialize<string?>(input, _options);
+ Assert.Equal(expected, result);
}
[Fact]
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs
new file mode 100644
index 000000000..fd77694b3
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs
@@ -0,0 +1,39 @@
+using System.Text.Json;
+using MediaBrowser.Common.Json.Converters;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Json
+{
+ public class JsonStringConverterTests
+ {
+ private readonly JsonSerializerOptions _jsonSerializerOptions
+ = new ()
+ {
+ Converters =
+ {
+ new JsonStringConverter()
+ }
+ };
+
+ [Theory]
+ [InlineData("\"test\"", "test")]
+ [InlineData("123", "123")]
+ [InlineData("123.45", "123.45")]
+ [InlineData("true", "true")]
+ [InlineData("false", "false")]
+ public void Deserialize_String_Valid_Success(string input, string output)
+ {
+ var deserialized = JsonSerializer.Deserialize<string>(input, _jsonSerializerOptions);
+ Assert.Equal(deserialized, output);
+ }
+
+ [Fact]
+ public void Deserialize_Int32asInt32_Valid_Success()
+ {
+ const string? input = "123";
+ const int output = 123;
+ var deserialized = JsonSerializer.Deserialize<int>(input, _jsonSerializerOptions);
+ Assert.Equal(deserialized, output);
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs
new file mode 100644
index 000000000..f2cefdbf8
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Text.Json;
+using MediaBrowser.Common.Json.Converters;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Json
+{
+ public class JsonVersionConverterTests
+ {
+ private readonly JsonSerializerOptions _options;
+
+ public JsonVersionConverterTests()
+ {
+ _options = new JsonSerializerOptions();
+ _options.Converters.Add(new JsonVersionConverter());
+ }
+
+ [Fact]
+ public void Deserialize_Version_Success()
+ {
+ var input = "\"1.025.222\"";
+ var output = new Version(1, 25, 222);
+ var deserializedInput = JsonSerializer.Deserialize<Version>(input, _options);
+ Assert.Equal(output, deserializedInput);
+ }
+
+ [Fact]
+ public void Serialize_Version_Success()
+ {
+ var input = new Version(1, 09, 59);
+ var output = "\"1.9.59\"";
+ var serializedInput = JsonSerializer.Serialize(input, _options);
+ Assert.Equal(output, serializedInput);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index c766c5445..6dec25aa4 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -10,18 +10,19 @@
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.2" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
</ItemGroup>
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
@@ -31,8 +32,4 @@
<ProjectReference Include="../../MediaBrowser.Controller/MediaBrowser.Controller.csproj" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
index 52a9e1193..5d52f94c0 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -5,18 +5,19 @@
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.2" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
</ItemGroup>
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
@@ -26,8 +27,4 @@
<ProjectReference Include="../../Emby.Dlna/Emby.Dlna.csproj" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
index c39ef0ce9..415682e85 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
@@ -16,7 +16,7 @@ namespace Jellyfin.MediaEncoding.Tests
var path = Path.Join("Test Data", fileName);
using (var stream = File.OpenRead(path))
{
- await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream, JsonDefaults.GetOptions()).ConfigureAwait(false);
+ await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream, JsonDefaults.Options).ConfigureAwait(false);
}
}
}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index 24f6fb356..4cc1d37ee 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -10,6 +10,8 @@
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
@@ -19,15 +21,14 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.2" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
</ItemGroup>
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
@@ -37,8 +38,4 @@
<ProjectReference Include="../../MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/SsaParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/SsaParserTests.cs
deleted file mode 100644
index d11cb242c..000000000
--- a/tests/Jellyfin.MediaEncoding.Tests/SsaParserTests.cs
+++ /dev/null
@@ -1,96 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Threading;
-using MediaBrowser.MediaEncoding.Subtitles;
-using MediaBrowser.Model.MediaInfo;
-using Xunit;
-
-namespace Jellyfin.MediaEncoding.Tests
-{
- public class SsaParserTests
- {
- // commonly shared invariant value between tests, assumes default format order
- private const string InvariantDialoguePrefix = "[Events]\nDialogue: ,0:00:00.00,0:00:00.01,,,,,,,";
-
- private SsaParser parser = new SsaParser();
-
- [Theory]
- [InlineData("[EvEnTs]\nDialogue: ,0:00:00.00,0:00:00.01,,,,,,,text", "text")] // label casing insensitivity
- [InlineData("[Events]\n,0:00:00.00,0:00:00.01,,,,,,,labelless dialogue", "labelless dialogue")] // no "Dialogue:" label, it is optional
- [InlineData("[Events]\nFormat: Text, Start, End, Layer, Effect, Style\nDialogue: reordered text,0:00:00.00,0:00:00.01", "reordered text")] // reordered formats
- [InlineData(InvariantDialoguePrefix + "Cased TEXT", "Cased TEXT")] // preserve text casing
- [InlineData(InvariantDialoguePrefix + " text ", " text ")] // do not trim text
- [InlineData(InvariantDialoguePrefix + "text, more text", "text, more text")] // append excess dialogue values (> 10) to text
- [InlineData(InvariantDialoguePrefix + "start {\\fnFont Name}text{\\fn} end", "start <font face=\"Font Name\">text</font> end")] // font name
- [InlineData(InvariantDialoguePrefix + "start {\\fs10}text{\\fs} end", "start <font size=\"10\">text</font> end")] // font size
- [InlineData(InvariantDialoguePrefix + "start {\\c&H112233}text{\\c} end", "start <font color=\"#332211\">text</font> end")] // color
- [InlineData(InvariantDialoguePrefix + "start {\\1c&H112233}text{\\1c} end", "start <font color=\"#332211\">text</font> end")] // primay color
- [InlineData(InvariantDialoguePrefix + "start {\\fnFont Name}text1 {\\fs10}text2{\\fs}{\\fn} {\\1c&H112233}text3{\\1c} end", "start <font face=\"Font Name\">text1 <font size=\"10\">text2</font></font> <font color=\"#332211\">text3</font> end")] // nested formatting
- public void Parse(string ssa, string expectedText)
- {
- using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
- {
- SubtitleTrackInfo subtitleTrackInfo = parser.Parse(stream, CancellationToken.None);
- SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[0];
- Assert.Equal(expectedText, actual.Text);
- }
- }
-
- [Theory]
- [MemberData(nameof(Parse_MultipleDialogues_TestData))]
- public void Parse_MultipleDialogues(string ssa, IReadOnlyList<SubtitleTrackEvent> expectedSubtitleTrackEvents)
- {
- using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
- {
- SubtitleTrackInfo subtitleTrackInfo = parser.Parse(stream, CancellationToken.None);
-
- Assert.Equal(expectedSubtitleTrackEvents.Count, subtitleTrackInfo.TrackEvents.Count);
-
- for (int i = 0; i < expectedSubtitleTrackEvents.Count; ++i)
- {
- SubtitleTrackEvent expected = expectedSubtitleTrackEvents[i];
- SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[i];
-
- Assert.Equal(expected.StartPositionTicks, actual.StartPositionTicks);
- Assert.Equal(expected.EndPositionTicks, actual.EndPositionTicks);
- Assert.Equal(expected.Text, actual.Text);
- }
- }
- }
-
- public static IEnumerable<object[]> Parse_MultipleDialogues_TestData()
- {
- yield return new object[]
- {
- @"[Events]
- Format: Layer, Start, End, Text
- Dialogue: ,0:00:01.18,0:00:01.85,dialogue1
- Dialogue: ,0:00:02.18,0:00:02.85,dialogue2
- Dialogue: ,0:00:03.18,0:00:03.85,dialogue3
- ",
- new List<SubtitleTrackEvent>
- {
- new SubtitleTrackEvent
- {
- StartPositionTicks = 11800000,
- EndPositionTicks = 18500000,
- Text = "dialogue1"
- },
- new SubtitleTrackEvent
- {
- StartPositionTicks = 21800000,
- EndPositionTicks = 28500000,
- Text = "dialogue2"
- },
- new SubtitleTrackEvent
- {
- StartPositionTicks = 31800000,
- EndPositionTicks = 38500000,
- Text = "dialogue3"
- }
- }
- };
- }
- }
-}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
index 14ad49839..3775555de 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/AssParserTests.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using System.IO;
using System.Threading;
using MediaBrowser.MediaEncoding.Subtitles;
+using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace Jellyfin.MediaEncoding.Subtitles.Tests
@@ -14,25 +15,15 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
{
using (var stream = File.OpenRead("Test Data/example.ass"))
{
- var parsed = new AssParser().Parse(stream, CancellationToken.None);
+ var parsed = new AssParser(new NullLogger<AssParser>()).Parse(stream, CancellationToken.None);
Assert.Single(parsed.TrackEvents);
var trackEvent = parsed.TrackEvents[0];
Assert.Equal("1", trackEvent.Id);
Assert.Equal(TimeSpan.Parse("00:00:01.18", CultureInfo.InvariantCulture).Ticks, trackEvent.StartPositionTicks);
Assert.Equal(TimeSpan.Parse("00:00:06.85", CultureInfo.InvariantCulture).Ticks, trackEvent.EndPositionTicks);
- Assert.Equal("Like an Angel with pity on nobody\r\nThe second line in subtitle", trackEvent.Text);
+ Assert.Equal("{\\pos(400,570)}Like an Angel with pity on nobody" + Environment.NewLine + "The second line in subtitle", trackEvent.Text);
}
}
-
- [Fact]
- public void ParseFieldHeaders_Valid_Success()
- {
- const string Line = "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text";
- var headers = AssParser.ParseFieldHeaders(Line);
- Assert.Equal(1, headers["Start"]);
- Assert.Equal(2, headers["End"]);
- Assert.Equal(9, headers["Text"]);
- }
}
}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
index 3e2d2de10..537a944b0 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SrtParserTests.cs
@@ -22,7 +22,7 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
Assert.Equal("1", trackEvent1.Id);
Assert.Equal(TimeSpan.Parse("00:02:17.440", CultureInfo.InvariantCulture).Ticks, trackEvent1.StartPositionTicks);
Assert.Equal(TimeSpan.Parse("00:02:20.375", CultureInfo.InvariantCulture).Ticks, trackEvent1.EndPositionTicks);
- Assert.Equal("Senator, we're making\r\nour final approach into Coruscant.", trackEvent1.Text);
+ Assert.Equal("Senator, we're making" + Environment.NewLine + "our final approach into Coruscant.", trackEvent1.Text);
var trackEvent2 = parsed.TrackEvents[1];
Assert.Equal("2", trackEvent2.Id);
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
new file mode 100644
index 000000000..5db80c300
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/SsaParserTests.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading;
+using MediaBrowser.MediaEncoding.Subtitles;
+using MediaBrowser.Model.MediaInfo;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Jellyfin.MediaEncoding.Subtitles.Tests
+{
+ public class SsaParserTests
+ {
+ private readonly SsaParser _parser = new SsaParser(new NullLogger<AssParser>());
+
+ [Theory]
+ [MemberData(nameof(Parse_MultipleDialogues_TestData))]
+ public void Parse_MultipleDialogues_Success(string ssa, IReadOnlyList<SubtitleTrackEvent> expectedSubtitleTrackEvents)
+ {
+ using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
+ {
+ SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, CancellationToken.None);
+
+ Assert.Equal(expectedSubtitleTrackEvents.Count, subtitleTrackInfo.TrackEvents.Count);
+
+ for (int i = 0; i < expectedSubtitleTrackEvents.Count; ++i)
+ {
+ SubtitleTrackEvent expected = expectedSubtitleTrackEvents[i];
+ SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[i];
+
+ Assert.Equal(expected.Id, actual.Id);
+ Assert.Equal(expected.Text, actual.Text);
+ Assert.Equal(expected.StartPositionTicks, actual.StartPositionTicks);
+ Assert.Equal(expected.EndPositionTicks, actual.EndPositionTicks);
+ }
+ }
+ }
+
+ public static IEnumerable<object[]> Parse_MultipleDialogues_TestData()
+ {
+ yield return new object[]
+ {
+ @"[Events]
+ Format: Layer, Start, End, Text
+ Dialogue: ,0:00:01.18,0:00:01.85,dialogue1
+ Dialogue: ,0:00:02.18,0:00:02.85,dialogue2
+ Dialogue: ,0:00:03.18,0:00:03.85,dialogue3
+ ",
+ new List<SubtitleTrackEvent>
+ {
+ new SubtitleTrackEvent("1", "dialogue1")
+ {
+ StartPositionTicks = 11800000,
+ EndPositionTicks = 18500000
+ },
+ new SubtitleTrackEvent("2", "dialogue2")
+ {
+ StartPositionTicks = 21800000,
+ EndPositionTicks = 28500000
+ },
+ new SubtitleTrackEvent("3", "dialogue3")
+ {
+ StartPositionTicks = 31800000,
+ EndPositionTicks = 38500000
+ }
+ }
+ };
+ }
+
+ [Fact]
+ public void Parse_Valid_Success()
+ {
+ using (var stream = File.OpenRead("Test Data/example.ssa"))
+ {
+ var parsed = _parser.Parse(stream, CancellationToken.None);
+ Assert.Single(parsed.TrackEvents);
+ var trackEvent = parsed.TrackEvents[0];
+
+ Assert.Equal("1", trackEvent.Id);
+ Assert.Equal(TimeSpan.Parse("00:00:01.18", CultureInfo.InvariantCulture).Ticks, trackEvent.StartPositionTicks);
+ Assert.Equal(TimeSpan.Parse("00:00:06.85", CultureInfo.InvariantCulture).Ticks, trackEvent.EndPositionTicks);
+ Assert.Equal("{\\pos(400,570)}Like an angel with pity on nobody", trackEvent.Text);
+ }
+ }
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ssa b/tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ssa
new file mode 100644
index 000000000..dcbb972eb
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ssa
@@ -0,0 +1,20 @@
+[Script Info]
+; This is a Sub Station Alpha v4 script.
+; For Sub Station Alpha info and downloads,
+; go to http://www.eswat.demon.co.uk/
+Title: Neon Genesis Evangelion - Episode 26 (neutral Spanish)
+Original Script: RoRo
+Script Updated By: version 2.8.01
+ScriptType: v4.00
+Collisions: Normal
+PlayResY: 600
+PlayDepth: 0
+Timer: 100,0000
+
+[V4 Styles]
+Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
+Style: DefaultVCD, Arial,28,11861244,11861244,11861244,-2147483640,-1,0,1,1,2,2,30,30,30,0,0
+
+[Events]
+Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+Dialogue: Marked=0,0:00:01.18,0:00:06.85,DefaultVCD, NTP,0000,0000,0000,,{\pos(400,570)}Like an angel with pity on nobody
diff --git a/tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs b/tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs
new file mode 100644
index 000000000..955d296cc
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs
@@ -0,0 +1,70 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using MediaBrowser.Model.Entities;
+using Xunit;
+
+namespace Jellyfin.Model.Tests.Entities
+{
+ public class JsonLowerCaseConverterTests
+ {
+ private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions()
+ {
+ Converters =
+ {
+ new JsonStringEnumConverter()
+ }
+ };
+
+ [Theory]
+ [InlineData(null, "{\"CollectionType\":null}")]
+ [InlineData(CollectionTypeOptions.Movies, "{\"CollectionType\":\"movies\"}")]
+ [InlineData(CollectionTypeOptions.MusicVideos, "{\"CollectionType\":\"musicvideos\"}")]
+ public void Serialize_CollectionTypeOptions_Correct(CollectionTypeOptions? collectionType, string expected)
+ {
+ Assert.Equal(expected, JsonSerializer.Serialize(new TestContainer(collectionType), _jsonOptions));
+ }
+
+ [Theory]
+ [InlineData("{\"CollectionType\":null}", null)]
+ [InlineData("{\"CollectionType\":\"movies\"}", CollectionTypeOptions.Movies)]
+ [InlineData("{\"CollectionType\":\"musicvideos\"}", CollectionTypeOptions.MusicVideos)]
+ public void Deserialize_CollectionTypeOptions_Correct(string json, CollectionTypeOptions? result)
+ {
+ var res = JsonSerializer.Deserialize<TestContainer>(json, _jsonOptions);
+ Assert.NotNull(res);
+ Assert.Equal(result, res!.CollectionType);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData(CollectionTypeOptions.Movies)]
+ [InlineData(CollectionTypeOptions.MusicVideos)]
+ public void RoundTrip_CollectionTypeOptions_Correct(CollectionTypeOptions? value)
+ {
+ var res = JsonSerializer.Deserialize<TestContainer>(JsonSerializer.Serialize(new TestContainer(value), _jsonOptions), _jsonOptions);
+ Assert.NotNull(res);
+ Assert.Equal(value, res!.CollectionType);
+ }
+
+ [Theory]
+ [InlineData("{\"CollectionType\":null}")]
+ [InlineData("{\"CollectionType\":\"movies\"}")]
+ [InlineData("{\"CollectionType\":\"musicvideos\"}")]
+ public void RoundTrip_String_Correct(string json)
+ {
+ var res = JsonSerializer.Serialize(JsonSerializer.Deserialize<TestContainer>(json, _jsonOptions), _jsonOptions);
+ Assert.Equal(json, res);
+ }
+
+ private class TestContainer
+ {
+ public TestContainer(CollectionTypeOptions? collectionType)
+ {
+ CollectionType = collectionType;
+ }
+
+ [JsonConverter(typeof(JsonLowerCaseConverter<CollectionTypeOptions?>))]
+ public CollectionTypeOptions? CollectionType { get; set; }
+ }
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs b/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs
new file mode 100644
index 000000000..a1ace8476
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Entities/ProviderIdsExtensionsTests.cs
@@ -0,0 +1,196 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+using Xunit;
+
+namespace Jellyfin.Model.Tests.Entities
+{
+ public class ProviderIdsExtensionsTests
+ {
+ private const string ExampleImdbId = "tt0113375";
+
+ [Fact]
+ public void HasProviderId_NullInstance_ThrowsArgumentNullException()
+ {
+ Assert.Throws<ArgumentNullException>(() => ProviderIdsExtensions.HasProviderId(null!, MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void HasProviderId_NullProvider_False()
+ {
+ var nullProvider = new ProviderIdsExtensionsTestsObject
+ {
+ ProviderIds = null!
+ };
+
+ Assert.False(nullProvider.HasProviderId(MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void HasProviderId_NullName_ThrowsArgumentNullException()
+ {
+ Assert.Throws<ArgumentNullException>(() => ProviderIdsExtensionsTestsObject.Empty.HasProviderId(null!));
+ }
+
+ [Fact]
+ public void HasProviderId_NotFoundName_False()
+ {
+ Assert.False(ProviderIdsExtensionsTestsObject.Empty.HasProviderId(MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void HasProviderId_FoundName_True()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.ProviderIds[MetadataProvider.Imdb.ToString()] = ExampleImdbId;
+
+ Assert.True(provider.HasProviderId(MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void HasProviderId_FoundNameEmptyValue_False()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.ProviderIds[MetadataProvider.Imdb.ToString()] = string.Empty;
+
+ Assert.False(provider.HasProviderId(MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void GetProviderId_NullInstance_ThrowsArgumentNullException()
+ {
+ Assert.Throws<ArgumentNullException>(() => ProviderIdsExtensions.GetProviderId(null!, MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void GetProviderId_NullName_ThrowsArgumentNullException()
+ {
+ Assert.Throws<ArgumentNullException>(() => ProviderIdsExtensionsTestsObject.Empty.GetProviderId(null!));
+ }
+
+ [Fact]
+ public void GetProviderId_NotFoundName_Null()
+ {
+ Assert.Null(ProviderIdsExtensionsTestsObject.Empty.GetProviderId(MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void GetProviderId_NullProvider_Null()
+ {
+ var nullProvider = new ProviderIdsExtensionsTestsObject
+ {
+ ProviderIds = null!
+ };
+
+ Assert.Null(nullProvider.GetProviderId(MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void TryGetProviderId_NotFoundName_False()
+ {
+ Assert.False(ProviderIdsExtensionsTestsObject.Empty.TryGetProviderId(MetadataProvider.Imdb, out _));
+ }
+
+ [Fact]
+ public void TryGetProviderId_NullProvider_False()
+ {
+ var nullProvider = new ProviderIdsExtensionsTestsObject
+ {
+ ProviderIds = null!
+ };
+
+ Assert.False(nullProvider.TryGetProviderId(MetadataProvider.Imdb, out _));
+ }
+
+ [Fact]
+ public void GetProviderId_FoundName_Id()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.ProviderIds[MetadataProvider.Imdb.ToString()] = ExampleImdbId;
+
+ Assert.Equal(ExampleImdbId, provider.GetProviderId(MetadataProvider.Imdb));
+ }
+
+ [Fact]
+ public void TryGetProviderId_FoundName_True()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.ProviderIds[MetadataProvider.Imdb.ToString()] = ExampleImdbId;
+
+ Assert.True(provider.TryGetProviderId(MetadataProvider.Imdb, out var id));
+ Assert.Equal(ExampleImdbId, id);
+ }
+
+ [Fact]
+ public void TryGetProviderId_FoundNameEmptyValue_False()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.ProviderIds[MetadataProvider.Imdb.ToString()] = string.Empty;
+
+ Assert.False(provider.TryGetProviderId(MetadataProvider.Imdb, out var id));
+ Assert.Null(id);
+ }
+
+ [Fact]
+ public void SetProviderId_NullInstance_ThrowsArgumentNullException()
+ {
+ Assert.Throws<ArgumentNullException>(() => ProviderIdsExtensions.SetProviderId(null!, MetadataProvider.Imdb, ExampleImdbId));
+ }
+
+ [Fact]
+ public void SetProviderId_Null_Remove()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.SetProviderId(MetadataProvider.Imdb, null!);
+ Assert.Empty(provider.ProviderIds);
+ }
+
+ [Fact]
+ public void SetProviderId_EmptyName_Remove()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.ProviderIds[MetadataProvider.Imdb.ToString()] = ExampleImdbId;
+ provider.SetProviderId(MetadataProvider.Imdb, string.Empty);
+ Assert.Empty(provider.ProviderIds);
+ }
+
+ [Fact]
+ public void SetProviderId_NonEmptyId_Success()
+ {
+ var provider = new ProviderIdsExtensionsTestsObject();
+ provider.SetProviderId(MetadataProvider.Imdb, ExampleImdbId);
+ Assert.Single(provider.ProviderIds);
+ }
+
+ [Fact]
+ public void SetProviderId_NullProvider_Success()
+ {
+ var nullProvider = new ProviderIdsExtensionsTestsObject
+ {
+ ProviderIds = null!
+ };
+
+ nullProvider.SetProviderId(MetadataProvider.Imdb, ExampleImdbId);
+ Assert.Single(nullProvider.ProviderIds);
+ }
+
+ [Fact]
+ public void SetProviderId_NullProviderAndEmptyName_Success()
+ {
+ var nullProvider = new ProviderIdsExtensionsTestsObject
+ {
+ ProviderIds = null!
+ };
+
+ nullProvider.SetProviderId(MetadataProvider.Imdb, string.Empty);
+ Assert.Null(nullProvider.ProviderIds);
+ }
+
+ private class ProviderIdsExtensionsTestsObject : IHasProviderIds
+ {
+ public static readonly ProviderIdsExtensionsTestsObject Empty = new ProviderIdsExtensionsTestsObject();
+
+ public Dictionary<string, string> ProviderIds { get; set; } = new Dictionary<string, string>();
+ }
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index 64d51e063..0c7e262f5 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -5,18 +5,19 @@
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.1" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
</ItemGroup>
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
@@ -26,8 +27,4 @@
<ProjectReference Include="../../MediaBrowser.Model/MediaBrowser.Model.csproj" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
index b3257ace3..ad63adadc 100644
--- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
@@ -10,7 +10,7 @@ namespace Jellyfin.Naming.Tests.AudioBook
{
private readonly NamingOptions _namingOptions = new NamingOptions();
- public static IEnumerable<object[]> GetResolveFileTestData()
+ public static IEnumerable<object[]> Resolve_ValidFileNameTestData()
{
yield return new object[]
{
@@ -36,7 +36,7 @@ namespace Jellyfin.Naming.Tests.AudioBook
}
[Theory]
- [MemberData(nameof(GetResolveFileTestData))]
+ [MemberData(nameof(Resolve_ValidFileNameTestData))]
public void Resolve_ValidFileName_Success(AudioBookFileInfo expectedResult)
{
var result = new AudioBookResolver(_namingOptions).Resolve(expectedResult.Path);
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index a4d5c0d6f..cc12a99a6 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -8,15 +8,17 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
- <Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
- <PackageReference Include="coverlet.collector" Version="3.0.2" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
</ItemGroup>
<ItemGroup>
@@ -25,14 +27,9 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
index fde06c5a1..a720bdade 100644
--- a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
@@ -7,18 +7,13 @@ namespace Jellyfin.Naming.Tests.Video
{
public sealed class CleanStringTests
{
- private readonly NamingOptions _namingOptions = new NamingOptions();
+ private readonly VideoResolver _videoResolver = new VideoResolver(new NamingOptions());
[Theory]
[InlineData("Super movie 480p.mp4", "Super movie")]
[InlineData("Super movie 480p 2001.mp4", "Super movie")]
[InlineData("Super movie [480p].mp4", "Super movie")]
[InlineData("480 Super movie [tmdbid=12345].mp4", "480 Super movie")]
- [InlineData("Super movie(2009).mp4", "Super movie(2009).mp4")]
- [InlineData("Run lola run (lola rennt) (2009).mp4", "Run lola run (lola rennt) (2009).mp4")]
- [InlineData(@"American.Psycho.mkv", "American.Psycho.mkv")]
- [InlineData(@"American Psycho.mkv", "American Psycho.mkv")]
- [InlineData(@"[rec].mkv", "[rec].mkv")]
[InlineData("Crouching.Tiger.Hidden.Dragon.4k.mkv", "Crouching.Tiger.Hidden.Dragon")]
[InlineData("Crouching.Tiger.Hidden.Dragon.UltraHD.mkv", "Crouching.Tiger.Hidden.Dragon")]
[InlineData("Crouching.Tiger.Hidden.Dragon.UHD.mkv", "Crouching.Tiger.Hidden.Dragon")]
@@ -29,17 +24,25 @@ namespace Jellyfin.Naming.Tests.Video
[InlineData("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
[InlineData("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
// FIXME: [InlineData("After The Sunset - [0004].mkv", "After The Sunset")]
- public void CleanStringTest(string input, string expectedName)
+ public void CleanStringTest_NeedsCleaning_Success(string input, string expectedName)
{
- if (new VideoResolver(_namingOptions).TryCleanString(input, out ReadOnlySpan<char> newName))
- {
- // TODO: compare spans when XUnit supports it
- Assert.Equal(expectedName, newName.ToString());
- }
- else
- {
- Assert.Equal(expectedName, input);
- }
+ Assert.True(_videoResolver.TryCleanString(input, out ReadOnlySpan<char> newName));
+ // TODO: compare spans when XUnit supports it
+ Assert.Equal(expectedName, newName.ToString());
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("Super movie(2009).mp4")]
+ [InlineData("[rec].mkv")]
+ [InlineData("American.Psycho.mkv")]
+ [InlineData("American Psycho.mkv")]
+ [InlineData("Run lola run (lola rennt) (2009).mp4")]
+ public void CleanStringTest_DoesntNeedCleaning_False(string? input)
+ {
+ Assert.False(_videoResolver.TryCleanString(input, out ReadOnlySpan<char> newName));
+ Assert.True(newName.IsEmpty);
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
index bc5e6fa63..6e803593e 100644
--- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
@@ -295,12 +295,9 @@ namespace Jellyfin.Naming.Tests.Video
FullName = i
}).ToList()).ToList();
- Assert.Single(result);
+ Assert.Equal(7, result.Count);
Assert.Empty(result[0].Extras);
- Assert.Equal(6, result[0].AlternateVersions.Count);
- Assert.False(result[0].AlternateVersions[2].Is3D);
- Assert.True(result[0].AlternateVersions[3].Is3D);
- Assert.True(result[0].AlternateVersions[4].Is3D);
+ Assert.Empty(result[0].AlternateVersions);
}
[Fact]
@@ -369,6 +366,44 @@ namespace Jellyfin.Naming.Tests.Video
}
[Fact]
+ public void Resolve_GivenFolderNameWithBracketsAndHyphens_GroupsBasedOnFolderName()
+ {
+ var files = new[]
+ {
+ @"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 1.mkv",
+ @"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 2.mkv"
+ };
+
+ var result = _videoListResolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ Assert.Empty(result[0].Extras);
+ Assert.Single(result[0].AlternateVersions);
+ }
+
+ [Fact]
+ public void Resolve_GivenUnclosedBrackets_DoesNotGroup()
+ {
+ var files = new[]
+ {
+ @"/movies/John Wick - Chapter 3 (2019)/John Wick - Chapter 3 (2019) [Version 1].mkv",
+ @"/movies/John Wick - Chapter 3 (2019)/John Wick - Chapter 3 (2019) [Version 2.mkv"
+ };
+
+ var result = _videoListResolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+ }).ToList()).ToList();
+
+ Assert.Equal(2, result.Count);
+ }
+
+ [Fact]
public void TestEmptyList()
{
var result = _videoListResolver.Resolve(new List<FileSystemMetadata>()).ToList();
diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs
index ba5eaf1af..9bbbe2970 100644
--- a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs
@@ -11,7 +11,7 @@ namespace Jellyfin.Naming.Tests.Video
{
private readonly VideoResolver _videoResolver = new VideoResolver(new NamingOptions());
- public static IEnumerable<object[]> GetResolveFileTestData()
+ public static IEnumerable<object[]> ResolveFile_ValidFileNameTestData()
{
yield return new object[]
{
@@ -156,7 +156,7 @@ namespace Jellyfin.Naming.Tests.Video
}
[Theory]
- [MemberData(nameof(GetResolveFileTestData))]
+ [MemberData(nameof(ResolveFile_ValidFileNameTestData))]
public void ResolveFile_ValidFileName_Success(VideoFileInfo expectedResult)
{
var result = _videoResolver.ResolveFile(expectedResult.Path);
diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
index d77645cd9..a76c0e9a0 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
@@ -8,32 +8,34 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
- <Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="3.0.2" />
- <PackageReference Include="Moq" Version="4.16.0" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
+ <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
+
<ItemGroup>
- <ProjectReference Include="..\..\..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" />
- <ProjectReference Include="..\..\..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
+ <ProjectReference Include="../../Emby.Server.Implementations/Emby.Server.Implementations.csproj" />
+ <ProjectReference Include="../../MediaBrowser.Common/MediaBrowser.Common.csproj" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG</DefineConstants>
</PropertyGroup>
+
</Project>
diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
index b7c1510d2..c3469035e 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
@@ -1,46 +1,18 @@
using System;
+using System.Collections.ObjectModel;
using System.Net;
using Jellyfin.Networking.Configuration;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
-using Moq;
using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
using Xunit;
-using System.Collections.ObjectModel;
namespace Jellyfin.Networking.Tests
{
public class NetworkParseTests
{
- /// <summary>
- /// Tries to identify the string and return an object of that class.
- /// </summary>
- /// <param name="addr">String to parse.</param>
- /// <param name="result">IPObject to return.</param>
- /// <returns>True if the value parsed successfully.</returns>
- private static bool TryParse(string addr, out IPObject result)
- {
- if (!string.IsNullOrEmpty(addr))
- {
- // Is it an IP address
- if (IPNetAddress.TryParse(addr, out IPNetAddress nw))
- {
- result = nw;
- return true;
- }
-
- if (IPHost.TryParse(addr, out IPHost h))
- {
- result = h;
- return true;
- }
- }
-
- result = IPNetAddress.None;
- return false;
- }
-
private static IConfigurationManager GetMockConfig(NetworkConfiguration conf)
{
var configManager = new Mock<IConfigurationManager>
@@ -52,15 +24,20 @@ namespace Jellyfin.Networking.Tests
}
/// <summary>
- /// Checks the ability to ignore interfaces
+ /// Checks the ability to ignore virtual interfaces.
/// </summary>
/// <param name="interfaces">Mock network setup, in the format (IP address, interface index, interface name) | .... </param>
/// <param name="lan">LAN addresses.</param>
/// <param name="value">Bind addresses that are excluded.</param>
[Theory]
+ // All valid
[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]")]
- [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.1.208/24,-16,vEthernet212|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", "[]")]
+ // 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]")]
public void IgnoreVirtualInterfaces(string interfaces, string lan, string value)
{
var conf = new NetworkConfiguration()
@@ -118,13 +95,34 @@ namespace Jellyfin.Networking.Tests
[InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]:124")]
[InlineData("fe80::7add:12ff:febb:c67b%16")]
[InlineData("[fe80::7add:12ff:febb:c67b%16]:123")]
+ [InlineData("fe80::7add:12ff:febb:c67b%16:123")]
+ [InlineData("[fe80::7add:12ff:febb:c67b%16]")]
[InlineData("192.168.1.2/255.255.255.0")]
[InlineData("192.168.1.2/24")]
- public void ValidIPStrings(string address)
+ public void ValidHostStrings(string address)
{
- Assert.True(TryParse(address, out _));
+ Assert.True(IPHost.TryParse(address, out _));
}
+ /// <summary>
+ /// Checks IP address formats.
+ /// </summary>
+ /// <param name="address"></param>
+ [Theory]
+ [InlineData("127.0.0.1")]
+ [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")]
+ [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")]
+ [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]")]
+ [InlineData("fe80::7add:12ff:febb:c67b%16")]
+ [InlineData("[fe80::7add:12ff:febb:c67b%16]:123")]
+ [InlineData("fe80::7add:12ff:febb:c67b%16:123")]
+ [InlineData("[fe80::7add:12ff:febb:c67b%16]")]
+ [InlineData("192.168.1.2/255.255.255.0")]
+ [InlineData("192.168.1.2/24")]
+ public void ValidIPStrings(string address)
+ {
+ Assert.True(IPNetAddress.TryParse(address, out _));
+ }
/// <summary>
/// All should be invalid address strings.
@@ -138,10 +136,10 @@ namespace Jellyfin.Networking.Tests
[InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517:1231]")]
public void InvalidAddressString(string address)
{
- Assert.False(TryParse(address, out _));
+ Assert.False(IPNetAddress.TryParse(address, out _));
+ Assert.False(IPHost.TryParse(address, out _));
}
-
/// <summary>
/// Test collection parsing.
/// </summary>
@@ -152,19 +150,22 @@ namespace Jellyfin.Networking.Tests
/// <param name="result4">Excluded IP4 addresses from the collection.</param>
/// <param name="result5">Network addresses of the collection.</param>
[Theory]
- [InlineData("127.0.0.1#",
+ [InlineData(
+ "127.0.0.1#",
"[]",
"[]",
"[]",
"[]",
"[]")]
- [InlineData("!127.0.0.1",
+ [InlineData(
+ "!127.0.0.1",
"[]",
"[]",
"[127.0.0.1/32]",
"[127.0.0.1/32]",
"[]")]
- [InlineData("",
+ [InlineData(
+ "",
"[]",
"[]",
"[]",
@@ -172,12 +173,13 @@ namespace Jellyfin.Networking.Tests
"[]")]
[InlineData(
"192.158.1.2/16, localhost, fd23:184f:2029:0:3139:7386:67d7:d517, !10.10.10.10",
- "[192.158.1.2/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]",
+ "[192.158.1.2/16,[127.0.0.1/32,::1/128],fd23:184f:2029:0:3139:7386:67d7:d517/128]",
"[192.158.1.2/16,127.0.0.1/32]",
"[10.10.10.10/32]",
"[10.10.10.10/32]",
- "[192.158.0.0/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]")]
- [InlineData("192.158.1.2/255.255.0.0,192.169.1.2/8",
+ "[192.158.0.0/16,127.0.0.1/32,::1/128,fd23:184f:2029:0:3139:7386:67d7:d517/128]")]
+ [InlineData(
+ "192.158.1.2/255.255.0.0,192.169.1.2/8",
"[192.158.1.2/16,192.169.1.2/8]",
"[192.158.1.2/16,192.169.1.2/8]",
"[]",
@@ -194,12 +196,12 @@ namespace Jellyfin.Networking.Tests
{
EnableIPV6 = true,
EnableIPV4 = true,
- };
+ };
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.
@@ -208,7 +210,7 @@ namespace Jellyfin.Networking.Tests
conf.EnableIPV6 = false;
nm.UpdateSettings(conf);
-
+
// Test IP4 included.
nc = nm.CreateIPCollection(settings.Split(","), false);
Assert.Equal(nc.AsString(), result2);
@@ -252,7 +254,6 @@ namespace Jellyfin.Networking.Tests
throw new ArgumentNullException(nameof(result));
}
-
var conf = new NetworkConfiguration()
{
EnableIPV6 = true,
@@ -333,8 +334,8 @@ namespace Jellyfin.Networking.Tests
public void TestSubnetContains(string network, string ip)
{
- Assert.True(TryParse(network, out IPObject? networkObj));
- Assert.True(TryParse(ip, out IPObject? ipObj));
+ Assert.True(IPNetAddress.TryParse(network, out var networkObj));
+ Assert.True(IPNetAddress.TryParse(ip, out var ipObj));
Assert.True(networkObj.Contains(ipObj));
}
@@ -378,7 +379,6 @@ namespace Jellyfin.Networking.Tests
Assert.True(ncResult.Compare(resultCollection));
}
-
[Theory]
[InlineData("10.1.1.1/32", "10.1.1.1")]
[InlineData("192.168.1.254/32", "192.168.1.254/255.255.255.255")]
@@ -455,7 +455,7 @@ namespace Jellyfin.Networking.Tests
// On my system eth16 is internal, eth11 external (Windows defines the indexes).
//
// This test is to replicate how subnet bound ServerPublisherUri work throughout the system.
-
+
// User on internal network, we're bound internal and external - so result is internal override.
[InlineData("192.168.1.1", "192.168.1.0/24", "eth16,eth11", false, "192.168.1.0/24=internal.jellyfin", "internal.jellyfin")]
@@ -468,7 +468,7 @@ namespace Jellyfin.Networking.Tests
// User on internal network, no binding specified - so result is the 1st internal.
[InlineData("192.168.1.1", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")]
- // User on external network, internal binding only - so asumption is a proxy forward, return external override.
+ // User on external network, internal binding only - so assumption is a proxy forward, return external override.
[InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
// User on external network, no binding - so result is the 1st external which is overriden.
@@ -479,7 +479,6 @@ namespace Jellyfin.Networking.Tests
// User is internal, no binding - so result is the 1st internal, which is then overridden.
[InlineData("192.168.1.1", "192.168.1.0/24", "", false, "eth16=http://helloworld.com", "http://helloworld.com")]
-
public void TestBindInterfaceOverrides(string source, string lan, string bindAddresses, bool ipv6enabled, string publishedServers, string result)
{
if (lan == null)
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 c3b3155fe..c3c258b68 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -10,6 +10,8 @@
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
<RootNamespace>Jellyfin.Server.Implementations.Tests</RootNamespace>
</PropertyGroup>
@@ -22,16 +24,15 @@
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.15.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
- <PackageReference Include="Moq" Version="4.16.0" />
+ <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.2" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
</ItemGroup>
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
@@ -42,8 +43,4 @@
<ProjectReference Include="..\..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
new file mode 100644
index 000000000..876519215
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
@@ -0,0 +1,65 @@
+using System;
+using Emby.Server.Implementations.Library.Resolvers.TV;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Library
+{
+ public class EpisodeResolverTest
+ {
+ [Fact]
+ public void Resolve_GivenVideoInExtrasFolder_DoesNotResolveToEpisode()
+ {
+ var season = new Season { Name = "Season 1" };
+ var parent = new Folder { Name = "extras" };
+ var libraryManagerMock = new Mock<ILibraryManager>();
+ libraryManagerMock.Setup(x => x.GetItemById(It.IsAny<Guid>())).Returns(season);
+
+ var episodeResolver = new EpisodeResolver(libraryManagerMock.Object);
+ var itemResolveArgs = new ItemResolveArgs(
+ Mock.Of<IServerApplicationPaths>(),
+ Mock.Of<IDirectoryService>())
+ {
+ Parent = parent,
+ CollectionType = CollectionType.TvShows,
+ Path = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv"
+ };
+
+ Assert.Null(episodeResolver.Resolve(itemResolveArgs));
+ }
+
+ [Fact]
+ public void Resolve_GivenVideoInExtrasSeriesFolder_ResolvesToEpisode()
+ {
+ var series = new Series { Name = "Extras" };
+
+ // Have to create a mock because of moq proxies not being castable to a concrete implementation
+ // https://github.com/jellyfin/jellyfin/blob/ab0cff8556403e123642dc9717ba778329554634/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs#L48
+ var episodeResolver = new EpisodeResolverMock(Mock.Of<ILibraryManager>());
+ var itemResolveArgs = new ItemResolveArgs(
+ Mock.Of<IServerApplicationPaths>(),
+ Mock.Of<IDirectoryService>())
+ {
+ Parent = series,
+ CollectionType = CollectionType.TvShows,
+ Path = "Extras/Extras S01E01.mkv"
+ };
+ Assert.NotNull(episodeResolver.Resolve(itemResolveArgs));
+ }
+
+ private class EpisodeResolverMock : EpisodeResolver
+ {
+ public EpisodeResolverMock(ILibraryManager libraryManager) : base(libraryManager)
+ {
+ }
+
+ protected override TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName) => new ();
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
index 6d768af89..e5508243f 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
@@ -24,5 +24,35 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
Assert.Throws<ArgumentException>(() => PathExtensions.GetAttributeValue(input, attribute));
}
+
+ [Theory]
+ [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("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/jeff's band", "/home/not jeff", "/home/not jeff/consistently inconsistent.mp3")]
+ [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/", "/home/jeff/", "/home/jeff/myfile.mkv")]
+ [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff/", "/", "/myfile.mkv")]
+ public void TryReplaceSubPath_ValidArgs_Correct(string path, string subPath, string newSubPath, string? expectedResult)
+ {
+ Assert.True(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result));
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Theory]
+ [InlineData(null, null, null)]
+ [InlineData(null, "/my/path", "/another/path")]
+ [InlineData("/my/path", null, "/another/path")]
+ [InlineData("/my/path", "/another/path", null)]
+ [InlineData("", "", "")]
+ [InlineData("/my/path", "", "")]
+ [InlineData("", "/another/path", "")]
+ [InlineData("", "", "/new/subpath")]
+ [InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/not jeff's band", "/home/not jeff")]
+ public void TryReplaceSubPath_InvalidInput_ReturnsFalseAndNull(string? path, string? subPath, string? newSubPath)
+ {
+ Assert.False(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result));
+ Assert.Null(result);
+ }
}
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
new file mode 100644
index 000000000..7e04a1ec1
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
@@ -0,0 +1,58 @@
+using System;
+using Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.LiveTv
+{
+ public class HdHomerunManagerTests
+ {
+ [Fact]
+ public void WriteNullTerminatedString_Empty_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 1, 0
+ };
+
+ 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)));
+ }
+
+ [Fact]
+ public void WriteNullTerminatedString_Valid_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 10, (byte)'T', (byte)'h', (byte)'e', (byte)' ', (byte)'q', (byte)'u', (byte)'i', (byte)'c', (byte)'k', 0
+ };
+
+ 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)));
+ }
+
+ [Fact]
+ public void WriteGetMessage_Valid_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 0, 4,
+ 0, 12,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 0xc0, 0xc9, 0x87, 0x33
+ };
+
+ 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)));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json
new file mode 100644
index 000000000..b766e668e
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/Updates/manifest-stable.json
@@ -0,0 +1,684 @@
+[
+ {
+ "guid": "a4df60c5-6ab4-412a-8f79-2cab93fb2bc5",
+ "name": "Anime",
+ "description": "Manage your anime in Jellyfin. This plugin supports several different metadata providers and options for organizing your collection.\n",
+ "overview": "Manage your anime from Jellyfin",
+ "owner": "jellyfin",
+ "category": "Metadata",
+ "versions": [
+ {
+ "version": "10.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/anime/anime_10.0.0.0.zip",
+ "checksum": "93e969adeba1050423fc8817ed3c36f8",
+ "timestamp": "2020-08-17T01:41:13Z"
+ },
+ {
+ "version": "9.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/anime/anime_9.0.0.0.zip",
+ "checksum": "9b1cebff835813e15f414f44b40c41c8",
+ "timestamp": "2020-07-20T01:30:16Z"
+ }
+ ]
+ },
+ {
+ "guid": "70b7b43b-471b-4159-b4be-56750c795499",
+ "name": "Auto Organize",
+ "description": "Automatically organize your media",
+ "overview": "Automatically organize your media",
+ "owner": "jellyfin",
+ "category": "General",
+ "versions": [
+ {
+ "version": "9.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/auto-organize/auto-organize_9.0.0.0.zip",
+ "checksum": "ff29ac3cbe05d208b6af94cd6d9dea39",
+ "timestamp": "2020-12-05T22:31:12Z"
+ },
+ {
+ "version": "8.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/auto-organize/auto-organize_8.0.0.0.zip",
+ "checksum": "460bbb45e556464a8476b18e41c097f5",
+ "timestamp": "2020-07-20T01:30:25Z"
+ }
+ ]
+ },
+ {
+ "guid": "9c4e63f1-031b-4f25-988b-4f7d78a8b53e",
+ "name": "Bookshelf",
+ "description": "Supports several different metadata providers and options for organizing your collection.\n",
+ "overview": "Manage your books",
+ "owner": "jellyfin",
+ "category": "Metadata",
+ "versions": [
+ {
+ "version": "5.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/bookshelf/bookshelf_5.0.0.0.zip",
+ "checksum": "2063fb8ab317b8d77b200fde41eb5e1e",
+ "timestamp": "2020-12-05T22:03:13Z"
+ },
+ {
+ "version": "4.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/bookshelf/bookshelf_4.0.0.0.zip",
+ "checksum": "fc9f76c0815d766491e5b0f30ede55ed",
+ "timestamp": "2020-07-20T01:30:33Z"
+ }
+ ]
+ },
+ {
+ "guid": "cfa0f7f4-4155-4d71-849b-d6598dc4c5bb",
+ "name": "Email",
+ "description": "Send SMTP email notifications",
+ "overview": "Send SMTP email notifications",
+ "owner": "jellyfin",
+ "category": "Notifications",
+ "versions": [
+ {
+ "version": "9.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/email/email_9.0.0.0.zip",
+ "checksum": "cfe7afc00f3fbd6d6ab8244d7ff968ce",
+ "timestamp": "2020-12-05T22:20:32Z"
+ },
+ {
+ "version": "7.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/email/email_7.0.0.0.zip",
+ "checksum": "680ca511d8ad84923cb04f024fd8eb19",
+ "timestamp": "2020-07-20T01:30:40Z"
+ }
+ ]
+ },
+ {
+ "guid": "170a157f-ac6c-437a-abdd-ca9c25cebd39",
+ "name": "Fanart",
+ "description": "Scrape poster images for movies, shows, and artists in your library.",
+ "overview": "Scrape poster images from Fanart",
+ "owner": "jellyfin",
+ "category": "Metadata",
+ "versions": [
+ {
+ "version": "6.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/fanart/fanart_6.0.0.0.zip",
+ "checksum": "ee4360bfcc8722d5a3a54cfe7eef640f",
+ "timestamp": "2020-12-05T22:25:43Z"
+ },
+ {
+ "version": "5.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/fanart/fanart_5.0.0.0.zip",
+ "checksum": "f842f7d65d23f377761c907d40b89647",
+ "timestamp": "2020-07-20T01:30:48Z"
+ }
+ ]
+ },
+ {
+ "guid": "e29621a5-fa9e-4330-982e-ef6e54c0cad2",
+ "name": "Gotify Notification",
+ "description": "You must have a Gotify server to use this plugin!\n",
+ "overview": "Sends notifications to your Gotify server",
+ "owner": "crobibero",
+ "category": "Notifications",
+ "versions": [
+ {
+ "version": "7.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/gotify-notification/gotify-notification_7.0.0.0.zip",
+ "checksum": "7c5ff9e8792c8cdee7e8a2aaeb6cc093",
+ "timestamp": "2020-07-20T01:30:56Z"
+ }
+ ]
+ },
+ {
+ "guid": "a59b5c4b-05a8-488f-bfa8-7a63fffc7639",
+ "name": "IPTV",
+ "description": "Enable IPTV support in Jellyfin",
+ "overview": "Enable IPTV support in Jellyfin",
+ "owner": "jellyfin",
+ "category": "Channel",
+ "versions": [
+ {
+ "version": "6.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/iptv/iptv_6.0.0.0.zip",
+ "checksum": "9cf103bf67a4eda7c3a42d9b235f6447",
+ "timestamp": "2020-07-20T01:31:05Z"
+ }
+ ]
+ },
+ {
+ "guid": "4682DD4C-A675-4F1B-8E7C-79ADF137A8F8",
+ "name": "ISO Mounter",
+ "description": "Mount your ISO files for Jellyfin.\n",
+ "overview": "Mount your ISO files for Jellyfin",
+ "owner": "jellyfin",
+ "category": "Metadata",
+ "versions": [
+ {
+ "version": "1.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/iso-mounter/iso-mounter_1.0.0.0.zip",
+ "checksum": "847e5bc7ac34c1bf4dc5b28173170fae",
+ "timestamp": "2020-07-20T01:31:13Z"
+ }
+ ]
+ },
+ {
+ "guid": "771e19d6-5385-4caf-b35c-28a0e865cf63",
+ "name": "Kodi Sync Queue",
+ "description": "This plugin will track all media changes while Kodi clients are offline to decrease sync times.",
+ "overview": "Sync all media changes with Kodi clients",
+ "owner": "jellyfin",
+ "category": "General",
+ "versions": [
+ {
+ "version": "6.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/kodi-sync-queue/kodi-sync-queue_6.0.0.0.zip",
+ "checksum": "787c856c0d2ad2224cdd8b3094cf0329",
+ "timestamp": "2020-12-05T22:10:37Z"
+ },
+ {
+ "version": "5.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/kodi-sync-queue/kodi-sync-queue_5.0.0.0.zip",
+ "checksum": "08285397aecd93ea64a4f15d38b1bd7b",
+ "timestamp": "2020-07-20T01:31:22Z"
+ }
+ ]
+ },
+ {
+ "guid": "958aad66-3784-4d2a-b89a-a7b6fab6e25c",
+ "name": "LDAP Authentication",
+ "description": "Authenticate your Jellyfin users against an LDAP database, and optionally create users who do not yet exist automatically.\nAllows the administrator to customize most aspects of the LDAP authentication process, including customizable search attributes, username attribute, and a search filter for administrative users (set on user creation). The user, via the \"Manual Login\" process, can enter any valid attribute value, which will be mapped back to the specified username attribute automatically as well.\n",
+ "overview": "Authenticate users against an LDAP database",
+ "owner": "jellyfin",
+ "category": "Authentication",
+ "versions": [
+ {
+ "version": "10.0.0.0",
+ "changelog": "Update for 10.7 support\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/ldap-authentication/ldap-authentication_10.0.0.0.zip",
+ "checksum": "62e7e1cd3ffae0944c14750a3c90df4f",
+ "timestamp": "2020-12-05T19:48:10Z"
+ },
+ {
+ "version": "9.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/ldap-authentication/ldap-authentication_9.0.0.0.zip",
+ "checksum": "7f2f83587a65a43ebf168e4058421463",
+ "timestamp": "2020-07-22T15:42:57Z"
+ },
+ {
+ "version": "8.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/ldap-authentication/ldap-authentication_8.0.0.0.zip",
+ "checksum": "8af8cee62717d63577f8b1e710839415",
+ "timestamp": "2020-07-20T01:31:30Z"
+ }
+ ]
+ },
+ {
+ "guid": "9574ac10-bf23-49bc-949f-924f23cfa48f",
+ "name": "NextPVR",
+ "description": "Provides access to live TV, program guide, and recordings from NextPVR.\n",
+ "overview": "Live TV plugin for NextPVR",
+ "owner": "jellyfin",
+ "category": "LiveTV",
+ "versions": [
+ {
+ "version": "5.0.0.0",
+ "changelog": "Updated to use NextPVR API v5, no longer compatable with API v4.\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/nextpvr/nextpvr_5.0.0.0.zip",
+ "checksum": "d70f694d14bf9462ba2b2ebe110068d3",
+ "timestamp": "2020-12-05T22:24:03Z"
+ },
+ {
+ "version": "4.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/nextpvr/nextpvr_4.0.0.0.zip",
+ "checksum": "b15949d895ac5a8c89496581db350478",
+ "timestamp": "2020-07-20T01:31:38Z"
+ }
+ ]
+ },
+ {
+ "guid": "4b9ed42f-5185-48b5-9803-6ff2989014c4",
+ "name": "Open Subtitles",
+ "description": "Download subtitles from the internet to use with your media files.",
+ "overview": "Download subtitles for your media",
+ "owner": "jellyfin",
+ "category": "Metadata",
+ "versions": [
+ {
+ "version": "10.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/open-subtitles/open-subtitles_10.0.0.0.zip",
+ "checksum": "ed99d03ec463bf15fca1256a113f57b4",
+ "timestamp": "2020-12-05T21:56:19Z"
+ },
+ {
+ "version": "9.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/open-subtitles/open-subtitles_9.0.0.0.zip",
+ "checksum": "16789b26497cea0509daf6b18c579340",
+ "timestamp": "2020-07-20T01:32:00Z"
+ }
+ ]
+ },
+ {
+ "guid": "5c534381-91a3-43cb-907a-35aa02eb9d2c",
+ "name": "Playback Reporting",
+ "description": "Collect and show user play statistics",
+ "overview": "Collect and show user play statistics",
+ "owner": "jellyfin",
+ "category": "General",
+ "versions": [
+ {
+ "version": "9.0.0.0",
+ "changelog": "Add authentication to plugin endpoints\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/playback-reporting/playback-reporting_9.0.0.0.zip",
+ "checksum": "ca323b3dcb2cb86cc2e72a7a0f1eee22",
+ "timestamp": "2020-12-05T22:15:48Z"
+ },
+ {
+ "version": "8.0.0.0",
+ "changelog": "Add authentication to plugin endpoints\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/playback-reporting/playback-reporting_8.0.0.0.zip",
+ "checksum": "58644c505586542ef0b8b65e2f704bd1",
+ "timestamp": "2020-11-18T03:01:51Z"
+ },
+ {
+ "version": "7.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/playback-reporting/playback-reporting_7.0.0.0.zip",
+ "checksum": "6a361ef33bca97f9155856d02ff47380",
+ "timestamp": "2020-07-20T01:32:09Z"
+ }
+ ]
+ },
+ {
+ "guid": "de228f12-e43e-4bd9-9fc0-2830819c3b92",
+ "name": "Pushbullet",
+ "description": "Get notifications via Pushbullet.\n",
+ "overview": "Pushbullet notification plugin",
+ "owner": "jellyfin",
+ "category": "Notifications",
+ "versions": [
+ {
+ "version": "6.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/pushbullet/pushbullet_6.0.0.0.zip",
+ "checksum": "248cf3d56644f1d909e75aaddbdfb3a6",
+ "timestamp": "2020-12-06T02:47:53Z"
+ },
+ {
+ "version": "5.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/pushbullet/pushbullet_5.0.0.0.zip",
+ "checksum": "dabbdd86328b2922a69dfa0c9e1c8343",
+ "timestamp": "2020-07-20T01:32:17Z"
+ }
+ ]
+ },
+ {
+ "guid": "F240D6BE-5743-441B-87F1-A70ECAC42642",
+ "name": "Pushover",
+ "description": "Send messages to a wide range of devices through Pushover.",
+ "overview": "Send notifications via Pushover",
+ "owner": "crobibero",
+ "category": "Notifications",
+ "versions": [
+ {
+ "version": "4.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/pushover/pushover_4.0.0.0.zip",
+ "checksum": "56a0da16c7e48cc184987737b7e155dd",
+ "timestamp": "2020-07-20T01:32:25Z"
+ }
+ ]
+ },
+ {
+ "guid": "d4312cd9-5c90-4f38-82e8-51da566790e8",
+ "name": "Reports",
+ "description": "Generate reports of your media library",
+ "overview": "Generate reports of your media library",
+ "owner": "jellyfin",
+ "category": "General",
+ "versions": [
+ {
+ "version": "11.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/reports/reports_11.0.0.0.zip",
+ "checksum": "d71bc6a4c008e58ee70ad44c83bfd310",
+ "timestamp": "2020-12-05T22:00:46Z"
+ },
+ {
+ "version": "10.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/reports/reports_10.0.0.0.zip",
+ "checksum": "3917e75839337475b42daf2ba0b5bd7b",
+ "timestamp": "2020-10-19T19:30:41Z"
+ },
+ {
+ "version": "9.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/reports/reports_9.0.0.0.zip",
+ "checksum": "5b5ad8d885616a21e8d1e8eecf5ea979",
+ "timestamp": "2020-10-16T23:52:37Z"
+ }
+ ]
+ },
+ {
+ "guid": "1fc322a1-af2e-49a5-b2eb-a89b4240f700",
+ "name": "ServerWMC",
+ "description": "Provides access to Live TV, Program Guide and Recordings from your Windows MediaCenter Server running ServerWMC. Requires ServerWMC to be installed and running on your Windows MediaCenter machine.\n",
+ "overview": "Jellyfin Live TV plugin for Windows MediaCenter with ServerWMC",
+ "owner": "jellyfin",
+ "category": "LiveTV",
+ "versions": [
+ {
+ "version": "6.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/serverwmc/serverwmc_6.0.0.0.zip",
+ "checksum": "3120af0cea2c1cb8b7cf578d9b4b862c",
+ "timestamp": "2020-12-05T22:28:15Z"
+ },
+ {
+ "version": "5.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/serverwmc/serverwmc_5.0.0.0.zip",
+ "checksum": "dc44b039aa1b66eaf40a44fbf02d37e2",
+ "timestamp": "2020-07-20T01:32:42Z"
+ }
+ ]
+ },
+ {
+ "guid": "94fb77c3-55ad-4c50-bf4e-4e5497467b79",
+ "name": "Slack Notifications",
+ "description": "Get notifications via Slack.\n",
+ "overview": "Get notifications via Slack",
+ "owner": "jellyfin",
+ "category": "Notifications",
+ "versions": [
+ {
+ "version": "7.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/slack-notifications/slack-notifications_7.0.0.0.zip",
+ "checksum": "1d5330a77ce7b2a9ac8e5d58088a012c",
+ "timestamp": "2020-12-05T22:40:02Z"
+ },
+ {
+ "version": "6.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/slack-notifications/slack-notifications_6.0.0.0.zip",
+ "checksum": "ede4cbe064542d1ecccc5823921bee4b",
+ "timestamp": "2020-07-20T01:32:50Z"
+ }
+ ]
+ },
+ {
+ "guid": "bc4aad2e-d3d0-4725-a5e2-fd07949e5b42",
+ "name": "TMDb Box Sets",
+ "description": "Automatically create movie box sets based on TMDb collections",
+ "overview": "Automatically create movie box sets based on TMDb collections",
+ "owner": "jellyfin",
+ "category": "Metadata",
+ "versions": [
+ {
+ "version": "7.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tmdb-box-sets/tmdb-box-sets_7.0.0.0.zip",
+ "checksum": "1551792e6af4d36f2cead01153c73cf0",
+ "timestamp": "2020-12-05T22:07:21Z"
+ },
+ {
+ "version": "6.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tmdb-box-sets/tmdb-box-sets_6.0.0.0.zip",
+ "checksum": "b92b68a922c5fcbb8f4d47b8601b01b6",
+ "timestamp": "2020-07-20T01:32:58Z"
+ }
+ ]
+ },
+ {
+ "guid": "4fe3201e-d6ae-4f2e-8917-e12bda571281",
+ "name": "Trakt",
+ "description": "Record your watched media with Trakt.\n",
+ "overview": "Record your watched media with Trakt",
+ "owner": "jellyfin",
+ "category": "General",
+ "versions": [
+ {
+ "version": "11.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/trakt/trakt_11.0.0.0.zip",
+ "checksum": "2257ccde1e39114644a27e0966a0bf2d",
+ "timestamp": "2020-12-05T19:56:12Z"
+ },
+ {
+ "version": "10.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/trakt/trakt_10.0.0.0.zip",
+ "checksum": "ab67e6b59ea2e7860a6a3ff7b8452759",
+ "timestamp": "2020-07-20T01:33:06Z"
+ }
+ ]
+ },
+ {
+ "guid": "3fd018e5-5e78-4e58-b280-a0c068febee0",
+ "name": "TVHeadend",
+ "description": "Manage TVHeadend from Jellyfin",
+ "overview": "Manage TVHeadend from Jellyfin",
+ "owner": "jellyfin",
+ "category": "LiveTV",
+ "versions": [
+ {
+ "version": "7.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tvheadend/tvheadend_7.0.0.0.zip",
+ "checksum": "1abbfce737b6962f4b1b2255dc63e932",
+ "timestamp": "2021-01-05T16:20:33Z"
+ },
+ {
+ "version": "6.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tvheadend/tvheadend_6.0.0.0.zip",
+ "checksum": "143c34fd70d7173b8912cc03ce4b517d",
+ "timestamp": "2020-07-20T01:33:15Z"
+ }
+ ]
+ },
+ {
+ "guid": "022a3003-993f-45f1-8565-87d12af2e12a",
+ "name": "InfuseSync",
+ "description": "This plugin will track all media changes while any Infuse clients are offline to decrease sync times when logging back in to your server.",
+ "overview": "Blazing fast indexing for Infuse",
+ "owner": "Firecore LLC",
+ "category": "General",
+ "versions": [
+ {
+ "version": "1.2.4.0",
+ "changelog": "New Playlist support.\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://github.com/firecore/InfuseSync/releases/download/v1.2.4/InfuseSync-jellyfin-1.2.4.zip",
+ "checksum": "7adde11b8c8404fd2923f59d98fb1a30",
+ "timestamp": "2020-10-12T08:00:00Z"
+ },
+ {
+ "version": "1.2.1.3",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://github.com/firecore/InfuseSync/releases/download/v1.2.3/InfuseSync-jellyfin-1.2.3.zip",
+ "checksum": "d8e2c5fe736a302097bb3bac3d04b1c4",
+ "timestamp": "2020-09-18T12:19:00Z"
+ },
+ {
+ "version": "1.2.1.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://github.com/firecore/InfuseSync/releases/download/v1.2.1/InfuseSync-jellyfin-1.2.1.zip",
+ "checksum": "1a853e926cc422f5d79d398d9ae18ee8",
+ "timestamp": "2020-08-21T10:48:00Z"
+ },
+ {
+ "version": "1.2.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://github.com/firecore/InfuseSync/releases/download/v1.2.0/InfuseSync-jellyfin-1.2.0.zip",
+ "checksum": "2d3c7859852695a7f05adc6d3fcbc783",
+ "timestamp": "2020-07-20T11:51:00Z"
+ }
+ ]
+ },
+ {
+ "guid": "8119f3c6-cfc2-4d9c-a0ba-028f1d93e526",
+ "name": "Cover Art Archive",
+ "description": "This plugin provides images from the Cover Art Archive https://musicbrainz.org/doc/Cover_Art_Archive and depends on the MusicBrainz metadata provider to know what images belong where\n",
+ "overview": "MusicBrainz Cover Art Archive",
+ "owner": "jellyfin",
+ "category": "Metadata",
+ "versions": [
+ {
+ "version": "2.0.0.0",
+ "changelog": "changelog\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/cover-art-archive/cover-art-archive_2.0.0.0.zip",
+ "checksum": "bea8fa4a37b3e7ed74e22266e7597a68",
+ "timestamp": "2020-12-06T02:51:03Z"
+ },
+ {
+ "version": "1.0.0.3",
+ "changelog": "changelog\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/cover-art-archive/cover-art-archive_1.0.0.3.zip",
+ "checksum": "c502a5c54b168810614c1c40709b9598",
+ "timestamp": "2020-08-06T21:21:22Z"
+ }
+ ]
+ },
+ {
+ "guid": "A4A488D0-17A3-4919-8D82-7F3DE4F6B209",
+ "name": "TV Maze",
+ "description": "Get TV metadata from TV Maze\n",
+ "overview": "Get TV metadata from TV Maze",
+ "owner": "jellyfin",
+ "category": "Metadata",
+ "versions": [
+ {
+ "version": "5.0.0.0",
+ "changelog": "Get additional image types\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tv-maze/tv-maze_5.0.0.0.zip",
+ "checksum": "509a85e40b1d1ac36eef45673deaf606",
+ "timestamp": "2020-12-06T02:51:56Z"
+ },
+ {
+ "version": "4.0.0.0",
+ "changelog": "Get additional image types\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tv-maze/tv-maze_4.0.0.0.zip",
+ "checksum": "58ee9ab3f129151bdfff033ad889ad87",
+ "timestamp": "2020-11-24T14:44:37Z"
+ },
+ {
+ "version": "3.0.0.0",
+ "changelog": "Remove unused dependencies \n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tv-maze/tv-maze_3.0.0.0.zip",
+ "checksum": "f3b2c70b3e136fb15c917e4420f4fdec",
+ "timestamp": "2020-11-09T14:32:56Z"
+ },
+ {
+ "version": "2.0.0.0",
+ "changelog": "Remove unused dependencies \n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tv-maze/tv-maze_2.0.0.0.zip",
+ "checksum": "c7662ae8ae52ce8a4e8d685d55f36e80",
+ "timestamp": "2020-11-09T02:33:11Z"
+ },
+ {
+ "version": "1.0.0.0",
+ "changelog": "Initial release.\n",
+ "targetAbi": "10.6.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/tv-maze/tv-maze_1.0.0.0.zip",
+ "checksum": "c90eee48c12f2c07880b4b28e507fd14",
+ "timestamp": "2020-11-08T19:05:32Z"
+ }
+ ]
+ },
+ {
+ "guid": "a677c0da-fac5-4cde-941a-7134223f14c8",
+ "name": "TheTVDB",
+ "description": "Get TV metadata from TheTvdb\n",
+ "overview": "Get TV metadata from TheTvdb",
+ "owner": "jellyfin",
+ "category": "Metadata",
+ "versions": [
+ {
+ "version": "2.0.0.0",
+ "changelog": "Remove from Jellyfin core.\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/thetvdb/thetvdb_2.0.0.0.zip",
+ "checksum": "e46cee334476a1b475e5c553171c4cb6",
+ "timestamp": "2020-12-16T20:03:28Z"
+ },
+ {
+ "version": "1.0.0.0",
+ "changelog": "Remove from Jellyfin core.\n",
+ "targetAbi": "10.7.0.0",
+ "sourceUrl": "https://repo.jellyfin.org/releases/plugin/thetvdb/thetvdb_1.0.0.0.zip",
+ "checksum": "5a3dca5c0db4824d83bfd4e7e2b7bf11",
+ "timestamp": "2020-12-06T02:56:40Z"
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs
new file mode 100644
index 000000000..4fa64d8a2
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs
@@ -0,0 +1,57 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Emby.Server.Implementations.Updates;
+using MediaBrowser.Model.Updates;
+using Moq;
+using Moq.Protected;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Updates
+{
+ public class InstallationManagerTests
+ {
+ private readonly Fixture _fixture;
+ private readonly InstallationManager _installationManager;
+
+ public InstallationManagerTests()
+ {
+ var messageHandler = new Mock<HttpMessageHandler>();
+ messageHandler.Protected()
+ .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
+ .Returns<HttpRequestMessage, CancellationToken>(
+ (m, _) =>
+ {
+ return Task.FromResult(new HttpResponseMessage()
+ {
+ Content = new StreamContent(File.OpenRead("Test Data/Updates/" + m.RequestUri?.Segments[^1]))
+ });
+ });
+
+ var http = new Mock<IHttpClientFactory>();
+ http.Setup(x => x.CreateClient(It.IsAny<string>()))
+ .Returns(new HttpClient(messageHandler.Object));
+ _fixture = new Fixture();
+ _fixture.Customize(new AutoMoqCustomization
+ {
+ ConfigureMembers = true
+ }).Inject(http);
+ _installationManager = _fixture.Create<InstallationManager>();
+ }
+
+ [Fact]
+ public async Task GetPackages_Valid_Success()
+ {
+ IList<PackageInfo> packages = await _installationManager.GetPackages(
+ "Jellyfin Stable",
+ "https://repo.jellyfin.org/releases/plugin/manifest-stable.json",
+ false);
+
+ Assert.Equal(25, packages.Count);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Api.Tests/BrandingServiceTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
index 1cbe94c5b..87136dfc8 100644
--- a/tests/Jellyfin.Api.Tests/BrandingServiceTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
@@ -1,15 +1,18 @@
+using System.Net;
+using System.Net.Mime;
+using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using MediaBrowser.Model.Branding;
using Xunit;
-namespace Jellyfin.Api.Tests
+namespace Jellyfin.Server.Integration.Tests
{
- public sealed class BrandingServiceTests : IClassFixture<JellyfinApplicationFactory>
+ public sealed class BrandingControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
- public BrandingServiceTests(JellyfinApplicationFactory factory)
+ public BrandingControllerTests(JellyfinApplicationFactory factory)
{
_factory = factory;
}
@@ -24,8 +27,9 @@ namespace Jellyfin.Api.Tests
var response = await client.GetAsync("/Branding/Configuration");
// Assert
- response.EnsureSuccessStatusCode();
- Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
var responseBody = await response.Content.ReadAsStreamAsync();
_ = await JsonSerializer.DeserializeAsync<BrandingOptions>(responseBody);
}
@@ -42,8 +46,9 @@ namespace Jellyfin.Api.Tests
var response = await client.GetAsync(url);
// Assert
- response.EnsureSuccessStatusCode();
- Assert.Equal("text/css; charset=utf-8", response.Content.Headers.ContentType?.ToString());
+ Assert.True(response.IsSuccessStatusCode);
+ Assert.Equal("text/css", response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
}
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
new file mode 100644
index 000000000..f5411dcb8
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
@@ -0,0 +1,86 @@
+using System.IO;
+using System.Net;
+using System.Net.Mime;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models;
+using MediaBrowser.Common.Json;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers
+{
+ public sealed class DashboardControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+ private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.Options;
+
+ public DashboardControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task GetDashboardConfigurationPage_NonExistingPage_NotFound()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.GetAsync("web/ConfigurationPage?name=ThisPageDoesntExists").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task GetDashboardConfigurationPage_ExistingPage_CorrectPage()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Text.Html, response.Content.Headers.ContentType?.MediaType);
+ StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Server.Integration.Tests.TestPage.html")!);
+ Assert.Equal(await response.Content.ReadAsStringAsync(), reader.ReadToEnd());
+ }
+
+ [Fact]
+ public async Task GetDashboardConfigurationPage_BrokenPage_NotFound()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.GetAsync("/web/ConfigurationPage?name=BrokenPage").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task GetConfigurationPages_NoParams_AllConfigurationPages()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.GetAsync("/web/ConfigurationPages").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var res = await response.Content.ReadAsStreamAsync();
+ _ = await JsonSerializer.DeserializeAsync<ConfigurationPageInfo[]>(res, _jsonOpions);
+ // TODO: check content
+ }
+
+ [Fact]
+ public async Task GetConfigurationPages_True_MainMenuConfigurationPages()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
+
+ var res = await response.Content.ReadAsStreamAsync();
+ var data = await JsonSerializer.DeserializeAsync<ConfigurationPageInfo[]>(res, _jsonOpions);
+ Assert.Empty(data);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
new file mode 100644
index 000000000..169a5a6c5
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
@@ -0,0 +1,119 @@
+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 MediaBrowser.Common.Json;
+using Xunit;
+using Xunit.Priority;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers
+{
+ [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+ public sealed class StartupControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
+
+ public StartupControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ [Priority(-2)]
+ public async Task Configuration_EditConfig_Success()
+ {
+ var client = _factory.CreateClient();
+
+ var config = new StartupConfigurationDto()
+ {
+ UICulture = "NewCulture",
+ MetadataCountryCode = "be",
+ PreferredMetadataLanguage = "nl"
+ };
+
+ using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(config, _jsonOptions));
+ postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ using var postResponse = await client.PostAsync("/Startup/Configuration", postContent).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
+
+ using var getResponse = await client.GetAsync("/Startup/Configuration").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
+
+ using var responseStream = await getResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var newConfig = await JsonSerializer.DeserializeAsync<StartupConfigurationDto>(responseStream, _jsonOptions).ConfigureAwait(false);
+ Assert.Equal(config.UICulture, newConfig!.UICulture);
+ Assert.Equal(config.MetadataCountryCode, newConfig.MetadataCountryCode);
+ Assert.Equal(config.PreferredMetadataLanguage, newConfig.PreferredMetadataLanguage);
+ }
+
+ [Fact]
+ [Priority(-2)]
+ public async Task User_DefaultUser_NameWithoutPassword()
+ {
+ var client = _factory.CreateClient();
+
+ using var response = await client.GetAsync("/Startup/User").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+
+ using var contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var user = await JsonSerializer.DeserializeAsync<StartupUserDto>(contentStream, _jsonOptions).ConfigureAwait(false);
+ Assert.NotEmpty(user!.Name);
+ Assert.Null(user.Password);
+ }
+
+ [Fact]
+ [Priority(-1)]
+ public async Task User_EditUser_Success()
+ {
+ var client = _factory.CreateClient();
+
+ var user = new StartupUserDto()
+ {
+ Name = "NewName",
+ Password = "NewPassword"
+ };
+
+ using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions));
+ postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ var postResponse = await client.PostAsync("/Startup/User", postContent).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
+
+ var getResponse = await client.GetAsync("/Startup/User").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
+
+ var contentStream = await getResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var newUser = await JsonSerializer.DeserializeAsync<StartupUserDto>(contentStream, _jsonOptions).ConfigureAwait(false);
+ Assert.Equal(user.Name, newUser!.Name);
+ Assert.NotEmpty(newUser.Password);
+ Assert.NotEqual(user.Password, newUser.Password);
+ }
+
+ [Fact]
+ [Priority(0)]
+ public async Task CompleteWizard_Success()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty<byte>())).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+ }
+
+ [Fact]
+ [Priority(1)]
+ public async Task GetFirstUser_CompleteWizard_Unauthorized()
+ {
+ var client = _factory.CreateClient();
+
+ using var response = await client.GetAsync("/Startup/User").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
new file mode 100644
index 000000000..34cef9005
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -0,0 +1,40 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
+ </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="Microsoft.Extensions.Options" Version="5.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Xunit.Priority" Version="1.1.6" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
+ <PackageReference Include="Moq" Version="4.16.0" />
+ </ItemGroup>
+
+ <!-- Code Analyzers -->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="../../Jellyfin.Server/Jellyfin.Server.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <EmbeddedResource Include="TestPage.html" />
+ </ItemGroup>
+
+</Project>
diff --git a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
index 54f8eb225..d9ec81a27 100644
--- a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
@@ -1,19 +1,20 @@
using System;
using System.Collections.Concurrent;
using System.IO;
+using System.Threading;
using Emby.Server.Implementations;
using Emby.Server.Implementations.IO;
-using Jellyfin.Server;
using MediaBrowser.Common;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Extensions.Logging;
-namespace Jellyfin.Api.Tests
+namespace Jellyfin.Server.Integration.Tests
{
/// <summary>
/// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests.
@@ -21,12 +22,12 @@ namespace Jellyfin.Api.Tests
public class JellyfinApplicationFactory : WebApplicationFactory<Startup>
{
private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data");
- private static readonly ConcurrentBag<IDisposable> _disposableComponents = new ConcurrentBag<IDisposable>();
+ private readonly ConcurrentBag<IDisposable> _disposableComponents = new ConcurrentBag<IDisposable>();
/// <summary>
- /// Initializes a new instance of the <see cref="JellyfinApplicationFactory"/> class.
+ /// Initializes static members of the <see cref="JellyfinApplicationFactory"/> class.
/// </summary>
- public JellyfinApplicationFactory()
+ static JellyfinApplicationFactory()
{
// Perform static initialization that only needs to happen once per test-run
Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
@@ -73,10 +74,11 @@ namespace Jellyfin.Api.Tests
_disposableComponents.Add(loggerFactory);
// Create the app host and initialize it
- var appHost = new CoreAppHost(
+ var appHost = new TestAppHost(
appPaths,
loggerFactory,
commandLineOpts,
+ new ConfigurationBuilder().Build(),
new ManagedFileSystem(loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
serviceCollection);
_disposableComponents.Add(appHost);
@@ -93,10 +95,10 @@ namespace Jellyfin.Api.Tests
var testServer = base.CreateServer(builder);
// Finish initializing the app host
- var appHost = (CoreAppHost)testServer.Services.GetRequiredService<IApplicationHost>();
+ var appHost = (TestAppHost)testServer.Services.GetRequiredService<IApplicationHost>();
appHost.ServiceProvider = testServer.Services;
appHost.InitializeServices().GetAwaiter().GetResult();
- appHost.RunStartupTasksAsync().GetAwaiter().GetResult();
+ appHost.RunStartupTasksAsync(CancellationToken.None).GetAwaiter().GetResult();
return testServer;
}
diff --git a/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
index 03ab56d1f..3cbd638f9 100644
--- a/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Branding;
using Xunit;
using Xunit.Abstractions;
-namespace Jellyfin.Api.Tests
+namespace Jellyfin.Server.Integration.Tests
{
public sealed class OpenApiSpecTests : IClassFixture<JellyfinApplicationFactory>
{
diff --git a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
new file mode 100644
index 000000000..4e5d0fcb6
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
@@ -0,0 +1,55 @@
+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;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ /// <summary>
+ /// Implementation of the abstract <see cref="ApplicationHost" /> class.
+ /// </summary>
+ public class TestAppHost : CoreAppHost
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TestAppHost" /> class.
+ /// </summary>
+ /// <param name="applicationPaths">The <see cref="ServerApplicationPaths" /> to be used by the <see cref="CoreAppHost" />.</param>
+ /// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
+ /// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
+ /// <param name="startup">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
+ /// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
+ /// <param name="collection">The <see cref="IServiceCollection"/> to be used by the <see cref="CoreAppHost"/>.</param>
+ public TestAppHost(
+ IServerApplicationPaths applicationPaths,
+ ILoggerFactory loggerFactory,
+ IStartupOptions options,
+ IConfiguration startup,
+ IFileSystem fileSystem,
+ IServiceCollection collection)
+ : base(
+ applicationPaths,
+ loggerFactory,
+ options,
+ startup,
+ fileSystem,
+ collection)
+ {
+ }
+
+ /// <inheritdoc />
+ protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
+ {
+ foreach (var a in base.GetAssembliesWithPartsInternal())
+ {
+ yield return a;
+ }
+
+ yield return typeof(TestPlugin).Assembly;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/TestPage.html b/tests/Jellyfin.Server.Integration.Tests/TestPage.html
new file mode 100644
index 000000000..8037af8a6
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/TestPage.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>TestPlugin</title>
+</head>
+<body>
+ <h1>This is a Test Page.</h1>
+</body>
+</html>
diff --git a/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs b/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs
new file mode 100644
index 000000000..1d67ac487
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs
@@ -0,0 +1,43 @@
+#pragma warning disable CS1591
+
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Serialization;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ public class TestPlugin : BasePlugin<BasePluginConfiguration>, IHasWebPages
+ {
+ public TestPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ : base(applicationPaths, xmlSerializer)
+ {
+ Instance = this;
+ }
+
+ public static TestPlugin? Instance { get; private set; }
+
+ public override Guid Id => new Guid("2d350a13-0bf7-4b61-859c-d5e601b5facf");
+
+ public override string Name => nameof(TestPlugin);
+
+ public override string Description => "Server test Plugin.";
+
+ public IEnumerable<PluginPageInfo> GetPages()
+ {
+ yield return new PluginPageInfo
+ {
+ Name = Name,
+ EmbeddedResourcePath = GetType().Namespace + ".TestPage.html"
+ };
+
+ yield return new PluginPageInfo
+ {
+ Name = "BrokenPage",
+ EmbeddedResourcePath = GetType().Namespace + ".foobar"
+ };
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs b/tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs
new file mode 100644
index 000000000..ac10c4784
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs
@@ -0,0 +1,27 @@
+#pragma warning disable CS1591
+
+using System;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Serialization;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ public class TestPluginWithoutPages : BasePlugin<BasePluginConfiguration>
+ {
+ public TestPluginWithoutPages(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ : base(applicationPaths, xmlSerializer)
+ {
+ Instance = this;
+ }
+
+ public static TestPluginWithoutPages? Instance { get; private set; }
+
+ public override Guid Id => new Guid("ae95cbe6-bd3d-4d73-8596-490db334611e");
+
+ public override string Name => nameof(TestPluginWithoutPages);
+
+ public override string Description => "Server test Plugin without web pages.";
+ }
+}
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
new file mode 100644
index 000000000..a310b0ea9
--- /dev/null
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -0,0 +1,36 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
+ </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="Microsoft.Extensions.Options" Version="5.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
+ <PackageReference Include="Moq" Version="4.16.0" />
+ </ItemGroup>
+
+ <!-- Code Analyzers -->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="../../Jellyfin.Server/Jellyfin.Server.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
index 6c3fd0ee1..0b714e80a 100644
--- a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs
+++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
@@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
-namespace Jellyfin.Api.Tests
+namespace Jellyfin.Server.Tests
{
public class ParseNetworkTests
{
@@ -37,28 +37,28 @@ namespace Jellyfin.Api.Tests
EnableIPV6 = ip6
};
- var result = match + ',';
+ var result = match + ",";
ForwardedHeadersOptions options = new ForwardedHeadersOptions();
// Need this here as ::1 and 127.0.0.1 are in them by default.
options.KnownProxies.Clear();
options.KnownNetworks.Clear();
- ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(","), options);
+ ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(','), options);
var sb = new StringBuilder();
foreach (var item in options.KnownProxies)
{
- sb.Append(item);
- sb.Append(',');
+ sb.Append(item)
+ .Append(',');
}
foreach (var item in options.KnownNetworks)
{
- sb.Append(item.Prefix);
- sb.Append('/');
- sb.Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture));
- sb.Append(',');
+ sb.Append(item.Prefix)
+ .Append('/')
+ .Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture))
+ .Append(',');
}
Assert.Equal(sb.ToString(), result);
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index aed3e8ac5..9380fe2af 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -5,6 +5,8 @@
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
@@ -14,16 +16,15 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
- <PackageReference Include="Moq" Version="4.16.0" />
+ <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.2" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
</ItemGroup>
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
@@ -34,8 +35,4 @@
<ProjectReference Include="../../MediaBrowser.Providers/MediaBrowser.Providers.csproj" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
index 438684473..9ad093a2b 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
@@ -49,7 +49,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
- public void Fetch_Valid_Succes()
+ public void Fetch_Valid_Success()
{
var result = new MetadataResult<Episode>()
{
@@ -105,6 +105,26 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
+ public void Fetch_Valid_MultiEpisode_Success()
+ {
+ var result = new MetadataResult<Episode>()
+ {
+ Item = new Episode()
+ };
+
+ _parser.Fetch(result, "Test Data/Rising.nfo", CancellationToken.None);
+
+ var item = result.Item;
+ Assert.Equal("Rising (1)", item.Name);
+ Assert.Equal(1, item.IndexNumber);
+ Assert.Equal(2, item.IndexNumberEnd);
+ Assert.Equal(1, item.ParentIndexNumber);
+ Assert.Equal("A new Stargate team embarks on a dangerous mission to a distant galaxy, where they discover a mythical lost city -- and a deadly new enemy.", item.Overview);
+ Assert.Equal(new DateTime(2004, 7, 16), item.PremiereDate);
+ Assert.Equal(2004, item.ProductionYear);
+ }
+
+ [Fact]
public void Fetch_WithNullItem_ThrowsArgumentException()
{
var result = new MetadataResult<Episode>();
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
index 4c556b4d6..d4ce74132 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
@@ -77,7 +77,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
- public void Fetch_Valid_Succes()
+ public void Fetch_Valid_Success()
{
var result = new MetadataResult<Video>()
{
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
index 29de2a95e..2129f3422 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
@@ -48,7 +48,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
- public void Fetch_Valid_Succes()
+ public void Fetch_Valid_Success()
{
var result = new MetadataResult<MusicAlbum>()
{
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
index e54b35eb5..3d8e13e96 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
@@ -47,7 +47,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
- public void Fetch_Valid_Succes()
+ public void Fetch_Valid_Success()
{
var result = new MetadataResult<MusicArtist>()
{
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs
index c497d4fa2..0e61fa2a1 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs
@@ -43,7 +43,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
- public void Fetch_Valid_Succes()
+ public void Fetch_Valid_Success()
{
var result = new MetadataResult<Season>()
{
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
index 7c7d698f0..bdedae205 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
@@ -35,7 +35,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
}
[Fact]
- public void Fetch_Valid_Succes()
+ public void Fetch_Valid_Success()
{
var result = new MetadataResult<Series>()
{
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Rising.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Rising.nfo
new file mode 100644
index 000000000..56250c09a
--- /dev/null
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Rising.nfo
@@ -0,0 +1,20 @@
+<episodedetails>
+ <title>Rising (1)</title>
+ <season>1</season>
+ <episode>1</episode>
+ <aired>2004-07-16</aired>
+ <plot>A new Stargate team embarks on a dangerous mission to a distant galaxy, where they discover a mythical lost city -- and a deadly new enemy.</plot>
+ <thumb>https://artworks.thetvdb.com/banners/episodes/70851/25333.jpg</thumb>
+ <watched>false</watched>
+ <rating>8.0</rating>
+</episodedetails>
+<episodedetails>
+ <title>Rising (2)</title>
+ <season>1</season>
+ <episode>2</episode>
+ <aired>2004-07-16</aired>
+ <plot>Sheppard tries to convince Weir to mount a rescue mission to free Colonel Sumner, Teyla, and the others captured by the Wraith.</plot>
+ <thumb>https://artworks.thetvdb.com/banners/episodes/70851/25334.jpg</thumb>
+ <watched>false</watched>
+ <rating>7.9</rating>
+</episodedetails>