mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-07-18 20:24:20 +01:00
Merge branch 'master' into video-resolver
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using AutoFixture;
|
||||
using AutoFixture.AutoMoq;
|
||||
using Jellyfin.Api.Auth;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@@ -24,12 +24,6 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
private readonly IFixture _fixture;
|
||||
|
||||
private readonly Mock<IAuthService> _jellyfinAuthServiceMock;
|
||||
private readonly Mock<IOptionsMonitor<AuthenticationSchemeOptions>> _optionsMonitorMock;
|
||||
private readonly Mock<ISystemClock> _clockMock;
|
||||
private readonly Mock<IServiceProvider> _serviceProviderMock;
|
||||
private readonly Mock<IAuthenticationService> _authenticationServiceMock;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
private readonly HttpContext _context;
|
||||
|
||||
private readonly CustomAuthenticationHandler _sut;
|
||||
private readonly AuthenticationScheme _scheme;
|
||||
@@ -45,26 +39,23 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
AllowFixtureCircularDependencies();
|
||||
|
||||
_jellyfinAuthServiceMock = _fixture.Freeze<Mock<IAuthService>>();
|
||||
_optionsMonitorMock = _fixture.Freeze<Mock<IOptionsMonitor<AuthenticationSchemeOptions>>>();
|
||||
_clockMock = _fixture.Freeze<Mock<ISystemClock>>();
|
||||
_serviceProviderMock = _fixture.Freeze<Mock<IServiceProvider>>();
|
||||
_authenticationServiceMock = _fixture.Freeze<Mock<IAuthenticationService>>();
|
||||
var optionsMonitorMock = _fixture.Freeze<Mock<IOptionsMonitor<AuthenticationSchemeOptions>>>();
|
||||
var serviceProviderMock = _fixture.Freeze<Mock<IServiceProvider>>();
|
||||
var authenticationServiceMock = _fixture.Freeze<Mock<IAuthenticationService>>();
|
||||
_fixture.Register<ILoggerFactory>(() => new NullLoggerFactory());
|
||||
|
||||
_urlEncoder = UrlEncoder.Default;
|
||||
serviceProviderMock.Setup(s => s.GetService(typeof(IAuthenticationService)))
|
||||
.Returns(authenticationServiceMock.Object);
|
||||
|
||||
_serviceProviderMock.Setup(s => s.GetService(typeof(IAuthenticationService)))
|
||||
.Returns(_authenticationServiceMock.Object);
|
||||
|
||||
_optionsMonitorMock.Setup(o => o.Get(It.IsAny<string>()))
|
||||
optionsMonitorMock.Setup(o => o.Get(It.IsAny<string>()))
|
||||
.Returns(new AuthenticationSchemeOptions
|
||||
{
|
||||
ForwardAuthenticate = null
|
||||
});
|
||||
|
||||
_context = new DefaultHttpContext
|
||||
HttpContext context = new DefaultHttpContext
|
||||
{
|
||||
RequestServices = _serviceProviderMock.Object
|
||||
RequestServices = serviceProviderMock.Object
|
||||
};
|
||||
|
||||
_scheme = new AuthenticationScheme(
|
||||
@@ -73,22 +64,7 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
typeof(CustomAuthenticationHandler));
|
||||
|
||||
_sut = _fixture.Create<CustomAuthenticationHandler>();
|
||||
_sut.InitializeAsync(_scheme, _context).Wait();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAuthenticateAsyncShouldFailWithNullUser()
|
||||
{
|
||||
_jellyfinAuthServiceMock.Setup(
|
||||
a => a.Authenticate(
|
||||
It.IsAny<HttpRequest>(),
|
||||
It.IsAny<AuthenticatedAttribute>()))
|
||||
.Returns((User?)null);
|
||||
|
||||
var authenticateResult = await _sut.AuthenticateAsync();
|
||||
|
||||
Assert.False(authenticateResult.Succeeded);
|
||||
Assert.Equal("Invalid user", authenticateResult.Failure.Message);
|
||||
_sut.InitializeAsync(_scheme, context).Wait();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -98,8 +74,7 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
|
||||
_jellyfinAuthServiceMock.Setup(
|
||||
a => a.Authenticate(
|
||||
It.IsAny<HttpRequest>(),
|
||||
It.IsAny<AuthenticatedAttribute>()))
|
||||
It.IsAny<HttpRequest>()))
|
||||
.Throws(new SecurityException(errorMessage));
|
||||
|
||||
var authenticateResult = await _sut.AuthenticateAsync();
|
||||
@@ -121,10 +96,10 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
[Fact]
|
||||
public async Task HandleAuthenticateAsyncShouldAssignNameClaim()
|
||||
{
|
||||
var user = SetupUser();
|
||||
var authorizationInfo = SetupUser();
|
||||
var authenticateResult = await _sut.AuthenticateAsync();
|
||||
|
||||
Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, user.Name));
|
||||
Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, authorizationInfo.User.Username));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -132,10 +107,10 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
[InlineData(false)]
|
||||
public async Task HandleAuthenticateAsyncShouldAssignRoleClaim(bool isAdmin)
|
||||
{
|
||||
var user = SetupUser(isAdmin);
|
||||
var authorizationInfo = SetupUser(isAdmin);
|
||||
var authenticateResult = await _sut.AuthenticateAsync();
|
||||
|
||||
var expectedRole = user.Policy.IsAdministrator ? UserRoles.Administrator : UserRoles.User;
|
||||
var expectedRole = authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User;
|
||||
Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Role, expectedRole));
|
||||
}
|
||||
|
||||
@@ -148,18 +123,18 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
Assert.Equal(_scheme.Name, authenticatedResult.Ticket.AuthenticationScheme);
|
||||
}
|
||||
|
||||
private User SetupUser(bool isAdmin = false)
|
||||
private AuthorizationInfo SetupUser(bool isAdmin = false)
|
||||
{
|
||||
var user = _fixture.Create<User>();
|
||||
user.Policy.IsAdministrator = isAdmin;
|
||||
var authorizationInfo = _fixture.Create<AuthorizationInfo>();
|
||||
authorizationInfo.User = _fixture.Create<User>();
|
||||
authorizationInfo.User.SetPermission(PermissionKind.IsAdministrator, isAdmin);
|
||||
|
||||
_jellyfinAuthServiceMock.Setup(
|
||||
a => a.Authenticate(
|
||||
It.IsAny<HttpRequest>(),
|
||||
It.IsAny<AuthenticatedAttribute>()))
|
||||
.Returns(user);
|
||||
It.IsAny<HttpRequest>()))
|
||||
.Returns(authorizationInfo);
|
||||
|
||||
return user;
|
||||
return authorizationInfo;
|
||||
}
|
||||
|
||||
private void AllowFixtureCircularDependencies()
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using AutoFixture;
|
||||
using AutoFixture.AutoMoq;
|
||||
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
||||
using Jellyfin.Api.Constants;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using AutoFixture;
|
||||
using AutoFixture.AutoMoq;
|
||||
using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
@@ -18,12 +18,16 @@ namespace Jellyfin.Api.Tests.Auth.FirstTimeSetupOrElevatedPolicy
|
||||
private readonly Mock<IConfigurationManager> _configurationManagerMock;
|
||||
private readonly List<IAuthorizationRequirement> _requirements;
|
||||
private readonly FirstTimeSetupOrElevatedHandler _sut;
|
||||
private readonly Mock<IUserManager> _userManagerMock;
|
||||
private readonly Mock<IHttpContextAccessor> _httpContextAccessor;
|
||||
|
||||
public FirstTimeSetupOrElevatedHandlerTests()
|
||||
{
|
||||
var fixture = new Fixture().Customize(new AutoMoqCustomization());
|
||||
_configurationManagerMock = fixture.Freeze<Mock<IConfigurationManager>>();
|
||||
_requirements = new List<IAuthorizationRequirement> { new FirstTimeSetupOrElevatedRequirement() };
|
||||
_userManagerMock = fixture.Freeze<Mock<IUserManager>>();
|
||||
_httpContextAccessor = fixture.Freeze<Mock<IHttpContextAccessor>>();
|
||||
|
||||
_sut = fixture.Create<FirstTimeSetupOrElevatedHandler>();
|
||||
}
|
||||
@@ -34,9 +38,13 @@ namespace Jellyfin.Api.Tests.Auth.FirstTimeSetupOrElevatedPolicy
|
||||
[InlineData(UserRoles.User)]
|
||||
public async Task ShouldSucceedIfStartupWizardIncomplete(string userRole)
|
||||
{
|
||||
SetupConfigurationManager(false);
|
||||
var user = SetupUser(userRole);
|
||||
var context = new AuthorizationHandlerContext(_requirements, user, null);
|
||||
TestHelpers.SetupConfigurationManager(_configurationManagerMock, false);
|
||||
var claims = TestHelpers.SetupUser(
|
||||
_userManagerMock,
|
||||
_httpContextAccessor,
|
||||
userRole);
|
||||
|
||||
var context = new AuthorizationHandlerContext(_requirements, claims, null);
|
||||
|
||||
await _sut.HandleAsync(context);
|
||||
Assert.True(context.HasSucceeded);
|
||||
@@ -48,30 +56,16 @@ namespace Jellyfin.Api.Tests.Auth.FirstTimeSetupOrElevatedPolicy
|
||||
[InlineData(UserRoles.User, false)]
|
||||
public async Task ShouldRequireAdministratorIfStartupWizardComplete(string userRole, bool shouldSucceed)
|
||||
{
|
||||
SetupConfigurationManager(true);
|
||||
var user = SetupUser(userRole);
|
||||
var context = new AuthorizationHandlerContext(_requirements, user, null);
|
||||
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
|
||||
var claims = TestHelpers.SetupUser(
|
||||
_userManagerMock,
|
||||
_httpContextAccessor,
|
||||
userRole);
|
||||
|
||||
var context = new AuthorizationHandlerContext(_requirements, claims, null);
|
||||
|
||||
await _sut.HandleAsync(context);
|
||||
Assert.Equal(shouldSucceed, context.HasSucceeded);
|
||||
}
|
||||
|
||||
private static ClaimsPrincipal SetupUser(string role)
|
||||
{
|
||||
var claims = new[] { new Claim(ClaimTypes.Role, role) };
|
||||
var identity = new ClaimsIdentity(claims);
|
||||
return new ClaimsPrincipal(identity);
|
||||
}
|
||||
|
||||
private void SetupConfigurationManager(bool startupWizardCompleted)
|
||||
{
|
||||
var commonConfiguration = new BaseApplicationConfiguration
|
||||
{
|
||||
IsStartupWizardCompleted = startupWizardCompleted
|
||||
};
|
||||
|
||||
_configurationManagerMock.Setup(c => c.CommonConfiguration)
|
||||
.Returns(commonConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using AutoFixture;
|
||||
using AutoFixture.AutoMoq;
|
||||
using Jellyfin.Api.Auth.IgnoreParentalControlPolicy;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.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 IgnoreParentalControlHandler _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 IgnoreParentalControlRequirement() };
|
||||
_userManagerMock = fixture.Freeze<Mock<IUserManager>>();
|
||||
_httpContextAccessor = fixture.Freeze<Mock<IHttpContextAccessor>>();
|
||||
|
||||
_sut = fixture.Create<IgnoreParentalControlHandler>();
|
||||
}
|
||||
|
||||
[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,58 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using AutoFixture;
|
||||
using AutoFixture.AutoMoq;
|
||||
using Jellyfin.Api.Auth.LocalAccessPolicy;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Api.Tests.Auth.LocalAccessPolicy
|
||||
{
|
||||
public class LocalAccessHandlerTests
|
||||
{
|
||||
private readonly Mock<IConfigurationManager> _configurationManagerMock;
|
||||
private readonly List<IAuthorizationRequirement> _requirements;
|
||||
private readonly LocalAccessHandler _sut;
|
||||
private readonly Mock<IUserManager> _userManagerMock;
|
||||
private readonly Mock<IHttpContextAccessor> _httpContextAccessor;
|
||||
private readonly Mock<INetworkManager> _networkManagerMock;
|
||||
|
||||
public LocalAccessHandlerTests()
|
||||
{
|
||||
var fixture = new Fixture().Customize(new AutoMoqCustomization());
|
||||
_configurationManagerMock = fixture.Freeze<Mock<IConfigurationManager>>();
|
||||
_requirements = new List<IAuthorizationRequirement> { new LocalAccessRequirement() };
|
||||
_userManagerMock = fixture.Freeze<Mock<IUserManager>>();
|
||||
_httpContextAccessor = fixture.Freeze<Mock<IHttpContextAccessor>>();
|
||||
_networkManagerMock = fixture.Freeze<Mock<INetworkManager>>();
|
||||
|
||||
_sut = fixture.Create<LocalAccessHandler>();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, true)]
|
||||
[InlineData(false, false)]
|
||||
public async Task LocalAccessOnly(bool isInLocalNetwork, bool shouldSucceed)
|
||||
{
|
||||
_networkManagerMock
|
||||
.Setup(n => n.IsInLocalNetwork(It.IsAny<string>()))
|
||||
.Returns(isInLocalNetwork);
|
||||
|
||||
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
|
||||
var claims = TestHelpers.SetupUser(
|
||||
_userManagerMock,
|
||||
_httpContextAccessor,
|
||||
UserRoles.User);
|
||||
|
||||
var context = new AuthorizationHandlerContext(_requirements, claims, null);
|
||||
await _sut.HandleAsync(context);
|
||||
Assert.Equal(shouldSucceed, context.HasSucceeded);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using AutoFixture;
|
||||
using AutoFixture.AutoMoq;
|
||||
using Jellyfin.Api.Auth.RequiresElevationPolicy;
|
||||
using Jellyfin.Api.Constants;
|
||||
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.RequiresElevationPolicy
|
||||
{
|
||||
public class RequiresElevationHandlerTests
|
||||
{
|
||||
private readonly Mock<IConfigurationManager> _configurationManagerMock;
|
||||
private readonly List<IAuthorizationRequirement> _requirements;
|
||||
private readonly RequiresElevationHandler _sut;
|
||||
private readonly Mock<IUserManager> _userManagerMock;
|
||||
private readonly Mock<IHttpContextAccessor> _httpContextAccessor;
|
||||
|
||||
public RequiresElevationHandlerTests()
|
||||
{
|
||||
_sut = new RequiresElevationHandler();
|
||||
var fixture = new Fixture().Customize(new AutoMoqCustomization());
|
||||
_configurationManagerMock = fixture.Freeze<Mock<IConfigurationManager>>();
|
||||
_requirements = new List<IAuthorizationRequirement> { new RequiresElevationRequirement() };
|
||||
_userManagerMock = fixture.Freeze<Mock<IUserManager>>();
|
||||
_httpContextAccessor = fixture.Freeze<Mock<IHttpContextAccessor>>();
|
||||
|
||||
_sut = fixture.Create<RequiresElevationHandler>();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -23,13 +38,13 @@ namespace Jellyfin.Api.Tests.Auth.RequiresElevationPolicy
|
||||
[InlineData(UserRoles.Guest, false)]
|
||||
public async Task ShouldHandleRolesCorrectly(string role, bool shouldSucceed)
|
||||
{
|
||||
var requirements = new List<IAuthorizationRequirement> { new RequiresElevationRequirement() };
|
||||
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
|
||||
var claims = TestHelpers.SetupUser(
|
||||
_userManagerMock,
|
||||
_httpContextAccessor,
|
||||
role);
|
||||
|
||||
var claims = new[] { new Claim(ClaimTypes.Role, role) };
|
||||
var identity = new ClaimsIdentity(claims);
|
||||
var user = new ClaimsPrincipal(identity);
|
||||
|
||||
var context = new AuthorizationHandlerContext(requirements, user, null);
|
||||
var context = new AuthorizationHandlerContext(_requirements, claims, null);
|
||||
|
||||
await _sut.HandleAsync(context);
|
||||
Assert.Equal(shouldSucceed, context.HasSucceeded);
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Branding;
|
||||
using Xunit;
|
||||
|
||||
namespace MediaBrowser.Api.Tests
|
||||
namespace Jellyfin.Api.Tests
|
||||
{
|
||||
public sealed class BrandingServiceTests : IClassFixture<JellyfinApplicationFactory>
|
||||
{
|
||||
@@ -43,7 +43,7 @@ namespace MediaBrowser.Api.Tests
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal("text/css", response.Content.Headers.ContentType.ToString());
|
||||
Assert.Equal("text/css; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using MediaBrowser.Api;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Api.Tests
|
||||
{
|
||||
public class GetPathValueTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("https://localhost:8096/ScheduledTasks/1234/Triggers", "", 1, "1234")]
|
||||
[InlineData("https://localhost:8096/emby/ScheduledTasks/1234/Triggers", "", 1, "1234")]
|
||||
[InlineData("https://localhost:8096/mediabrowser/ScheduledTasks/1234/Triggers", "", 1, "1234")]
|
||||
[InlineData("https://localhost:8096/jellyfin/2/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
|
||||
[InlineData("https://localhost:8096/jellyfin/2/emby/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
|
||||
[InlineData("https://localhost:8096/jellyfin/2/mediabrowser/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
|
||||
[InlineData("https://localhost:8096/JELLYFIN/2/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
|
||||
[InlineData("https://localhost:8096/JELLYFIN/2/Emby/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
|
||||
[InlineData("https://localhost:8096/JELLYFIN/2/MediaBrowser/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
|
||||
public void GetPathValueTest(string path, string baseUrl, int index, string value)
|
||||
{
|
||||
var reqMock = Mock.Of<IRequest>(x => x.PathInfo == path);
|
||||
var conf = new ServerConfiguration()
|
||||
{
|
||||
BaseUrl = baseUrl
|
||||
};
|
||||
|
||||
var confManagerMock = Mock.Of<IServerConfigurationManager>(x => x.Configuration == conf);
|
||||
|
||||
var service = new BrandingService(
|
||||
new NullLogger<BrandingService>(),
|
||||
confManagerMock,
|
||||
Mock.Of<IHttpResultFactory>())
|
||||
{
|
||||
Request = reqMock
|
||||
};
|
||||
|
||||
Assert.Equal(value, service.GetPathValue(index).ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,20 +13,32 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="4.11.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.11.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="AutoFixture" Version="4.13.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.13.0" />
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.13.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
<PackageReference Include="Moq" Version="4.13.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
<PackageReference Include="Moq" Version="4.14.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers -->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../MediaBrowser.Api/MediaBrowser.Api.csproj" />
|
||||
<ProjectReference Include="../../Jellyfin.Api/Jellyfin.Api.csproj" />
|
||||
<ProjectReference Include="..\..\Jellyfin.Server\Jellyfin.Server.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using Serilog.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Api.Tests
|
||||
namespace Jellyfin.Api.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests.
|
||||
@@ -47,8 +47,7 @@ namespace MediaBrowser.Api.Tests
|
||||
// Specify the startup command line options
|
||||
var commandLineOpts = new StartupOptions
|
||||
{
|
||||
NoWebClient = true,
|
||||
NoAutoRunWebApp = true
|
||||
NoWebClient = true
|
||||
};
|
||||
|
||||
// Use a temporary directory for the application paths
|
||||
@@ -72,6 +71,7 @@ namespace MediaBrowser.Api.Tests
|
||||
var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths);
|
||||
|
||||
ILoggerFactory loggerFactory = new SerilogLoggerFactory();
|
||||
var serviceCollection = new ServiceCollection();
|
||||
_disposableComponents.Add(loggerFactory);
|
||||
|
||||
// Create the app host and initialize it
|
||||
@@ -80,10 +80,10 @@ namespace MediaBrowser.Api.Tests
|
||||
loggerFactory,
|
||||
commandLineOpts,
|
||||
new ManagedFileSystem(loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
||||
new NetworkManager(loggerFactory.CreateLogger<NetworkManager>()));
|
||||
new NetworkManager(loggerFactory.CreateLogger<NetworkManager>()),
|
||||
serviceCollection);
|
||||
_disposableComponents.Add(appHost);
|
||||
var serviceCollection = new ServiceCollection();
|
||||
appHost.Init(serviceCollection);
|
||||
appHost.Init();
|
||||
|
||||
// Configure the web host builder
|
||||
Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths);
|
||||
@@ -0,0 +1,225 @@
|
||||
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.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Api.Tests.ModelBinders
|
||||
{
|
||||
public sealed class CommaDelimitedArrayModelBinderTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedStringArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = new[] { "lol", "xd" };
|
||||
var queryParamString = "lol,xd";
|
||||
var queryParamType = typeof(string[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
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((string[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedIntArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = new[] { 42, 0 };
|
||||
var queryParamString = "42,0";
|
||||
var queryParamType = typeof(int[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
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((int[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
var queryParamString = "How,Much";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
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((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQueryWithDoubleCommas()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
var queryParamString = "How,,Much";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
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((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidEnumArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
var queryParamString1 = "How";
|
||||
var queryParamString2 = "Much";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
|
||||
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((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsEmptyEnumArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = Array.Empty<TestType>();
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
|
||||
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((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_ThrowsIfCommaDelimitedEnumArrayQueryIsInvalid()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamString = "🔥,😢";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
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);
|
||||
|
||||
Func<Task> act = async () => await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
await Assert.ThrowsAsync<FormatException>(act);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_ThrowsIfCommaDelimitedEnumArrayQueryIsInvalid2()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamString1 = "How";
|
||||
var queryParamString2 = "😱";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
|
||||
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);
|
||||
|
||||
Func<Task> act = async () => await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
await Assert.ThrowsAsync<FormatException>(act);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs
Normal file
17
tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Jellyfin.Api.Tests.ModelBinders
|
||||
{
|
||||
public enum TestType
|
||||
{
|
||||
#pragma warning disable SA1602 // Enumeration items should be documented
|
||||
How,
|
||||
Much,
|
||||
Is,
|
||||
The,
|
||||
Fish
|
||||
#pragma warning restore SA1602 // Enumeration items should be documented
|
||||
}
|
||||
}
|
||||
42
tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs
Normal file
42
tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Branding;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Jellyfin.Api.Tests
|
||||
{
|
||||
public sealed class OpenApiSpecTests : IClassFixture<JellyfinApplicationFactory>
|
||||
{
|
||||
private readonly JellyfinApplicationFactory _factory;
|
||||
private readonly ITestOutputHelper _outputHelper;
|
||||
|
||||
public OpenApiSpecTests(JellyfinApplicationFactory factory, ITestOutputHelper outputHelper)
|
||||
{
|
||||
_factory = factory;
|
||||
_outputHelper = outputHelper;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSpec_ReturnsCorrectResponse()
|
||||
{
|
||||
// Arrange
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetAsync("/api-docs/openapi.json");
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||
|
||||
// Write out for publishing
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json"));
|
||||
_outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath);
|
||||
File.WriteAllText(outputPath, responseBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
81
tests/Jellyfin.Api.Tests/TestHelpers.cs
Normal file
81
tests/Jellyfin.Api.Tests/TestHelpers.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.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.Data.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);
|
||||
|
||||
// Set administrator flag.
|
||||
user.SetPermission(PermissionKind.IsAdministrator, role.Equals(UserRoles.Administrator, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Add access schedules if set.
|
||||
if (accessSchedules != 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.Empty.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,14 +13,26 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers -->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../MediaBrowser.Common/MediaBrowser.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
32
tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs
Normal file
32
tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using MediaBrowser.Common.Json.Converters;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Common.Tests.Extensions
|
||||
{
|
||||
public static class JsonGuidConverterTests
|
||||
{
|
||||
[Fact]
|
||||
public static void Deserialize_Valid_Success()
|
||||
{
|
||||
var options = new JsonSerializerOptions();
|
||||
options.Converters.Add(new JsonGuidConverter());
|
||||
Guid value = JsonSerializer.Deserialize<Guid>(@"""a852a27afe324084ae66db579ee3ee18""", options);
|
||||
Assert.Equal(new Guid("a852a27afe324084ae66db579ee3ee18"), value);
|
||||
|
||||
value = JsonSerializer.Deserialize<Guid>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", options);
|
||||
Assert.Equal(new Guid("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void Roundtrip_Valid_Success()
|
||||
{
|
||||
var options = new JsonSerializerOptions();
|
||||
options.Converters.Add(new JsonGuidConverter());
|
||||
Guid guid = new Guid("a852a27afe324084ae66db579ee3ee18");
|
||||
string value = JsonSerializer.Serialize(guid, options);
|
||||
Assert.Equal(guid, JsonSerializer.Deserialize<Guid>(value, options));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ namespace Jellyfin.Common.Tests
|
||||
public class PasswordHashTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
|
||||
[InlineData(
|
||||
"$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
|
||||
"PBKDF2",
|
||||
"",
|
||||
"62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]
|
||||
|
||||
@@ -13,14 +13,26 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers -->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../MediaBrowser.Controller/MediaBrowser.Controller.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,37 +9,44 @@ namespace Jellyfin.MediaEncoding.Tests
|
||||
{
|
||||
public class EncoderValidatorTests
|
||||
{
|
||||
private class GetFFmpegVersionTestData : IEnumerable<object?[]>
|
||||
{
|
||||
public IEnumerator<object?[]> GetEnumerator()
|
||||
{
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) };
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4) };
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput, null };
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[ClassData(typeof(GetFFmpegVersionTestData))]
|
||||
public void GetFFmpegVersionTest(string versionOutput, Version? version)
|
||||
{
|
||||
Assert.Equal(version, EncoderValidator.GetFFmpegVersion(versionOutput));
|
||||
var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
|
||||
Assert.Equal(version, val.GetFFmpegVersion(versionOutput));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(EncoderValidatorTestsData.FFmpegV431Output, true)]
|
||||
[InlineData(EncoderValidatorTestsData.FFmpegV43Output, true)]
|
||||
[InlineData(EncoderValidatorTestsData.FFmpegV421Output, true)]
|
||||
[InlineData(EncoderValidatorTestsData.FFmpegV42Output, true)]
|
||||
[InlineData(EncoderValidatorTestsData.FFmpegV414Output, true)]
|
||||
[InlineData(EncoderValidatorTestsData.FFmpegV404Output, true)]
|
||||
[InlineData(EncoderValidatorTestsData.FFmpegGitUnknownOutput2, true)]
|
||||
[InlineData(EncoderValidatorTestsData.FFmpegGitUnknownOutput, false)]
|
||||
public void ValidateVersionInternalTest(string versionOutput, bool valid)
|
||||
{
|
||||
var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
|
||||
Assert.Equal(valid, val.ValidateVersionInternal(versionOutput));
|
||||
}
|
||||
|
||||
private class GetFFmpegVersionTestData : IEnumerable<object?[]>
|
||||
{
|
||||
public IEnumerator<object?[]> GetEnumerator()
|
||||
{
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV431Output, new Version(4, 3, 1) };
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV43Output, new Version(4, 3) };
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) };
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4) };
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput2, new Version(4, 0) };
|
||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput, null };
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,30 @@ namespace Jellyfin.MediaEncoding.Tests
|
||||
{
|
||||
internal static class EncoderValidatorTestsData
|
||||
{
|
||||
public const string FFmpegV431Output = @"ffmpeg version n4.3.1 Copyright (c) 2000-2020 the FFmpeg developers
|
||||
built with gcc 10.1.0 (GCC)
|
||||
configuration: --prefix=/usr --disable-debug --disable-static --disable-stripping --enable-avisynth --enable-fontconfig --enable-gmp --enable-gnutls --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libiec61883 --enable-libjack --enable-libmfx --enable-libmodplug --enable-libmp3lame --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-librav1e --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-nvdec --enable-nvenc --enable-omx --enable-shared --enable-version3
|
||||
libavutil 56. 51.100 / 56. 51.100
|
||||
libavcodec 58. 91.100 / 58. 91.100
|
||||
libavformat 58. 45.100 / 58. 45.100
|
||||
libavdevice 58. 10.100 / 58. 10.100
|
||||
libavfilter 7. 85.100 / 7. 85.100
|
||||
libswscale 5. 7.100 / 5. 7.100
|
||||
libswresample 3. 7.100 / 3. 7.100
|
||||
libpostproc 55. 7.100 / 55. 7.100";
|
||||
|
||||
public const string FFmpegV43Output = @"ffmpeg version 4.3 Copyright (c) 2000-2020 the FFmpeg developers
|
||||
built with gcc 7 (Ubuntu 7.5.0-3ubuntu1~18.04)
|
||||
configuration: --prefix=/usr/lib/jellyfin-ffmpeg --target-os=linux --disable-doc --disable-ffplay --disable-shared --disable-libxcb --disable-vdpau --disable-sdl2 --disable-xlib --enable-gpl --enable-version3 --enable-static --enable-libfontconfig --enable-fontconfig --enable-gmp --enable-gnutls --enable-libass --enable-libbluray --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libx264 --enable-libx265 --enable-libzvbi --arch=amd64 --enable-amf --enable-nvenc --enable-nvdec --enable-vaapi --enable-opencl
|
||||
libavutil 56. 51.100 / 56. 51.100
|
||||
libavcodec 58. 91.100 / 58. 91.100
|
||||
libavformat 58. 45.100 / 58. 45.100
|
||||
libavdevice 58. 10.100 / 58. 10.100
|
||||
libavfilter 7. 85.100 / 7. 85.100
|
||||
libswscale 5. 7.100 / 5. 7.100
|
||||
libswresample 3. 7.100 / 3. 7.100
|
||||
libpostproc 55. 7.100 / 55. 7.100";
|
||||
|
||||
public const string FFmpegV421Output = @"ffmpeg version 4.2.1 Copyright (c) 2000-2019 the FFmpeg developers
|
||||
built with gcc 9.1.1 (GCC) 20190807
|
||||
configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
|
||||
@@ -51,7 +75,7 @@ libswscale 5. 1.100 / 5. 1.100
|
||||
libswresample 3. 1.100 / 3. 1.100
|
||||
libpostproc 55. 1.100 / 55. 1.100";
|
||||
|
||||
public const string FFmpegGitUnknownOutput = @"ffmpeg version N-94303-g7cb4f8c962 Copyright (c) 2000-2019 the FFmpeg developers
|
||||
public const string FFmpegGitUnknownOutput2 = @"ffmpeg version N-94303-g7cb4f8c962 Copyright (c) 2000-2019 the FFmpeg developers
|
||||
built with gcc 9.1.1 (GCC) 20190716
|
||||
configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
|
||||
libavutil 56. 30.100 / 56. 30.100
|
||||
@@ -62,5 +86,17 @@ libavfilter 7. 56.101 / 7. 56.101
|
||||
libswscale 5. 4.101 / 5. 4.101
|
||||
libswresample 3. 4.100 / 3. 4.100
|
||||
libpostproc 55. 4.100 / 55. 4.100";
|
||||
|
||||
public const string FFmpegGitUnknownOutput = @"ffmpeg version N-45325-gb173e0353-static https://johnvansickle.com/ffmpeg/ Copyright (c) 2000-2018 the FFmpeg developers
|
||||
built with gcc 6.3.0 (Debian 6.3.0-18+deb9u1) 20170516
|
||||
configuration: --enable-gpl --enable-version3 --enable-static --disable-debug --disable-ffplay --disable-indev=sndio --disable-outdev=sndio --cc=gcc-6 --enable-fontconfig --enable-frei0r --enable-gnutls --enable-gray --enable-libfribidi --enable-libass --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librubberband --enable-libsoxr --enable-libspeex --enable-libvorbis --enable-libopus --enable-libtheora --enable-libvidstab --enable-libvo-amrwbenc --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libzimg
|
||||
libavutil 56. 9.100 / 56. 9.100
|
||||
libavcodec 58. 14.100 / 58. 14.100
|
||||
libavformat 58. 10.100 / 58. 10.100
|
||||
libavdevice 58. 2.100 / 58. 2.100
|
||||
libavfilter 7. 13.100 / 7. 13.100
|
||||
libswscale 5. 0.102 / 5. 0.102
|
||||
libswresample 3. 0.101 / 3. 0.101
|
||||
libpostproc 55. 0.100 / 55. 0.100";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.MediaEncoding.Probing;
|
||||
using Xunit;
|
||||
|
||||
@@ -15,7 +16,7 @@ namespace Jellyfin.MediaEncoding.Tests
|
||||
var path = Path.Join("Test Data", fileName);
|
||||
using (var stream = File.OpenRead(path))
|
||||
{
|
||||
await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream).ConfigureAwait(false);
|
||||
await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream, JsonDefaults.GetOptions()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,26 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers -->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -14,8 +14,20 @@
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers -->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../MediaBrowser.Model/MediaBrowser.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using Emby.Naming.AudioBook;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Naming.Tests.AudioBook
|
||||
{
|
||||
public class AudioBookFileInfoTests
|
||||
{
|
||||
[Fact]
|
||||
public void CompareTo_Same_Success()
|
||||
{
|
||||
var info = new AudioBookFileInfo();
|
||||
Assert.Equal(0, info.CompareTo(info));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareTo_Null_Success()
|
||||
{
|
||||
var info = new AudioBookFileInfo();
|
||||
Assert.Equal(1, info.CompareTo(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareTo_Empty_Success()
|
||||
{
|
||||
var info1 = new AudioBookFileInfo();
|
||||
var info2 = new AudioBookFileInfo();
|
||||
Assert.Equal(0, info1.CompareTo(info2));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.Linq;
|
||||
using Emby.Naming.AudioBook;
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Naming.Tests.AudioBook
|
||||
{
|
||||
public class AudioBookListResolverTests
|
||||
{
|
||||
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||
|
||||
[Fact]
|
||||
public void TestStackAndExtras()
|
||||
{
|
||||
// No stacking here because there is no part/disc/etc
|
||||
var files = new[]
|
||||
{
|
||||
"Harry Potter and the Deathly Hallows/Part 1.mp3",
|
||||
"Harry Potter and the Deathly Hallows/Part 2.mp3",
|
||||
"Harry Potter and the Deathly Hallows/book.nfo",
|
||||
|
||||
"Batman/Chapter 1.mp3",
|
||||
"Batman/Chapter 2.mp3",
|
||||
"Batman/Chapter 3.mp3",
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i
|
||||
})).ToList();
|
||||
|
||||
Assert.Equal(2, result[0].Files.Count);
|
||||
// Assert.Empty(result[0].Extras); FIXME: AudioBookListResolver should resolve extra files properly
|
||||
Assert.Equal("Harry Potter and the Deathly Hallows", result[0].Name);
|
||||
|
||||
Assert.Equal(3, result[1].Files.Count);
|
||||
Assert.Empty(result[1].Extras);
|
||||
Assert.Equal("Batman", result[1].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestWithMetadata()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
"Harry Potter and the Deathly Hallows/Chapter 1.ogg",
|
||||
"Harry Potter and the Deathly Hallows/Harry Potter and the Deathly Hallows.nfo"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i
|
||||
}));
|
||||
|
||||
Assert.Single(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestWithExtra()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
"Harry Potter and the Deathly Hallows/Chapter 1.mp3",
|
||||
"Harry Potter and the Deathly Hallows/Harry Potter and the Deathly Hallows trailer.mp3"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i
|
||||
})).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
}
|
||||
|
||||
private AudioBookListResolver GetResolver()
|
||||
{
|
||||
return new AudioBookListResolver(_namingOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Emby.Naming.AudioBook;
|
||||
using Emby.Naming.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Naming.Tests.AudioBook
|
||||
{
|
||||
public class AudioBookResolverTests
|
||||
{
|
||||
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||
|
||||
public static IEnumerable<object[]> GetResolveFileTestData()
|
||||
{
|
||||
yield return new object[]
|
||||
{
|
||||
new AudioBookFileInfo()
|
||||
{
|
||||
Path = @"/server/AudioBooks/Larry Potter/Larry Potter.mp3",
|
||||
Container = "mp3",
|
||||
}
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new AudioBookFileInfo()
|
||||
{
|
||||
Path = @"/server/AudioBooks/Berry Potter/Chapter 1 .ogg",
|
||||
Container = "ogg",
|
||||
ChapterNumber = 1
|
||||
}
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new AudioBookFileInfo()
|
||||
{
|
||||
Path = @"/server/AudioBooks/Nerry Potter/Part 3 - Chapter 2.mp3",
|
||||
Container = "mp3",
|
||||
ChapterNumber = 2,
|
||||
PartNumber = 3
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetResolveFileTestData))]
|
||||
public void Resolve_ValidFileName_Success(AudioBookFileInfo expectedResult)
|
||||
{
|
||||
var result = new AudioBookResolver(_namingOptions).Resolve(expectedResult.Path);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(result!.Path, expectedResult.Path);
|
||||
Assert.Equal(result!.Container, expectedResult.Container);
|
||||
Assert.Equal(result!.ChapterNumber, expectedResult.ChapterNumber);
|
||||
Assert.Equal(result!.PartNumber, expectedResult.PartNumber);
|
||||
Assert.Equal(result!.IsDirectory, expectedResult.IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_EmptyFileName_ArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new AudioBookResolver(_namingOptions).Resolve(string.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,23 +9,26 @@
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Emby.Naming\Emby.Naming.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -48,6 +48,10 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
[InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
|
||||
[InlineData("3 days to kill (2005).mkv", "3 days to kill", 2005)]
|
||||
[InlineData(@"Rain Man 1988 REMASTERED 1080p BluRay x264 AAC - Ozlem.mp4", "Rain Man", 1988)]
|
||||
[InlineData("My Movie 2013.12.09", "My Movie 2013.12.09", null)]
|
||||
[InlineData("My Movie 2013-12-09", "My Movie 2013-12-09", null)]
|
||||
[InlineData("My Movie 20131209", "My Movie 20131209", null)]
|
||||
[InlineData("My Movie 2013-12-09 2013", "My Movie 2013-12-09", 2013)]
|
||||
public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)
|
||||
{
|
||||
input = Path.GetFileName(input);
|
||||
|
||||
@@ -44,14 +44,14 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ExtraType.BehindTheScenes, "behind the scenes" )]
|
||||
[InlineData(ExtraType.DeletedScene, "deleted scenes" )]
|
||||
[InlineData(ExtraType.Interview, "interviews" )]
|
||||
[InlineData(ExtraType.Scene, "scenes" )]
|
||||
[InlineData(ExtraType.Sample, "samples" )]
|
||||
[InlineData(ExtraType.Clip, "shorts" )]
|
||||
[InlineData(ExtraType.Clip, "featurettes" )]
|
||||
[InlineData(ExtraType.Unknown, "extras" )]
|
||||
[InlineData(ExtraType.BehindTheScenes, "behind the scenes")]
|
||||
[InlineData(ExtraType.DeletedScene, "deleted scenes")]
|
||||
[InlineData(ExtraType.Interview, "interviews")]
|
||||
[InlineData(ExtraType.Scene, "scenes")]
|
||||
[InlineData(ExtraType.Sample, "samples")]
|
||||
[InlineData(ExtraType.Clip, "shorts")]
|
||||
[InlineData(ExtraType.Clip, "featurettes")]
|
||||
[InlineData(ExtraType.Unknown, "extras")]
|
||||
public void TestDirectories(ExtraType type, string dirName)
|
||||
{
|
||||
Test(dirName + "/300.mp4", type, _videoOptions);
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Tests.HttpServer
|
||||
{
|
||||
public class ResponseFilterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null, null)]
|
||||
[InlineData("", "")]
|
||||
[InlineData("This is a clean string.", "This is a clean string.")]
|
||||
[InlineData("This isn't \n\ra clean string.", "This isn't a clean string.")]
|
||||
public void RemoveControlCharacters_ValidArgs_Correct(string? input, string? result)
|
||||
{
|
||||
Assert.Equal(result, ResponseFilter.RemoveControlCharacters(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,25 +5,38 @@
|
||||
<ProjectGuid>{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Jellyfin.Server.Implementations.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Jellyfin.Server.Implementations.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="4.11.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
|
||||
<PackageReference Include="Moq" Version="4.13.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="4.13.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.13.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Moq" Version="4.14.6" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" />
|
||||
</ItemGroup>
|
||||
<!-- Code Analyzers -->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -7,12 +7,30 @@ namespace Jellyfin.Server.Implementations.Tests.Library
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("/media/small.jpg", true)]
|
||||
[InlineData("/media/albumart.jpg", true)]
|
||||
[InlineData("/media/movie.sample.mp4", true)]
|
||||
[InlineData("/media/movie/sample.mp4", true)]
|
||||
[InlineData("/media/movie/sample/movie.mp4", true)]
|
||||
[InlineData("/foo/sample/bar/baz.mkv", false)]
|
||||
[InlineData("/media/movies/the sample/the sample.mkv", false)]
|
||||
[InlineData("/media/movies/sampler.mkv", false)]
|
||||
[InlineData("/media/movies/#Recycle/test.txt", true)]
|
||||
[InlineData("/media/movies/#recycle/", true)]
|
||||
[InlineData("/media/movies/#recycle", true)]
|
||||
[InlineData("thumbs.db", true)]
|
||||
[InlineData(@"C:\media\movies\movie.avi", false)]
|
||||
[InlineData("/media/.hiddendir/file.mp4", true)]
|
||||
[InlineData("/media/.hiddendir/file.mp4", false)]
|
||||
[InlineData("/media/dir/.hiddenfile.mp4", true)]
|
||||
[InlineData("/media/dir/._macjunk.mp4", true)]
|
||||
[InlineData("/volume1/video/Series/@eaDir", true)]
|
||||
[InlineData("/volume1/video/Series/@eaDir/file.txt", true)]
|
||||
[InlineData("/directory/@Recycle", true)]
|
||||
[InlineData("/directory/@Recycle/file.mp3", true)]
|
||||
[InlineData("/media/movies/.@__thumb", true)]
|
||||
[InlineData("/media/movies/.@__thumb/foo-bar-thumbnail.png", true)]
|
||||
[InlineData("/media/music/Foo B.A.R./epic.flac", false)]
|
||||
[InlineData("/media/music/Foo B.A.R", false)]
|
||||
[InlineData("/media/music/Foo B.A.R.", false)]
|
||||
public void PathIgnored(string path, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, IgnorePatterns.ShouldIgnore(path));
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library
|
||||
[InlineData("Superman: Red Son [imdbid=tt10985510]", "imdbid", "tt10985510")]
|
||||
[InlineData("Superman: Red Son - tt10985510", "imdbid", "tt10985510")]
|
||||
[InlineData("Superman: Red Son", "imdbid", null)]
|
||||
[InlineData("Superman: Red Son", "something", null)]
|
||||
public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult)
|
||||
{
|
||||
Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute));
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Jellyfin.Server\Jellyfin.Server.csproj" />
|
||||
<ProjectReference Include="..\..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RuleSet Name="Rules for MediaBrowser.Api.Tests" Description="Code analysis rules for MediaBrowser.Api.Tests.csproj" ToolsVersion="14.0">
|
||||
<RuleSet Name="Rules for Jellyfin.Api.Tests" Description="Code analysis rules for Jellyfin.Api.Tests.csproj" ToolsVersion="14.0">
|
||||
|
||||
<!-- Include the solution default RuleSet. The rules in this file will override the defaults. -->
|
||||
<Include Path="../jellyfin.ruleset" Action="Default" />
|
||||
@@ -17,6 +17,6 @@
|
||||
<!-- CA2007: Consider calling ConfigureAwait on the awaited task -->
|
||||
<Rule Id="CA2007" Action="None" />
|
||||
<!-- CA2234: Pass system uri objects instead of strings -->
|
||||
<Rule Id="CA2234" Action="Info" />
|
||||
<Rule Id="CA2234" Action="Info" />
|
||||
</Rules>
|
||||
</RuleSet>
|
||||
|
||||
Reference in New Issue
Block a user