Moves derived parameter tests into HubConnectionHandlerTests

This commit is contained in:
Nick Darvey 2019-06-25 15:49:51 +10:00
parent f80dbd3918
commit 47bb845d48
3 changed files with 92 additions and 143 deletions

View File

@ -661,6 +661,30 @@ namespace Microsoft.AspNetCore.SignalR.Tests
return output.Reader;
}
public async IAsyncEnumerable<string> DerivedParameterInterfaceAsyncEnumerable(IDerivedParameterTestObject param)
{
await Task.Yield();
yield return param.Value;
}
public async IAsyncEnumerable<string> DerivedParameterBaseClassAsyncEnumerable(DerivedParameterTestObjectBase param)
{
await Task.Yield();
yield return param.Value;
}
public async IAsyncEnumerable<string> DerivedParameterInterfaceAsyncEnumerableWithCancellation(IDerivedParameterTestObject param, [EnumeratorCancellation] CancellationToken token)
{
await Task.Yield();
yield return param.Value;
}
public async IAsyncEnumerable<string> DerivedParameterBaseClassAsyncEnumerableWithCancellation(DerivedParameterTestObjectBase param, [EnumeratorCancellation] CancellationToken token)
{
await Task.Yield();
yield return param.Value;
}
public class AsyncEnumerableImpl<T> : IAsyncEnumerable<T>
{
private readonly IAsyncEnumerable<T> _inner;
@ -753,6 +777,18 @@ namespace Microsoft.AspNetCore.SignalR.Tests
}
}
}
public interface IDerivedParameterTestObject
{
public string Value { get; set; }
}
public abstract class DerivedParameterTestObjectBase : IDerivedParameterTestObject
{
public string Value { get; set; }
}
public class DerivedParameterTestObject : DerivedParameterTestObjectBase { }
}
public class SimpleHub : Hub

View File

