Refactoring HubEndpointTests (#1231)
This commit is contained in:
parent
3bb71255d4
commit
ac236efdae
|
|
@ -0,0 +1,575 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Tests.HubEndpointTestUtils
|
||||
{
|
||||
public class MethodHub : TestHub
|
||||
{
|
||||
public Task GroupRemoveMethod(string groupName)
|
||||
{
|
||||
return Groups.RemoveAsync(Context.ConnectionId, groupName);
|
||||
}
|
||||
|
||||
public Task ClientSendMethod(string userId, string message)
|
||||
{
|
||||
return Clients.User(userId).InvokeAsync("Send", message);
|
||||
}
|
||||
|
||||
public Task ConnectionSendMethod(string connectionId, string message)
|
||||
{
|
||||
return Clients.Client(connectionId).InvokeAsync("Send", message);
|
||||
}
|
||||
|
||||
public Task GroupAddMethod(string groupName)
|
||||
{
|
||||
return Groups.AddAsync(Context.ConnectionId, groupName);
|
||||
}
|
||||
|
||||
public Task GroupSendMethod(string groupName, string message)
|
||||
{
|
||||
return Clients.Group(groupName).InvokeAsync("Send", message);
|
||||
}
|
||||
|
||||
public Task GroupExceptSendMethod(string groupName, string message, IReadOnlyList<string> excludedIds)
|
||||
{
|
||||
return Clients.GroupExcept(groupName, excludedIds).InvokeAsync("Send", message);
|
||||
}
|
||||
|
||||
public Task SendToOthersInGroup(string groupName, string message)
|
||||
{
|
||||
return Clients.OthersInGroup(groupName).InvokeAsync("Send", message);
|
||||
}
|
||||
|
||||
public Task BroadcastMethod(string message)
|
||||
{
|
||||
return Clients.All.InvokeAsync("Broadcast", message);
|
||||
}
|
||||
|
||||
public Task BroadcastItem()
|
||||
{
|
||||
return Clients.All.InvokeAsync("Broadcast", new Result { Message = "test", paramName = "param" });
|
||||
}
|
||||
|
||||
public Task SendArray()
|
||||
{
|
||||
return Clients.All.InvokeAsync("Array", new int[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
public Task<int> TaskValueMethod()
|
||||
{
|
||||
return Task.FromResult(42);
|
||||
}
|
||||
|
||||
public int ValueMethod()
|
||||
{
|
||||
return 43;
|
||||
}
|
||||
|
||||
[HubMethodName("RenamedMethod")]
|
||||
public int ATestMethodThatIsRenamedByTheAttribute()
|
||||
{
|
||||
return 43;
|
||||
}
|
||||
|
||||
public string Echo(string data)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
public void VoidMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public string ConcatString(byte b, int i, char c, string s)
|
||||
{
|
||||
return $"{b}, {i}, {c}, {s}";
|
||||
}
|
||||
|
||||
public Task SendAnonymousObject()
|
||||
{
|
||||
return Clients.Client(Context.ConnectionId).InvokeAsync("Send", new { });
|
||||
}
|
||||
|
||||
public override Task OnDisconnectedAsync(Exception e)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void MethodThatThrows()
|
||||
{
|
||||
throw new InvalidOperationException("BOOM!");
|
||||
}
|
||||
|
||||
public Task MethodThatYieldsFailedTask()
|
||||
{
|
||||
return Task.FromException(new InvalidOperationException("BOOM!"));
|
||||
}
|
||||
|
||||
public static void StaticMethod()
|
||||
{
|
||||
}
|
||||
|
||||
[Authorize("test")]
|
||||
public void AuthMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public Task SendToAllExcept(string message, IReadOnlyList<string> excludedIds)
|
||||
{
|
||||
return Clients.AllExcept(excludedIds).InvokeAsync("Send", message);
|
||||
}
|
||||
|
||||
public bool HasHttpContext()
|
||||
{
|
||||
return Context.Connection.GetHttpContext() != null;
|
||||
}
|
||||
|
||||
public Task SendToOthers(string message)
|
||||
{
|
||||
return Clients.Others.InvokeAsync("Send", message);
|
||||
}
|
||||
|
||||
public Task SendToCaller(string message)
|
||||
{
|
||||
return Clients.Caller.InvokeAsync("Send", message);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TestHub : Hub
|
||||
{
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
var tcs = (TaskCompletionSource<bool>)Context.Connection.Metadata["ConnectedTask"];
|
||||
tcs?.TrySetResult(true);
|
||||
return base.OnConnectedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class DynamicTestHub : DynamicHub
|
||||
{
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
var tcs = (TaskCompletionSource<bool>)Context.Connection.Metadata["ConnectedTask"];
|
||||
tcs?.TrySetResult(true);
|
||||
return base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public string Echo(string data)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
public Task ClientSendMethod(string userId, string message)
|
||||
{
|
||||
return Clients.User(userId).Send(message);
|
||||
}
|
||||
|
||||
public Task ConnectionSendMethod(string connectionId, string message)
|
||||
{
|
||||
return Clients.Client(connectionId).Send(message);
|
||||
}
|
||||
|
||||
public Task GroupAddMethod(string groupName)
|
||||
{
|
||||
return Groups.AddAsync(Context.ConnectionId, groupName);
|
||||
}
|
||||
|
||||
public Task GroupSendMethod(string groupName, string message)
|
||||
{
|
||||
return Clients.Group(groupName).Send(message);
|
||||
}
|
||||
|
||||
public Task GroupExceptSendMethod(string groupName, string message, IReadOnlyList<string> excludedIds)
|
||||
{
|
||||
return Clients.GroupExcept(groupName, excludedIds).Send(message);
|
||||
}
|
||||
|
||||
public Task SendToOthersInGroup(string groupName, string message)
|
||||
{
|
||||
return Clients.OthersInGroup(groupName).Send(message);
|
||||
}
|
||||
|
||||
public Task BroadcastMethod(string message)
|
||||
{
|
||||
return Clients.All.Broadcast(message);
|
||||
}
|
||||
|
||||
public Task SendToAllExcept(string message, IReadOnlyList<string> excludedIds)
|
||||
{
|
||||
return Clients.AllExcept(excludedIds).Send(message);
|
||||
}
|
||||
|
||||
public Task SendToOthers(string message)
|
||||
{
|
||||
return Clients.Others.Send(message);
|
||||
}
|
||||
|
||||
public Task SendToCaller(string message)
|
||||
{
|
||||
return Clients.Caller.Send(message);
|
||||
}
|
||||
}
|
||||
|
||||
public class HubT : Hub<Test>
|
||||
{
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
var tcs = (TaskCompletionSource<bool>)Context.Connection.Metadata["ConnectedTask"];
|
||||
tcs?.TrySetResult(true);
|
||||
return base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public string Echo(string data)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
public Task ClientSendMethod(string userId, string message)
|
||||
{
|
||||
return Clients.User(userId).Send(message);
|
||||
}
|
||||
|
||||
public Task ConnectionSendMethod(string connectionId, string message)
|
||||
{
|
||||
return Clients.Client(connectionId).Send(message);
|
||||
}
|
||||
public async Task DelayedSend(string connectionId, string message)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
await Clients.Client(connectionId).Send(message);
|
||||
}
|
||||
public Task GroupAddMethod(string groupName)
|
||||
{
|
||||
return Groups.AddAsync(Context.ConnectionId, groupName);
|
||||
}
|
||||
|
||||
public Task GroupSendMethod(string groupName, string message)
|
||||
{
|
||||
return Clients.Group(groupName).Send(message);
|
||||
}
|
||||
|
||||
public Task GroupExceptSendMethod(string groupName, string message, IReadOnlyList<string> excludedIds)
|
||||
{
|
||||
return Clients.GroupExcept(groupName, excludedIds).Send(message);
|
||||
}
|
||||
|
||||
public Task SendToOthersInGroup(string groupName, string message)
|
||||
{
|
||||
return Clients.OthersInGroup(groupName).Send(message);
|
||||
}
|
||||
|
||||
public Task BroadcastMethod(string message)
|
||||
{
|
||||
return Clients.All.Broadcast(message);
|
||||
}
|
||||
|
||||
public Task SendToAllExcept(string message, IReadOnlyList<string> excludedIds)
|
||||
{
|
||||
return Clients.AllExcept(excludedIds).Send(message);
|
||||
}
|
||||
|
||||
public Task SendToOthers(string message)
|
||||
{
|
||||
return Clients.Others.Send(message);
|
||||
}
|
||||
|
||||
public Task SendToCaller(string message)
|
||||
{
|
||||
return Clients.Caller.Send(message);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Test
|
||||
{
|
||||
Task Send(string message);
|
||||
Task Broadcast(string message);
|
||||
}
|
||||
|
||||
public class OnConnectedThrowsHub : Hub
|
||||
{
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
tcs.SetException(new InvalidOperationException("Hub OnConnected failed."));
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
public class OnDisconnectedThrowsHub : TestHub
|
||||
{
|
||||
public override Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
tcs.SetException(new InvalidOperationException("Hub OnDisconnected failed."));
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
public class InheritedHub : BaseHub
|
||||
{
|
||||
public override int VirtualMethod(int num)
|
||||
{
|
||||
return num - 10;
|
||||
}
|
||||
|
||||
public override int VirtualMethodRenamed()
|
||||
{
|
||||
return 34;
|
||||
}
|
||||
}
|
||||
|
||||
public class BaseHub : TestHub
|
||||
{
|
||||
public string BaseMethod(string message)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
public virtual int VirtualMethod(int num)
|
||||
{
|
||||
return num;
|
||||
}
|
||||
|
||||
[HubMethodName("RenamedVirtualMethod")]
|
||||
public virtual int VirtualMethodRenamed()
|
||||
{
|
||||
return 43;
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidHub : TestHub
|
||||
{
|
||||
public void OverloadedMethod(int num)
|
||||
{
|
||||
}
|
||||
|
||||
public void OverloadedMethod(string message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class DisposeTrackingHub : TestHub
|
||||
{
|
||||
private TrackDispose _trackDispose;
|
||||
|
||||
public DisposeTrackingHub(TrackDispose trackDispose)
|
||||
{
|
||||
_trackDispose = trackDispose;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool dispose)
|
||||
{
|
||||
if (dispose)
|
||||
{
|
||||
_trackDispose.DisposeCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ObservableHub : Hub
|
||||
{
|
||||
private readonly Observable<int> _numbers;
|
||||
|
||||
public ObservableHub(Observable<int> numbers)
|
||||
{
|
||||
_numbers = numbers;
|
||||
}
|
||||
|
||||
public IObservable<int> Subscribe() => _numbers;
|
||||
}
|
||||
|
||||
public class AbortHub : Hub
|
||||
{
|
||||
public void Kill()
|
||||
{
|
||||
Context.Connection.Abort();
|
||||
}
|
||||
}
|
||||
|
||||
public class Observable<T> : IObservable<T>
|
||||
{
|
||||
public List<IObserver<T>> Observers = new List<IObserver<T>>();
|
||||
|
||||
public Action<IObserver<T>> OnSubscribe;
|
||||
|
||||
public Action<IObserver<T>> OnDispose;
|
||||
|
||||
public IDisposable Subscribe(IObserver<T> observer)
|
||||
{
|
||||
lock (Observers)
|
||||
{
|
||||
Observers.Add(observer);
|
||||
}
|
||||
|
||||
OnSubscribe?.Invoke(observer);
|
||||
|
||||
return new DisposableAction(() =>
|
||||
{
|
||||
lock (Observers)
|
||||
{
|
||||
Observers.Remove(observer);
|
||||
}
|
||||
|
||||
OnDispose?.Invoke(observer);
|
||||
});
|
||||
}
|
||||
|
||||
public void OnNext(T value)
|
||||
{
|
||||
lock (Observers)
|
||||
{
|
||||
foreach (var observer in Observers)
|
||||
{
|
||||
observer.OnNext(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
lock (Observers)
|
||||
{
|
||||
foreach (var observer in Observers)
|
||||
{
|
||||
observer.OnCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DisposableAction : IDisposable
|
||||
{
|
||||
private readonly Action _action;
|
||||
public DisposableAction(Action action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_action();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class StreamingHub : TestHub
|
||||
{
|
||||
public IObservable<string> CounterObservable(int count)
|
||||
{
|
||||
return new CountingObservable(count);
|
||||
}
|
||||
|
||||
public ChannelReader<string> CounterChannel(int count)
|
||||
{
|
||||
var channel = Channel.CreateUnbounded<string>();
|
||||
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
await channel.Writer.WriteAsync(i.ToString());
|
||||
}
|
||||
channel.Writer.Complete();
|
||||
});
|
||||
|
||||
return channel.Reader;
|
||||
}
|
||||
|
||||
public ChannelReader<string> BlockingStream()
|
||||
{
|
||||
return Channel.CreateUnbounded<string>().Reader;
|
||||
}
|
||||
|
||||
private class CountingObservable : IObservable<string>
|
||||
{
|
||||
private int _count;
|
||||
|
||||
public CountingObservable(int count)
|
||||
{
|
||||
_count = count;
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<string> observer)
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
Task.Run(() =>
|
||||
{
|
||||
for (int i = 0; !cts.Token.IsCancellationRequested && i < _count; i++)
|
||||
{
|
||||
observer.OnNext(i.ToString());
|
||||
}
|
||||
observer.OnCompleted();
|
||||
});
|
||||
|
||||
return new CancellationDisposable(cts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SimpleHub : Hub
|
||||
{
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
await Clients.All.InvokeAsync("Send", $"{Context.ConnectionId} joined");
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class SimpleTypedHub : Hub<ITypedHubClient>
|
||||
{
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
await Clients.All.Send($"{Context.ConnectionId} joined");
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ITypedHubClient
|
||||
{
|
||||
Task Send(string message);
|
||||
}
|
||||
|
||||
public class ConnectionLifetimeHub : Hub
|
||||
{
|
||||
private ConnectionLifetimeState _state;
|
||||
|
||||
public ConnectionLifetimeHub(ConnectionLifetimeState state)
|
||||
{
|
||||
_state = state;
|
||||
}
|
||||
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
_state.TokenStateInConnected = Context.Connection.ConnectionAbortedToken.IsCancellationRequested;
|
||||
|
||||
Context.Connection.ConnectionAbortedToken.Register(() =>
|
||||
{
|
||||
_state.TokenCallbackTriggered = true;
|
||||
});
|
||||
|
||||
return base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
_state.TokenStateInDisconnected = Context.Connection.ConnectionAbortedToken.IsCancellationRequested;
|
||||
|
||||
return base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public class ConnectionLifetimeState
|
||||
{
|
||||
public bool TokenCallbackTriggered { get; set; }
|
||||
|
||||
public bool TokenStateInConnected { get; set; }
|
||||
|
||||
public bool TokenStateInDisconnected { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
// 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 Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Tests.HubEndpointTestUtils
|
||||
{
|
||||
public class HubEndPointTestUtils
|
||||
{
|
||||
public static Type GetEndPointType(Type hubType)
|
||||
{
|
||||
var endPointType = typeof(HubEndPoint<>);
|
||||
return endPointType.MakeGenericType(hubType);
|
||||
}
|
||||
|
||||
public static Type GetGenericType(Type genericType, Type hubType)
|
||||
{
|
||||
return genericType.MakeGenericType(hubType);
|
||||
}
|
||||
|
||||
public static void AssertHubMessage(HubMessage expected, HubMessage actual)
|
||||
{
|
||||
// We aren't testing InvocationIds here
|
||||
switch (expected)
|
||||
{
|
||||
case CompletionMessage expectedCompletion:
|
||||
var actualCompletion = Assert.IsType<CompletionMessage>(actual);
|
||||
Assert.Equal(expectedCompletion.Error, actualCompletion.Error);
|
||||
Assert.Equal(expectedCompletion.HasResult, actualCompletion.HasResult);
|
||||
Assert.Equal(expectedCompletion.Result, actualCompletion.Result);
|
||||
break;
|
||||
case StreamItemMessage expectedStreamItem:
|
||||
var actualStreamItem = Assert.IsType<StreamItemMessage>(actual);
|
||||
Assert.Equal(expectedStreamItem.Item, actualStreamItem.Item);
|
||||
break;
|
||||
case InvocationMessage expectedInvocation:
|
||||
var actualInvocation = Assert.IsType<InvocationMessage>(actual);
|
||||
|
||||
// Either both must have non-null invocationIds or both must have null invocation IDs. Checking the exact value is NOT desired here though as it could be randomly generated
|
||||
Assert.True((expectedInvocation.InvocationId == null && actualInvocation.InvocationId == null) ||
|
||||
(expectedInvocation.InvocationId != null && actualInvocation.InvocationId != null));
|
||||
Assert.Equal(expectedInvocation.Target, actualInvocation.Target);
|
||||
Assert.Equal(expectedInvocation.Arguments, actualInvocation.Arguments);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported Hub Message type {expected.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
public static IServiceProvider CreateServiceProvider(Action<ServiceCollection> addServices = null)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddOptions()
|
||||
.AddLogging()
|
||||
.AddSignalR();
|
||||
|
||||
addServices?.Invoke(services);
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
public static dynamic GetHubEndpoint(Type hubType)
|
||||
{
|
||||
var serviceProvider = CreateServiceProvider();
|
||||
dynamic endPoint = serviceProvider.GetService(HubEndPointTestUtils.GetEndPointType(hubType));
|
||||
return endPoint;
|
||||
}
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
public string Message { get; set; }
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
// testing casing
|
||||
public string paramName { get; set; }
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
}
|
||||
|
||||
public class TrackDispose
|
||||
{
|
||||
public int DisposeCount = 0;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue