Merge branch 'master' into video-resolver

This commit is contained in:
BaronGreenback
2020-10-17 09:26:14 +01:00
committed by GitHub
1500 changed files with 55972 additions and 54616 deletions

View File

@@ -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()

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -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());
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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);
}
}
}

View 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
}
}

View 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);
}
}
}

View 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);
}
}
}

View File

@@ -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>

View 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));
}
}
}

View File

@@ -7,7 +7,8 @@ namespace Jellyfin.Common.Tests
public class PasswordHashTests
{
[Theory]
[InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
[InlineData(
"$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D",
"PBKDF2",
"",
"62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")]

View File

@@ -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>

View File

@@ -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();
}
}
}

View File

@@ -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";
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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' ">

View File

@@ -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);

View File

@@ -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);

View File

@@ -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));
}
}
}

View File

@@ -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>

View File

@@ -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));

View File

@@ -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));

View File

@@ -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>

View File

@@ -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>