@ -3576,6 +3576,62 @@ namespace Microsoft.AspNetCore.SignalR.Tests
}
}
/// <summary>
/// Hub methods might be written by users in a way that accepts an interface or base class as a parameter
/// and deserialization could supply a derived class.
/// This test ensures implementation and subclass arguments are correctly bound for dispatch.
/// </summary>
[Theory]
[InlineData(nameof(StreamingHub.DerivedParameterInterfaceAsyncEnumerable))]
[InlineData(nameof(StreamingHub.DerivedParameterBaseClassAsyncEnumerable))]
[InlineData(nameof(StreamingHub.DerivedParameterInterfaceAsyncEnumerableWithCancellation))]
[InlineData(nameof(StreamingHub.DerivedParameterBaseClassAsyncEnumerableWithCancellation))]
public async Task CanPassDerivedParameterToStreamHubMethod(string method)
{
using (StartVerifiableLog())
{
var argument = new StreamingHub.DerivedParameterTestObject { Value = "test" };
var protocolOptions = new NewtonsoftJsonHubProtocolOptions
{
PayloadSerializerSettings = new JsonSerializerSettings()
{
// The usage of TypeNameHandling.All is a security risk.
// If you're implementing this in your own application instead use your own 'type' field and a custom JsonConverter
// or ensure you're restricting to only known types with a custom SerializationBinder.
// See https://github.com/aspnet/AspNetCore/issues/11495#issuecomment-505047422
TypeNameHandling = TypeNameHandling.All
}
};
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(
services => services.AddSignalR()
.AddNewtonsoftJsonProtocol(o => o.PayloadSerializerSettings = protocolOptions.PayloadSerializerSettings),
LoggerFactory);
var connectionHandler = serviceProvider.GetService<HubConnectionHandler<StreamingHub>>();
var invocationBinder = new Mock<IInvocationBinder>();
invocationBinder.Setup(b => b.GetStreamItemType(It.IsAny<string>())).Returns(typeof(string));
using (var client = new TestClient(
protocol: new NewtonsoftJsonHubProtocol(Options.Create(protocolOptions)),
invocationBinder: invocationBinder.Object))
{
var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
// Wait for a connection, or for the endpoint to fail.
await client.Connected.OrThrowIfOtherFails(connectionHandlerTask).OrTimeout();
var messages = await client.StreamAsync(method, argument).OrTimeout();
Assert.Equal(2, messages.Count);
HubConnectionHandlerTestUtils.AssertHubMessage(new StreamItemMessage(string.Empty, argument.Value), messages[0]);
HubConnectionHandlerTestUtils.AssertHubMessage(CompletionMessage.Empty(string.Empty), messages[1]);
client.Dispose();
await connectionHandlerTask.OrTimeout();
}
}
}
private class CustomHubActivator<THub> : IHubActivator<THub> where THub : Hub
{
public int ReleaseCount;

View File

@ -1,143 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.SignalR.Tests.Internal
{
public class DefaultHubDispatcherTests
{
private class MockHubConnectionContext<TValue> : HubConnectionContext
{
public TaskCompletionSource<object> ReceivedCompleted = new TaskCompletionSource<object>();
public List<TValue> Values = new List<TValue>();
public MockHubConnectionContext(ConnectionContext connectionContext, HubConnectionContextOptions contextOptions, ILoggerFactory loggerFactory)
: base(connectionContext, contextOptions, loggerFactory) { }
public override ValueTask WriteAsync(HubMessage message, CancellationToken cancellationToken)
{
if (message is StreamItemMessage streamItemMessage)
Values.Add((TValue)streamItemMessage.Item);
else if (message is CompletionMessage completionMessage)
{
ReceivedCompleted.TrySetResult(null);
if (!string.IsNullOrEmpty(completionMessage.Error))
{
throw new Exception("Error invoking hub method: " + completionMessage.Error);
}
}
else throw new NotImplementedException();
return default;
}
}
private static DefaultHubDispatcher<THub> CreateDispatcher<THub>() where THub : Hub
{
var serviceCollection = new ServiceCollection();
serviceCollection.TryAddScoped(typeof(IHubActivator<>), typeof(DefaultHubActivator<>));
var provider = serviceCollection.BuildServiceProvider();
var serviceScopeFactory = provider.GetService<IServiceScopeFactory>();
return new DefaultHubDispatcher<THub>(
serviceScopeFactory,
new HubContext<THub>(new DefaultHubLifetimeManager<THub>(NullLogger<DefaultHubLifetimeManager<THub>>.Instance)),
Options.Create(new HubOptions<THub>()),
Options.Create(new HubOptions()),
new Logger<DefaultHubDispatcher<THub>>(NullLoggerFactory.Instance));
}
private static MockHubConnectionContext<TValue> CreateConnectionContext<TValue>()
{
var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
var connection = new DefaultConnectionContext(Guid.NewGuid().ToString(), pair.Application, pair.Transport);
var contextOptions = new HubConnectionContextOptions() { KeepAliveInterval = TimeSpan.Zero };
return new MockHubConnectionContext<TValue>(
connection,
contextOptions,
NullLoggerFactory.Instance);
}
/// <summary>
/// For <see cref="DispatchesDerivedArguments"/>.
/// </summary>
private interface ITestDerivedParameter
{
public string Value { get; }
}
/// <summary>
/// For <see cref="DispatchesDerivedArguments"/>.
/// </summary>
private abstract class TestDerivedParameterBase
{
public TestDerivedParameterBase(string value) => Value = value;
public string Value { get; }
}
/// <summary>
/// For <see cref="DispatchesDerivedArguments"/>.
/// </summary>
private class TestDerivedParameter : TestDerivedParameterBase, ITestDerivedParameter
{
public TestDerivedParameter(string value) : base(value) { }
}
/// <summary>
/// For <see cref="DispatchesDerivedArguments"/>.
/// </summary>
private class TestDerivedParameterHub : Hub
{
public async IAsyncEnumerable<string> TestSubclass(TestDerivedParameterBase param, [EnumeratorCancellation]CancellationToken token)
{
await Task.Yield();
yield return param.Value;
}
public async IAsyncEnumerable<string> TestImplementation(ITestDerivedParameter param, [EnumeratorCancellation]CancellationToken token)
{
await Task.Yield();
yield return param.Value;
}
}
/// <summary>
/// Hub methods might be written by users in a way that accepts an interface or base class as a parameter
/// and deserialization could supply a derived class (e.g. Json.NET's TypeNameHandling = TypeNameHandling.All).
/// This test ensures implementation and subclass arguments are correctly bound for dispatch.
/// </summary>
[Theory]
[InlineData(nameof(TestDerivedParameterHub.TestImplementation))]
[InlineData(nameof(TestDerivedParameterHub.TestSubclass))]
public async Task DispatchesDerivedArguments(string methodName)
{
var message = new TestDerivedParameter("Yup");
var connectionContext = CreateConnectionContext<string>();
var dispatcher = CreateDispatcher<TestDerivedParameterHub>();
await dispatcher.DispatchMessageAsync(connectionContext, new StreamInvocationMessage("123", methodName, new[] { message }));
await (connectionContext as MockHubConnectionContext<string>).ReceivedCompleted.Task;
Assert.Single(connectionContext.Values, message.Value);
}
}
}