aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj8
-rw-r--r--tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs185
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs1
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs1
-rw-r--r--tests/Jellyfin.Common.Tests/PasswordHashTests.cs31
-rw-r--r--tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs131
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj3
-rw-r--r--tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs1
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkParseTests.cs12
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs229
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs34
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj5
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs11
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs280
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs59
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs30
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs170
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj15
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs1
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs32
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/xunit.runner.json4
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj8
-rw-r--r--tests/Jellyfin.Server.Tests/ParseNetworkTests.cs1
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs1
29 files changed, 1188 insertions, 71 deletions
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 57277be15..397b863b7 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -15,10 +15,10 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" />
+ <PackageReference Include="AutoFixture" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
diff --git a/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs
new file mode 100644
index 000000000..e6c325bac
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Cryptography/PasswordHashTests.cs
@@ -0,0 +1,185 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Common.Cryptography;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Cryptography
+{
+ public static class PasswordHashTests
+ {
+ [Fact]
+ public static void Ctor_Null_ThrowsArgumentNullException()
+ {
+ Assert.Throws<ArgumentNullException>(() => new PasswordHash(null!, Array.Empty<byte>()));
+ }
+
+ [Fact]
+ public static void Ctor_Empty_ThrowsArgumentException()
+ {
+ Assert.Throws<ArgumentException>(() => new PasswordHash(string.Empty, Array.Empty<byte>()));
+ }
+
+ public static IEnumerable<object[]> Parse_Valid_TestData()
+ {
+ // Id
+ yield return new object[]
+ {
+ "$PBKDF2",
+ new PasswordHash("PBKDF2", Array.Empty<byte>())
+ };
+
+ // Id + parameter
+ yield return new object[]
+ {
+ "$PBKDF2$iterations=1000",
+ new PasswordHash(
+ "PBKDF2",
+ Array.Empty<byte>(),
+ Array.Empty<byte>(),
+ new Dictionary<string, string>()
+ {
+ { "iterations", "1000" },
+ })
+ };
+
+ // Id + parameters
+ yield return new object[]
+ {
+ "$PBKDF2$iterations=1000,m=120",
+ new PasswordHash(
+ "PBKDF2",
+ Array.Empty<byte>(),
+ Array.Empty<byte>(),
+ new Dictionary<string, string>()
+ {
+ { "iterations", "1000" },
+ { "m", "120" }
+ })
+ };
+
+ // Id + hash
+ yield return new object[]
+ {
+ "$PBKDF2$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
+ new PasswordHash(
+ "PBKDF2",
+ Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
+ Array.Empty<byte>(),
+ new Dictionary<string, string>())
+ };
+
+ // Id + salt + hash
+ yield return new object[]
+ {
+ "$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
+ new PasswordHash(
+ "PBKDF2",
+ Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
+ Convert.FromHexString("69F420"),
+ new Dictionary<string, string>())
+ };
+
+ // Id + parameter + hash
+ yield return new object[]
+ {
+ "$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
+ new PasswordHash(
+ "PBKDF2",
+ Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
+ Array.Empty<byte>(),
+ new Dictionary<string, string>()
+ {
+ { "iterations", "1000" }
+ })
+ };
+
+ // Id + parameters + hash
+ yield return new object[]
+ {
+ "$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
+ new PasswordHash(
+ "PBKDF2",
+ Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
+ Array.Empty<byte>(),
+ new Dictionary<string, string>()
+ {
+ { "iterations", "1000" },
+ { "m", "120" }
+ })
+ };
+
+ // Id + parameters + salt + hash
+ yield return new object[]
+ {
+ "$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
+ new PasswordHash(
+ "PBKDF2",
+ Convert.FromHexString("62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"),
+ Convert.FromHexString("69F420"),
+ new Dictionary<string, string>()
+ {
+ { "iterations", "1000" },
+ { "m", "120" }
+ })
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(Parse_Valid_TestData))]
+ public static void Parse_Valid_Success(string passwordHashString, PasswordHash expected)
+ {
+ var passwordHash = PasswordHash.Parse(passwordHashString);
+ Assert.Equal(expected.Id, passwordHash.Id);
+ Assert.Equal(expected.Parameters, passwordHash.Parameters);
+ Assert.Equal(expected.Salt.ToArray(), passwordHash.Salt.ToArray());
+ Assert.Equal(expected.Hash.ToArray(), passwordHash.Hash.ToArray());
+ Assert.Equal(expected.ToString(), passwordHash.ToString());
+ }
+
+ [Theory]
+ [InlineData("$PBKDF2")]
+ [InlineData("$PBKDF2$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
+ [InlineData("$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
+ [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
+ [InlineData("$PBKDF2$iterations=1000,m=120$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
+ [InlineData("$PBKDF2$iterations=1000,m=120$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
+ [InlineData("$PBKDF2$iterations=1000,m=120")]
+ public static void ToString_Roundtrip_Success(string passwordHash)
+ {
+ Assert.Equal(passwordHash, PasswordHash.Parse(passwordHash).ToString());
+ }
+
+ [Fact]
+ public static void Parse_Null_ThrowsArgumentException()
+ {
+ Assert.Throws<ArgumentException>(() => PasswordHash.Parse(null));
+ }
+
+ [Fact]
+ public static void Parse_Empty_ThrowsArgumentException()
+ {
+ Assert.Throws<ArgumentException>(() => PasswordHash.Parse(string.Empty));
+ }
+
+ [Theory]
+ [InlineData("$")] // No id
+ [InlineData("$$")] // Empty segments
+ [InlineData("PBKDF2$")] // Doesn't start with $
+ [InlineData("$PBKDF2$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty segment
+ [InlineData("$PBKDF2$iterations=1000$$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Empty salt segment
+ [InlineData("$PBKDF2$iterations=1000$69F420$")] // Empty hash segment
+ [InlineData("$PBKDF2$=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
+ [InlineData("$PBKDF2$=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
+ [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid parmeter
+ [InlineData("$PBKDF2$iterations=$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Ends on $
+ [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$")] // Extra segment
+ [InlineData("$PBKDF2$iterations=$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D$anotherone")] // Extra segment
+ [InlineData("$PBKDF2$iterations=$invalidstalt$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] // Invalid salt
+ [InlineData("$PBKDF2$iterations=$69F420$invalid hash")] // Invalid hash
+ [InlineData("$PBKDF2$69F420$")] // Empty hash
+ public static void Parse_InvalidFormat_ThrowsFormatException(string passwordHash)
+ {
+ Assert.Throws<FormatException>(() => PasswordHash.Parse(passwordHash));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs
index 1e1cde957..dbfad3c2f 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs
+++ b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Globalization;
using System.Text.Json;
using MediaBrowser.Common.Json.Converters;
using Xunit;
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
index 22bc7afb9..cb3b66c4c 100644
--- a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
+++ b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Globalization;
using System.Text.Json;
using MediaBrowser.Common.Json.Converters;
using Xunit;
diff --git a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/PasswordHashTests.cs
deleted file mode 100644
index c4422bd10..000000000
--- a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using MediaBrowser.Common;
-using MediaBrowser.Common.Cryptography;
-using Xunit;
-
-namespace Jellyfin.Common.Tests
-{
- public class PasswordHashTests
- {
- [Theory]
- [InlineData(
- "$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
- "PBKDF2",
- "",
- "62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
- public void ParseTest(string passwordHash, string id, string salt, string hash)
- {
- var pass = PasswordHash.Parse(passwordHash);
- Assert.Equal(id, pass.Id);
- Assert.Equal(salt, Convert.ToHexString(pass.Salt));
- Assert.Equal(hash, Convert.ToHexString(pass.Hash));
- }
-
- [Theory]
- [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
- public void ToStringTest(string passwordHash)
- {
- Assert.Equal(passwordHash, PasswordHash.Parse(passwordHash).ToString());
- }
- }
-}
diff --git a/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs
new file mode 100644
index 000000000..668bd8f87
--- /dev/null
+++ b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs
@@ -0,0 +1,131 @@
+using Emby.Dlna;
+using Emby.Dlna.PlayTo;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Serialization;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Dlna.Tests
+{
+ public class DlnaManagerTests
+ {
+ private DlnaManager GetManager()
+ {
+ var xmlSerializer = new Mock<IXmlSerializer>();
+ var fileSystem = new Mock<IFileSystem>();
+ var appPaths = new Mock<IApplicationPaths>();
+ var loggerFactory = new Mock<ILoggerFactory>();
+ var appHost = new Mock<IServerApplicationHost>();
+
+ return new DlnaManager(xmlSerializer.Object, fileSystem.Object, appPaths.Object, loggerFactory.Object, appHost.Object);
+ }
+
+ [Fact]
+ public void IsMatch_GivenMatchingName_ReturnsTrue()
+ {
+ var device = new DeviceInfo()
+ {
+ Name = "My Device",
+ Manufacturer = "LG Electronics",
+ ManufacturerUrl = "http://www.lge.com",
+ ModelDescription = "LG WebOSTV DMRplus",
+ ModelName = "LG TV",
+ ModelNumber = "1.0",
+ };
+
+ var profile = new DeviceProfile()
+ {
+ Name = "Test Profile",
+ FriendlyName = "My Device",
+ Manufacturer = "LG Electronics",
+ ManufacturerUrl = "http://www.lge.com",
+ ModelDescription = "LG WebOSTV DMRplus",
+ ModelName = "LG TV",
+ ModelNumber = "1.0",
+ Identification = new ()
+ {
+ FriendlyName = "My Device",
+ Manufacturer = "LG Electronics",
+ ManufacturerUrl = "http://www.lge.com",
+ ModelDescription = "LG WebOSTV DMRplus",
+ ModelName = "LG TV",
+ ModelNumber = "1.0",
+ }
+ };
+
+ var profile2 = new DeviceProfile()
+ {
+ Name = "Test Profile",
+ FriendlyName = "My Device",
+ Identification = new DeviceIdentification()
+ {
+ FriendlyName = "My Device",
+ }
+ };
+
+ var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile2.Identification);
+ var deviceMatch2 = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
+
+ Assert.True(deviceMatch);
+ Assert.True(deviceMatch2);
+ }
+
+ [Fact]
+ public void IsMatch_GivenNamesAndManufacturersDoNotMatch_ReturnsFalse()
+ {
+ var device = new DeviceInfo()
+ {
+ Name = "My Device",
+ Manufacturer = "JVC"
+ };
+
+ var profile = new DeviceProfile()
+ {
+ Name = "Test Profile",
+ FriendlyName = "My Device",
+ Manufacturer = "LG Electronics",
+ ManufacturerUrl = "http://www.lge.com",
+ ModelDescription = "LG WebOSTV DMRplus",
+ ModelName = "LG TV",
+ ModelNumber = "1.0",
+ Identification = new ()
+ {
+ FriendlyName = "My Device",
+ Manufacturer = "LG Electronics",
+ ManufacturerUrl = "http://www.lge.com",
+ ModelDescription = "LG WebOSTV DMRplus",
+ ModelName = "LG TV",
+ ModelNumber = "1.0",
+ }
+ };
+
+ var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
+
+ Assert.False(deviceMatch);
+ }
+
+ [Fact]
+ public void IsMatch_GivenNamesAndRegExMatch_ReturnsTrue()
+ {
+ var device = new DeviceInfo()
+ {
+ Name = "My Device"
+ };
+
+ var profile = new DeviceProfile()
+ {
+ Name = "Test Profile",
+ FriendlyName = "My .*",
+ Identification = new ()
+ };
+
+ var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
+
+ Assert.True(deviceMatch);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
index c2c0dca1b..f7c21f072 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -10,7 +10,8 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs
index 51633e157..5864a0509 100644
--- a/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs
+++ b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs
@@ -1,4 +1,3 @@
-using System;
using MediaBrowser.Model.Extensions;
using Xunit;
diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
index e5768b620..d9e77dd2e 100644
--- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookListResolverTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
diff --git a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
index ad63adadc..53b35c2d6 100644
--- a/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
+++ b/tests/Jellyfin.Naming.Tests/AudioBook/AudioBookResolverTests.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
index f3abacb4f..2446660f3 100644
--- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
@@ -1,4 +1,3 @@
-using System;
using Emby.Naming.Common;
using Emby.Naming.Subtitles;
using Xunit;
diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
index d34f65409..2f173b0ce 100644
--- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
@@ -1,4 +1,3 @@
-using System;
using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Model.Entities;
diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
index caea0d265..9b0da2b3c 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
@@ -34,10 +34,12 @@ namespace Jellyfin.Networking.Tests
[InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.0/24", "[192.168.1.208/24,200.200.200.200/24]")]
// eth16 only
[InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")]
- // All interfaces excluded.
- [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[]")]
+ // All interfaces excluded. (including loopbacks)
+ [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[127.0.0.1/8,::1/128]")]
// vEthernet1 and vEthernet212 should be excluded.
- [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24]")]
+ [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24,127.0.0.1/8,::1/128]")]
+ // Overlapping interface,
+ [InlineData("192.168.1.110/24,-20,br0|192.168.1.10/24,-16,br0|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.110/24,192.168.1.10/24]")]
public void IgnoreVirtualInterfaces(string interfaces, string lan, string value)
{
var conf = new NetworkConfiguration()
@@ -239,7 +241,7 @@ namespace Jellyfin.Networking.Tests
Collection<IPObject> nc1 = nm.CreateIPCollection(settings.Split(','), false);
Collection<IPObject> nc2 = nm.CreateIPCollection(compare.Split(','), false);
- Assert.Equal(nc1.Union(nc2).AsString(), result);
+ Assert.Equal(nc1.ThatAreContainedInNetworks(nc2).AsString(), result);
}
[Theory]
@@ -348,7 +350,7 @@ namespace Jellyfin.Networking.Tests
// Test included, IP6.
Collection<IPObject> ncSource = nm.CreateIPCollection(source.Split(','));
Collection<IPObject> ncDest = nm.CreateIPCollection(dest.Split(','));
- Collection<IPObject> ncResult = ncSource.Union(ncDest);
+ Collection<IPObject> ncResult = ncSource.ThatAreContainedInNetworks(ncDest);
Collection<IPObject> resultCollection = nm.CreateIPCollection(result.Split(','));
Assert.True(ncResult.Compare(resultCollection));
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs
new file mode 100644
index 000000000..af6ec3245
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections.Generic;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Emby.Server.Implementations.Data;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Data
+{
+ public class SqliteItemRepositoryTests
+ {
+ public const string VirtualMetaDataPath = "%MetadataPath%";
+ public const string MetaDataPath = "/meta/data/path";
+
+ private readonly IFixture _fixture;
+ private readonly SqliteItemRepository _sqliteItemRepository;
+
+ public SqliteItemRepositoryTests()
+ {
+ var appHost = new Mock<IServerApplicationHost>();
+ appHost.Setup(x => x.ExpandVirtualPath(It.IsAny<string>()))
+ .Returns((string x) => x.Replace(VirtualMetaDataPath, MetaDataPath, StringComparison.Ordinal));
+ appHost.Setup(x => x.ReverseVirtualPath(It.IsAny<string>()))
+ .Returns((string x) => x.Replace(MetaDataPath, VirtualMetaDataPath, StringComparison.Ordinal));
+
+ _fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
+ _fixture.Inject(appHost);
+ _sqliteItemRepository = _fixture.Create<SqliteItemRepository>();
+ }
+
+ public static IEnumerable<object[]> ItemImageInfoFromValueString_Valid_TestData()
+ {
+ yield return new object[]
+ {
+ "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN",
+ new ItemImageInfo()
+ {
+ Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg",
+ Type = ImageType.Primary,
+ DateModified = new DateTime(637452096478512963, DateTimeKind.Utc),
+ Width = 1920,
+ Height = 1080,
+ BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN"
+ }
+ };
+
+ yield return new object[]
+ {
+ "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0*Primary*0*0",
+ new ItemImageInfo()
+ {
+ Path = "https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg",
+ Type = ImageType.Primary,
+ }
+ };
+
+ yield return new object[]
+ {
+ "%MetadataPath%/library/68/68578562b96c80a7ebd530848801f645/poster.jpg*637264380567586027*Primary*600*336",
+ new ItemImageInfo()
+ {
+ Path = "/meta/data/path/library/68/68578562b96c80a7ebd530848801f645/poster.jpg",
+ Type = ImageType.Primary,
+ DateModified = new DateTime(637264380567586027, DateTimeKind.Utc),
+ Width = 600,
+ Height = 336
+ }
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(ItemImageInfoFromValueString_Valid_TestData))]
+ public void ItemImageInfoFromValueString_Valid_Success(string value, ItemImageInfo expected)
+ {
+ var result = _sqliteItemRepository.ItemImageInfoFromValueString(value);
+ Assert.Equal(expected.Path, result.Path);
+ Assert.Equal(expected.Type, result.Type);
+ Assert.Equal(expected.DateModified, result.DateModified);
+ Assert.Equal(expected.Width, result.Width);
+ Assert.Equal(expected.Height, result.Height);
+ Assert.Equal(expected.BlurHash, result.BlurHash);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("*")]
+ public void ItemImageInfoFromValueString_Invalid_Null(string value)
+ {
+ Assert.Null(_sqliteItemRepository.ItemImageInfoFromValueString(value));
+ }
+
+ public static IEnumerable<object[]> DeserializeImages_Valid_TestData()
+ {
+ yield return new object[]
+ {
+ "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN",
+ new ItemImageInfo[]
+ {
+ new ItemImageInfo()
+ {
+ Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg",
+ Type = ImageType.Primary,
+ DateModified = new DateTime(637452096478512963, DateTimeKind.Utc),
+ Width = 1920,
+ Height = 1080,
+ BlurHash = "WjQbtJtSO8nhNZ%L_Io#R*oaS6o}-;adXAoIn7j[%hW9s:WGw[nN"
+ }
+ }
+ };
+
+ yield return new object[]
+ {
+ "%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/poster.jpg*637261226720645297*Primary*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/logo.png*637261226720805297*Logo*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/landscape.jpg*637261226721285297*Thumb*0*0|%MetadataPath%/library/2a/2a27372f1e9bc757b1db99721bbeae1e/backdrop.jpg*637261226721685297*Backdrop*0*0",
+ new ItemImageInfo[]
+ {
+ new ItemImageInfo()
+ {
+ Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/poster.jpg",
+ Type = ImageType.Primary,
+ DateModified = new DateTime(637261226720645297, DateTimeKind.Utc),
+ },
+ new ItemImageInfo()
+ {
+ Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/logo.png",
+ Type = ImageType.Logo,
+ DateModified = new DateTime(637261226720805297, DateTimeKind.Utc),
+ },
+ new ItemImageInfo()
+ {
+ Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/landscape.jpg",
+ Type = ImageType.Thumb,
+ DateModified = new DateTime(637261226721285297, DateTimeKind.Utc),
+ },
+ new ItemImageInfo()
+ {
+ Path = "/meta/data/path/library/2a/2a27372f1e9bc757b1db99721bbeae1e/backdrop.jpg",
+ Type = ImageType.Backdrop,
+ DateModified = new DateTime(637261226721685297, DateTimeKind.Utc),
+ }
+ }
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(DeserializeImages_Valid_TestData))]
+ public void DeserializeImages_Valid_Success(string value, ItemImageInfo[] expected)
+ {
+ var result = _sqliteItemRepository.DeserializeImages(value);
+ Assert.Equal(expected.Length, result.Length);
+ for (int i = 0; i < expected.Length; i++)
+ {
+ Assert.Equal(expected[i].Path, result[i].Path);
+ Assert.Equal(expected[i].Type, result[i].Type);
+ Assert.Equal(expected[i].DateModified, result[i].DateModified);
+ Assert.Equal(expected[i].Width, result[i].Width);
+ Assert.Equal(expected[i].Height, result[i].Height);
+ Assert.Equal(expected[i].BlurHash, result[i].BlurHash);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(DeserializeImages_Valid_TestData))]
+ public void SerializeImages_Valid_Success(string expected, ItemImageInfo[] value)
+ {
+ Assert.Equal(expected, _sqliteItemRepository.SerializeImages(value));
+ }
+
+ public static IEnumerable<object[]> DeserializeProviderIds_Valid_TestData()
+ {
+ yield return new object[]
+ {
+ "Imdb=tt0119567",
+ new Dictionary<string, string>()
+ {
+ { "Imdb", "tt0119567" },
+ }
+ };
+
+ yield return new object[]
+ {
+ "Imdb=tt0119567|Tmdb=330|TmdbCollection=328",
+ new Dictionary<string, string>()
+ {
+ { "Imdb", "tt0119567" },
+ { "Tmdb", "330" },
+ { "TmdbCollection", "328" },
+ }
+ };
+
+ yield return new object[]
+ {
+ "MusicBrainzAlbum=9d363e43-f24f-4b39-bc5a-7ef305c677c7|MusicBrainzReleaseGroup=63eba062-847c-3b73-8b0f-6baf27bba6fa|AudioDbArtist=111352|AudioDbAlbum=2116560|MusicBrainzAlbumArtist=20244d07-534f-4eff-b4d4-930878889970",
+ new Dictionary<string, string>()
+ {
+ { "MusicBrainzAlbum", "9d363e43-f24f-4b39-bc5a-7ef305c677c7" },
+ { "MusicBrainzReleaseGroup", "63eba062-847c-3b73-8b0f-6baf27bba6fa" },
+ { "AudioDbArtist", "111352" },
+ { "AudioDbAlbum", "2116560" },
+ { "MusicBrainzAlbumArtist", "20244d07-534f-4eff-b4d4-930878889970" },
+ }
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(DeserializeProviderIds_Valid_TestData))]
+ public void DeserializeProviderIds_Valid_Success(string value, Dictionary<string, string> expected)
+ {
+ var result = new ProviderIdsExtensionsTestsObject();
+ SqliteItemRepository.DeserializeProviderIds(value, result);
+ Assert.Equal(expected, result.ProviderIds);
+ }
+
+ [Theory]
+ [MemberData(nameof(DeserializeProviderIds_Valid_TestData))]
+ public void SerializeProviderIds_Valid_Success(string expected, Dictionary<string, string> values)
+ {
+ Assert.Equal(expected, SqliteItemRepository.SerializeProviderIds(values));
+ }
+
+ private class ProviderIdsExtensionsTestsObject : IHasProviderIds
+ {
+ public Dictionary<string, string> ProviderIds { get; set; } = new Dictionary<string, string>();
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
index 671c59b2e..30e6542f9 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
@@ -1,3 +1,6 @@
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Runtime.InteropServices;
using AutoFixture;
using AutoFixture.AutoMoq;
using Emby.Server.Implementations.IO;
@@ -38,5 +41,36 @@ namespace Jellyfin.Server.Implementations.Tests.IO
Assert.Equal(expectedAbsolutePath, generatedPath);
}
}
+
+ [Theory]
+ [InlineData("ValidFileName", "ValidFileName")]
+ [InlineData("AC/DC", "AC DC")]
+ [InlineData("Invalid\0", "Invalid ")]
+ [InlineData("AC/DC\0KD/A", "AC DC KD A")]
+ public void GetValidFilename_ReturnsValidFilename(string filename, string expectedFileName)
+ {
+ Assert.Equal(expectedFileName, _sut.GetValidFilename(filename));
+ }
+
+ [SkippableFact]
+ public void GetFileInfo_DanglingSymlink_ExistsFalse()
+ {
+ Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+
+ string testFileDir = Path.Combine(Path.GetTempPath(), "jellyfin-test-data");
+ string testFileName = Path.Combine(testFileDir, Path.GetRandomFileName() + "-danglingsym.link");
+
+ Directory.CreateDirectory(testFileDir);
+ Assert.Equal(0, symlink("thispathdoesntexist", testFileName));
+ Assert.True(File.Exists(testFileName));
+
+ var metadata = _sut.GetFileInfo(testFileName);
+ Assert.False(metadata.Exists);
+ }
+
+ [SuppressMessage("Naming Rules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Have to")]
+ [DllImport("libc", SetLastError = true, CharSet = CharSet.Ansi)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)]
+ private static extern int symlink(string target, string linkpath);
}
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index fe26ec3ae..27713d58a 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -22,12 +22,13 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
+ <PackageReference Include="AutoFixture" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
</ItemGroup>
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
index 876519215..c393742eb 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
@@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
using Moq;
using Xunit;
@@ -28,7 +29,10 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
Parent = parent,
CollectionType = CollectionType.TvShows,
- Path = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv"
+ FileInfo = new FileSystemMetadata()
+ {
+ FullName = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv"
+ }
};
Assert.Null(episodeResolver.Resolve(itemResolveArgs));
@@ -48,7 +52,10 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
Parent = series,
CollectionType = CollectionType.TvShows,
- Path = "Extras/Extras S01E01.mkv"
+ FileInfo = new FileSystemMetadata()
+ {
+ FullName = "Extras/Extras S01E01.mkv"
+ }
};
Assert.NotNull(episodeResolver.Resolve(itemResolveArgs));
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
index 7e04a1ec1..fd499d9cf 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Text;
using Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun;
using Xunit;
@@ -17,8 +18,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
Span<byte> buffer = stackalloc byte[128];
int len = HdHomerunManager.WriteNullTerminatedString(buffer, string.Empty);
- Assert.Equal(expected.Length, len);
- Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
}
[Fact]
@@ -32,8 +34,9 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
Span<byte> buffer = stackalloc byte[128];
int len = HdHomerunManager.WriteNullTerminatedString(buffer, "The quick");
- Assert.Equal(expected.Length, len);
- Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
}
[Fact]
@@ -51,8 +54,273 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
Span<byte> buffer = stackalloc byte[128];
int len = HdHomerunManager.WriteGetMessage(buffer, 0, "N");
- Assert.Equal(expected.Length, len);
- Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
+ }
+
+ [Fact]
+ public void WriteSetMessage_NoLockKey_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 0, 4,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xa9, 0x49, 0xd0, 0x68
+ };
+
+ Span<byte> buffer = stackalloc byte[128];
+ int len = HdHomerunManager.WriteSetMessage(buffer, 0, "N", "value", null);
+
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
+ }
+
+ [Fact]
+ public void WriteSetMessage_LockKey_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 0, 4,
+ 0, 26,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 21,
+ 4, 0x00, 0x01, 0x38, 0xd5,
+ 0x8e, 0xb6, 0x06, 0x82
+ };
+
+ Span<byte> buffer = stackalloc byte[128];
+ int len = HdHomerunManager.WriteSetMessage(buffer, 0, "N", "value", 80085);
+
+ Assert.Equal(
+ Convert.ToHexString(expected),
+ Convert.ToHexString(buffer.Slice(0, len)));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_Valid_Success()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.True(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out var value));
+ Assert.Equal("value", Encoding.UTF8.GetString(value));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidCrc_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf4
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidPacketType_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 4,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xa9, 0x49, 0xd0, 0x68
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidPacket_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 0x7d, 0xa3, 0xa3
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooSmallMessageLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 19,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x25, 0x25, 0x44, 0x9a
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooLargeMessageLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 21,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xe3, 0x20, 0x79, 0x6c
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooLargeNameLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 20, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xe1, 0x8e, 0x9c, 0x74
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidGetSetNameTag_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 4,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xee, 0x05, 0xe7, 0x12
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_InvalidGetSetValueTag_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 3,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x64, 0xaa, 0x66, 0xf9
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void TryGetReturnValueOfGetSet_TooLargeValueLength_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 7, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0xc9, 0xa8, 0xd4, 0x55
+ };
+
+ Assert.False(HdHomerunManager.TryGetReturnValueOfGetSet(packet, out _));
+ }
+
+ [Fact]
+ public void VerifyReturnValueOfGetSet_Valid_True()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.True(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "value"));
+ }
+
+ [Fact]
+ public void VerifyReturnValueOfGetSet_WrongValue_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 5,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.False(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "none"));
+ }
+
+ [Fact]
+ public void VerifyReturnValueOfGetSet_InvalidPacket_False()
+ {
+ ReadOnlySpan<byte> packet = new byte[]
+ {
+ 0, 4,
+ 0, 20,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 4,
+ 6, (byte)'v', (byte)'a', (byte)'l', (byte)'u', (byte)'e', 0,
+ 0x7d, 0xa3, 0xa3, 0xf3
+ };
+
+ Assert.False(HdHomerunManager.VerifyReturnValueOfGetSet(packet, "value"));
}
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
new file mode 100644
index 000000000..ea6838682
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net.Mime;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models.StartupDtos;
+using Jellyfin.Api.Models.UserDtos;
+using MediaBrowser.Common.Json;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ public static class AuthHelper
+ {
+ public const string AuthHeaderName = "X-Emby-Authorization";
+ public const string DummyAuthHeader = "MediaBrowser Client=\"Jellyfin.Server Integration Tests\", DeviceId=\"69420\", Device=\"Apple II\", Version=\"10.8.0\"";
+
+ public static async Task<string> CompleteStartupAsync(HttpClient client)
+ {
+ var jsonOptions = JsonDefaults.Options;
+ var userResponse = await client.GetByteArrayAsync("/Startup/User").ConfigureAwait(false);
+ var user = JsonSerializer.Deserialize<StartupUserDto>(userResponse, jsonOptions);
+
+ using var completeResponse = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty<byte>())).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, completeResponse.StatusCode);
+
+ using var content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(
+ new AuthenticateUserByName()
+ {
+ Username = user!.Name,
+ Pw = user.Password,
+ },
+ jsonOptions));
+ content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ content.Headers.Add("X-Emby-Authorization", DummyAuthHeader);
+
+ using var authResponse = await client.PostAsync("/Users/AuthenticateByName", content).ConfigureAwait(false);
+ var auth = await JsonSerializer.DeserializeAsync<AuthenticationResultDto>(
+ await authResponse.Content.ReadAsStreamAsync().ConfigureAwait(false),
+ jsonOptions).ConfigureAwait(false);
+
+ return auth!.AccessToken;
+ }
+
+ public static void AddAuthHeader(this HttpHeaders headers, string accessToken)
+ {
+ headers.Add(AuthHeaderName, DummyAuthHeader + $", Token={accessToken}");
+ }
+
+ private class AuthenticationResultDto
+ {
+ public string AccessToken { get; set; } = string.Empty;
+
+ public string ServerId { get; set; } = string.Empty;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs
new file mode 100644
index 000000000..be89fbc9a
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs
@@ -0,0 +1,30 @@
+using System.Net;
+using System.Net.Mime;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers
+{
+ public sealed class ActivityLogControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+ private static string? _accessToken;
+
+ public ActivityLogControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task ActivityLog_GetEntries_Ok()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ var response = await client.GetAsync("System/ActivityLog/Entries").ConfigureAwait(false);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
new file mode 100644
index 000000000..6584490de
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net.Mime;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models.UserDtos;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Model.Dto;
+using Xunit;
+using Xunit.Priority;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers
+{
+ [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+ public sealed class UserControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private const string TestUsername = "testUser01";
+
+ private readonly JellyfinApplicationFactory _factory;
+ private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.Options;
+ private static string? _accessToken;
+ private static Guid _testUserId = Guid.Empty;
+
+ public UserControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ private Task<HttpResponseMessage> CreateUserByName(HttpClient httpClient, CreateUserByName request)
+ {
+ using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions));
+ postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ return httpClient.PostAsync("Users/New", postContent);
+ }
+
+ private Task<HttpResponseMessage> UpdateUserPassword(HttpClient httpClient, Guid userId, UpdateUserPassword request)
+ {
+ using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(request, _jsonOpions));
+ postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ return httpClient.PostAsync("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Password", postContent);
+ }
+
+ [Fact]
+ [Priority(-1)]
+ public async Task GetPublicUsers_Valid_Success()
+ {
+ var client = _factory.CreateClient();
+
+ using var response = await client.GetAsync("Users/Public").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
+ await response.Content.ReadAsStreamAsync().ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
+ // User are hidden by default
+ Assert.Empty(users);
+ }
+
+ [Fact]
+ [Priority(-1)]
+ public async Task GetUsers_Valid_Success()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ using var response = await client.GetAsync("Users").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
+ await response.Content.ReadAsStreamAsync().ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
+ Assert.Single(users);
+ Assert.False(users![0].HasConfiguredPassword);
+ }
+
+ [Fact]
+ [Priority(0)]
+ public async Task New_Valid_Success()
+ {
+ var client = _factory.CreateClient();
+
+ // access token can't be null here as the previous test populated it
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
+
+ var createRequest = new CreateUserByName()
+ {
+ Name = TestUsername
+ };
+
+ using var response = await CreateUserByName(client, createRequest).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var user = await JsonSerializer.DeserializeAsync<UserDto>(
+ await response.Content.ReadAsStreamAsync().ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
+ Assert.Equal(TestUsername, user!.Name);
+ Assert.False(user.HasPassword);
+ Assert.False(user.HasConfiguredPassword);
+
+ _testUserId = user.Id;
+
+ Console.WriteLine(user.Id.ToString("N", CultureInfo.InvariantCulture));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData("‼️")]
+ [Priority(0)]
+ public async Task New_Invalid_Fail(string? username)
+ {
+ var client = _factory.CreateClient();
+
+ // access token can't be null here as the previous test populated it
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
+
+ var createRequest = new CreateUserByName()
+ {
+ Name = username
+ };
+
+ using var response = await CreateUserByName(client, createRequest).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ }
+
+ [Fact]
+ [Priority(1)]
+ public async Task UpdateUserPassword_Valid_Success()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
+
+ var createRequest = new UpdateUserPassword()
+ {
+ NewPw = "4randomPa$$word"
+ };
+
+ using var response = await UpdateUserPassword(client, _testUserId, createRequest).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+
+ var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
+ await client.GetStreamAsync("Users").ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
+ var user = users!.First(x => x.Id == _testUserId);
+ Assert.True(user.HasPassword);
+ Assert.True(user.HasConfiguredPassword);
+ }
+
+ [Fact]
+ [Priority(2)]
+ public async Task UpdateUserPassword_Empty_RemoveSetPassword()
+ {
+ var client = _factory.CreateClient();
+
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
+
+ var createRequest = new UpdateUserPassword()
+ {
+ CurrentPw = "4randomPa$$word",
+ };
+
+ using var response = await UpdateUserPassword(client, _testUserId, createRequest).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+
+ var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
+ await client.GetStreamAsync("Users").ConfigureAwait(false), _jsonOpions).ConfigureAwait(false);
+ var user = users!.First(x => x.Id == _testUserId);
+ Assert.False(user.HasPassword);
+ Assert.False(user.HasConfiguredPassword);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
index 079021e61..938385a2a 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -9,10 +9,10 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" />
+ <PackageReference Include="AutoFixture" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
@@ -22,6 +22,13 @@
<PackageReference Include="Moq" Version="4.16.0" />
</ItemGroup>
+ <ItemGroup>
+ <!-- Don't run tests in parallel -->
+ <None Update="xunit.runner.json">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
+
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
diff --git a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
index 3cbd638f9..0ade345a1 100644
--- a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
@@ -1,8 +1,6 @@
using System.IO;
using System.Reflection;
-using System.Text.Json;
using System.Threading.Tasks;
-using MediaBrowser.Model.Branding;
using Xunit;
using Xunit.Abstractions;
diff --git a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
index 4e5d0fcb6..0a463cfa3 100644
--- a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Reflection;
using Emby.Server.Implementations;
-using Jellyfin.Server;
using MediaBrowser.Controller;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Configuration;
diff --git a/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs b/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs
new file mode 100644
index 000000000..ffdc04eba
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ public sealed class WebSocketTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+
+ public WebSocketTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task WebSocket_Unauthenticated_ThrowsInvalidOperationException()
+ {
+ var server = _factory.Server;
+ var client = server.CreateWebSocketClient();
+
+ await Assert.ThrowsAsync<InvalidOperationException>(
+ () => client.ConnectAsync(
+ new UriBuilder(server.BaseAddress)
+ {
+ Scheme = "ws",
+ Path = "websocket"
+ }.Uri, CancellationToken.None));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json b/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json
new file mode 100644
index 000000000..809e880c7
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/xunit.runner.json
@@ -0,0 +1,4 @@
+{
+ "parallelizeAssembly": false,
+ "parallelizeTestCollections": false
+}
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index 3ba49ff6a..72e40ebcb 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -10,10 +10,10 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" />
+ <PackageReference Include="AutoFixture" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
index 0b714e80a..146b16cf9 100644
--- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
+++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
@@ -1,4 +1,3 @@
-using System;
using System.Globalization;
using System.Text;
using Jellyfin.Networking.Configuration;
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
index 3d8e13e96..8ca3dd96e 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Linq;
using System.Threading;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities.Audio;