Adding Redis Unit tests (#1013)
This commit is contained in:
parent
878a70226c
commit
0e70c7950b
11
SignalR.sln
11
SignalR.sln
|
|
@ -1,6 +1,6 @@
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.26823.1
|
VisualStudioVersion = 15.0.26919.1
|
||||||
MinimumVisualStudioVersion = 15.0.26730.03
|
MinimumVisualStudioVersion = 15.0.26730.03
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DA69F624-5398-4884-87E4-B816698CDE65}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DA69F624-5398-4884-87E4-B816698CDE65}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
|
@ -85,7 +85,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Socket
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{8A4582C8-DC59-4B61-BCE7-119FBAA99EFB}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{8A4582C8-DC59-4B61-BCE7-119FBAA99EFB}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.SignalR.Client", "src\Microsoft.AspNetCore.SignalR.Client\Microsoft.AspNetCore.SignalR.Client.csproj", "{BE982591-F4BB-42D9-ABD4-A5D44C65971E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SignalR.Client", "src\Microsoft.AspNetCore.SignalR.Client\Microsoft.AspNetCore.SignalR.Client.csproj", "{BE982591-F4BB-42D9-ABD4-A5D44C65971E}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.SignalR.Redis.Tests", "test\Microsoft.AspNetCore.SignalR.Redis.Tests\Microsoft.AspNetCore.SignalR.Redis.Tests.csproj", "{0B083AE6-86CA-4E0B-AE02-59154D1FD005}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|
@ -189,6 +191,10 @@ Global
|
||||||
{BE982591-F4BB-42D9-ABD4-A5D44C65971E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{BE982591-F4BB-42D9-ABD4-A5D44C65971E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{BE982591-F4BB-42D9-ABD4-A5D44C65971E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{BE982591-F4BB-42D9-ABD4-A5D44C65971E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{BE982591-F4BB-42D9-ABD4-A5D44C65971E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{BE982591-F4BB-42D9-ABD4-A5D44C65971E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{0B083AE6-86CA-4E0B-AE02-59154D1FD005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0B083AE6-86CA-4E0B-AE02-59154D1FD005}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0B083AE6-86CA-4E0B-AE02-59154D1FD005}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0B083AE6-86CA-4E0B-AE02-59154D1FD005}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -219,6 +225,7 @@ Global
|
||||||
{B0243F99-2D3F-4CC6-AD71-E3F891B64724} = {DA69F624-5398-4884-87E4-B816698CDE65}
|
{B0243F99-2D3F-4CC6-AD71-E3F891B64724} = {DA69F624-5398-4884-87E4-B816698CDE65}
|
||||||
{E081EE41-D95F-4AD2-BC0B-4B562C0A2A47} = {DA69F624-5398-4884-87E4-B816698CDE65}
|
{E081EE41-D95F-4AD2-BC0B-4B562C0A2A47} = {DA69F624-5398-4884-87E4-B816698CDE65}
|
||||||
{BE982591-F4BB-42D9-ABD4-A5D44C65971E} = {DA69F624-5398-4884-87E4-B816698CDE65}
|
{BE982591-F4BB-42D9-ABD4-A5D44C65971E} = {DA69F624-5398-4884-87E4-B816698CDE65}
|
||||||
|
{0B083AE6-86CA-4E0B-AE02-59154D1FD005} = {6A35B453-52EC-48AF-89CA-D4A69800F131}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {7945A4E4-ACDB-4F6E-95CA-6AC6E7C2CD59}
|
SolutionGuid = {7945A4E4-ACDB-4F6E-95CA-6AC6E7C2CD59}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ namespace ChatSample
|
||||||
private const int ScanInterval = 5; //seconds
|
private const int ScanInterval = 5; //seconds
|
||||||
private const int ServerInactivityTimeout = 30; // seconds
|
private const int ServerInactivityTimeout = 30; // seconds
|
||||||
|
|
||||||
private readonly ConnectionMultiplexer _redisConnection;
|
private readonly IConnectionMultiplexer _redisConnection;
|
||||||
private readonly IDatabase _redisDatabase;
|
private readonly IDatabase _redisDatabase;
|
||||||
private readonly ISubscriber _redisSubscriber;
|
private readonly ISubscriber _redisSubscriber;
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ namespace ChatSample
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private (ConnectionMultiplexer, IDatabase) StartRedisConnection(RedisOptions options)
|
private (IConnectionMultiplexer, IDatabase) StartRedisConnection(RedisOptions options)
|
||||||
{
|
{
|
||||||
// TODO: handle connection failures
|
// TODO: handle connection failures
|
||||||
var redisConnection = ConnectToRedis(options, _logger);
|
var redisConnection = ConnectToRedis(options, _logger);
|
||||||
|
|
@ -101,7 +101,7 @@ namespace ChatSample
|
||||||
return (redisConnection, redisDatabase);
|
return (redisConnection, redisDatabase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConnectionMultiplexer ConnectToRedis(RedisOptions options, ILogger logger)
|
private static IConnectionMultiplexer ConnectToRedis(RedisOptions options, ILogger logger)
|
||||||
{
|
{
|
||||||
var loggerTextWriter = new LoggerTextWriter(logger);
|
var loggerTextWriter = new LoggerTextWriter(logger);
|
||||||
if (options.Factory != null)
|
if (options.Factory != null)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.SignalR.Redis
|
||||||
private readonly HubConnectionList _connections = new HubConnectionList();
|
private readonly HubConnectionList _connections = new HubConnectionList();
|
||||||
// TODO: Investigate "memory leak" entries never get removed
|
// TODO: Investigate "memory leak" entries never get removed
|
||||||
private readonly ConcurrentDictionary<string, GroupData> _groups = new ConcurrentDictionary<string, GroupData>();
|
private readonly ConcurrentDictionary<string, GroupData> _groups = new ConcurrentDictionary<string, GroupData>();
|
||||||
private readonly ConnectionMultiplexer _redisServerConnection;
|
private readonly IConnectionMultiplexer _redisServerConnection;
|
||||||
private readonly ISubscriber _bus;
|
private readonly ISubscriber _bus;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly RedisOptions _options;
|
private readonly RedisOptions _options;
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@ namespace Microsoft.AspNetCore.SignalR.Redis
|
||||||
{
|
{
|
||||||
public ConfigurationOptions Options { get; set; } = new ConfigurationOptions();
|
public ConfigurationOptions Options { get; set; } = new ConfigurationOptions();
|
||||||
|
|
||||||
public Func<TextWriter, ConnectionMultiplexer> Factory { get; set; }
|
public Func<TextWriter, IConnectionMultiplexer> Factory { get; set; }
|
||||||
|
|
||||||
// TODO: Async
|
// TODO: Async
|
||||||
internal ConnectionMultiplexer Connect(TextWriter log)
|
internal IConnectionMultiplexer Connect(TextWriter log)
|
||||||
{
|
{
|
||||||
if (Factory == null)
|
if (Factory == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||||
|
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netcoreapp2.0</TargetFrameworks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\Common\TestClient.cs" Link="TestClient.cs" />
|
||||||
|
<Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.SignalR.Redis\Microsoft.AspNetCore.SignalR.Redis.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Sockets\Microsoft.AspNetCore.Sockets.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,352 @@
|
||||||
|
// 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.Threading.Tasks;
|
||||||
|
using System.Threading.Tasks.Channels;
|
||||||
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||||
|
using Microsoft.AspNetCore.SignalR.Tests;
|
||||||
|
using Microsoft.AspNetCore.SignalR.Tests.Common;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SignalR.Redis.Tests
|
||||||
|
{
|
||||||
|
public class RedisHubLifetimeManagerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeAllAsyncWritesToAllConnectionsOutput()
|
||||||
|
{
|
||||||
|
using (var client1 = new TestClient())
|
||||||
|
using (var client2 = new TestClient())
|
||||||
|
{
|
||||||
|
var output1 = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
var output2 = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
|
||||||
|
var manager = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
var connection1 = new HubConnectionContext(output1, client1.Connection);
|
||||||
|
var connection2 = new HubConnectionContext(output2, client2.Connection);
|
||||||
|
|
||||||
|
await manager.OnConnectedAsync(connection1).OrTimeout();
|
||||||
|
await manager.OnConnectedAsync(connection2).OrTimeout();
|
||||||
|
|
||||||
|
await manager.InvokeAllAsync("Hello", new object[] { "World" }).OrTimeout();
|
||||||
|
|
||||||
|
AssertMessage(output1);
|
||||||
|
AssertMessage(output2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeAllAsyncDoesNotWriteToDisconnectedConnectionsOutput()
|
||||||
|
{
|
||||||
|
using (var client1 = new TestClient())
|
||||||
|
using (var client2 = new TestClient())
|
||||||
|
{
|
||||||
|
var output1 = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
var output2 = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
|
||||||
|
var manager = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
var connection1 = new HubConnectionContext(output1, client1.Connection);
|
||||||
|
var connection2 = new HubConnectionContext(output2, client2.Connection);
|
||||||
|
|
||||||
|
await manager.OnConnectedAsync(connection1).OrTimeout();
|
||||||
|
await manager.OnConnectedAsync(connection2).OrTimeout();
|
||||||
|
|
||||||
|
await manager.OnDisconnectedAsync(connection2).OrTimeout();
|
||||||
|
|
||||||
|
await manager.InvokeAllAsync("Hello", new object[] { "World" }).OrTimeout();
|
||||||
|
|
||||||
|
AssertMessage(output1);
|
||||||
|
|
||||||
|
Assert.False(output2.In.TryRead(out var item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeGroupAsyncWritesToAllConnectionsInGroupOutput()
|
||||||
|
{
|
||||||
|
using (var client1 = new TestClient())
|
||||||
|
using (var client2 = new TestClient())
|
||||||
|
{
|
||||||
|
var output1 = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
var output2 = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
|
||||||
|
var manager = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
var connection1 = new HubConnectionContext(output1, client1.Connection);
|
||||||
|
var connection2 = new HubConnectionContext(output2, client2.Connection);
|
||||||
|
|
||||||
|
await manager.OnConnectedAsync(connection1).OrTimeout();
|
||||||
|
await manager.OnConnectedAsync(connection2).OrTimeout();
|
||||||
|
|
||||||
|
await manager.AddGroupAsync(connection1.ConnectionId, "gunit").OrTimeout();
|
||||||
|
|
||||||
|
await manager.InvokeGroupAsync("gunit", "Hello", new object[] { "World" }).OrTimeout();
|
||||||
|
|
||||||
|
AssertMessage(output1);
|
||||||
|
|
||||||
|
Assert.False(output2.In.TryRead(out var item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeConnectionAsyncWritesToConnectionOutput()
|
||||||
|
{
|
||||||
|
using (var client = new TestClient())
|
||||||
|
{
|
||||||
|
var output = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
var manager = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
var connection = new HubConnectionContext(output, client.Connection);
|
||||||
|
|
||||||
|
await manager.OnConnectedAsync(connection).OrTimeout();
|
||||||
|
|
||||||
|
await manager.InvokeConnectionAsync(connection.ConnectionId, "Hello", new object[] { "World" }).OrTimeout();
|
||||||
|
|
||||||
|
AssertMessage(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeConnectionAsyncOnNonExistentConnectionDoesNotThrow()
|
||||||
|
{
|
||||||
|
var manager = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
await manager.InvokeConnectionAsync("NotARealConnectionId", "Hello", new object[] { "World" }).OrTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeAllAsyncWithMultipleServersWritesToAllConnectionsOutput()
|
||||||
|
{
|
||||||
|
var manager1 = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
var manager2 = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
|
||||||
|
using (var client1 = new TestClient())
|
||||||
|
using (var client2 = new TestClient())
|
||||||
|
{
|
||||||
|
var output1 = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
var output2 = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
|
||||||
|
var connection1 = new HubConnectionContext(output1, client1.Connection);
|
||||||
|
var connection2 = new HubConnectionContext(output2, client2.Connection);
|
||||||
|
|
||||||
|
await manager1.OnConnectedAsync(connection1).OrTimeout();
|
||||||
|
await manager2.OnConnectedAsync(connection2).OrTimeout();
|
||||||
|
|
||||||
|
await manager1.InvokeAllAsync("Hello", new object[] { "World" }).OrTimeout();
|
||||||
|
|
||||||
|
AssertMessage(output1);
|
||||||
|
AssertMessage(output2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeAllAsyncWithMultipleServersDoesNotWriteToDisconnectedConnectionsOutput()
|
||||||
|
{
|
||||||
|
var manager1 = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
var manager2 = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
|
||||||
|
using (var client1 = new TestClient())
|
||||||
|
using (var client2 = new TestClient())
|
||||||
|
{
|
||||||
|
var output1 = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
var output2 = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
|
||||||
|
var connection1 = new HubConnectionContext(output1, client1.Connection);
|
||||||
|
var connection2 = new HubConnectionContext(output2, client2.Connection);
|
||||||
|
|
||||||
|
await manager1.OnConnectedAsync(connection1).OrTimeout();
|
||||||
|
await manager2.OnConnectedAsync(connection2).OrTimeout();
|
||||||
|
|
||||||
|
await manager2.OnDisconnectedAsync(connection2).OrTimeout();
|
||||||
|
|
||||||
|
await manager2.InvokeAllAsync("Hello", new object[] { "World" }).OrTimeout();
|
||||||
|
|
||||||
|
AssertMessage(output1);
|
||||||
|
|
||||||
|
Assert.False(output2.In.TryRead(out var item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeConnectionAsyncOnServerWithoutConnectionWritesOutputToConnection()
|
||||||
|
{
|
||||||
|
var manager1 = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
var manager2 = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
|
||||||
|
using (var client = new TestClient())
|
||||||
|
{
|
||||||
|
var output = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
|
||||||
|
var connection = new HubConnectionContext(output, client.Connection);
|
||||||
|
|
||||||
|
await manager1.OnConnectedAsync(connection).OrTimeout();
|
||||||
|
|
||||||
|
await manager2.InvokeConnectionAsync(connection.ConnectionId, "Hello", new object[] { "World" }).OrTimeout();
|
||||||
|
|
||||||
|
AssertMessage(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvokeGroupAsyncOnServerWithoutConnectionWritesOutputToGroupConnection()
|
||||||
|
{
|
||||||
|
var manager1 = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
var manager2 = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
|
||||||
|
using (var client = new TestClient())
|
||||||
|
{
|
||||||
|
var output = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
|
||||||
|
var connection = new HubConnectionContext(output, client.Connection);
|
||||||
|
|
||||||
|
await manager1.OnConnectedAsync(connection).OrTimeout();
|
||||||
|
|
||||||
|
await manager1.AddGroupAsync(connection.ConnectionId, "name").OrTimeout();
|
||||||
|
|
||||||
|
await manager2.InvokeGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
|
||||||
|
|
||||||
|
AssertMessage(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DisconnectConnectionRemovesConnectionFromGroup()
|
||||||
|
{
|
||||||
|
var manager = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
|
||||||
|
using (var client = new TestClient())
|
||||||
|
{
|
||||||
|
var output = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
|
||||||
|
var connection = new HubConnectionContext(output, client.Connection);
|
||||||
|
|
||||||
|
await manager.OnConnectedAsync(connection).OrTimeout();
|
||||||
|
|
||||||
|
await manager.AddGroupAsync(connection.ConnectionId, "name").OrTimeout();
|
||||||
|
|
||||||
|
await manager.OnDisconnectedAsync(connection).OrTimeout();
|
||||||
|
|
||||||
|
await manager.InvokeGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
|
||||||
|
|
||||||
|
Assert.False(output.In.TryRead(out var item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RemoveGroupFromLocalConnectionNotInGroupDoesNothing()
|
||||||
|
{
|
||||||
|
var manager = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
|
||||||
|
using (var client = new TestClient())
|
||||||
|
{
|
||||||
|
var output = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
|
||||||
|
var connection = new HubConnectionContext(output, client.Connection);
|
||||||
|
|
||||||
|
await manager.OnConnectedAsync(connection).OrTimeout();
|
||||||
|
|
||||||
|
await manager.RemoveGroupAsync(connection.ConnectionId, "name").OrTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RemoveGroupFromConnectionOnDifferentServerNotInGroupDoesNothing()
|
||||||
|
{
|
||||||
|
var manager1 = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
var manager2 = new RedisHubLifetimeManager<MyHub>(new LoggerFactory().CreateLogger<RedisHubLifetimeManager<MyHub>>(),
|
||||||
|
Options.Create(new RedisOptions()
|
||||||
|
{
|
||||||
|
Factory = t => new TestConnectionMultiplexer()
|
||||||
|
}));
|
||||||
|
|
||||||
|
using (var client = new TestClient())
|
||||||
|
{
|
||||||
|
var output = Channel.CreateUnbounded<HubMessage>();
|
||||||
|
|
||||||
|
var connection = new HubConnectionContext(output, client.Connection);
|
||||||
|
|
||||||
|
await manager1.OnConnectedAsync(connection).OrTimeout();
|
||||||
|
|
||||||
|
await manager2.RemoveGroupAsync(connection.ConnectionId, "name").OrTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertMessage(Channel<HubMessage> channel)
|
||||||
|
{
|
||||||
|
Assert.True(channel.In.TryRead(out var item));
|
||||||
|
var message = item as InvocationMessage;
|
||||||
|
Assert.NotNull(message);
|
||||||
|
Assert.Equal("Hello", message.Target);
|
||||||
|
Assert.Single(message.Arguments);
|
||||||
|
Assert.Equal("World", (string)message.Arguments[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyHub : Hub
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,329 @@
|
||||||
|
// 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.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SignalR.Redis.Tests
|
||||||
|
{
|
||||||
|
public class TestConnectionMultiplexer : IConnectionMultiplexer
|
||||||
|
{
|
||||||
|
public string ClientName => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public string Configuration => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public int TimeoutMilliseconds => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public long OperationCount => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public bool PreserveAsyncOrder { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||||
|
|
||||||
|
public bool IsConnected => true;
|
||||||
|
|
||||||
|
public bool IncludeDetailInExceptions { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||||
|
public int StormLogThreshold { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||||
|
|
||||||
|
public event EventHandler<RedisErrorEventArgs> ErrorMessage
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<ConnectionFailedEventArgs> ConnectionFailed
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<InternalErrorEventArgs> InternalError
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<ConnectionFailedEventArgs> ConnectionRestored
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<EndPointEventArgs> ConfigurationChanged
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<EndPointEventArgs> ConfigurationChangedBroadcast
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<HashSlotMovedEventArgs> HashSlotMoved
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private ISubscriber _subscriber = new TestSubscriber();
|
||||||
|
|
||||||
|
public void BeginProfiling(object forContext)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close(bool allowCommandsToComplete = true)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task CloseAsync(bool allowCommandsToComplete = true)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Configure(TextWriter log = null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> ConfigureAsync(TextWriter log = null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProfiledCommandEnumerable FinishProfiling(object forContext, bool allowCleanupSweep = true)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerCounters GetCounters()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDatabase GetDatabase(int db = -1, object asyncState = null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EndPoint[] GetEndPoints(bool configuredOnly = false)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IServer GetServer(string host, int port, object asyncState = null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IServer GetServer(string hostAndPort, object asyncState = null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IServer GetServer(IPAddress host, int port)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IServer GetServer(EndPoint endpoint, object asyncState = null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetStatus()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetStatus(TextWriter log)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetStormLog()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISubscriber GetSubscriber(object asyncState = null)
|
||||||
|
{
|
||||||
|
return _subscriber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int HashSlot(RedisKey key)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long PublishReconfigure(CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<long> PublishReconfigureAsync(CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterProfiler(IProfiler profiler)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetStormLog()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Wait(Task task)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Wait<T>(Task<T> task)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitAll(params Task[] tasks)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestSubscriber : ISubscriber
|
||||||
|
{
|
||||||
|
// _globalSubscriptions represents the Redis Server you are connected to.
|
||||||
|
// So when publishing from a TestSubscriber you fake sending through the server by grabbing the callbacks
|
||||||
|
// from the _globalSubscriptions and inoking them inplace.
|
||||||
|
private static ConcurrentDictionary<RedisChannel, List<Action<RedisChannel, RedisValue>>> _globalSubscriptions =
|
||||||
|
new ConcurrentDictionary<RedisChannel, List<Action<RedisChannel, RedisValue>>>();
|
||||||
|
|
||||||
|
private ConcurrentDictionary<RedisChannel, Action<RedisChannel, RedisValue>> _subscriptions =
|
||||||
|
new ConcurrentDictionary<RedisChannel, Action<RedisChannel, RedisValue>>();
|
||||||
|
|
||||||
|
public ConnectionMultiplexer Multiplexer => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public EndPoint IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<EndPoint> IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsConnected(RedisChannel channel = default)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan Ping(CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
if (_globalSubscriptions.TryGetValue(channel, out var handlers))
|
||||||
|
{
|
||||||
|
foreach (var handler in handlers)
|
||||||
|
{
|
||||||
|
handler(channel, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handlers != null ? handlers.Count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return Publish(channel, message, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Subscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
_globalSubscriptions.AddOrUpdate(channel, _ => new List<Action<RedisChannel, RedisValue>> { handler }, (_, list) =>
|
||||||
|
{
|
||||||
|
list.Add(handler);
|
||||||
|
return list;
|
||||||
|
});
|
||||||
|
_subscriptions.AddOrUpdate(channel, handler, (_, __) => handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
Subscribe(channel, handler, flags);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EndPoint SubscribedEndpoint(RedisChannel channel)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryWait(Task task)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unsubscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handler = null, CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
_subscriptions.TryRemove(channel, out var handle);
|
||||||
|
if (_globalSubscriptions.TryGetValue(channel, out var list))
|
||||||
|
{
|
||||||
|
list.Remove(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnsubscribeAll(CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UnsubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handler = null, CommandFlags flags = CommandFlags.None)
|
||||||
|
{
|
||||||
|
Unsubscribe(channel, handler, flags);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Wait(Task task)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Wait<T>(Task<T> task)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitAll(params Task[] tasks)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
<Compile Include="..\Common\ServerFixture.cs" Link="ServerFixture.cs" />
|
<Compile Include="..\Common\ServerFixture.cs" Link="ServerFixture.cs" />
|
||||||
<Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" />
|
<Compile Include="..\Common\TaskExtensions.cs" Link="TaskExtensions.cs" />
|
||||||
<Compile Include="..\Common\TestHelpers.cs" Link="TestHelpers.cs" />
|
<Compile Include="..\Common\TestHelpers.cs" Link="TestHelpers.cs" />
|
||||||
|
<Compile Include="..\Common\TestClient.cs" Link="TestClient.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue