repo creation with initial code after cloning public repo

This commit is contained in:
2026-02-19 07:36:25 -05:00
commit 460884fea3
2860 changed files with 650825 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
<Project>
<!-- Sets defaults for all test projects -->
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<!-- Code Analyzers -->
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" />
</ItemGroup>
</Project>
@@ -0,0 +1,158 @@
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using AutoFixture;
using AutoFixture.AutoMoq;
using Jellyfin.Api.Auth;
using Jellyfin.Api.Constants;
using Jellyfin.Data;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Jellyfin.Api.Tests.Auth
{
public class CustomAuthenticationHandlerTests
{
private readonly IFixture _fixture;
private readonly Mock<IAuthService> _jellyfinAuthServiceMock;
private readonly CustomAuthenticationHandler _sut;
private readonly AuthenticationScheme _scheme;
public CustomAuthenticationHandlerTests()
{
var fixtureCustomizations = new AutoMoqCustomization
{
ConfigureMembers = true
};
_fixture = new Fixture().Customize(fixtureCustomizations);
AllowFixtureCircularDependencies();
_jellyfinAuthServiceMock = _fixture.Freeze<Mock<IAuthService>>();
var optionsMonitorMock = _fixture.Freeze<Mock<IOptionsMonitor<AuthenticationSchemeOptions>>>();
var serviceProviderMock = _fixture.Freeze<Mock<IServiceProvider>>();
var authenticationServiceMock = _fixture.Freeze<Mock<IAuthenticationService>>();
_fixture.Register<ILoggerFactory>(() => new NullLoggerFactory());
serviceProviderMock.Setup(s => s.GetService(typeof(IAuthenticationService)))
.Returns(authenticationServiceMock.Object);
optionsMonitorMock.Setup(o => o.Get(It.IsAny<string>()))
.Returns(new AuthenticationSchemeOptions
{
ForwardAuthenticate = null
});
HttpContext context = new DefaultHttpContext
{
RequestServices = serviceProviderMock.Object
};
_scheme = new AuthenticationScheme(
_fixture.Create<string>(),
null,
typeof(CustomAuthenticationHandler));
_sut = _fixture.Create<CustomAuthenticationHandler>();
_sut.InitializeAsync(_scheme, context).Wait();
}
[Fact]
public async Task HandleAuthenticateAsyncShouldProvideNoResultOnAuthenticationException()
{
var errorMessage = _fixture.Create<string>();
_jellyfinAuthServiceMock.Setup(
a => a.Authenticate(
It.IsAny<HttpRequest>()))
.Throws(new AuthenticationException(errorMessage));
var authenticateResult = await _sut.AuthenticateAsync();
Assert.False(authenticateResult.Succeeded);
Assert.True(authenticateResult.None);
}
[Fact]
public async Task HandleAuthenticateAsyncShouldSucceedWithUser()
{
SetupUser();
var authenticateResult = await _sut.AuthenticateAsync();
Assert.True(authenticateResult.Succeeded);
Assert.Null(authenticateResult.Failure);
}
[Fact]
public async Task HandleAuthenticateAsyncShouldAssignNameClaim()
{
var authorizationInfo = SetupUser();
var authenticateResult = await _sut.AuthenticateAsync();
Assert.NotNull(authorizationInfo.User);
Assert.True(authenticateResult.Principal?.HasClaim(ClaimTypes.Name, authorizationInfo.User.Username));
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task HandleAuthenticateAsyncShouldAssignRoleClaim(bool isAdmin)
{
var authorizationInfo = SetupUser(isAdmin);
var authenticateResult = await _sut.AuthenticateAsync();
Assert.NotNull(authorizationInfo.User);
var expectedRole = authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User;
Assert.True(authenticateResult.Principal?.HasClaim(ClaimTypes.Role, expectedRole));
}
[Fact]
public async Task HandleAuthenticateAsyncShouldAssignTicketCorrectScheme()
{
SetupUser();
var authenticatedResult = await _sut.AuthenticateAsync();
Assert.Equal(_scheme.Name, authenticatedResult.Ticket?.AuthenticationScheme);
}
private AuthorizationInfo SetupUser(bool isAdmin = false)
{
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;
authorizationInfo.Token = "fake-token";
_jellyfinAuthServiceMock.Setup(
a => a.Authenticate(
It.IsAny<HttpRequest>()))
.Returns(Task.FromResult(authorizationInfo));
return authorizationInfo;
}
private void AllowFixtureCircularDependencies()
{
// A circular dependency exists in the User entity around parent folders,
// this allows Autofixture to generate a User regardless, rather than throw
// an error.
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
.ForEach(b => _fixture.Behaviors.Remove(b));
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
}
}
}
@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
using AutoFixture;
using AutoFixture.AutoMoq;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Constants;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Server.Implementations.Security;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Moq;
using Xunit;
namespace Jellyfin.Api.Tests.Auth.DefaultAuthorizationPolicy
{
public class DefaultAuthorizationHandlerTests
{
private readonly Mock<IConfigurationManager> _configurationManagerMock;
private readonly List<IAuthorizationRequirement> _requirements;
private readonly DefaultAuthorizationHandler _sut;
private readonly Mock<IUserManager> _userManagerMock;
private readonly Mock<IHttpContextAccessor> _httpContextAccessor;
public DefaultAuthorizationHandlerTests()
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
_configurationManagerMock = fixture.Freeze<Mock<IConfigurationManager>>();
_requirements = new List<IAuthorizationRequirement> { new DefaultAuthorizationRequirement() };
_userManagerMock = fixture.Freeze<Mock<IUserManager>>();
_httpContextAccessor = fixture.Freeze<Mock<IHttpContextAccessor>>();
_sut = fixture.Create<DefaultAuthorizationHandler>();
}
[Theory]
[InlineData(UserRoles.Administrator)]
[InlineData(UserRoles.Guest)]
[InlineData(UserRoles.User)]
public async Task ShouldSucceedOnUser(string userRole)
{
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
var claims = TestHelpers.SetupUser(
_userManagerMock,
_httpContextAccessor,
userRole);
var context = new AuthorizationHandlerContext(_requirements, claims, null);
await _sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Fact]
public async Task ShouldSucceedOnApiKey()
{
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
_httpContextAccessor
.Setup(h => h.HttpContext!.Connection.RemoteIpAddress)
.Returns(new IPAddress(0));
_userManagerMock
.Setup(u => u.GetUserById(It.IsAny<Guid>()))
.Returns<User?>(null);
var claims = new[]
{
new Claim(InternalClaimTypes.IsApiKey, bool.TrueString)
};
var identity = new ClaimsIdentity(claims, string.Empty);
var principal = new ClaimsPrincipal(identity);
var context = new AuthorizationHandlerContext(_requirements, principal, null);
await _sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory]
[MemberData(nameof(GetParts_ValidAuthHeader_Success_Data))]
public void GetParts_ValidAuthHeader_Success(string input, Dictionary<string, string> parts)
{
var dict = AuthorizationContext.GetParts(input);
foreach (var (key, value) in parts)
{
Assert.Equal(dict[key], value);
}
}
public static TheoryData<string, Dictionary<string, string>> GetParts_ValidAuthHeader_Success_Data()
{
var data = new TheoryData<string, Dictionary<string, string>>();
data.Add(
"x=\"123,123\",y=\"123\"",
new Dictionary<string, string>
{
{ "x", "123,123" },
{ "y", "123" }
});
data.Add(
"x=\"123,123\", y=\"123\",z=\"'hi'\"",
new Dictionary<string, string>
{
{ "x", "123,123" },
{ "y", "123" },
{ "z", "'hi'" }
});
data.Add(
"x=\"ab\"",
new Dictionary<string, string>
{
{ "x", "ab" }
});
data.Add(
"param=Hörbücher",
new Dictionary<string, string>
{
{ "param", "Hörbücher" }
});
data.Add(
"param=%22%Hörbücher",
new Dictionary<string, string>
{
{ "param", "\"%Hörbücher" }
});
return data;
}
}
}
@@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using AutoFixture;
using AutoFixture.AutoMoq;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Auth.FirstTimeSetupPolicy;
using Jellyfin.Api.Constants;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace Jellyfin.Api.Tests.Auth.FirstTimeSetupPolicy
{
public class FirstTimeSetupHandlerTests
{
private readonly Mock<IConfigurationManager> _configurationManagerMock;
private readonly List<IAuthorizationRequirement> _requirements;
private readonly DefaultAuthorizationHandler _defaultAuthorizationHandler;
private readonly FirstTimeSetupHandler _firstTimeSetupHandler;
private readonly IAuthorizationService _authorizationService;
private readonly Mock<IUserManager> _userManagerMock;
private readonly Mock<IHttpContextAccessor> _httpContextAccessor;
public FirstTimeSetupHandlerTests()
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
_configurationManagerMock = fixture.Freeze<Mock<IConfigurationManager>>();
_requirements = new List<IAuthorizationRequirement> { new FirstTimeSetupRequirement() };
_userManagerMock = fixture.Freeze<Mock<IUserManager>>();
_httpContextAccessor = fixture.Freeze<Mock<IHttpContextAccessor>>();
_firstTimeSetupHandler = fixture.Create<FirstTimeSetupHandler>();
_defaultAuthorizationHandler = fixture.Create<DefaultAuthorizationHandler>();
var services = new ServiceCollection();
services.AddAuthorizationCore();
services.AddLogging();
services.AddOptions();
services.AddSingleton<IAuthorizationHandler>(_defaultAuthorizationHandler);
services.AddSingleton<IAuthorizationHandler>(_firstTimeSetupHandler);
services.AddAuthorization(options =>
{
options.AddPolicy("FirstTime", policy => policy.Requirements.Add(new FirstTimeSetupRequirement()));
options.AddPolicy("FirstTimeNoAdmin", policy => policy.Requirements.Add(new FirstTimeSetupRequirement(false, false)));
options.AddPolicy("FirstTimeSchedule", policy => policy.Requirements.Add(new FirstTimeSetupRequirement(true, false)));
});
_authorizationService = services.BuildServiceProvider().GetRequiredService<IAuthorizationService>();
}
[Theory]
[InlineData(UserRoles.Administrator)]
[InlineData(UserRoles.Guest)]
[InlineData(UserRoles.User)]
public async Task ShouldSucceedIfStartupWizardIncomplete(string userRole)
{
TestHelpers.SetupConfigurationManager(_configurationManagerMock, false);
var claims = TestHelpers.SetupUser(
_userManagerMock,
_httpContextAccessor,
userRole);
var allowed = await _authorizationService.AuthorizeAsync(claims, "FirstTime");
Assert.True(allowed.Succeeded);
}
[Theory]
[InlineData(UserRoles.Administrator, true)]
[InlineData(UserRoles.Guest, false)]
[InlineData(UserRoles.User, false)]
public async Task ShouldRequireAdministratorIfStartupWizardComplete(string userRole, bool shouldSucceed)
{
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
var claims = TestHelpers.SetupUser(
_userManagerMock,
_httpContextAccessor,
userRole);
var allowed = await _authorizationService.AuthorizeAsync(claims, "FirstTime");
Assert.Equal(shouldSucceed, allowed.Succeeded);
}
[Theory]
[InlineData(UserRoles.Administrator, true)]
[InlineData(UserRoles.Guest, false)]
[InlineData(UserRoles.User, true)]
public async Task ShouldRequireUserIfNotAdministrator(string userRole, bool shouldSucceed)
{
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
var claims = TestHelpers.SetupUser(
_userManagerMock,
_httpContextAccessor,
userRole);
var allowed = await _authorizationService.AuthorizeAsync(claims, "FirstTimeNoAdmin");
Assert.Equal(shouldSucceed, allowed.Succeeded);
}
[Fact]
public async Task ShouldDisallowUserIfOutsideSchedule()
{
AccessSchedule[] accessSchedules = { new AccessSchedule(DynamicDayOfWeek.Everyday, 0, 0, Guid.Empty) };
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
var claims = TestHelpers.SetupUser(
_userManagerMock,
_httpContextAccessor,
UserRoles.User,
accessSchedules);
var allowed = await _authorizationService.AuthorizeAsync(claims, "FirstTimeSchedule");
Assert.False(allowed.Succeeded);
}
}
}
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AutoFixture;
using AutoFixture.AutoMoq;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Constants;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Moq;
using Xunit;
namespace Jellyfin.Api.Tests.Auth.IgnoreSchedulePolicy
{
public class IgnoreScheduleHandlerTests
{
private readonly Mock<IConfigurationManager> _configurationManagerMock;
private readonly List<IAuthorizationRequirement> _requirements;
private readonly DefaultAuthorizationHandler _sut;
private readonly Mock<IUserManager> _userManagerMock;
private readonly Mock<IHttpContextAccessor> _httpContextAccessor;
/// <summary>
/// Globally disallow access.
/// </summary>
private readonly AccessSchedule[] _accessSchedules = { new AccessSchedule(DynamicDayOfWeek.Everyday, 0, 0, Guid.Empty) };
public IgnoreScheduleHandlerTests()
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
_configurationManagerMock = fixture.Freeze<Mock<IConfigurationManager>>();
_requirements = new List<IAuthorizationRequirement> { new DefaultAuthorizationRequirement(validateParentalSchedule: false) };
_userManagerMock = fixture.Freeze<Mock<IUserManager>>();
_httpContextAccessor = fixture.Freeze<Mock<IHttpContextAccessor>>();
_sut = fixture.Create<DefaultAuthorizationHandler>();
}
[Theory]
[InlineData(UserRoles.Administrator, true)]
[InlineData(UserRoles.User, true)]
[InlineData(UserRoles.Guest, true)]
public async Task ShouldAllowScheduleCorrectly(string role, bool shouldSucceed)
{
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
var claims = TestHelpers.SetupUser(
_userManagerMock,
_httpContextAccessor,
role,
_accessSchedules);
var context = new AuthorizationHandlerContext(_requirements, claims, null);
await _sut.HandleAsync(context);
Assert.Equal(shouldSucceed, context.HasSucceeded);
}
}
}
@@ -0,0 +1,45 @@
using System;
using Jellyfin.Api.Controllers;
using Xunit;
namespace Jellyfin.Api.Tests.Controllers
{
public class DynamicHlsControllerTests
{
[Theory]
[MemberData(nameof(GetSegmentLengths_Success_TestData))]
public void GetSegmentLengths_Success(long runtimeTicks, int segmentlength, double[] expected)
{
var res = DynamicHlsController.GetSegmentLengthsInternal(runtimeTicks, segmentlength);
Assert.Equal(expected.Length, res.Length);
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], res[i]);
}
}
public static TheoryData<long, int, double[]> GetSegmentLengths_Success_TestData()
{
var data = new TheoryData<long, int, double[]>();
data.Add(0, 6, Array.Empty<double>());
data.Add(
TimeSpan.FromSeconds(3).Ticks,
6,
new double[] { 3 });
data.Add(
TimeSpan.FromSeconds(6).Ticks,
6,
new double[] { 6 });
data.Add(
TimeSpan.FromSeconds(3.3333333).Ticks,
6,
new double[] { 3.3333333 });
data.Add(
TimeSpan.FromSeconds(9.3333333).Ticks,
6,
new double[] { 6, 3.3333333 });
return data;
}
}
}
@@ -0,0 +1,35 @@
using Jellyfin.Api.Controllers;
using Xunit;
namespace Jellyfin.Api.Tests.Controllers;
public static class ImageControllerTests
{
[Theory]
[InlineData("image/apng", ".apng")]
[InlineData("image/avif", ".avif")]
[InlineData("image/bmp", ".bmp")]
[InlineData("image/gif", ".gif")]
[InlineData("image/x-icon", ".ico")]
[InlineData("image/jpeg", ".jpg")]
[InlineData("image/png", ".png")]
[InlineData("image/png; charset=utf-8", ".png")]
[InlineData("image/svg+xml", ".svg")]
[InlineData("image/tiff", ".tiff")]
[InlineData("image/webp", ".webp")]
public static void TryGetImageExtensionFromContentType_Valid_True(string contentType, string extension)
{
Assert.True(ImageController.TryGetImageExtensionFromContentType(contentType, out var ex));
Assert.Equal(extension, ex);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("text/html")]
public static void TryGetImageExtensionFromContentType_InValid_False(string? contentType)
{
Assert.False(ImageController.TryGetImageExtensionFromContentType(contentType, out var ex));
Assert.Null(ex);
}
}
@@ -0,0 +1,36 @@
using Jellyfin.Api.Controllers;
using Jellyfin.Server.Implementations.SystemBackupService;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace Jellyfin.Api.Tests.Controllers
{
public class SystemControllerTests
{
[Fact]
public void GetLogFile_FileDoesNotExist_ReturnsNotFound()
{
var mockFileSystem = new Mock<IFileSystem>();
mockFileSystem
.Setup(fs => fs.GetFiles(It.IsAny<string>(), It.IsAny<bool>()))
.Returns([new() { Name = "file1.txt" }, new() { Name = "file2.txt" }]);
var controller = new SystemController(
Mock.Of<ILogger<SystemController>>(),
Mock.Of<IServerApplicationHost>(),
Mock.Of<IServerApplicationPaths>(),
mockFileSystem.Object,
Mock.Of<INetworkManager>(),
Mock.Of<ISystemManager>());
var result = controller.GetLogFile("DOES_NOT_EXIST.txt");
Assert.IsType<NotFoundObjectResult>(result);
}
}
}
@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using AutoFixture.Xunit2;
using Jellyfin.Api.Controllers;
using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.QuickConnect;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Users;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using Nikse.SubtitleEdit.Core.Common;
using Xunit;
namespace Jellyfin.Api.Tests.Controllers;
public class UserControllerTests
{
private readonly UserController _subject;
private readonly Mock<IUserManager> _mockUserManager;
private readonly Mock<ISessionManager> _mockSessionManager;
private readonly Mock<INetworkManager> _mockNetworkManager;
private readonly Mock<IDeviceManager> _mockDeviceManager;
private readonly Mock<IAuthorizationContext> _mockAuthorizationContext;
private readonly Mock<IServerConfigurationManager> _mockServerConfigurationManager;
private readonly Mock<ILogger<UserController>> _mockLogger;
private readonly Mock<IQuickConnect> _mockQuickConnect;
private readonly Mock<IPlaylistManager> _mockPlaylistManager;
public UserControllerTests()
{
_mockUserManager = new Mock<IUserManager>();
_mockSessionManager = new Mock<ISessionManager>();
_mockNetworkManager = new Mock<INetworkManager>();
_mockDeviceManager = new Mock<IDeviceManager>();
_mockAuthorizationContext = new Mock<IAuthorizationContext>();
_mockServerConfigurationManager = new Mock<IServerConfigurationManager>();
_mockLogger = new Mock<ILogger<UserController>>();
_mockQuickConnect = new Mock<IQuickConnect>();
_mockPlaylistManager = new Mock<IPlaylistManager>();
_subject = new UserController(
_mockUserManager.Object,
_mockSessionManager.Object,
_mockNetworkManager.Object,
_mockDeviceManager.Object,
_mockAuthorizationContext.Object,
_mockServerConfigurationManager.Object,
_mockLogger.Object,
_mockQuickConnect.Object,
_mockPlaylistManager.Object);
}
[Theory]
[AutoData]
public async Task UpdateUserPolicy_WhenUserNotFound_ReturnsNotFound(Guid userId, UserPolicy userPolicy)
{
User? nullUser = null;
_mockUserManager.
Setup(m => m.GetUserById(userId))
.Returns(nullUser);
Assert.IsType<NotFoundResult>(await _subject.UpdateUserPolicy(userId, userPolicy));
}
[Theory]
[InlineAutoData(null)]
[InlineAutoData("")]
[InlineAutoData(" ")]
public void UpdateUserPolicy_WhenPasswordResetProviderIdNotSupplied_ReturnsBadRequest(string? passwordResetProviderId)
{
var userPolicy = new UserPolicy
{
PasswordResetProviderId = passwordResetProviderId,
AuthenticationProviderId = "AuthenticationProviderId"
};
Assert.Contains(
Validate(userPolicy), v =>
v.MemberNames.Contains("PasswordResetProviderId") &&
v.ErrorMessage is not null &&
v.ErrorMessage.Contains("required", StringComparison.CurrentCultureIgnoreCase));
}
[Theory]
[InlineAutoData(null)]
[InlineAutoData("")]
[InlineAutoData(" ")]
public void UpdateUserPolicy_WhenAuthenticationProviderIdNotSupplied_ReturnsBadRequest(string? authenticationProviderId)
{
var userPolicy = new UserPolicy
{
AuthenticationProviderId = authenticationProviderId,
PasswordResetProviderId = "PasswordResetProviderId"
};
Assert.Contains(Validate(userPolicy), v =>
v.MemberNames.Contains("AuthenticationProviderId") &&
v.ErrorMessage is not null &&
v.ErrorMessage.Contains("required", StringComparison.CurrentCultureIgnoreCase));
}
private List<ValidationResult> Validate(object model)
{
var result = new List<ValidationResult>();
var context = new ValidationContext(model, null, null);
Validator.TryValidateObject(model, context, result, true);
return result;
}
}
@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Claims;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Net;
using Xunit;
namespace Jellyfin.Api.Tests.Helpers
{
public static class RequestHelpersTests
{
[Theory]
[MemberData(nameof(GetOrderBy_Success_TestData))]
public static void GetOrderBy_Success(IReadOnlyList<ItemSortBy> sortBy, IReadOnlyList<SortOrder> requestedSortOrder, (ItemSortBy, SortOrder)[] expected)
{
Assert.Equal(expected, RequestHelpers.GetOrderBy(sortBy, requestedSortOrder));
}
[Fact]
public static void GetUserId_IsAdmin()
{
Guid? requestUserId = Guid.NewGuid();
Guid? authUserId = Guid.NewGuid();
var claims = new[]
{
new Claim(InternalClaimTypes.UserId, authUserId.Value.ToString("N", CultureInfo.InvariantCulture)),
new Claim(InternalClaimTypes.IsApiKey, bool.FalseString),
new Claim(ClaimTypes.Role, UserRoles.Administrator)
};
var identity = new ClaimsIdentity(claims, string.Empty);
var principal = new ClaimsPrincipal(identity);
var userId = RequestHelpers.GetUserId(principal, requestUserId);
Assert.Equal(requestUserId, userId);
}
[Fact]
public static void GetUserId_IsApiKey_EmptyGuid()
{
Guid? requestUserId = Guid.Empty;
var claims = new[]
{
new Claim(InternalClaimTypes.IsApiKey, bool.TrueString)
};
var identity = new ClaimsIdentity(claims, string.Empty);
var principal = new ClaimsPrincipal(identity);
var userId = RequestHelpers.GetUserId(principal, requestUserId);
Assert.Equal(Guid.Empty, userId);
}
[Fact]
public static void GetUserId_IsApiKey_Null()
{
Guid? requestUserId = null;
var claims = new[]
{
new Claim(InternalClaimTypes.IsApiKey, bool.TrueString)
};
var identity = new ClaimsIdentity(claims, string.Empty);
var principal = new ClaimsPrincipal(identity);
var userId = RequestHelpers.GetUserId(principal, requestUserId);
Assert.Equal(Guid.Empty, userId);
}
[Fact]
public static void GetUserId_IsUser()
{
Guid? requestUserId = Guid.NewGuid();
Guid? authUserId = Guid.NewGuid();
var claims = new[]
{
new Claim(InternalClaimTypes.UserId, authUserId.Value.ToString("N", CultureInfo.InvariantCulture)),
new Claim(InternalClaimTypes.IsApiKey, bool.FalseString),
new Claim(ClaimTypes.Role, UserRoles.User)
};
var identity = new ClaimsIdentity(claims, string.Empty);
var principal = new ClaimsPrincipal(identity);
Assert.Throws<SecurityException>(() => RequestHelpers.GetUserId(principal, requestUserId));
}
public static TheoryData<IReadOnlyList<ItemSortBy>, IReadOnlyList<SortOrder>, (ItemSortBy, SortOrder)[]> GetOrderBy_Success_TestData()
{
var data = new TheoryData<IReadOnlyList<ItemSortBy>, IReadOnlyList<SortOrder>, (ItemSortBy, SortOrder)[]>();
data.Add(
Array.Empty<ItemSortBy>(),
Array.Empty<SortOrder>(),
Array.Empty<(ItemSortBy, SortOrder)>());
data.Add(
new[]
{
ItemSortBy.IsFavoriteOrLiked,
ItemSortBy.Random
},
Array.Empty<SortOrder>(),
new (ItemSortBy, SortOrder)[]
{
(ItemSortBy.IsFavoriteOrLiked, SortOrder.Ascending),
(ItemSortBy.Random, SortOrder.Ascending),
});
data.Add(
new[]
{
ItemSortBy.SortName,
ItemSortBy.ProductionYear
},
new[]
{
SortOrder.Descending
},
new (ItemSortBy, SortOrder)[]
{
(ItemSortBy.SortName, SortOrder.Descending),
(ItemSortBy.ProductionYear, SortOrder.Descending),
});
return data;
}
}
}
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoFixture" />
<PackageReference Include="AutoFixture.AutoMoq" />
<PackageReference Include="AutoFixture.Xunit2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../Jellyfin.Api/Jellyfin.Api.csproj" />
<ProjectReference Include="../../Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Primitives;
using Xunit;
namespace Jellyfin.Api.Middleware.Tests
{
public static class UrlDecodeQueryFeatureTests
{
[Theory]
[InlineData("e0a72cb2a2c7", "e0a72cb2a2c7")] // isn't encoded
public static void EmptyValueTest(string query, string key)
{
var dict = new Dictionary<string, StringValues>
{
{ query, StringValues.Empty }
};
var test = new UrlDecodeQueryFeature(new QueryFeature(new QueryCollection(dict)));
Assert.Single(test.Query);
var (k, v) = test.Query.First();
Assert.Equal(key, k);
Assert.True(StringValues.IsNullOrEmpty(v));
}
}
}
@@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Jellyfin.Api.ModelBinders;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Primitives;
using Moq;
using Xunit;
namespace Jellyfin.Api.Tests.ModelBinders
{
public sealed class CommaDelimitedCollectionModelBinderTests
{
[Fact]
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedStringArrayQuery()
{
var queryParamName = "test";
IReadOnlyList<string> queryParamValues = new[] { "lol", "xd" };
var queryParamString = "lol,xd";
var queryParamType = typeof(string[]);
var modelBinder = new CommaDelimitedCollectionModelBinder(new NullLogger<CommaDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<string>?)bindingContextMock.Object?.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedIntArrayQuery()
{
var queryParamName = "test";
IReadOnlyList<int> queryParamValues = new[] { 42, 0 };
var queryParamString = "42,0";
var queryParamType = typeof(int[]);
var modelBinder = new CommaDelimitedCollectionModelBinder(new NullLogger<CommaDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<int>?)bindingContextMock.Object.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQuery()
{
var queryParamName = "test";
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
var queryParamString = "How,Much";
var queryParamType = typeof(TestType[]);
var modelBinder = new CommaDelimitedCollectionModelBinder(new NullLogger<CommaDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQueryWithDoubleCommas()
{
var queryParamName = "test";
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
var queryParamString = "How,,Much";
var queryParamType = typeof(TestType[]);
var modelBinder = new CommaDelimitedCollectionModelBinder(new NullLogger<CommaDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_CorrectlyBindsValidEnumArrayQuery()
{
var queryParamName = "test";
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
var queryParamString1 = "How";
var queryParamString2 = "Much";
var queryParamType = typeof(TestType[]);
var modelBinder = new CommaDelimitedCollectionModelBinder(new NullLogger<CommaDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues>
{
{ queryParamName, new StringValues(new[] { queryParamString1, queryParamString2 }) },
}),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_CorrectlyBindsEmptyEnumArrayQuery()
{
var queryParamName = "test";
IReadOnlyList<TestType> queryParamValues = Array.Empty<TestType>();
var queryParamType = typeof(TestType[]);
var modelBinder = new CommaDelimitedCollectionModelBinder(new NullLogger<CommaDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues>
{
{ queryParamName, new StringValues(value: null) },
}),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_EnumArrayQuery_BindValidOnly()
{
var queryParamName = "test";
var queryParamString = "🔥,😢";
var queryParamType = typeof(IReadOnlyList<TestType>);
var modelBinder = new CommaDelimitedCollectionModelBinder(new NullLogger<CommaDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
var listResult = (IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model;
Assert.NotNull(listResult);
Assert.Empty(listResult);
}
[Fact]
public async Task BindModelAsync_EnumArrayQuery_BindValidOnly_2()
{
var queryParamName = "test";
var queryParamString1 = "How";
var queryParamString2 = "😱";
var queryParamType = typeof(IReadOnlyList<TestType>);
var modelBinder = new CommaDelimitedCollectionModelBinder(new NullLogger<CommaDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues>
{
{ queryParamName, new StringValues(new[] { queryParamString1, queryParamString2 }) },
}),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
var listResult = (IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model;
Assert.NotNull(listResult);
Assert.Single(listResult);
}
}
}
@@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Jellyfin.Api.ModelBinders;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Primitives;
using Moq;
using Xunit;
namespace Jellyfin.Api.Tests.ModelBinders
{
public sealed class PipeDelimitedCollectionModelBinderTests
{
[Fact]
public async Task BindModelAsync_CorrectlyBindsValidPipeDelimitedStringArrayQuery()
{
var queryParamName = "test";
IReadOnlyList<string> queryParamValues = new[] { "lol", "xd" };
var queryParamString = "lol|xd";
var queryParamType = typeof(string[]);
var modelBinder = new PipeDelimitedCollectionModelBinder(new NullLogger<PipeDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<string>?)bindingContextMock.Object?.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_CorrectlyBindsValidDelimitedIntArrayQuery()
{
var queryParamName = "test";
IReadOnlyList<int> queryParamValues = new[] { 42, 0 };
var queryParamString = "42|0";
var queryParamType = typeof(int[]);
var modelBinder = new PipeDelimitedCollectionModelBinder(new NullLogger<PipeDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<int>?)bindingContextMock.Object.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_CorrectlyBindsValidPipeDelimitedEnumArrayQuery()
{
var queryParamName = "test";
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
var queryParamString = "How|Much";
var queryParamType = typeof(TestType[]);
var modelBinder = new PipeDelimitedCollectionModelBinder(new NullLogger<PipeDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_CorrectlyBindsValidPipeDelimitedEnumArrayQueryWithDoublePipes()
{
var queryParamName = "test";
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
var queryParamString = "How||Much";
var queryParamType = typeof(TestType[]);
var modelBinder = new PipeDelimitedCollectionModelBinder(new NullLogger<PipeDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_CorrectlyBindsValidEnumArrayQuery()
{
var queryParamName = "test";
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
var queryParamString1 = "How";
var queryParamString2 = "Much";
var queryParamType = typeof(TestType[]);
var modelBinder = new PipeDelimitedCollectionModelBinder(new NullLogger<PipeDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues>
{
{ queryParamName, new StringValues(new[] { queryParamString1, queryParamString2 }) },
}),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_CorrectlyBindsEmptyEnumArrayQuery()
{
var queryParamName = "test";
IReadOnlyList<TestType> queryParamValues = Array.Empty<TestType>();
var queryParamType = typeof(TestType[]);
var modelBinder = new PipeDelimitedCollectionModelBinder(new NullLogger<PipeDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues>
{
{ queryParamName, new StringValues(value: null) },
}),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
}
[Fact]
public async Task BindModelAsync_EnumArrayQuery_BindValidOnly()
{
var queryParamName = "test";
var queryParamString = "🔥|😢";
var queryParamType = typeof(IReadOnlyList<TestType>);
var modelBinder = new PipeDelimitedCollectionModelBinder(new NullLogger<PipeDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
var listResult = (IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model;
Assert.NotNull(listResult);
Assert.Empty(listResult);
}
[Fact]
public async Task BindModelAsync_EnumArrayQuery_BindValidOnly_2()
{
var queryParamName = "test";
var queryParamString1 = "How";
var queryParamString2 = "😱";
var queryParamType = typeof(IReadOnlyList<TestType>);
var modelBinder = new PipeDelimitedCollectionModelBinder(new NullLogger<PipeDelimitedCollectionModelBinder>());
var valueProvider = new QueryStringValueProvider(
new BindingSource(string.Empty, string.Empty, false, false),
new QueryCollection(new Dictionary<string, StringValues>
{
{ queryParamName, new StringValues(new[] { queryParamString1, queryParamString2 }) },
}),
CultureInfo.InvariantCulture);
var bindingContextMock = new Mock<ModelBindingContext>();
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
bindingContextMock.SetupProperty(b => b.Result);
await modelBinder.BindModelAsync(bindingContextMock.Object);
Assert.True(bindingContextMock.Object.Result.IsModelSet);
var listResult = (IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model;
Assert.NotNull(listResult);
Assert.Single(listResult);
}
}
}
@@ -0,0 +1,11 @@
namespace Jellyfin.Api.Tests.ModelBinders
{
public enum TestType
{
How,
Much,
Is,
The,
Fish
}
}
+85
View File
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Security.Claims;
using Jellyfin.Api.Constants;
using Jellyfin.Data;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Server.Implementations.Users;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
using Microsoft.AspNetCore.Http;
using Moq;
using AccessSchedule = Jellyfin.Database.Implementations.Entities.AccessSchedule;
namespace Jellyfin.Api.Tests
{
public static class TestHelpers
{
public static ClaimsPrincipal SetupUser(
Mock<IUserManager> userManagerMock,
Mock<IHttpContextAccessor> httpContextAccessorMock,
string role,
IEnumerable<AccessSchedule>? accessSchedules = null)
{
var user = new User(
"jellyfin",
typeof(DefaultAuthenticationProvider).FullName!,
typeof(DefaultPasswordResetProvider).FullName!);
user.AddDefaultPermissions();
user.AddDefaultPreferences();
// Set administrator flag.
user.SetPermission(PermissionKind.IsAdministrator, role.Equals(UserRoles.Administrator, StringComparison.OrdinalIgnoreCase));
// Add access schedules if set.
if (accessSchedules is not null)
{
foreach (var accessSchedule in accessSchedules)
{
user.AccessSchedules.Add(accessSchedule);
}
}
var claims = new[]
{
new Claim(ClaimTypes.Role, role),
new Claim(ClaimTypes.Name, "jellyfin"),
new Claim(InternalClaimTypes.UserId, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)),
new Claim(InternalClaimTypes.DeviceId, Guid.Empty.ToString("N", CultureInfo.InvariantCulture)),
new Claim(InternalClaimTypes.Device, "test"),
new Claim(InternalClaimTypes.Client, "test"),
new Claim(InternalClaimTypes.Version, "test"),
new Claim(InternalClaimTypes.Token, "test"),
};
var identity = new ClaimsIdentity(claims);
userManagerMock
.Setup(u => u.GetUserById(It.IsAny<Guid>()))
.Returns(user);
httpContextAccessorMock
.Setup(h => h.HttpContext!.Connection.RemoteIpAddress)
.Returns(new IPAddress(0));
return new ClaimsPrincipal(identity);
}
public static void SetupConfigurationManager(in Mock<IConfigurationManager> configurationManagerMock, bool startupWizardCompleted)
{
var commonConfiguration = new BaseApplicationConfiguration
{
IsStartupWizardCompleted = startupWizardCompleted
};
configurationManagerMock
.Setup(c => c.CommonConfiguration)
.Returns(commonConfiguration);
}
}
}
@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")]
@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Jellyfin.Api.Tests")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("Jellyfin.Api.Tests")]
[assembly: System.Reflection.AssemblyTitleAttribute("Jellyfin.Api.Tests")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
@@ -0,0 +1 @@
f9af71ecaf73f337dce62aaa214e4d5af1e2c81f25a171bfd0507a3bc49c0e83
@@ -0,0 +1,27 @@
is_global = true
build_property.TargetFramework = net10.0
build_property.TargetFramework = net10.0
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkVersion = v10.0
build_property.TargetFrameworkVersion = v10.0
build_property.TargetPlatformMinVersion =
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb =
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = Jellyfin.Api.Tests
build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/tests/Jellyfin.Api.Tests/
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.EffectiveAnalysisLevelStyle = 10.0
build_property.EnableCodeStyleSeverity =
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="/home/wjones/.nuget/packages/" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)xunit.runner.visualstudio/2.8.2/build/net6.0/xunit.runner.visualstudio.props" Condition="Exists('$(NuGetPackageRoot)xunit.runner.visualstudio/2.8.2/build/net6.0/xunit.runner.visualstudio.props')" />
<Import Project="$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.props" Condition="Exists('$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.props" Condition="Exists('$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.props" Condition="Exists('$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Configuration.UserSecrets.props" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Configuration.UserSecrets.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Pkgxunit_analyzers Condition=" '$(Pkgxunit_analyzers)' == '' ">/home/wjones/.nuget/packages/xunit.analyzers/1.18.0</Pkgxunit_analyzers>
<PkgMicrosoft_Extensions_ApiDescription_Server Condition=" '$(PkgMicrosoft_Extensions_ApiDescription_Server)' == '' ">/home/wjones/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5</PkgMicrosoft_Extensions_ApiDescription_Server>
<PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556</PkgStyleCop_Analyzers_Unstable>
<PkgSmartAnalyzers_MultithreadingAnalyzer Condition=" '$(PkgSmartAnalyzers_MultithreadingAnalyzer)' == '' ">/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31</PkgSmartAnalyzers_MultithreadingAnalyzer>
<PkgSerilogAnalyzer Condition=" '$(PkgSerilogAnalyzer)' == '' ">/home/wjones/.nuget/packages/seriloganalyzer/0.15.0</PkgSerilogAnalyzer>
<PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers)' == '' ">/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0</PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers>
</PropertyGroup>
</Project>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.targets" Condition="Exists('$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.targets')" />
<Import Project="$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3/2.1.11/buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets" Condition="Exists('$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3/2.1.11/buildTransitive/net9.0/SQLitePCLRaw.lib.e_sqlite3.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Configuration.UserSecrets.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Configuration.UserSecrets.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.aspnetcore.mvc.testing/10.0.3/buildTransitive/net10.0/Microsoft.AspNetCore.Mvc.Testing.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.aspnetcore.mvc.testing/10.0.3/buildTransitive/net10.0/Microsoft.AspNetCore.Mvc.Testing.targets')" />
<Import Project="$(NuGetPackageRoot)coverlet.collector/8.0.0/build/net8.0/coverlet.collector.targets" Condition="Exists('$(NuGetPackageRoot)coverlet.collector/8.0.0/build/net8.0/coverlet.collector.targets')" />
</ImportGroup>
</Project>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,105 @@
{
"version": 2,
"dgSpecHash": "uq+sjmMmOw8=",
"success": true,
"projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj",
"expectedPackageFiles": [
"/home/wjones/.nuget/packages/asynckeyedlock/8.0.2/asynckeyedlock.8.0.2.nupkg.sha512",
"/home/wjones/.nuget/packages/autofixture/4.18.1/autofixture.4.18.1.nupkg.sha512",
"/home/wjones/.nuget/packages/autofixture.automoq/4.18.1/autofixture.automoq.4.18.1.nupkg.sha512",
"/home/wjones/.nuget/packages/autofixture.xunit2/4.18.1/autofixture.xunit2.4.18.1.nupkg.sha512",
"/home/wjones/.nuget/packages/bdinfo/0.8.0/bdinfo.0.8.0.nupkg.sha512",
"/home/wjones/.nuget/packages/bitfaster.caching/2.5.4/bitfaster.caching.2.5.4.nupkg.sha512",
"/home/wjones/.nuget/packages/castle.core/5.1.1/castle.core.5.1.1.nupkg.sha512",
"/home/wjones/.nuget/packages/coverlet.collector/8.0.0/coverlet.collector.8.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/diacritics/4.1.4/diacritics.4.1.4.nupkg.sha512",
"/home/wjones/.nuget/packages/fare/2.1.1/fare.2.1.1.nupkg.sha512",
"/home/wjones/.nuget/packages/icu4n/60.1.0-alpha.356/icu4n.60.1.0-alpha.356.nupkg.sha512",
"/home/wjones/.nuget/packages/icu4n.transliterator/60.1.0-alpha.356/icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512",
"/home/wjones/.nuget/packages/j2n/2.0.0/j2n.2.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/libse/4.0.12/libse.4.0.12.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.aspnetcore.authorization/10.0.3/microsoft.aspnetcore.authorization.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.aspnetcore.metadata/10.0.3/microsoft.aspnetcore.metadata.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.aspnetcore.mvc.testing/10.0.3/microsoft.aspnetcore.mvc.testing.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.aspnetcore.testhost/10.0.3/microsoft.aspnetcore.testhost.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.codecoverage/18.0.1/microsoft.codecoverage.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.data.sqlite.core/10.0.3/microsoft.data.sqlite.core.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.sqlite/10.0.3/microsoft.entityframeworkcore.sqlite.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.sqlite.core/10.0.3/microsoft.entityframeworkcore.sqlite.core.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5/microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.caching.abstractions/10.0.3/microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.caching.memory/10.0.3/microsoft.extensions.caching.memory.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration/10.0.3/microsoft.extensions.configuration.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.abstractions/10.0.3/microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.binder/10.0.3/microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.commandline/10.0.3/microsoft.extensions.configuration.commandline.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.environmentvariables/10.0.3/microsoft.extensions.configuration.environmentvariables.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.fileextensions/10.0.3/microsoft.extensions.configuration.fileextensions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.json/10.0.3/microsoft.extensions.configuration.json.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.usersecrets/10.0.3/microsoft.extensions.configuration.usersecrets.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection/10.0.3/microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/10.0.3/microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.dependencymodel/10.0.3/microsoft.extensions.dependencymodel.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.diagnostics/10.0.3/microsoft.extensions.diagnostics.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.diagnostics.abstractions/10.0.3/microsoft.extensions.diagnostics.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.fileproviders.abstractions/10.0.3/microsoft.extensions.fileproviders.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.fileproviders.physical/10.0.3/microsoft.extensions.fileproviders.physical.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.filesystemglobbing/10.0.3/microsoft.extensions.filesystemglobbing.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.hosting/10.0.3/microsoft.extensions.hosting.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.hosting.abstractions/10.0.3/microsoft.extensions.hosting.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.http/10.0.3/microsoft.extensions.http.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging/10.0.3/microsoft.extensions.logging.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.abstractions/10.0.3/microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.configuration/10.0.3/microsoft.extensions.logging.configuration.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.console/10.0.3/microsoft.extensions.logging.console.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.debug/10.0.3/microsoft.extensions.logging.debug.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.eventlog/10.0.3/microsoft.extensions.logging.eventlog.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.eventsource/10.0.3/microsoft.extensions.logging.eventsource.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.options/10.0.3/microsoft.extensions.options.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.options.configurationextensions/10.0.3/microsoft.extensions.options.configurationextensions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.primitives/10.0.3/microsoft.extensions.primitives.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.net.test.sdk/18.0.1/microsoft.net.test.sdk.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.netcore.platforms/1.1.0/microsoft.netcore.platforms.1.1.0.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.openapi/1.6.22/microsoft.openapi.1.6.22.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.testplatform.objectmodel/18.0.1/microsoft.testplatform.objectmodel.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.testplatform.testhost/18.0.1/microsoft.testplatform.testhost.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.win32.systemevents/9.0.2/microsoft.win32.systemevents.9.0.2.nupkg.sha512",
"/home/wjones/.nuget/packages/moq/4.18.4/moq.4.18.4.nupkg.sha512",
"/home/wjones/.nuget/packages/nebml/1.1.0.5/nebml.1.1.0.5.nupkg.sha512",
"/home/wjones/.nuget/packages/netstandard.library/1.6.1/netstandard.library.1.6.1.nupkg.sha512",
"/home/wjones/.nuget/packages/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512",
"/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512",
"/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512",
"/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512",
"/home/wjones/.nuget/packages/sqlitepclraw.bundle_e_sqlite3/2.1.11/sqlitepclraw.bundle_e_sqlite3.2.1.11.nupkg.sha512",
"/home/wjones/.nuget/packages/sqlitepclraw.core/2.1.11/sqlitepclraw.core.2.1.11.nupkg.sha512",
"/home/wjones/.nuget/packages/sqlitepclraw.lib.e_sqlite3/2.1.11/sqlitepclraw.lib.e_sqlite3.2.1.11.nupkg.sha512",
"/home/wjones/.nuget/packages/sqlitepclraw.provider.e_sqlite3/2.1.11/sqlitepclraw.provider.e_sqlite3.2.1.11.nupkg.sha512",
"/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512",
"/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512",
"/home/wjones/.nuget/packages/swashbuckle.aspnetcore/7.3.2/swashbuckle.aspnetcore.7.3.2.nupkg.sha512",
"/home/wjones/.nuget/packages/swashbuckle.aspnetcore.redoc/6.9.0/swashbuckle.aspnetcore.redoc.6.9.0.nupkg.sha512",
"/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swagger/7.3.2/swashbuckle.aspnetcore.swagger.7.3.2.nupkg.sha512",
"/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swaggergen/7.3.2/swashbuckle.aspnetcore.swaggergen.7.3.2.nupkg.sha512",
"/home/wjones/.nuget/packages/swashbuckle.aspnetcore.swaggerui/7.3.2/swashbuckle.aspnetcore.swaggerui.7.3.2.nupkg.sha512",
"/home/wjones/.nuget/packages/system.diagnostics.eventlog/10.0.3/system.diagnostics.eventlog.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/system.drawing.common/9.0.2/system.drawing.common.9.0.2.nupkg.sha512",
"/home/wjones/.nuget/packages/utf.unknown/2.6.0/utf.unknown.2.6.0.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit/2.9.3/xunit.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.abstractions/2.0.3/xunit.abstractions.2.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.analyzers/1.18.0/xunit.analyzers.1.18.0.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.assert/2.9.3/xunit.assert.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.core/2.9.3/xunit.core.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.extensibility.core/2.9.3/xunit.extensibility.core.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.extensibility.execution/2.9.3/xunit.extensibility.execution.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.runner.visualstudio/2.8.2/xunit.runner.visualstudio.2.8.2.nupkg.sha512",
"/home/wjones/.nuget/packages/zlib.net-mutliplatform/1.0.8/zlib.net-mutliplatform.1.0.8.nupkg.sha512"
],
"logs": []
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
17714532041200000
@@ -0,0 +1 @@
17715044196800000
+33
View File
@@ -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)));
}
}
}
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{DF194677-DFD3-42AF-9F75-D44D5A416478}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" />
<PackageReference Include="FsCheck.Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../MediaBrowser.Common/MediaBrowser.Common.csproj" />
<ProjectReference Include="../../MediaBrowser.Providers/MediaBrowser.Providers.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,85 @@
using System;
using MediaBrowser.Common.Providers;
using Xunit;
namespace Jellyfin.Common.Tests.Providers
{
public class ProviderIdParserTests
{
[Theory]
[InlineData("tt1234567", "tt1234567")]
[InlineData("tt12345678", "tt12345678")]
[InlineData("https://www.imdb.com/title/tt1234567", "tt1234567")]
[InlineData("https://www.imdb.com/title/tt12345678", "tt12345678")]
[InlineData(@"multiline\nhttps://www.imdb.com/title/tt1234567", "tt1234567")]
[InlineData(@"multiline\nhttps://www.imdb.com/title/tt12345678", "tt12345678")]
[InlineData("tt1234567tt7654321", "tt1234567")]
[InlineData("tt12345678tt7654321", "tt12345678")]
[InlineData("tt123456789", "tt12345678")]
public void FindImdbId_Valid_Success(string text, string expected)
{
Assert.True(ProviderIdParsers.TryFindImdbId(text, out ReadOnlySpan<char> parsedId));
Assert.Equal(expected, parsedId.ToString());
}
[Theory]
[InlineData("tt123456")]
[InlineData("https://www.imdb.com/title/tt123456")]
[InlineData("Jellyfin")]
public void FindImdbId_Invalid_Success(string text)
{
Assert.False(ProviderIdParsers.TryFindImdbId(text, out _));
}
[Theory]
[InlineData("https://www.themoviedb.org/movie/30287-fallo", "30287")]
[InlineData("themoviedb.org/movie/30287", "30287")]
public void FindTmdbMovieId_Valid_Success(string text, string expected)
{
Assert.True(ProviderIdParsers.TryFindTmdbMovieId(text, out ReadOnlySpan<char> parsedId));
Assert.Equal(expected, parsedId.ToString());
}
[Theory]
[InlineData("https://www.themoviedb.org/movie/fallo-30287")]
[InlineData("https://www.themoviedb.org/tv/1668-friends")]
public void FindTmdbMovieId_Invalid_Success(string text)
{
Assert.False(ProviderIdParsers.TryFindTmdbMovieId(text, out _));
}
[Theory]
[InlineData("https://www.themoviedb.org/tv/1668-friends", "1668")]
[InlineData("themoviedb.org/tv/1668", "1668")]
public void FindTmdbSeriesId_Valid_Success(string text, string expected)
{
Assert.True(ProviderIdParsers.TryFindTmdbSeriesId(text, out ReadOnlySpan<char> parsedId));
Assert.Equal(expected, parsedId.ToString());
}
[Theory]
[InlineData("https://www.themoviedb.org/tv/friends-1668")]
[InlineData("https://www.themoviedb.org/movie/30287-fallo")]
public void FindTmdbSeriesId_Invalid_Success(string text)
{
Assert.False(ProviderIdParsers.TryFindTmdbSeriesId(text, out _));
}
[Theory]
[InlineData("https://www.thetvdb.com/?tab=series&id=121361", "121361")]
[InlineData("thetvdb.com/?tab=series&id=121361", "121361")]
public void FindTvdbId_Valid_Success(string text, string expected)
{
Assert.True(ProviderIdParsers.TryFindTvdbId(text, out ReadOnlySpan<char> parsedId));
Assert.Equal(expected, parsedId.ToString());
}
[Theory]
[InlineData("thetvdb.com/?tab=series&id=Jellyfin121361")]
[InlineData("https://www.themoviedb.org/tv/1668-friends")]
public void FindTvdbId_Invalid_Success(string text)
{
Assert.False(ProviderIdParsers.TryFindTvdbId(text, out _));
}
}
}
@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")]
@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Jellyfin.Common.Tests")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("Jellyfin.Common.Tests")]
[assembly: System.Reflection.AssemblyTitleAttribute("Jellyfin.Common.Tests")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
@@ -0,0 +1 @@
7caeff2ceeaabb6e24f6cf2d95714f70640ca3e1a3986cf5dbb286032ea619f7
@@ -0,0 +1,27 @@
is_global = true
build_property.TargetFramework = net10.0
build_property.TargetFramework = net10.0
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkVersion = v10.0
build_property.TargetFrameworkVersion = v10.0
build_property.TargetPlatformMinVersion =
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb =
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = Jellyfin.Common.Tests
build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/tests/Jellyfin.Common.Tests/
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.EffectiveAnalysisLevelStyle = 10.0
build_property.EnableCodeStyleSeverity =
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="/home/wjones/.nuget/packages/" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)xunit.runner.visualstudio/2.8.2/build/net6.0/xunit.runner.visualstudio.props" Condition="Exists('$(NuGetPackageRoot)xunit.runner.visualstudio/2.8.2/build/net6.0/xunit.runner.visualstudio.props')" />
<Import Project="$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.props" Condition="Exists('$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.props" Condition="Exists('$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.props" Condition="Exists('$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Pkgxunit_analyzers Condition=" '$(Pkgxunit_analyzers)' == '' ">/home/wjones/.nuget/packages/xunit.analyzers/1.18.0</Pkgxunit_analyzers>
<PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556</PkgStyleCop_Analyzers_Unstable>
<PkgSmartAnalyzers_MultithreadingAnalyzer Condition=" '$(PkgSmartAnalyzers_MultithreadingAnalyzer)' == '' ">/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31</PkgSmartAnalyzers_MultithreadingAnalyzer>
<PkgSerilogAnalyzer Condition=" '$(PkgSerilogAnalyzer)' == '' ">/home/wjones/.nuget/packages/seriloganalyzer/0.15.0</PkgSerilogAnalyzer>
<PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers)' == '' ">/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0</PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers>
</PropertyGroup>
</Project>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.targets" Condition="Exists('$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets')" />
<Import Project="$(NuGetPackageRoot)coverlet.collector/8.0.0/build/net8.0/coverlet.collector.targets" Condition="Exists('$(NuGetPackageRoot)coverlet.collector/8.0.0/build/net8.0/coverlet.collector.targets')" />
</ImportGroup>
</Project>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,67 @@
{
"version": 2,
"dgSpecHash": "+Sm/Zy9Ooz8=",
"success": true,
"projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj",
"expectedPackageFiles": [
"/home/wjones/.nuget/packages/asynckeyedlock/8.0.2/asynckeyedlock.8.0.2.nupkg.sha512",
"/home/wjones/.nuget/packages/bitfaster.caching/2.5.4/bitfaster.caching.2.5.4.nupkg.sha512",
"/home/wjones/.nuget/packages/coverlet.collector/8.0.0/coverlet.collector.8.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/diacritics/4.1.4/diacritics.4.1.4.nupkg.sha512",
"/home/wjones/.nuget/packages/fscheck/3.3.2/fscheck.3.3.2.nupkg.sha512",
"/home/wjones/.nuget/packages/fscheck.xunit/3.3.2/fscheck.xunit.3.3.2.nupkg.sha512",
"/home/wjones/.nuget/packages/fsharp.core/5.0.2/fsharp.core.5.0.2.nupkg.sha512",
"/home/wjones/.nuget/packages/icu4n/60.1.0-alpha.356/icu4n.60.1.0-alpha.356.nupkg.sha512",
"/home/wjones/.nuget/packages/icu4n.transliterator/60.1.0-alpha.356/icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512",
"/home/wjones/.nuget/packages/j2n/2.0.0/j2n.2.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/lrcparser/2025.623.0/lrcparser.2025.623.0.nupkg.sha512",
"/home/wjones/.nuget/packages/metabrainz.common/4.1.1/metabrainz.common.4.1.1.nupkg.sha512",
"/home/wjones/.nuget/packages/metabrainz.common.json/7.2.0/metabrainz.common.json.7.2.0.nupkg.sha512",
"/home/wjones/.nuget/packages/metabrainz.musicbrainz/8.0.1/metabrainz.musicbrainz.8.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.codecoverage/18.0.1/microsoft.codecoverage.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.caching.abstractions/10.0.3/microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.caching.memory/10.0.3/microsoft.extensions.caching.memory.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration/10.0.3/microsoft.extensions.configuration.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.abstractions/10.0.3/microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.binder/10.0.3/microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection/10.0.3/microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/10.0.3/microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.diagnostics/10.0.3/microsoft.extensions.diagnostics.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.diagnostics.abstractions/10.0.3/microsoft.extensions.diagnostics.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.http/10.0.3/microsoft.extensions.http.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging/10.0.3/microsoft.extensions.logging.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.abstractions/10.0.3/microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.options/10.0.3/microsoft.extensions.options.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.options.configurationextensions/10.0.3/microsoft.extensions.options.configurationextensions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.primitives/10.0.3/microsoft.extensions.primitives.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.net.test.sdk/18.0.1/microsoft.net.test.sdk.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.testplatform.objectmodel/18.0.1/microsoft.testplatform.objectmodel.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.testplatform.testhost/18.0.1/microsoft.testplatform.testhost.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/nebml/1.1.0.5/nebml.1.1.0.5.nupkg.sha512",
"/home/wjones/.nuget/packages/newtonsoft.json/13.0.4/newtonsoft.json.13.0.4.nupkg.sha512",
"/home/wjones/.nuget/packages/playlistsnet/1.4.1/playlistsnet.1.4.1.nupkg.sha512",
"/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512",
"/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512",
"/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512",
"/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512",
"/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512",
"/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512",
"/home/wjones/.nuget/packages/tmdblib/2.3.0/tmdblib.2.3.0.nupkg.sha512",
"/home/wjones/.nuget/packages/ude.netstandard/1.2.0/ude.netstandard.1.2.0.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit/2.9.3/xunit.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.abstractions/2.0.3/xunit.abstractions.2.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.analyzers/1.18.0/xunit.analyzers.1.18.0.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.assert/2.9.3/xunit.assert.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.core/2.9.3/xunit.core.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.extensibility.core/2.9.3/xunit.extensibility.core.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.extensibility.execution/2.9.3/xunit.extensibility.execution.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.runner.visualstudio/2.8.2/xunit.runner.visualstudio.2.8.2.nupkg.sha512",
"/home/wjones/.nuget/packages/z440.atl.core/7.11.0/z440.atl.core.7.11.0.nupkg.sha512"
],
"logs": []
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
17714532037300000
@@ -0,0 +1 @@
17715044196000000
@@ -0,0 +1,80 @@
using System;
using MediaBrowser.Controller.BaseItemManager;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Model.Configuration;
using Moq;
using Xunit;
namespace Jellyfin.Controller.Tests
{
public class BaseItemManagerTests
{
[Theory]
[InlineData(typeof(Book), "LibraryEnabled", true)]
[InlineData(typeof(Book), "LibraryDisabled", false)]
[InlineData(typeof(MusicArtist), "Enabled", true)]
[InlineData(typeof(MusicArtist), "ServerDisabled", false)]
public void IsMetadataFetcherEnabled_ChecksOptions_ReturnsExpected(Type itemType, string fetcherName, bool expected)
{
BaseItem item = (BaseItem)Activator.CreateInstance(itemType)!;
var libraryTypeOptions = itemType == typeof(Book)
? new TypeOptions
{
Type = "Book",
MetadataFetchers = new[] { "LibraryEnabled" }
}
: null;
var serverConfiguration = new ServerConfiguration();
foreach (var typeConfig in serverConfiguration.MetadataOptions)
{
typeConfig.DisabledMetadataFetchers = new[] { "ServerDisabled" };
}
var serverConfigurationManager = new Mock<IServerConfigurationManager>();
serverConfigurationManager.Setup(scm => scm.Configuration)
.Returns(serverConfiguration);
var baseItemManager = new BaseItemManager(serverConfigurationManager.Object);
var actual = baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, fetcherName);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(typeof(Book), "LibraryEnabled", true)]
[InlineData(typeof(Book), "LibraryDisabled", false)]
[InlineData(typeof(MusicArtist), "Enabled", true)]
[InlineData(typeof(MusicArtist), "ServerDisabled", false)]
public void IsImageFetcherEnabled_ChecksOptions_ReturnsExpected(Type itemType, string fetcherName, bool expected)
{
BaseItem item = (BaseItem)Activator.CreateInstance(itemType)!;
var libraryTypeOptions = itemType == typeof(Book)
? new TypeOptions
{
Type = "Book",
ImageFetchers = new[] { "LibraryEnabled" }
}
: null;
var serverConfiguration = new ServerConfiguration();
foreach (var typeConfig in serverConfiguration.MetadataOptions)
{
typeConfig.DisabledImageFetchers = new[] { "ServerDisabled" };
}
var serverConfigurationManager = new Mock<IServerConfigurationManager>();
serverConfigurationManager.Setup(scm => scm.Configuration)
.Returns(serverConfiguration);
var baseItemManager = new BaseItemManager(serverConfigurationManager.Object);
var actual = baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, fetcherName);
Assert.Equal(expected, actual);
}
}
}
@@ -0,0 +1,252 @@
using System.Linq;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.IO;
using Moq;
using Xunit;
namespace Jellyfin.Controller.Tests
{
public class DirectoryServiceTests
{
private const string LowerCasePath = "/music/someartist";
private const string UpperCasePath = "/music/SOMEARTIST";
private static readonly FileSystemMetadata[] _lowerCaseFileSystemMetadata =
{
new()
{
FullName = LowerCasePath + "/Artwork",
IsDirectory = true
},
new()
{
FullName = LowerCasePath + "/Some Other Folder",
IsDirectory = true
},
new()
{
FullName = LowerCasePath + "/Song 2.mp3",
IsDirectory = false
},
new()
{
FullName = LowerCasePath + "/Song 3.mp3",
IsDirectory = false
}
};
private static readonly FileSystemMetadata[] _upperCaseFileSystemMetadata =
{
new()
{
FullName = UpperCasePath + "/Lyrics",
IsDirectory = true
},
new()
{
FullName = UpperCasePath + "/Song 1.mp3",
IsDirectory = false
}
};
[Fact]
public void GetFileSystemEntries_GivenPathsWithDifferentCasing_CachesAll()
{
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
var directoryService = new DirectoryService(fileSystemMock.Object);
var upperCaseResult = directoryService.GetFileSystemEntries(UpperCasePath);
var lowerCaseResult = directoryService.GetFileSystemEntries(LowerCasePath);
Assert.Equal(_upperCaseFileSystemMetadata, upperCaseResult);
Assert.Equal(_lowerCaseFileSystemMetadata, lowerCaseResult);
}
[Fact]
public void GetFiles_GivenPathsWithDifferentCasing_ReturnsCorrectFiles()
{
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
var directoryService = new DirectoryService(fileSystemMock.Object);
var upperCaseResult = directoryService.GetFiles(UpperCasePath);
var lowerCaseResult = directoryService.GetFiles(LowerCasePath);
Assert.Equal(_upperCaseFileSystemMetadata.Where(f => !f.IsDirectory), upperCaseResult);
Assert.Equal(_lowerCaseFileSystemMetadata.Where(f => !f.IsDirectory), lowerCaseResult);
}
[Fact]
public void GetDirectories_GivenPathsWithDifferentCasing_ReturnsCorrectDirectories()
{
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
var directoryService = new DirectoryService(fileSystemMock.Object);
var upperCaseResult = directoryService.GetDirectories(UpperCasePath);
var lowerCaseResult = directoryService.GetDirectories(LowerCasePath);
Assert.Equal(_upperCaseFileSystemMetadata.Where(f => f.IsDirectory), upperCaseResult);
Assert.Equal(_lowerCaseFileSystemMetadata.Where(f => f.IsDirectory), lowerCaseResult);
}
[Fact]
public void GetFile_GivenFilePathsWithDifferentCasing_ReturnsCorrectFile()
{
const string lowerCasePath = "/music/someartist/song 1.mp3";
var lowerCaseFileSystemMetadata = new FileSystemMetadata
{
FullName = lowerCasePath,
Exists = true
};
const string upperCasePath = "/music/SOMEARTIST/SONG 1.mp3";
var upperCaseFileSystemMetadata = new FileSystemMetadata
{
FullName = upperCasePath,
Exists = false
};
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
var directoryService = new DirectoryService(fileSystemMock.Object);
var lowerCaseDirResult = directoryService.GetDirectory(lowerCasePath);
var lowerCaseFileResult = directoryService.GetFile(lowerCasePath);
var upperCaseDirResult = directoryService.GetDirectory(upperCasePath);
var upperCaseFileResult = directoryService.GetFile(upperCasePath);
Assert.Null(lowerCaseDirResult);
Assert.Equal(lowerCaseFileSystemMetadata, lowerCaseFileResult);
Assert.Null(upperCaseDirResult);
Assert.Null(upperCaseFileResult);
}
[Fact]
public void GetDirectory_GivenFilePathsWithDifferentCasing_ReturnsCorrectDirectory()
{
const string lowerCasePath = "/music/someartist/Lyrics";
var lowerCaseFileSystemMetadata = new FileSystemMetadata
{
FullName = lowerCasePath,
IsDirectory = true,
Exists = true
};
const string upperCasePath = "/music/SOMEARTIST/LYRICS";
var upperCaseFileSystemMetadata = new FileSystemMetadata
{
FullName = upperCasePath,
IsDirectory = true,
Exists = false
};
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
var directoryService = new DirectoryService(fileSystemMock.Object);
var lowerCaseDirResult = directoryService.GetDirectory(lowerCasePath);
var lowerCaseFileResult = directoryService.GetFile(lowerCasePath);
var upperCaseDirResult = directoryService.GetDirectory(upperCasePath);
var upperCaseFileResult = directoryService.GetFile(upperCasePath);
Assert.Equal(lowerCaseFileSystemMetadata, lowerCaseDirResult);
Assert.Null(lowerCaseFileResult);
Assert.Null(upperCaseDirResult);
Assert.Null(upperCaseFileResult);
}
[Fact]
public void GetFile_GivenCachedPath_ReturnsCachedFile()
{
const string path = "/music/someartist/song 1.mp3";
var cachedFileSystemMetadata = new FileSystemMetadata
{
FullName = path,
Exists = true
};
var newFileSystemMetadata = new FileSystemMetadata
{
FullName = "/music/SOMEARTIST/song 1.mp3",
Exists = true
};
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == path))).Returns(cachedFileSystemMetadata);
var directoryService = new DirectoryService(fileSystemMock.Object);
var result = directoryService.GetFile(path);
fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == path))).Returns(newFileSystemMetadata);
var secondResult = directoryService.GetFile(path);
Assert.Equivalent(cachedFileSystemMetadata, result);
Assert.Equivalent(cachedFileSystemMetadata, secondResult);
}
[Fact]
public void GetFilePaths_GivenCachedFilePathWithoutClear_ReturnsOnlyCachedPaths()
{
const string path = "/music/someartist";
var cachedPaths = new[]
{
"/music/someartist/song 1.mp3",
"/music/someartist/song 2.mp3",
"/music/someartist/song 3.mp3",
"/music/someartist/song 4.mp3",
};
var newPaths = new[]
{
"/music/someartist/song 5.mp3",
"/music/someartist/song 6.mp3",
"/music/someartist/song 7.mp3",
"/music/someartist/song 8.mp3",
};
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
var directoryService = new DirectoryService(fileSystemMock.Object);
var result = directoryService.GetFilePaths(path);
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
var secondResult = directoryService.GetFilePaths(path);
Assert.Equal(cachedPaths, result);
Assert.Equal(cachedPaths, secondResult);
}
[Fact]
public void GetFilePaths_GivenCachedFilePathWithClear_ReturnsNewPaths()
{
const string path = "/music/someartist";
var cachedPaths = new[]
{
"/music/someartist/song 1.mp3",
"/music/someartist/song 2.mp3",
"/music/someartist/song 3.mp3",
"/music/someartist/song 4.mp3",
};
var newPaths = new[]
{
"/music/someartist/song 5.mp3",
"/music/someartist/song 6.mp3",
"/music/someartist/song 7.mp3",
"/music/someartist/song 8.mp3",
};
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
var directoryService = new DirectoryService(fileSystemMock.Object);
var result = directoryService.GetFilePaths(path);
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
var secondResult = directoryService.GetFilePaths(path, true);
Assert.Equal(cachedPaths, result);
Assert.Equal(newPaths, secondResult);
}
}
}
@@ -0,0 +1,46 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.MediaInfo;
using Moq;
using Xunit;
namespace Jellyfin.Controller.Tests.Entities;
public class BaseItemTests
{
[Theory]
[InlineData("", "")]
[InlineData("1", "0000000001")]
[InlineData("t", "t")]
[InlineData("test", "test")]
[InlineData("test1", "test0000000001")]
[InlineData("1test 2", "0000000001test 0000000002")]
public void BaseItem_ModifySortChunks_Valid(string input, string expected)
=> Assert.Equal(expected, BaseItem.ModifySortChunks(input));
[Theory]
[InlineData("/Movies/Ted/Ted.mp4", "/Movies/Ted/Ted - Unrated Edition.mp4", "Ted", "Unrated Edition")]
[InlineData("/Movies/Deadpool 2 (2018)/Deadpool 2 (2018).mkv", "/Movies/Deadpool 2 (2018)/Deadpool 2 (2018) - Super Duper Cut.mkv", "Deadpool 2 (2018)", "Super Duper Cut")]
public void GetMediaSourceName_Valid(string primaryPath, string altPath, string name, string altName)
{
var mediaSourceManager = new Mock<IMediaSourceManager>();
mediaSourceManager.Setup(x => x.GetPathProtocol(It.IsAny<string>()))
.Returns((string x) => MediaProtocol.File);
BaseItem.MediaSourceManager = mediaSourceManager.Object;
var video = new Video()
{
Path = primaryPath
};
var videoAlt = new Video()
{
Path = altPath,
};
video.LocalAlternateVersions = [videoAlt.Path];
Assert.Equal(name, video.GetMediaSourceName(video));
Assert.Equal(altName, video.GetMediaSourceName(videoAlt));
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{462584F7-5023-4019-9EAC-B98CA458C0A0}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../MediaBrowser.Controller/MediaBrowser.Controller.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")]
@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Jellyfin.Controller.Tests")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("Jellyfin.Controller.Tests")]
[assembly: System.Reflection.AssemblyTitleAttribute("Jellyfin.Controller.Tests")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
@@ -0,0 +1 @@
2d250bb2e810bbfa1d235cfcb2bb22e618f92fcf53f0da388cf881c2c470f040
@@ -0,0 +1,27 @@
is_global = true
build_property.TargetFramework = net10.0
build_property.TargetFramework = net10.0
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkVersion = v10.0
build_property.TargetFrameworkVersion = v10.0
build_property.TargetPlatformMinVersion =
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb =
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = Jellyfin.Controller.Tests
build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/tests/Jellyfin.Controller.Tests/
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.EffectiveAnalysisLevelStyle = 10.0
build_property.EnableCodeStyleSeverity =
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/wjones/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="/home/wjones/.nuget/packages/" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)xunit.runner.visualstudio/2.8.2/build/net6.0/xunit.runner.visualstudio.props" Condition="Exists('$(NuGetPackageRoot)xunit.runner.visualstudio/2.8.2/build/net6.0/xunit.runner.visualstudio.props')" />
<Import Project="$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.props" Condition="Exists('$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.props" Condition="Exists('$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.props" Condition="Exists('$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore/10.0.3/buildTransitive/net10.0/Microsoft.EntityFrameworkCore.props')" />
<Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Pkgxunit_analyzers Condition=" '$(Pkgxunit_analyzers)' == '' ">/home/wjones/.nuget/packages/xunit.analyzers/1.18.0</Pkgxunit_analyzers>
<PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556</PkgStyleCop_Analyzers_Unstable>
<PkgSmartAnalyzers_MultithreadingAnalyzer Condition=" '$(PkgSmartAnalyzers_MultithreadingAnalyzer)' == '' ">/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31</PkgSmartAnalyzers_MultithreadingAnalyzer>
<PkgSerilogAnalyzer Condition=" '$(PkgSerilogAnalyzer)' == '' ">/home/wjones/.nuget/packages/seriloganalyzer/0.15.0</PkgSerilogAnalyzer>
<PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers)' == '' ">/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0</PkgMicrosoft_CodeAnalysis_BannedApiAnalyzers>
</PropertyGroup>
</Project>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.targets" Condition="Exists('$(NuGetPackageRoot)xunit.core/2.9.3/build/xunit.core.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.testplatform.testhost/18.0.1/build/net8.0/Microsoft.TestPlatform.TestHost.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codecoverage/18.0.1/build/netstandard2.0/Microsoft.CodeCoverage.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.net.test.sdk/18.0.1/build/net8.0/Microsoft.NET.Test.Sdk.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Options.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/10.0.3/buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder/10.0.3/buildTransitive/netstandard2.0/Microsoft.Extensions.Configuration.Binder.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.bannedapianalyzers/4.14.0/buildTransitive/Microsoft.CodeAnalysis.BannedApiAnalyzers.targets')" />
<Import Project="$(NuGetPackageRoot)coverlet.collector/8.0.0/build/net8.0/coverlet.collector.targets" Condition="Exists('$(NuGetPackageRoot)coverlet.collector/8.0.0/build/net8.0/coverlet.collector.targets')" />
</ImportGroup>
</Project>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,54 @@
{
"version": 2,
"dgSpecHash": "EAQhPy0F7pc=",
"success": true,
"projectFilePath": "/srv/common_drive/Projects/pgsql-jellyfin/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj",
"expectedPackageFiles": [
"/home/wjones/.nuget/packages/bitfaster.caching/2.5.4/bitfaster.caching.2.5.4.nupkg.sha512",
"/home/wjones/.nuget/packages/castle.core/5.1.1/castle.core.5.1.1.nupkg.sha512",
"/home/wjones/.nuget/packages/coverlet.collector/8.0.0/coverlet.collector.8.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/diacritics/4.1.4/diacritics.4.1.4.nupkg.sha512",
"/home/wjones/.nuget/packages/icu4n/60.1.0-alpha.356/icu4n.60.1.0-alpha.356.nupkg.sha512",
"/home/wjones/.nuget/packages/icu4n.transliterator/60.1.0-alpha.356/icu4n.transliterator.60.1.0-alpha.356.nupkg.sha512",
"/home/wjones/.nuget/packages/j2n/2.0.0/j2n.2.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.codeanalysis.bannedapianalyzers/4.14.0/microsoft.codeanalysis.bannedapianalyzers.4.14.0.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.codecoverage/18.0.1/microsoft.codecoverage.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore/10.0.3/microsoft.entityframeworkcore.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.abstractions/10.0.3/microsoft.entityframeworkcore.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.analyzers/10.0.3/microsoft.entityframeworkcore.analyzers.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.3/microsoft.entityframeworkcore.relational.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.caching.abstractions/10.0.3/microsoft.extensions.caching.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.caching.memory/10.0.3/microsoft.extensions.caching.memory.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration/10.0.3/microsoft.extensions.configuration.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.abstractions/10.0.3/microsoft.extensions.configuration.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.configuration.binder/10.0.3/microsoft.extensions.configuration.binder.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection/10.0.3/microsoft.extensions.dependencyinjection.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/10.0.3/microsoft.extensions.dependencyinjection.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging/10.0.3/microsoft.extensions.logging.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.logging.abstractions/10.0.3/microsoft.extensions.logging.abstractions.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.options/10.0.3/microsoft.extensions.options.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.extensions.primitives/10.0.3/microsoft.extensions.primitives.10.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.net.test.sdk/18.0.1/microsoft.net.test.sdk.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.testplatform.objectmodel/18.0.1/microsoft.testplatform.objectmodel.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/microsoft.testplatform.testhost/18.0.1/microsoft.testplatform.testhost.18.0.1.nupkg.sha512",
"/home/wjones/.nuget/packages/moq/4.18.4/moq.4.18.4.nupkg.sha512",
"/home/wjones/.nuget/packages/nebml/1.1.0.5/nebml.1.1.0.5.nupkg.sha512",
"/home/wjones/.nuget/packages/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/polly/8.6.5/polly.8.6.5.nupkg.sha512",
"/home/wjones/.nuget/packages/polly.core/8.6.5/polly.core.8.6.5.nupkg.sha512",
"/home/wjones/.nuget/packages/seriloganalyzer/0.15.0/seriloganalyzer.0.15.0.nupkg.sha512",
"/home/wjones/.nuget/packages/smartanalyzers.multithreadinganalyzer/1.1.31/smartanalyzers.multithreadinganalyzer.1.1.31.nupkg.sha512",
"/home/wjones/.nuget/packages/stylecop.analyzers/1.2.0-beta.556/stylecop.analyzers.1.2.0-beta.556.nupkg.sha512",
"/home/wjones/.nuget/packages/stylecop.analyzers.unstable/1.2.0.556/stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512",
"/home/wjones/.nuget/packages/system.diagnostics.eventlog/6.0.0/system.diagnostics.eventlog.6.0.0.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit/2.9.3/xunit.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.abstractions/2.0.3/xunit.abstractions.2.0.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.analyzers/1.18.0/xunit.analyzers.1.18.0.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.assert/2.9.3/xunit.assert.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.core/2.9.3/xunit.core.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.extensibility.core/2.9.3/xunit.extensibility.core.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.extensibility.execution/2.9.3/xunit.extensibility.execution.2.9.3.nupkg.sha512",
"/home/wjones/.nuget/packages/xunit.runner.visualstudio/2.8.2/xunit.runner.visualstudio.2.8.2.nupkg.sha512"
],
"logs": []
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
17714532033600000
@@ -0,0 +1 @@
17715044195600000
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Xunit;
namespace Jellyfin.Extensions.Tests
{
public static class CopyToExtensionsTests
{
public static TheoryData<IReadOnlyList<int>, IList<int>, int, IList<int>> CopyTo_Valid_Correct_TestData()
{
var data = new TheoryData<IReadOnlyList<int>, IList<int>, int, IList<int>>
{
{ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 0, new[] { 0, 1, 2, 3, 4, 5 } },
{ new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } }
};
return data;
}
[Theory]
[MemberData(nameof(CopyTo_Valid_Correct_TestData))]
public static void CopyTo_Valid_Correct(IReadOnlyList<int> source, IList<int> destination, int index, IList<int> expected)
{
source.CopyTo(destination, index);
Assert.Equal(expected, destination);
}
public static TheoryData<IReadOnlyList<int>, IList<int>, int> CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData()
{
var data = new TheoryData<IReadOnlyList<int>, IList<int>, int>
{
{ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, -1 },
{ new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 6 },
{ new[] { 0, 1, 2 }, Array.Empty<int>(), 0 },
{ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0 }, 0 },
{ new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 }
};
return data;
}
[Theory]
[MemberData(nameof(CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData))]
public static void CopyTo_Invalid_ThrowsArgumentOutOfRangeException(IReadOnlyList<int> source, IList<int> destination, int index)
{
Assert.Throws<ArgumentOutOfRangeException>(() => source.CopyTo(destination, index));
}
}
}
@@ -0,0 +1,23 @@
using System.IO;
using Xunit;
namespace Jellyfin.Extensions.Tests;
public static class FileHelperTests
{
[Fact]
public static void CreateEmpty_Valid_Correct()
{
var path = Path.Join(Path.GetTempPath(), Path.GetRandomFileName());
var fileInfo = new FileInfo(path);
Assert.False(fileInfo.Exists);
FileHelper.CreateEmpty(path);
fileInfo.Refresh();
Assert.True(fileInfo.Exists);
File.Delete(path);
}
}
@@ -0,0 +1,23 @@
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using Xunit;
namespace Jellyfin.Extensions.Tests;
public static class FormattingStreamWriterTests
{
[Fact]
public static void Shuffle_Valid_Correct()
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE", false);
using (var ms = new MemoryStream())
using (var txt = new FormattingStreamWriter(ms, CultureInfo.InvariantCulture))
{
txt.Write("{0}", 3.14159);
txt.Close();
Assert.Equal("3.14159", Encoding.UTF8.GetString(ms.ToArray()));
}
}
}
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FsCheck.Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../MediaBrowser.Model/MediaBrowser.Model.csproj" />
<ProjectReference Include="../../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,45 @@
using System.Text.Json;
using FsCheck;
using FsCheck.Fluent;
using FsCheck.Xunit;
using Jellyfin.Extensions.Json.Converters;
using Xunit;
namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonBoolNumberTests
{
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions()
{
Converters =
{
new JsonBoolNumberConverter()
}
};
[Theory]
[InlineData("1", true)]
[InlineData("0", false)]
[InlineData("2", true)]
[InlineData("true", true)]
[InlineData("false", false)]
public void Deserialize_Number_Valid_Success(string input, bool? output)
{
var value = JsonSerializer.Deserialize<bool>(input, _jsonOptions);
Assert.Equal(value, output);
}
[Theory]
[InlineData(true, "true")]
[InlineData(false, "false")]
public void Serialize_Bool_Success(bool input, string output)
{
var value = JsonSerializer.Serialize(input, _jsonOptions);
Assert.Equal(value, output);
}
[Property]
public Property Deserialize_NonZeroInt_True(NonZeroInt input)
=> JsonSerializer.Deserialize<bool>(input.ToString(), _jsonOptions).ToProperty();
}
}
@@ -0,0 +1,37 @@
using System.Text.Json;
using Jellyfin.Extensions.Json.Converters;
using Xunit;
namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonBoolStringTests
{
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions()
{
Converters =
{
new JsonBoolStringConverter()
}
};
[Theory]
[InlineData(@"{ ""Value"": ""true"" }", true)]
[InlineData(@"{ ""Value"": ""false"" }", false)]
public void Deserialize_String_Valid_Success(string input, bool output)
{
var s = JsonSerializer.Deserialize<TestStruct>(input, _jsonOptions);
Assert.Equal(s.Value, output);
}
[Theory]
[InlineData(true, "true")]
[InlineData(false, "false")]
public void Serialize_Bool_Success(bool input, string output)
{
var value = JsonSerializer.Serialize(input, _jsonOptions);
Assert.Equal(value, output);
}
private readonly record struct TestStruct(bool Value);
}
}
@@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Jellyfin.Extensions.Tests.Json.Models;
using MediaBrowser.Model.Session;
using Xunit;
namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonCommaDelimitedCollectionTests
{
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions()
{
Converters =
{
new JsonStringEnumConverter()
}
};
[Fact]
public void Deserialize_String_Null_Success()
{
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": null }", _jsonOptions);
Assert.Null(value?.Value);
}
[Fact]
public void Deserialize_Empty_Success()
{
var desiredValue = new GenericBodyArrayModel<string>
{
Value = Array.Empty<string>()
};
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": """" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_EmptyList_Success()
{
var desiredValue = new GenericBodyListModel<string>
{
Value = []
};
Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<GenericBodyListModel<string>>(@"{ ""Value"": """" }", _jsonOptions));
}
[Fact]
public void Deserialize_EmptyIReadOnlyList_Success()
{
var desiredValue = new GenericBodyIReadOnlyListModel<string>
{
Value = []
};
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": """" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_String_Valid_Success()
{
var desiredValue = new GenericBodyArrayModel<string>
{
Value = ["a", "b", "c"]
};
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_StringList_Valid_Success()
{
var desiredValue = new GenericBodyListModel<string>
{
Value = ["a", "b", "c"]
};
Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<GenericBodyListModel<string>>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions));
}
[Fact]
public void Deserialize_String_Space_Valid_Success()
{
var desiredValue = new GenericBodyArrayModel<string>
{
Value = ["a", "b", "c"]
};
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a, b, c"" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_GenericCommandType_Valid_Success()
{
var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
{
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown]
};
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_GenericCommandType_EmptyEntry_Success()
{
var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
{
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown]
};
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_GenericCommandType_Invalid_Success()
{
var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
{
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown]
};
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,TotallyNotAValidCommand,MoveDown"" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_GenericCommandType_Space_Valid_Success()
{
var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
{
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown]
};
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_String_Array_Valid_Success()
{
var desiredValue = new GenericBodyArrayModel<string>
{
Value = ["a", "b", "c"]
};
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_GenericCommandType_Array_Valid_Success()
{
var desiredValue = new GenericBodyArrayModel<GeneralCommandType>
{
Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown]
};
var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Serialize_GenericCommandType_ReadOnlyArray_Valid_Success()
{
var valueToSerialize = new GenericBodyIReadOnlyCollectionModel<GeneralCommandType>
{
Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }.AsReadOnly()
};
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyCollectionModel<GeneralCommandType>>(valueToSerialize, _jsonOptions);
Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value);
}
[Fact]
public void Serialize_GenericCommandType_ImmutableArrayArray_Valid_Success()
{
var valueToSerialize = new GenericBodyIReadOnlyCollectionModel<GeneralCommandType>
{
Value = ImmutableArray.Create(new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown })
};
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyCollectionModel<GeneralCommandType>>(valueToSerialize, _jsonOptions);
Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value);
}
[Fact]
public void Serialize_GenericCommandType_List_Valid_Success()
{
var valueToSerialize = new GenericBodyIReadOnlyListModel<GeneralCommandType>
{
Value = new List<GeneralCommandType> { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
};
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(valueToSerialize, _jsonOptions);
Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value);
}
}
}
@@ -0,0 +1,104 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Jellyfin.Extensions.Tests.Json.Models;
using MediaBrowser.Model.Session;
using Xunit;
namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonCommaDelimitedIReadOnlyListTests
{
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions()
{
Converters =
{
new JsonStringEnumConverter()
}
};
[Fact]
public void Deserialize_String_Valid_Success()
{
var desiredValue = new GenericBodyIReadOnlyListModel<string>
{
Value = new[] { "a", "b", "c" }
};
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_String_Space_Valid_Success()
{
var desiredValue = new GenericBodyIReadOnlyListModel<string>
{
Value = new[] { "a", "b", "c" }
};
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a, b, c"" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_GenericCommandType_Valid_Success()
{
var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType>
{
Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
};
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_GenericCommandType_Space_Valid_Success()
{
var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType>
{
Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
};
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_String_Array_Valid_Success()
{
var desiredValue = new GenericBodyIReadOnlyListModel<string>
{
Value = new[] { "a", "b", "c" }
};
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Deserialize_GenericCommandType_Array_Valid_Success()
{
var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType>
{
Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
};
var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions);
Assert.Equal(desiredValue.Value, value?.Value);
}
[Fact]
public void Serialize_GenericCommandType_IReadOnlyList_Valid_Success()
{
var valueToSerialize = new GenericBodyIReadOnlyListModel<GeneralCommandType>
{
Value = new List<GeneralCommandType> { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
};
string value = JsonSerializer.Serialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(valueToSerialize, _jsonOptions);
Assert.Equal(@"{""Value"":[""MoveUp"",""MoveDown""]}", value);
}
}
}
@@ -0,0 +1,112 @@
using System.Text.Json;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions.Json.Converters;
using Xunit;
namespace Jellyfin.Extensions.Tests.Json.Converters;
public class JsonDefaultStringEnumConverterTests
{
private readonly JsonSerializerOptions _jsonOptions = new() { Converters = { new JsonDefaultStringEnumConverterFactory() } };
/// <summary>
/// Test to ensure that `null` and empty string are deserialized to the default value.
/// </summary>
/// <param name="input">The input string.</param>
/// <param name="output">The expected enum value.</param>
[Theory]
[InlineData("\"\"", MediaStreamProtocol.http)]
[InlineData("\"Http\"", MediaStreamProtocol.http)]
[InlineData("\"Hls\"", MediaStreamProtocol.hls)]
public void Deserialize_Enum_Direct(string input, MediaStreamProtocol output)
{
var value = JsonSerializer.Deserialize<MediaStreamProtocol>(input, _jsonOptions);
Assert.Equal(output, value);
}
/// <summary>
/// Test to ensure that `null` and empty string are deserialized to the default value.
/// </summary>
/// <param name="input">The input string.</param>
/// <param name="output">The expected enum value.</param>
[Theory]
[InlineData(null, MediaStreamProtocol.http)]
[InlineData("\"\"", MediaStreamProtocol.http)]
[InlineData("\"Http\"", MediaStreamProtocol.http)]
[InlineData("\"Hls\"", MediaStreamProtocol.hls)]
public void Deserialize_Enum(string? input, MediaStreamProtocol output)
{
input ??= "null";
var json = $"{{ \"EnumValue\": {input} }}";
var value = JsonSerializer.Deserialize<TestClass>(json, _jsonOptions);
Assert.NotNull(value);
Assert.Equal(output, value.EnumValue);
}
/// <summary>
/// Test to ensure that empty string is deserialized to the default value,
/// and `null` is deserialized to `null`.
/// </summary>
/// <param name="input">The input string.</param>
/// <param name="output">The expected enum value.</param>
[Theory]
[InlineData(null, null)]
[InlineData("\"\"", MediaStreamProtocol.http)]
[InlineData("\"Http\"", MediaStreamProtocol.http)]
[InlineData("\"Hls\"", MediaStreamProtocol.hls)]
public void Deserialize_Enum_Nullable(string? input, MediaStreamProtocol? output)
{
input ??= "null";
var json = $"{{ \"EnumValue\": {input} }}";
var value = JsonSerializer.Deserialize<NullTestClass>(json, _jsonOptions);
Assert.NotNull(value);
Assert.Equal(output, value.EnumValue);
}
/// <summary>
/// Ensures that the roundtrip serialization & deserialization is successful.
/// </summary>
/// <param name="input">Input enum.</param>
/// <param name="output">Output enum.</param>
[Theory]
[InlineData(MediaStreamProtocol.http, MediaStreamProtocol.http)]
[InlineData(MediaStreamProtocol.hls, MediaStreamProtocol.hls)]
public void Enum_RoundTrip(MediaStreamProtocol input, MediaStreamProtocol output)
{
var inputObj = new TestClass { EnumValue = input };
var outputObj = JsonSerializer.Deserialize<TestClass>(JsonSerializer.Serialize(inputObj, _jsonOptions), _jsonOptions);
Assert.NotNull(outputObj);
Assert.Equal(output, outputObj.EnumValue);
}
/// <summary>
/// Ensures that the roundtrip serialization & deserialization is successful, including null.
/// </summary>
/// <param name="input">Input enum.</param>
/// <param name="output">Output enum.</param>
[Theory]
[InlineData(MediaStreamProtocol.http, MediaStreamProtocol.http)]
[InlineData(MediaStreamProtocol.hls, MediaStreamProtocol.hls)]
[InlineData(null, null)]
public void Enum_RoundTrip_Nullable(MediaStreamProtocol? input, MediaStreamProtocol? output)
{
var inputObj = new NullTestClass { EnumValue = input };
var outputObj = JsonSerializer.Deserialize<NullTestClass>(JsonSerializer.Serialize(inputObj, _jsonOptions), _jsonOptions);
Assert.NotNull(outputObj);
Assert.Equal(output, outputObj.EnumValue);
}
private sealed class TestClass
{
public MediaStreamProtocol EnumValue { get; set; }
}
private sealed class NullTestClass
{
public MediaStreamProtocol? EnumValue { get; set; }
}
}
@@ -0,0 +1,28 @@
using System.Text.Json;
using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Model.Session;
using Xunit;
namespace Jellyfin.Extensions.Tests.Json.Converters;
public class JsonFlagEnumTests
{
private readonly JsonSerializerOptions _jsonOptions = new()
{
Converters =
{
new JsonFlagEnumConverter<TranscodeReason>()
}
};
[Theory]
[InlineData(TranscodeReason.AudioIsExternal | TranscodeReason.ContainerNotSupported, "[\"ContainerNotSupported\",\"AudioIsExternal\"]")]
[InlineData(TranscodeReason.AudioIsExternal | TranscodeReason.ContainerNotSupported | TranscodeReason.VideoBitDepthNotSupported, "[\"ContainerNotSupported\",\"AudioIsExternal\",\"VideoBitDepthNotSupported\"]")]
[InlineData((TranscodeReason)0, "[]")]
public void Serialize_Transcode_Reason(TranscodeReason transcodeReason, string output)
{
var result = JsonSerializer.Serialize(transcodeReason, _jsonOptions);
Assert.Equal(output, result);
}
}
@@ -0,0 +1,68 @@
using System;
using System.Text.Json;
using Jellyfin.Extensions.Json.Converters;
using Xunit;
namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonGuidConverterTests
{
private readonly JsonSerializerOptions _options;
public JsonGuidConverterTests()
{
_options = new JsonSerializerOptions();
_options.Converters.Add(new JsonGuidConverter());
}
[Fact]
public void Deserialize_Valid_Success()
{
Guid value = JsonSerializer.Deserialize<Guid>(@"""a852a27afe324084ae66db579ee3ee18""", _options);
Assert.Equal(new Guid("a852a27afe324084ae66db579ee3ee18"), value);
}
[Fact]
public void Deserialize_ValidDashed_Success()
{
Guid value = JsonSerializer.Deserialize<Guid>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", _options);
Assert.Equal(new Guid("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value);
}
[Fact]
public void Roundtrip_Valid_Success()
{
Guid guid = new Guid("a852a27afe324084ae66db579ee3ee18");
string value = JsonSerializer.Serialize(guid, _options);
Assert.Equal(guid, JsonSerializer.Deserialize<Guid>(value, _options));
}
[Fact]
public void Deserialize_Null_EmptyGuid()
{
Assert.Equal(Guid.Empty, JsonSerializer.Deserialize<Guid>("null", _options));
}
[Fact]
public void Serialize_EmptyGuid_EmptyGuid()
{
Assert.Equal($"\"{Guid.Empty:N}\"", JsonSerializer.Serialize(Guid.Empty, _options));
}
[Fact]
public void Serialize_Valid_NoDash_Success()
{
var guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA");
var str = JsonSerializer.Serialize(guid, _options);
Assert.Equal($"\"{guid:N}\"", str);
}
[Fact]
public void Serialize_Nullable_Success()
{
Guid? guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA");
var str = JsonSerializer.Serialize(guid, _options);
Assert.Equal($"\"{guid:N}\"", str);
}
}
}
@@ -0,0 +1,80 @@
using System;
using System.Text.Json;
using Jellyfin.Extensions.Json.Converters;
using Xunit;
namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonNullableGuidConverterTests
{
private readonly JsonSerializerOptions _options;
public JsonNullableGuidConverterTests()
{
_options = new JsonSerializerOptions();
_options.Converters.Add(new JsonNullableGuidConverter());
}
[Fact]
public void Deserialize_Valid_Success()
{
Guid? value = JsonSerializer.Deserialize<Guid?>(@"""a852a27afe324084ae66db579ee3ee18""", _options);
Assert.Equal(new Guid("a852a27afe324084ae66db579ee3ee18"), value);
}
[Fact]
public void Deserialize_ValidDashed_Success()
{
Guid? value = JsonSerializer.Deserialize<Guid?>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", _options);
Assert.Equal(new Guid("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value);
}
[Fact]
public void Roundtrip_Valid_Success()
{
Guid guid = new Guid("a852a27afe324084ae66db579ee3ee18");
string value = JsonSerializer.Serialize(guid, _options);
Assert.Equal(guid, JsonSerializer.Deserialize<Guid?>(value, _options));
}
[Fact]
public void Deserialize_Null_Null()
{
Assert.Null(JsonSerializer.Deserialize<Guid?>("null", _options));
}
[Fact]
public void Deserialize_EmptyGuid_EmptyGuid()
{
Assert.Equal(Guid.Empty, JsonSerializer.Deserialize<Guid?>(@"""00000000-0000-0000-0000-000000000000""", _options));
}
[Fact]
public void Serialize_EmptyGuid_Null()
{
Assert.Equal("null", JsonSerializer.Serialize((Guid?)Guid.Empty, _options));
}
[Fact]
public void Serialize_Null_Null()
{
Assert.Equal("null", JsonSerializer.Serialize((Guid?)null, _options));
}
[Fact]
public void Serialize_Valid_NoDash_Success()
{
var guid = (Guid?)new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA");
var str = JsonSerializer.Serialize(guid, _options);
Assert.Equal($"\"{guid:N}\"", str);
}
[Fact]
public void Serialize_Nullable_Success()
{
Guid? guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA");
var str = JsonSerializer.Serialize(guid, _options);
Assert.Equal($"\"{guid:N}\"", str);
}
}
}
@@ -0,0 +1,38 @@
using System.Text.Json;
using Jellyfin.Extensions.Json.Converters;
using Xunit;
namespace Jellyfin.Extensions.Tests.Json.Converters
{
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(output, deserialized);
}
}
}
@@ -0,0 +1,36 @@
using System;
using System.Text.Json;
using Jellyfin.Extensions.Json.Converters;
using Xunit;
namespace Jellyfin.Extensions.Tests.Json.Converters
{
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);
}
}
}
@@ -0,0 +1,20 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using Jellyfin.Extensions.Json.Converters;
namespace Jellyfin.Extensions.Tests.Json.Models
{
/// <summary>
/// The generic body model.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
public sealed class GenericBodyArrayModel<T>
{
/// <summary>
/// Gets or sets the value.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1819:Properties should not return arrays", MessageId = "Value", Justification = "Imported from ServiceStack")]
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
public T[] Value { get; set; } = default!;
}
}
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Jellyfin.Extensions.Json.Converters;
namespace Jellyfin.Extensions.Tests.Json.Models
{
/// <summary>
/// The generic body <c>IReadOnlyCollection</c> model.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
public sealed class GenericBodyIReadOnlyCollectionModel<T>
{
/// <summary>
/// Gets or sets the value.
/// </summary>
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
public IReadOnlyCollection<T> Value { get; set; } = default!;
}
}
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Jellyfin.Extensions.Json.Converters;
namespace Jellyfin.Extensions.Tests.Json.Models
{
/// <summary>
/// The generic body <c>IReadOnlyList</c> model.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
public sealed class GenericBodyIReadOnlyListModel<T>
{
/// <summary>
/// Gets or sets the value.
/// </summary>
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
public IReadOnlyList<T> Value { get; set; } = default!;
}
}
@@ -0,0 +1,22 @@
#pragma warning disable CA1002 // Do not expose generic lists
#pragma warning disable CA2227 // Collection properties should be read only
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Jellyfin.Extensions.Json.Converters;
namespace Jellyfin.Extensions.Tests.Json.Models
{
/// <summary>
/// The generic body <c>List</c> model.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
public sealed class GenericBodyListModel<T>
{
/// <summary>
/// Gets or sets the value.
/// </summary>
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
public List<T> Value { get; set; } = default!;
}
}
@@ -0,0 +1,19 @@
using System;
using Xunit;
namespace Jellyfin.Extensions.Tests
{
public static class ShuffleExtensionsTests
{
[Fact]
public static void Shuffle_Valid_Correct()
{
byte[] original = new byte[1 << 6];
Random.Shared.NextBytes(original);
byte[] shuffled = (byte[])original.Clone();
shuffled.Shuffle();
Assert.NotEqual(original, shuffled);
}
}
}
@@ -0,0 +1,79 @@
using System;
using Xunit;
namespace Jellyfin.Extensions.Tests
{
public class StringExtensionsTests
{
[Theory]
[InlineData("", "")] // Identity edge-case (no diacritics)
[InlineData("Indiana Jones", "Indiana Jones")] // Identity (no diacritics)
[InlineData("a\ud800b", "ab")] // Invalid UTF-16 char stripping
[InlineData("åäö", "aao")] // Issue #7484
[InlineData("Jön", "Jon")] // Issue #7484
[InlineData("Jönssonligan", "Jonssonligan")] // Issue #7484
[InlineData("Kieślowski", "Kieslowski")] // Issue #7450
[InlineData("Cidadão Kane", "Cidadao Kane")] // Issue #7560
[InlineData("운명처럼 널 사랑해", "운명처럼 널 사랑해")] // Issue #6393 (Korean language support)
[InlineData("애타는 로맨스", "애타는 로맨스")] // Issue #6393
[InlineData("Le cœur a ses raisons", "Le coeur a ses raisons")] // Issue #8893
[InlineData("Béla Tarr", "Bela Tarr")] // Issue #8893
public void RemoveDiacritics_ValidInput_Corrects(string input, string expectedResult)
{
string result = input.RemoveDiacritics();
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("", false)] // Identity edge-case (no diacritics)
[InlineData("Indiana Jones", false)] // Identity (no diacritics)
[InlineData("a\ud800b", true)] // Invalid UTF-16 char stripping
[InlineData("åäö", true)] // Issue #7484
[InlineData("Jön", true)] // Issue #7484
[InlineData("Jönssonligan", true)] // Issue #7484
[InlineData("Kieślowski", true)] // Issue #7450
[InlineData("Cidadão Kane", true)] // Issue #7560
[InlineData("운명처럼 널 사랑해", false)] // Issue #6393 (Korean language support)
[InlineData("애타는 로맨스", false)] // Issue #6393
[InlineData("Le cœur a ses raisons", true)] // Issue #8893
[InlineData("Béla Tarr", true)] // Issue #8893
public void HasDiacritics_ValidInput_Corrects(string input, bool expectedResult)
{
bool result = input.HasDiacritics();
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("", '_', 0)]
[InlineData("___", '_', 3)]
[InlineData("test\x00", '\x00', 1)]
[InlineData("Imdb=tt0119567|Tmdb=330|TmdbCollection=328", '|', 2)]
public void ReadOnlySpan_Count_Success(string str, char needle, int count)
{
Assert.Equal(count, str.AsSpan().Count(needle));
}
[Theory]
[InlineData("", 'q', "")]
[InlineData("Banana split", ' ', "Banana")]
[InlineData("Banana split", 'q', "Banana split")]
[InlineData("Banana split 2", ' ', "Banana")]
public void LeftPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult)
{
var result = str.AsSpan().LeftPart(needle).ToString();
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("", 'q', "")]
[InlineData("Banana split", ' ', "split")]
[InlineData("Banana split", 'q', "Banana split")]
[InlineData("Banana split.", '.', "")]
[InlineData("Banana split 2", ' ', "2")]
public void RightPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult)
{
var result = str.AsSpan().RightPart(needle).ToString();
Assert.Equal(expectedResult, result);
}
}
}
@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")]
@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Jellyfin.Extensions.Tests")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("Jellyfin.Extensions.Tests")]
[assembly: System.Reflection.AssemblyTitleAttribute("Jellyfin.Extensions.Tests")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
@@ -0,0 +1 @@
a9cd9f1b54cb0c14d8cfa5b8178fecf508c852c97b2e4a7c895ddcc7cc974ad2
@@ -0,0 +1,27 @@
is_global = true
build_property.TargetFramework = net10.0
build_property.TargetFramework = net10.0
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkVersion = v10.0
build_property.TargetFrameworkVersion = v10.0
build_property.TargetPlatformMinVersion =
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb =
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = Jellyfin.Extensions.Tests
build_property.ProjectDir = /srv/common_drive/Projects/pgsql-jellyfin/tests/Jellyfin.Extensions.Tests/
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.EffectiveAnalysisLevelStyle = 10.0
build_property.EnableCodeStyleSeverity =

Some files were not shown because too many files have changed in this diff Show More