mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-03 06:18:28 +01:00
Merge remote-tracking branch 'upstream/master' into video-resolver
This commit is contained in:
@@ -8,6 +8,7 @@ using Jellyfin.Api.Auth;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@@ -68,19 +69,19 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAuthenticateAsyncShouldFailOnSecurityException()
|
||||
public async Task HandleAuthenticateAsyncShouldFailOnAuthenticationException()
|
||||
{
|
||||
var errorMessage = _fixture.Create<string>();
|
||||
|
||||
_jellyfinAuthServiceMock.Setup(
|
||||
a => a.Authenticate(
|
||||
It.IsAny<HttpRequest>()))
|
||||
.Throws(new SecurityException(errorMessage));
|
||||
.Throws(new AuthenticationException(errorMessage));
|
||||
|
||||
var authenticateResult = await _sut.AuthenticateAsync();
|
||||
|
||||
Assert.False(authenticateResult.Succeeded);
|
||||
Assert.Equal(errorMessage, authenticateResult.Failure.Message);
|
||||
Assert.Equal(errorMessage, authenticateResult.Failure?.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -99,7 +100,7 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
var authorizationInfo = SetupUser();
|
||||
var authenticateResult = await _sut.AuthenticateAsync();
|
||||
|
||||
Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, authorizationInfo.User.Username));
|
||||
Assert.True(authenticateResult.Principal?.HasClaim(ClaimTypes.Name, authorizationInfo.User.Username));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -111,7 +112,7 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
var authenticateResult = await _sut.AuthenticateAsync();
|
||||
|
||||
var expectedRole = authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User;
|
||||
Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Role, expectedRole));
|
||||
Assert.True(authenticateResult.Principal?.HasClaim(ClaimTypes.Role, expectedRole));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -120,7 +121,7 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
SetupUser();
|
||||
var authenticatedResult = await _sut.AuthenticateAsync();
|
||||
|
||||
Assert.Equal(_scheme.Name, authenticatedResult.Ticket.AuthenticationScheme);
|
||||
Assert.Equal(_scheme.Name, authenticatedResult.Ticket?.AuthenticationScheme);
|
||||
}
|
||||
|
||||
private AuthorizationInfo SetupUser(bool isAdmin = false)
|
||||
@@ -128,6 +129,7 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
var authorizationInfo = _fixture.Create<AuthorizationInfo>();
|
||||
authorizationInfo.User = _fixture.Create<User>();
|
||||
authorizationInfo.User.SetPermission(PermissionKind.IsAdministrator, isAdmin);
|
||||
authorizationInfo.IsApiKey = false;
|
||||
|
||||
_jellyfinAuthServiceMock.Setup(
|
||||
a => a.Authenticate(
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Jellyfin.Api.Tests
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
|
||||
var responseBody = await response.Content.ReadAsStreamAsync();
|
||||
_ = await JsonSerializer.DeserializeAsync<BrandingOptions>(responseBody);
|
||||
}
|
||||
@@ -43,7 +43,7 @@ namespace Jellyfin.Api.Tests
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal("text/css; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||
Assert.Equal("text/css; charset=utf-8", response.Content.Headers.ContentType?.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
@@ -16,13 +16,13 @@
|
||||
<PackageReference Include="AutoFixture" Version="4.14.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.14.0" />
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.14.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="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
<PackageReference Include="Moq" Version="4.14.7" />
|
||||
<PackageReference Include="Moq" Version="4.15.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers -->
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
@@ -17,11 +18,11 @@ namespace Jellyfin.Api.Tests.ModelBinders
|
||||
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedStringArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = new[] { "lol", "xd" };
|
||||
IReadOnlyList<string> queryParamValues = new[] { "lol", "xd" };
|
||||
var queryParamString = "lol,xd";
|
||||
var queryParamType = typeof(string[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger<CommaDelimitedArrayModelBinder>());
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
|
||||
@@ -35,18 +36,18 @@ namespace Jellyfin.Api.Tests.ModelBinders
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((string[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
Assert.Equal((IReadOnlyList<string>?)bindingContextMock.Object?.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedIntArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = new[] { 42, 0 };
|
||||
IReadOnlyList<int> queryParamValues = new[] { 42, 0 };
|
||||
var queryParamString = "42,0";
|
||||
var queryParamType = typeof(int[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger<CommaDelimitedArrayModelBinder>());
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
|
||||
@@ -60,18 +61,18 @@ namespace Jellyfin.Api.Tests.ModelBinders
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((int[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
Assert.Equal((IReadOnlyList<int>?)bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
var queryParamString = "How,Much";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger<CommaDelimitedArrayModelBinder>());
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
|
||||
@@ -85,18 +86,18 @@ namespace Jellyfin.Api.Tests.ModelBinders
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQueryWithDoubleCommas()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
var queryParamString = "How,,Much";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger<CommaDelimitedArrayModelBinder>());
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
|
||||
@@ -110,19 +111,19 @@ namespace Jellyfin.Api.Tests.ModelBinders
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidEnumArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
var queryParamString1 = "How";
|
||||
var queryParamString2 = "Much";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger<CommaDelimitedArrayModelBinder>());
|
||||
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
@@ -140,17 +141,17 @@ namespace Jellyfin.Api.Tests.ModelBinders
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsEmptyEnumArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamValues = Array.Empty<TestType>();
|
||||
IReadOnlyList<TestType> queryParamValues = Array.Empty<TestType>();
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger<CommaDelimitedArrayModelBinder>());
|
||||
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
@@ -168,17 +169,17 @@ namespace Jellyfin.Api.Tests.ModelBinders
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_ThrowsIfCommaDelimitedEnumArrayQueryIsInvalid()
|
||||
public async Task BindModelAsync_EnumArrayQuery_BindValidOnly()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamString = "🔥,😢";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
var queryParamType = typeof(IReadOnlyList<TestType>);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger<CommaDelimitedArrayModelBinder>());
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
|
||||
@@ -189,20 +190,20 @@ namespace Jellyfin.Api.Tests.ModelBinders
|
||||
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);
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Empty((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_ThrowsIfCommaDelimitedEnumArrayQueryIsInvalid2()
|
||||
public async Task BindModelAsync_EnumArrayQuery_BindValidOnly_2()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamString1 = "How";
|
||||
var queryParamString2 = "😱";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
var queryParamType = typeof(IReadOnlyList<TestType>);
|
||||
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder();
|
||||
var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger<CommaDelimitedArrayModelBinder>());
|
||||
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
@@ -217,9 +218,9 @@ namespace Jellyfin.Api.Tests.ModelBinders
|
||||
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);
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Single((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Api.Tests.ModelBinders
|
||||
{
|
||||
public sealed class PipeDelimitedArrayModelBinderTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidPipeDelimitedStringArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
IReadOnlyList<string> queryParamValues = new[] { "lol", "xd" };
|
||||
var queryParamString = "lol|xd";
|
||||
var queryParamType = typeof(string[]);
|
||||
|
||||
var modelBinder = new PipeDelimitedArrayModelBinder(new NullLogger<PipeDelimitedArrayModelBinder>());
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
|
||||
CultureInfo.InvariantCulture);
|
||||
var bindingContextMock = new Mock<ModelBindingContext>();
|
||||
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
|
||||
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
|
||||
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
|
||||
bindingContextMock.SetupProperty(b => b.Result);
|
||||
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((IReadOnlyList<string>?)bindingContextMock.Object?.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidDelimitedIntArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
IReadOnlyList<int> queryParamValues = new[] { 42, 0 };
|
||||
var queryParamString = "42|0";
|
||||
var queryParamType = typeof(int[]);
|
||||
|
||||
var modelBinder = new PipeDelimitedArrayModelBinder(new NullLogger<PipeDelimitedArrayModelBinder>());
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
|
||||
CultureInfo.InvariantCulture);
|
||||
var bindingContextMock = new Mock<ModelBindingContext>();
|
||||
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
|
||||
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
|
||||
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
|
||||
bindingContextMock.SetupProperty(b => b.Result);
|
||||
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((IReadOnlyList<int>?)bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidPipeDelimitedEnumArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
var queryParamString = "How|Much";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new PipeDelimitedArrayModelBinder(new NullLogger<PipeDelimitedArrayModelBinder>());
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
|
||||
CultureInfo.InvariantCulture);
|
||||
var bindingContextMock = new Mock<ModelBindingContext>();
|
||||
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
|
||||
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
|
||||
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
|
||||
bindingContextMock.SetupProperty(b => b.Result);
|
||||
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidPipeDelimitedEnumArrayQueryWithDoublePipes()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
var queryParamString = "How||Much";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new PipeDelimitedArrayModelBinder(new NullLogger<PipeDelimitedArrayModelBinder>());
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
new QueryCollection(new Dictionary<string, StringValues> { { queryParamName, new StringValues(queryParamString) } }),
|
||||
CultureInfo.InvariantCulture);
|
||||
var bindingContextMock = new Mock<ModelBindingContext>();
|
||||
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
|
||||
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
|
||||
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
|
||||
bindingContextMock.SetupProperty(b => b.Result);
|
||||
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsValidEnumArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
|
||||
var queryParamString1 = "How";
|
||||
var queryParamString2 = "Much";
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new PipeDelimitedArrayModelBinder(new NullLogger<PipeDelimitedArrayModelBinder>());
|
||||
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
new QueryCollection(new Dictionary<string, StringValues>
|
||||
{
|
||||
{ queryParamName, new StringValues(new[] { queryParamString1, queryParamString2 }) },
|
||||
}),
|
||||
CultureInfo.InvariantCulture);
|
||||
var bindingContextMock = new Mock<ModelBindingContext>();
|
||||
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
|
||||
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
|
||||
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
|
||||
bindingContextMock.SetupProperty(b => b.Result);
|
||||
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_CorrectlyBindsEmptyEnumArrayQuery()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
IReadOnlyList<TestType> queryParamValues = Array.Empty<TestType>();
|
||||
var queryParamType = typeof(TestType[]);
|
||||
|
||||
var modelBinder = new PipeDelimitedArrayModelBinder(new NullLogger<PipeDelimitedArrayModelBinder>());
|
||||
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
new BindingSource(string.Empty, string.Empty, false, false),
|
||||
new QueryCollection(new Dictionary<string, StringValues>
|
||||
{
|
||||
{ queryParamName, new StringValues(value: null) },
|
||||
}),
|
||||
CultureInfo.InvariantCulture);
|
||||
var bindingContextMock = new Mock<ModelBindingContext>();
|
||||
bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider);
|
||||
bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName);
|
||||
bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType);
|
||||
bindingContextMock.SetupProperty(b => b.Result);
|
||||
|
||||
await modelBinder.BindModelAsync(bindingContextMock.Object);
|
||||
|
||||
Assert.True(bindingContextMock.Object.Result.IsModelSet);
|
||||
Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_EnumArrayQuery_BindValidOnly()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamString = "🔥|😢";
|
||||
var queryParamType = typeof(IReadOnlyList<TestType>);
|
||||
|
||||
var modelBinder = new PipeDelimitedArrayModelBinder(new NullLogger<PipeDelimitedArrayModelBinder>());
|
||||
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.Empty((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_EnumArrayQuery_BindValidOnly_2()
|
||||
{
|
||||
var queryParamName = "test";
|
||||
var queryParamString1 = "How";
|
||||
var queryParamString2 = "😱";
|
||||
var queryParamType = typeof(IReadOnlyList<TestType>);
|
||||
|
||||
var modelBinder = new PipeDelimitedArrayModelBinder(new NullLogger<PipeDelimitedArrayModelBinder>());
|
||||
|
||||
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.Single((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace Jellyfin.Api.Tests
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
|
||||
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
|
||||
|
||||
// Write out for publishing
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Jellyfin.Api.Tests
|
||||
{
|
||||
new Claim(ClaimTypes.Role, role),
|
||||
new Claim(ClaimTypes.Name, "jellyfin"),
|
||||
new Claim(InternalClaimTypes.UserId, Guid.Empty.ToString("N", CultureInfo.InvariantCulture)),
|
||||
new Claim(InternalClaimTypes.UserId, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)),
|
||||
new Claim(InternalClaimTypes.DeviceId, Guid.Empty.ToString("N", CultureInfo.InvariantCulture)),
|
||||
new Claim(InternalClaimTypes.Device, "test"),
|
||||
new Claim(InternalClaimTypes.Client, "test"),
|
||||
@@ -60,7 +60,7 @@ namespace Jellyfin.Api.Tests
|
||||
.Returns(user);
|
||||
|
||||
httpContextAccessorMock
|
||||
.Setup(h => h.HttpContext.Connection.RemoteIpAddress)
|
||||
.Setup(h => h.HttpContext!.Connection.RemoteIpAddress)
|
||||
.Returns(new IPAddress(0));
|
||||
|
||||
return new ClaimsPrincipal(identity);
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
@@ -19,7 +19,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Emby.Naming.AudioBook;
|
||||
using Emby.Naming.AudioBook;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Naming.Tests.AudioBook
|
||||
@@ -8,22 +8,22 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
[Fact]
|
||||
public void CompareTo_Same_Success()
|
||||
{
|
||||
var info = new AudioBookFileInfo();
|
||||
var info = new AudioBookFileInfo(string.Empty, string.Empty);
|
||||
Assert.Equal(0, info.CompareTo(info));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareTo_Null_Success()
|
||||
{
|
||||
var info = new AudioBookFileInfo();
|
||||
var info = new AudioBookFileInfo(string.Empty, string.Empty);
|
||||
Assert.Equal(1, info.CompareTo(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompareTo_Empty_Success()
|
||||
{
|
||||
var info1 = new AudioBookFileInfo();
|
||||
var info2 = new AudioBookFileInfo();
|
||||
var info1 = new AudioBookFileInfo(string.Empty, string.Empty);
|
||||
var info2 = new AudioBookFileInfo(string.Empty, string.Empty);
|
||||
Assert.Equal(0, info1.CompareTo(info2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Emby.Naming.AudioBook;
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Model.IO;
|
||||
@@ -18,11 +20,22 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
{
|
||||
"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",
|
||||
"Harry Potter and the Deathly Hallows/Extra.mp3",
|
||||
|
||||
"Batman/Chapter 1.mp3",
|
||||
"Batman/Chapter 2.mp3",
|
||||
"Batman/Chapter 3.mp3",
|
||||
|
||||
"Badman/audiobook.mp3",
|
||||
"Badman/extra.mp3",
|
||||
|
||||
"Superman (2020)/Part 1.mp3",
|
||||
"Superman (2020)/extra.mp3",
|
||||
|
||||
"Ready Player One (2020)/audiobook.mp3",
|
||||
"Ready Player One (2020)/extra.mp3",
|
||||
|
||||
".mp3"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
@@ -33,13 +46,141 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
FullName = i
|
||||
})).ToList();
|
||||
|
||||
Assert.Equal(5, result.Count);
|
||||
|
||||
Assert.Equal(2, result[0].Files.Count);
|
||||
// Assert.Empty(result[0].Extras); FIXME: AudioBookListResolver should resolve extra files properly
|
||||
Assert.Single(result[0].Extras);
|
||||
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);
|
||||
|
||||
Assert.Single(result[2].Files);
|
||||
Assert.Single(result[2].Extras);
|
||||
Assert.Equal("Badman", result[2].Name);
|
||||
|
||||
Assert.Single(result[3].Files);
|
||||
Assert.Single(result[3].Extras);
|
||||
Assert.Equal("Superman", result[3].Name);
|
||||
|
||||
Assert.Single(result[4].Files);
|
||||
Assert.Single(result[4].Extras);
|
||||
Assert.Equal("Ready Player One", result[4].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestAlternativeVersions()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
"Harry Potter and the Deathly Hallows/Chapter 1.ogg",
|
||||
"Harry Potter and the Deathly Hallows/Chapter 1.mp3",
|
||||
|
||||
"Deadpool.mp3",
|
||||
"Deadpool [HQ].mp3",
|
||||
|
||||
"Superman/audiobook.mp3",
|
||||
"Superman/Superman.mp3",
|
||||
"Superman/Superman [HQ].mp3",
|
||||
"Superman/extra.mp3",
|
||||
|
||||
"Batman/ Chapter 1 .mp3",
|
||||
"Batman/Chapter 1[loss-less].mp3"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i
|
||||
})).ToList();
|
||||
|
||||
Assert.Equal(5, result.Count);
|
||||
// HP - Same name so we don't care which file is alternative
|
||||
Assert.Single(result[0].AlternateVersions);
|
||||
// DP
|
||||
Assert.Empty(result[1].AlternateVersions);
|
||||
// DP HQ (directory missing so we do not group deadpools together)
|
||||
Assert.Empty(result[2].AlternateVersions);
|
||||
// Superman
|
||||
// Priority:
|
||||
// 1. Name
|
||||
// 2. audiobook
|
||||
// 3. Names with modifiers
|
||||
Assert.Equal(2, result[3].AlternateVersions.Count);
|
||||
var paths = result[3].AlternateVersions.Select(x => x.Path).ToList();
|
||||
Assert.Contains("Superman/audiobook.mp3", paths);
|
||||
Assert.Contains("Superman/Superman [HQ].mp3", paths);
|
||||
// Batman
|
||||
Assert.Single(result[4].AlternateVersions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestNameYearExtraction()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new NameYearPath
|
||||
{
|
||||
Name = "Harry Potter and the Deathly Hallows",
|
||||
Path = "Harry Potter and the Deathly Hallows (2007)/Chapter 1.ogg",
|
||||
Year = 2007
|
||||
},
|
||||
new NameYearPath
|
||||
{
|
||||
Name = "Batman",
|
||||
Path = "Batman (2020).ogg",
|
||||
Year = 2020
|
||||
},
|
||||
new NameYearPath
|
||||
{
|
||||
Name = "Batman",
|
||||
Path = "Batman( 2021 ).mp3",
|
||||
Year = 2021
|
||||
},
|
||||
new NameYearPath
|
||||
{
|
||||
Name = "Batman(*2021*)",
|
||||
Path = "Batman(*2021*).mp3",
|
||||
Year = null
|
||||
},
|
||||
new NameYearPath
|
||||
{
|
||||
Name = "Batman",
|
||||
Path = "Batman.mp3",
|
||||
Year = null
|
||||
},
|
||||
new NameYearPath
|
||||
{
|
||||
Name = "+ Batman .",
|
||||
Path = " + Batman . .mp3",
|
||||
Year = null
|
||||
},
|
||||
new NameYearPath
|
||||
{
|
||||
Name = " ",
|
||||
Path = " .mp3",
|
||||
Year = null
|
||||
}
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(data.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i.Path
|
||||
})).ToList();
|
||||
|
||||
Assert.Equal(data.Length, result.Count);
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
Assert.Equal(data[i].Name, result[i].Name);
|
||||
Assert.Equal(data[i].Year, result[i].Year);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -82,9 +223,51 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
Assert.Single(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestWithoutFolder()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
"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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEmpty()
|
||||
{
|
||||
var files = Array.Empty<string>();
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i
|
||||
})).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
private AudioBookListResolver GetResolver()
|
||||
{
|
||||
return new AudioBookListResolver(_namingOptions);
|
||||
}
|
||||
|
||||
internal struct NameYearPath
|
||||
{
|
||||
public string Name;
|
||||
public string Path;
|
||||
public int? Year;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Emby.Naming.AudioBook;
|
||||
using Emby.Naming.Common;
|
||||
@@ -14,30 +14,24 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
{
|
||||
yield return new object[]
|
||||
{
|
||||
new AudioBookFileInfo()
|
||||
{
|
||||
Path = @"/server/AudioBooks/Larry Potter/Larry Potter.mp3",
|
||||
Container = "mp3",
|
||||
}
|
||||
new AudioBookFileInfo(
|
||||
@"/server/AudioBooks/Larry Potter/Larry Potter.mp3",
|
||||
"mp3")
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new AudioBookFileInfo()
|
||||
{
|
||||
Path = @"/server/AudioBooks/Berry Potter/Chapter 1 .ogg",
|
||||
Container = "ogg",
|
||||
ChapterNumber = 1
|
||||
}
|
||||
new AudioBookFileInfo(
|
||||
@"/server/AudioBooks/Berry Potter/Chapter 1 .ogg",
|
||||
"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
|
||||
}
|
||||
new AudioBookFileInfo(
|
||||
@"/server/AudioBooks/Nerry Potter/Part 3 - Chapter 2.mp3",
|
||||
"mp3",
|
||||
chapterNumber: 2,
|
||||
partNumber: 3)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,13 +46,22 @@ namespace Jellyfin.Naming.Tests.AudioBook
|
||||
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()
|
||||
public void Resolve_InvalidExtension()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new AudioBookResolver(_namingOptions).Resolve(string.Empty));
|
||||
var result = new AudioBookResolver(_namingOptions).Resolve(@"/server/AudioBooks/Larry Potter/Larry Potter.mp9");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolve_EmptyFileName()
|
||||
{
|
||||
var result = new AudioBookResolver(_namingOptions).Resolve(string.Empty);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs
Normal file
36
tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Emby.Naming.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Naming.Tests.Common
|
||||
{
|
||||
public class NamingOptionsTest
|
||||
{
|
||||
[Fact]
|
||||
public void TestNamingOptionsCompile()
|
||||
{
|
||||
var options = new NamingOptions();
|
||||
|
||||
Assert.NotEmpty(options.VideoFileStackingRegexes);
|
||||
Assert.NotEmpty(options.CleanDateTimeRegexes);
|
||||
Assert.NotEmpty(options.CleanStringRegexes);
|
||||
Assert.NotEmpty(options.EpisodeWithoutSeasonRegexes);
|
||||
Assert.NotEmpty(options.EpisodeMultiPartRegexes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestNamingOptionsEpisodeExpressions()
|
||||
{
|
||||
var exp = new EpisodeExpression(string.Empty);
|
||||
|
||||
Assert.False(exp.IsOptimistic);
|
||||
exp.IsOptimistic = true;
|
||||
Assert.True(exp.IsOptimistic);
|
||||
|
||||
Assert.Equal(string.Empty, exp.Expression);
|
||||
Assert.NotNull(exp.Regex);
|
||||
exp.Expression = "test";
|
||||
Assert.Equal("test", exp.Expression);
|
||||
Assert.NotNull(exp.Regex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Subtitles;
|
||||
using Xunit;
|
||||
@@ -26,21 +26,17 @@ namespace Jellyfin.Naming.Tests.Subtitles
|
||||
Assert.Equal(language, result?.Language, true);
|
||||
Assert.Equal(isDefault, result?.IsDefault);
|
||||
Assert.Equal(isForced, result?.IsForced);
|
||||
Assert.Equal(input, result?.Path);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("The Skin I Live In (2011).mp4")]
|
||||
[InlineData("")]
|
||||
public void SubtitleParser_InvalidFileName_ReturnsNull(string input)
|
||||
{
|
||||
var parser = new SubtitleParser(_namingOptions);
|
||||
|
||||
Assert.Null(parser.ParseFile(input));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubtitleParser_EmptyFileName_ThrowsArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new SubtitleParser(_namingOptions).ParseFile(string.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.TV;
|
||||
using Xunit;
|
||||
|
||||
@@ -7,43 +7,98 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
public class EpisodePathParserTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("/media/Foo/Foo-S01E01", "Foo", 1, 1)]
|
||||
[InlineData("/media/Foo - S04E011", "Foo", 4, 11)]
|
||||
[InlineData("/media/Foo/Foo s01x01", "Foo", 1, 1)]
|
||||
[InlineData("/media/Foo (2019)/Season 4/Foo (2019).S04E03", "Foo (2019)", 4, 3)]
|
||||
[InlineData("D:\\media\\Foo\\Foo-S01E01", "Foo", 1, 1)]
|
||||
[InlineData("D:\\media\\Foo - S04E011", "Foo", 4, 11)]
|
||||
[InlineData("D:\\media\\Foo\\Foo s01x01", "Foo", 1, 1)]
|
||||
[InlineData("D:\\media\\Foo (2019)\\Season 4\\Foo (2019).S04E03", "Foo (2019)", 4, 3)]
|
||||
[InlineData("/Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", "Elementary", 2, 3)]
|
||||
[InlineData("/Season 1/seriesname S01E02 blah.avi", "seriesname", 1, 2)]
|
||||
[InlineData("/Running Man/Running Man S2017E368.mkv", "Running Man", 2017, 368)]
|
||||
[InlineData("/Season 1/seriesname 01x02 blah.avi", "seriesname", 1, 2)]
|
||||
[InlineData("/Season 25/The Simpsons.S25E09.Steal this episode.mp4", "The Simpsons", 25, 9)]
|
||||
[InlineData("/Season 1/seriesname S01x02 blah.avi", "seriesname", 1, 2)]
|
||||
[InlineData("/Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", "Elementary", 2, 3)]
|
||||
[InlineData("/Season 1/seriesname S01xE02 blah.avi", "seriesname", 1, 2)]
|
||||
[InlineData("/Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", "Elementary", 2, 3)]
|
||||
[InlineData("/Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", "Elementary", 2, 3)]
|
||||
[InlineData("/Season 02/Elementary - 02x03-E15 - Ep Name.mp4", "Elementary", 2, 3)]
|
||||
[InlineData("/Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", "Elementary", 1, 23)]
|
||||
[InlineData("/The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi", "The Wonder Years", 4, 7)]
|
||||
[InlineData("/media/Foo/Foo-S01E01", true, "Foo", 1, 1)]
|
||||
[InlineData("/media/Foo - S04E011", true, "Foo", 4, 11)]
|
||||
[InlineData("/media/Foo/Foo s01x01", true, "Foo", 1, 1)]
|
||||
[InlineData("/media/Foo (2019)/Season 4/Foo (2019).S04E03", true, "Foo (2019)", 4, 3)]
|
||||
[InlineData("D:\\media\\Foo\\Foo-S01E01", true, "Foo", 1, 1)]
|
||||
[InlineData("D:\\media\\Foo - S04E011", true, "Foo", 4, 11)]
|
||||
[InlineData("D:\\media\\Foo\\Foo s01x01", true, "Foo", 1, 1)]
|
||||
[InlineData("D:\\media\\Foo (2019)\\Season 4\\Foo (2019).S04E03", true, "Foo (2019)", 4, 3)]
|
||||
[InlineData("/Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", false, "Elementary", 2, 3)]
|
||||
[InlineData("/Season 1/seriesname S01E02 blah.avi", false, "seriesname", 1, 2)]
|
||||
[InlineData("/Running Man/Running Man S2017E368.mkv", false, "Running Man", 2017, 368)]
|
||||
[InlineData("/Season 1/seriesname 01x02 blah.avi", false, "seriesname", 1, 2)]
|
||||
[InlineData("/Season 25/The Simpsons.S25E09.Steal this episode.mp4", false, "The Simpsons", 25, 9)]
|
||||
[InlineData("/Season 1/seriesname S01x02 blah.avi", false, "seriesname", 1, 2)]
|
||||
[InlineData("/Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", false, "Elementary", 2, 3)]
|
||||
[InlineData("/Season 1/seriesname S01xE02 blah.avi", false, "seriesname", 1, 2)]
|
||||
[InlineData("/Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", false, "Elementary", 2, 3)]
|
||||
[InlineData("/Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", false, "Elementary", 2, 3)]
|
||||
[InlineData("/Season 02/Elementary - 02x03-E15 - Ep Name.mp4", false, "Elementary", 2, 3)]
|
||||
[InlineData("/Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", false, "Elementary", 1, 23)]
|
||||
[InlineData("/The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi", false, "The Wonder Years", 4, 7)]
|
||||
// TODO: [InlineData("/Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", "Castle Rock", 2, 1)]
|
||||
// TODO: [InlineData("/After Life 1x06 Episodio 6 [WEB-DL NF 1080p h264 Dual DD 5.1 Sub].mkv", "After Life", 1, 6)]
|
||||
// TODO: [InlineData("/Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", "Uchuu Senkan Yamoto 2199", 4, 3)]
|
||||
// TODO: [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", "The Daily Show", 25, 22)]
|
||||
// TODO: [InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", "Watchmen (2019)", 1, 3)]
|
||||
// TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", "The Legend of Condor Heroes 2017", 1, 7)]
|
||||
public void ParseEpisodesCorrectly(string path, string name, int season, int episode)
|
||||
public void ParseEpisodesCorrectly(string path, bool isDirectory, string name, int season, int episode)
|
||||
{
|
||||
NamingOptions o = new NamingOptions();
|
||||
EpisodePathParser p = new EpisodePathParser(o);
|
||||
var res = p.Parse(path, false);
|
||||
var res = p.Parse(path, isDirectory);
|
||||
|
||||
Assert.True(res.Success);
|
||||
Assert.Equal(name, res.SeriesName);
|
||||
Assert.Equal(season, res.SeasonNumber);
|
||||
Assert.Equal(episode, res.EpisodeNumber);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/test/01-03.avi", true, true)]
|
||||
public void EpisodePathParserTest_DifferentExpressionsParameters(string path, bool? isNamed, bool? isOptimistic)
|
||||
{
|
||||
NamingOptions o = new NamingOptions();
|
||||
EpisodePathParser p = new EpisodePathParser(o);
|
||||
var res = p.Parse(path, false, isNamed, isOptimistic);
|
||||
|
||||
Assert.True(res.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EpisodePathParserTest_FalsePositivePixelRate()
|
||||
{
|
||||
NamingOptions o = new NamingOptions();
|
||||
EpisodePathParser p = new EpisodePathParser(o);
|
||||
var res = p.Parse("Series Special (1920x1080).mkv", false);
|
||||
|
||||
Assert.False(res.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EpisodeResolverTest_WrongExtension()
|
||||
{
|
||||
var res = new EpisodeResolver(new NamingOptions()).Resolve("test.mp3", false);
|
||||
Assert.Null(res);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EpisodeResolverTest_WrongExtensionStub()
|
||||
{
|
||||
var res = new EpisodeResolver(new NamingOptions()).Resolve("dvd.disc", false);
|
||||
Assert.NotNull(res);
|
||||
Assert.True(res!.IsStub);
|
||||
}
|
||||
|
||||
/*
|
||||
* EpisodePathParser.cs:130 is currently unreachable, but the piece of code is useful and could be reached with addition of new EpisodeExpressions.
|
||||
* In order to preserve it but achieve 100% code coverage the test case below with made up expressions and filename is used.
|
||||
*/
|
||||
[Fact]
|
||||
public void EpisodePathParserTest_EmptyDateParsers()
|
||||
{
|
||||
NamingOptions o = new NamingOptions()
|
||||
{
|
||||
EpisodeExpressions = new[] { new EpisodeExpression("(([0-9]{4})-([0-9]{2})-([0-9]{2}) [0-9]{2}:[0-9]{2}:[0-9]{2})", true) }
|
||||
};
|
||||
o.Compile();
|
||||
|
||||
EpisodePathParser p = new EpisodePathParser(o);
|
||||
var res = p.Parse("ABC_2019_10_21 11:00:00", false);
|
||||
|
||||
Assert.True(res.Success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
var result = new EpisodePathParser(options)
|
||||
.Parse(filename, false);
|
||||
|
||||
Assert.Equal(result.EndingEpsiodeNumber, endingEpisodeNumber);
|
||||
Assert.Equal(result.EndingEpisodeNumber, endingEpisodeNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Emby.Naming.TV;
|
||||
using Emby.Naming.TV;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Naming.Tests.TV
|
||||
@@ -6,26 +6,30 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
public class SeasonFolderTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(@"/Drive/Season 1", 1)]
|
||||
[InlineData(@"/Drive/Season 2", 2)]
|
||||
[InlineData(@"/Drive/Season 02", 2)]
|
||||
[InlineData(@"/Drive/Seinfeld/S02", 2)]
|
||||
[InlineData(@"/Drive/Seinfeld/2", 2)]
|
||||
[InlineData(@"/Drive/Season 2009", 2009)]
|
||||
[InlineData(@"/Drive/Season1", 1)]
|
||||
[InlineData(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH", 4)]
|
||||
[InlineData(@"/Drive/Season 7 (2016)", 7)]
|
||||
[InlineData(@"/Drive/Staffel 7 (2016)", 7)]
|
||||
[InlineData(@"/Drive/Stagione 7 (2016)", 7)]
|
||||
[InlineData(@"/Drive/Season (8)", null)]
|
||||
[InlineData(@"/Drive/3.Staffel", 3)]
|
||||
[InlineData(@"/Drive/s06e05", null)]
|
||||
[InlineData(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv", null)]
|
||||
public void GetSeasonNumberFromPathTest(string path, int? seasonNumber)
|
||||
[InlineData(@"/Drive/Season 1", 1, true)]
|
||||
[InlineData(@"/Drive/Season 2", 2, true)]
|
||||
[InlineData(@"/Drive/Season 02", 2, true)]
|
||||
[InlineData(@"/Drive/Seinfeld/S02", 2, true)]
|
||||
[InlineData(@"/Drive/Seinfeld/2", 2, true)]
|
||||
[InlineData(@"/Drive/Season 2009", 2009, true)]
|
||||
[InlineData(@"/Drive/Season1", 1, true)]
|
||||
[InlineData(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH", 4, true)]
|
||||
[InlineData(@"/Drive/Season 7 (2016)", 7, false)]
|
||||
[InlineData(@"/Drive/Staffel 7 (2016)", 7, false)]
|
||||
[InlineData(@"/Drive/Stagione 7 (2016)", 7, false)]
|
||||
[InlineData(@"/Drive/Season (8)", null, false)]
|
||||
[InlineData(@"/Drive/3.Staffel", 3, false)]
|
||||
[InlineData(@"/Drive/s06e05", null, false)]
|
||||
[InlineData(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv", null, false)]
|
||||
[InlineData(@"/Drive/extras", 0, true)]
|
||||
[InlineData(@"/Drive/specials", 0, true)]
|
||||
public void GetSeasonNumberFromPathTest(string path, int? seasonNumber, bool isSeasonDirectory)
|
||||
{
|
||||
var result = SeasonPathParser.Parse(path, true, true);
|
||||
|
||||
Assert.Equal(result.SeasonNumber != null, result.Success);
|
||||
Assert.Equal(result.SeasonNumber, seasonNumber);
|
||||
Assert.Equal(isSeasonDirectory, result.IsSeasonFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Emby.Naming.Common;
|
||||
using System.IO;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.TV;
|
||||
using Xunit;
|
||||
|
||||
@@ -15,7 +16,6 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
[InlineData("/server/The Walking Dead 4x01.mp4", "The Walking Dead", 4, 1)]
|
||||
[InlineData("/server/the_simpsons-s02e01_18536.mp4", "the_simpsons", 2, 1)]
|
||||
[InlineData("/server/Temp/S01E02 foo.mp4", "", 1, 2)]
|
||||
[InlineData("Series/4-12 - The Woman.mp4", "", 4, 12)]
|
||||
[InlineData("Series/4x12 - The Woman.mp4", "", 4, 12)]
|
||||
[InlineData("Series/LA X, Pt. 1_s06e32.mp4", "LA X, Pt. 1", 6, 32)]
|
||||
[InlineData("[Baz-Bar]Foo - [1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)]
|
||||
@@ -24,16 +24,37 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
// TODO: [InlineData("[Baz-Bar]Foo - 01 - 12[1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)]
|
||||
// TODO: [InlineData("E:\\Anime\\Yahari Ore no Seishun Love Comedy wa Machigatteiru\\Yahari Ore no Seishun Love Comedy wa Machigatteiru. Zoku\\Oregairu Zoku 11 - Hayama Hayato Always Renconds to Everyone's Expectations..mkv", "Yahari Ore no Seishun Love Comedy wa Machigatteiru", null, 11)]
|
||||
// TODO: [InlineData(@"/Library/Series/The Grand Tour (2016)/Season 1/S01E01 The Holy Trinity.mkv", "The Grand Tour", 1, 1)]
|
||||
public void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber)
|
||||
public void TestSimple(string path, string seriesName, int? seasonNumber, int? episodeNumber)
|
||||
{
|
||||
Test(path, seriesName, seasonNumber, episodeNumber, null);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Series/4-12 - The Woman.mp4", "", 4, 12, 12)]
|
||||
public void TestWithPossibleEpisodeEnd(string path, string seriesName, int? seasonNumber, int? episodeNumber, int? episodeEndNumber)
|
||||
{
|
||||
Test(path, seriesName, seasonNumber, episodeNumber, episodeEndNumber);
|
||||
}
|
||||
|
||||
private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber, int? episodeEndNumber)
|
||||
{
|
||||
var options = new NamingOptions();
|
||||
|
||||
var result = new EpisodeResolver(options)
|
||||
.Resolve(path, false);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(seasonNumber, result?.SeasonNumber);
|
||||
Assert.Equal(episodeNumber, result?.EpisodeNumber);
|
||||
Assert.Equal(seriesName, result?.SeriesName, true);
|
||||
Assert.Equal(path, result?.Path);
|
||||
Assert.Equal(Path.GetExtension(path).Substring(1), result?.Container);
|
||||
Assert.Null(result?.Format3D);
|
||||
Assert.False(result?.Is3D);
|
||||
Assert.False(result?.IsStub);
|
||||
Assert.Null(result?.StubType);
|
||||
Assert.Equal(episodeEndNumber, result?.EndingEpisodeNumber);
|
||||
Assert.False(result?.IsByDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Video;
|
||||
using Xunit;
|
||||
@@ -52,6 +52,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
[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)]
|
||||
[InlineData(null, null, null)]
|
||||
[InlineData("", "", null)]
|
||||
public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)
|
||||
{
|
||||
input = Path.GetFileName(input);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Emby.Naming.Common;
|
||||
using System;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Video;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Xunit;
|
||||
using MediaType = Emby.Naming.Common.MediaType;
|
||||
|
||||
namespace Jellyfin.Naming.Tests.Video
|
||||
{
|
||||
@@ -93,6 +95,23 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestExtraInfo_InvalidRuleType()
|
||||
{
|
||||
var rule = new ExtraRule(ExtraType.Unknown, ExtraRuleType.Regex, @"([eE]x(tra)?\.\w+)", MediaType.Video);
|
||||
var options = new NamingOptions { VideoExtraRules = new[] { rule } };
|
||||
var res = GetExtraTypeParser(options).GetExtraInfo("extra.mp4");
|
||||
|
||||
Assert.Equal(rule, res.Rule);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestFlagsParser()
|
||||
{
|
||||
var flags = new FlagParser(_videoOptions).GetFlags(string.Empty);
|
||||
Assert.Empty(flags);
|
||||
}
|
||||
|
||||
private ExtraResolver GetExtraTypeParser(NamingOptions videoOptions)
|
||||
{
|
||||
return new ExtraResolver(videoOptions);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Video;
|
||||
using MediaBrowser.Model.IO;
|
||||
@@ -11,8 +12,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiEdition1()
|
||||
[Fact]
|
||||
public void TestMultiEdition1()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@@ -35,8 +36,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiEdition2()
|
||||
[Fact]
|
||||
public void TestMultiEdition2()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@@ -81,8 +82,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestLetterFolders()
|
||||
[Fact]
|
||||
public void TestLetterFolders()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@@ -109,8 +110,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiVersionLimit()
|
||||
[Fact]
|
||||
public void TestMultiVersionLimit()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@@ -138,8 +139,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiVersionLimit2()
|
||||
[Fact]
|
||||
public void TestMultiVersionLimit2()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@@ -168,8 +169,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiVersion3()
|
||||
[Fact]
|
||||
public void TestMultiVersion3()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@@ -194,8 +195,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiVersion4()
|
||||
[Fact]
|
||||
public void TestMultiVersion4()
|
||||
{
|
||||
// Test for false positive
|
||||
|
||||
@@ -221,9 +222,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
Assert.Empty(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiVersion5()
|
||||
[Fact]
|
||||
public void TestMultiVersion5()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@@ -254,8 +254,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiVersion6()
|
||||
[Fact]
|
||||
public void TestMultiVersion6()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@@ -285,9 +285,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
Assert.True(result[0].AlternateVersions[5].Is3D);
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiVersion7()
|
||||
[Fact]
|
||||
public void TestMultiVersion7()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@@ -306,12 +305,9 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
Assert.Equal(2, result.Count);
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiVersion8()
|
||||
[Fact]
|
||||
public void TestMultiVersion8()
|
||||
{
|
||||
// This is not actually supported yet
|
||||
|
||||
var files = new[]
|
||||
{
|
||||
@"/movies/Iron Man/Iron Man.mkv",
|
||||
@@ -339,9 +335,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
Assert.True(result[0].AlternateVersions[4].Is3D);
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiVersion9()
|
||||
[Fact]
|
||||
public void TestMultiVersion9()
|
||||
{
|
||||
// Test for false positive
|
||||
|
||||
@@ -367,9 +362,8 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
Assert.Empty(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiVersion10()
|
||||
[Fact]
|
||||
public void TestMultiVersion10()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@@ -390,12 +384,9 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
Assert.Single(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// [Fact]
|
||||
private void TestMultiVersion11()
|
||||
[Fact]
|
||||
public void TestMultiVersion11()
|
||||
{
|
||||
// Currently not supported but we should probably handle this.
|
||||
|
||||
var files = new[]
|
||||
{
|
||||
@"/movies/X-Men Apocalypse (2016)/X-Men Apocalypse (2016) [1080p] Blu-ray.x264.DTS.mkv",
|
||||
@@ -415,6 +406,16 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
Assert.Single(result[0].AlternateVersions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEmptyList()
|
||||
{
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(new List<FileSystemMetadata>()).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
private VideoListResolver GetResolver()
|
||||
{
|
||||
return new VideoListResolver(_namingOptions);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Video;
|
||||
using Xunit;
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
Test("video.hdtv.disc", true, "tv");
|
||||
Test("video.pdtv.disc", true, "tv");
|
||||
Test("video.dsr.disc", true, "tv");
|
||||
Test(string.Empty, false, "tv");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Video;
|
||||
using MediaBrowser.Model.IO;
|
||||
@@ -369,6 +369,26 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
Assert.Single(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestFourRooms()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
@"Four Rooms - A.avi",
|
||||
@"Four Rooms - A.mp4"
|
||||
};
|
||||
|
||||
var resolver = GetResolver();
|
||||
|
||||
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
|
||||
{
|
||||
IsDirectory = false,
|
||||
FullName = i
|
||||
}).ToList()).ToList();
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestMovieTrailer()
|
||||
{
|
||||
@@ -431,6 +451,13 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
Assert.Single(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDirectoryStack()
|
||||
{
|
||||
var stack = new FileStack();
|
||||
Assert.False(stack.ContainsFile("XX", true));
|
||||
}
|
||||
|
||||
private VideoListResolver GetResolver()
|
||||
{
|
||||
return new VideoListResolver(_namingOptions);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Video;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@@ -14,165 +15,135 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
{
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/7 Psychos.mkv/7 Psychos.mkv",
|
||||
Container = "mkv",
|
||||
Name = "7 Psychos"
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/7 Psychos.mkv/7 Psychos.mkv",
|
||||
container: "mkv",
|
||||
name: "7 Psychos")
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv",
|
||||
Container = "mkv",
|
||||
Name = "3 days to kill",
|
||||
Year = 2005
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/3 days to kill (2005)/3 days to kill (2005).mkv",
|
||||
container: "mkv",
|
||||
name: "3 days to kill",
|
||||
year: 2005)
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/American Psycho/American.Psycho.mkv",
|
||||
Container = "mkv",
|
||||
Name = "American.Psycho",
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/American Psycho/American.Psycho.mkv",
|
||||
container: "mkv",
|
||||
name: "American.Psycho")
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv",
|
||||
Container = "mkv",
|
||||
Name = "brave",
|
||||
Year = 2006,
|
||||
Is3D = true,
|
||||
Format3D = "sbs",
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/brave (2007)/brave (2006).3d.sbs.mkv",
|
||||
container: "mkv",
|
||||
name: "brave",
|
||||
year: 2006,
|
||||
is3D: true,
|
||||
format3D: "sbs")
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv",
|
||||
Container = "mkv",
|
||||
Name = "300",
|
||||
Year = 2006
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/300 (2007)/300 (2006).3d1.sbas.mkv",
|
||||
container: "mkv",
|
||||
name: "300",
|
||||
year: 2006)
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv",
|
||||
Container = "mkv",
|
||||
Name = "300",
|
||||
Year = 2006,
|
||||
Is3D = true,
|
||||
Format3D = "sbs",
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/300 (2007)/300 (2006).3d.sbs.mkv",
|
||||
container: "mkv",
|
||||
name: "300",
|
||||
year: 2006,
|
||||
is3D: true,
|
||||
format3D: "sbs")
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc",
|
||||
Container = "disc",
|
||||
Name = "brave",
|
||||
Year = 2006,
|
||||
IsStub = true,
|
||||
StubType = "bluray",
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/brave (2007)/brave (2006)-trailer.bluray.disc",
|
||||
container: "disc",
|
||||
name: "brave",
|
||||
year: 2006,
|
||||
isStub: true,
|
||||
stubType: "bluray")
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc",
|
||||
Container = "disc",
|
||||
Name = "300",
|
||||
Year = 2006,
|
||||
IsStub = true,
|
||||
StubType = "bluray",
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/300 (2007)/300 (2006)-trailer.bluray.disc",
|
||||
container: "disc",
|
||||
name: "300",
|
||||
year: 2006,
|
||||
isStub: true,
|
||||
stubType: "bluray")
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/Brave (2007)/Brave (2006).bluray.disc",
|
||||
Container = "disc",
|
||||
Name = "Brave",
|
||||
Year = 2006,
|
||||
IsStub = true,
|
||||
StubType = "bluray",
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/Brave (2007)/Brave (2006).bluray.disc",
|
||||
container: "disc",
|
||||
name: "Brave",
|
||||
year: 2006,
|
||||
isStub: true,
|
||||
stubType: "bluray")
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/300 (2007)/300 (2006).bluray.disc",
|
||||
Container = "disc",
|
||||
Name = "300",
|
||||
Year = 2006,
|
||||
IsStub = true,
|
||||
StubType = "bluray",
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/300 (2007)/300 (2006).bluray.disc",
|
||||
container: "disc",
|
||||
name: "300",
|
||||
year: 2006,
|
||||
isStub: true,
|
||||
stubType: "bluray")
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/300 (2007)/300 (2006)-trailer.mkv",
|
||||
Container = "mkv",
|
||||
Name = "300",
|
||||
Year = 2006,
|
||||
ExtraType = ExtraType.Trailer,
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/300 (2007)/300 (2006)-trailer.mkv",
|
||||
container: "mkv",
|
||||
name: "300",
|
||||
year: 2006,
|
||||
extraType: ExtraType.Trailer)
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv",
|
||||
Container = "mkv",
|
||||
Name = "Brave",
|
||||
Year = 2006,
|
||||
ExtraType = ExtraType.Trailer,
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/Brave (2007)/Brave (2006)-trailer.mkv",
|
||||
container: "mkv",
|
||||
name: "Brave",
|
||||
year: 2006,
|
||||
extraType: ExtraType.Trailer)
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/300 (2007)/300 (2006).mkv",
|
||||
Container = "mkv",
|
||||
Name = "300",
|
||||
Year = 2006
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/300 (2007)/300 (2006).mkv",
|
||||
container: "mkv",
|
||||
name: "300",
|
||||
year: 2006)
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv",
|
||||
Container = "mkv",
|
||||
Name = "Bad Boys",
|
||||
Year = 1995,
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/Bad Boys (1995)/Bad Boys (1995).mkv",
|
||||
container: "mkv",
|
||||
name: "Bad Boys",
|
||||
year: 1995)
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
new VideoFileInfo()
|
||||
{
|
||||
Path = @"/server/Movies/Brave (2007)/Brave (2006).mkv",
|
||||
Container = "mkv",
|
||||
Name = "Brave",
|
||||
Year = 2006,
|
||||
}
|
||||
new VideoFileInfo(
|
||||
path: @"/server/Movies/Brave (2007)/Brave (2006).mkv",
|
||||
container: "mkv",
|
||||
name: "Brave",
|
||||
year: 2006)
|
||||
};
|
||||
yield return new object[]
|
||||
{
|
||||
@@ -204,6 +175,34 @@ namespace Jellyfin.Naming.Tests.Video
|
||||
Assert.Equal(result?.StubType, expectedResult.StubType);
|
||||
Assert.Equal(result?.IsDirectory, expectedResult.IsDirectory);
|
||||
Assert.Equal(result?.FileNameWithoutExtension, expectedResult.FileNameWithoutExtension);
|
||||
Assert.Equal(result?.ToString(), expectedResult.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveFile_EmptyPath()
|
||||
{
|
||||
var result = new VideoResolver(_namingOptions).ResolveFile(string.Empty);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveDirectoryTest()
|
||||
{
|
||||
var paths = new[]
|
||||
{
|
||||
@"/Server/Iron Man",
|
||||
@"Batman",
|
||||
string.Empty
|
||||
};
|
||||
|
||||
var resolver = new VideoResolver(_namingOptions);
|
||||
var results = paths.Select(path => resolver.ResolveDirectory(path)).ToList();
|
||||
|
||||
Assert.Equal(3, results.Count);
|
||||
Assert.NotNull(results[0]);
|
||||
Assert.NotNull(results[1]);
|
||||
Assert.Null(results[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
@@ -16,8 +16,8 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="4.14.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Moq" Version="4.14.7" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="Moq" Version="4.15.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||
|
||||
Reference in New Issue
Block a user