diff --git a/SignalR.sln b/SignalR.sln
index 1d03870349..f53f22d4ca 100644
--- a/SignalR.sln
+++ b/SignalR.sln
@@ -89,6 +89,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Crankier", "benchmarkapps\C
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{43F352F3-4E2B-4ED7-901B-36E6671251F5}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.SignalR.Specification.Tests", "src\Microsoft.AspNetCore.SignalR.Specification.Tests\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj", "{2B03333F-3ACD-474C-862B-FA97D3BA03B5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -207,6 +209,10 @@ Global
{8D3E3E7D-452B-44F4-86CA-111003EA11ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D3E3E7D-452B-44F4-86CA-111003EA11ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D3E3E7D-452B-44F4-86CA-111003EA11ED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2B03333F-3ACD-474C-862B-FA97D3BA03B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2B03333F-3ACD-474C-862B-FA97D3BA03B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2B03333F-3ACD-474C-862B-FA97D3BA03B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2B03333F-3ACD-474C-862B-FA97D3BA03B5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -240,6 +246,7 @@ Global
{896FA5EE-63A5-4EAC-9F09-346584BB4830} = {DA69F624-5398-4884-87E4-B816698CDE65}
{8C75AC94-C980-4FE1-9F79-6CED3C8665CE} = {43F352F3-4E2B-4ED7-901B-36E6671251F5}
{8D3E3E7D-452B-44F4-86CA-111003EA11ED} = {43F352F3-4E2B-4ED7-901B-36E6671251F5}
+ {2B03333F-3ACD-474C-862B-FA97D3BA03B5} = {DA69F624-5398-4884-87E4-B816698CDE65}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7945A4E4-ACDB-4F6E-95CA-6AC6E7C2CD59}
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 25afcd374d..b484e85b5e 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -11,4 +11,4 @@
-
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj b/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj
index f09e38f0e4..8130586cf1 100644
--- a/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj
+++ b/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj
@@ -23,4 +23,4 @@
-
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.SignalR.Core/HubConnectionContext.cs b/src/Microsoft.AspNetCore.SignalR.Core/HubConnectionContext.cs
index 880e16248a..6a076e3c19 100644
--- a/src/Microsoft.AspNetCore.SignalR.Core/HubConnectionContext.cs
+++ b/src/Microsoft.AspNetCore.SignalR.Core/HubConnectionContext.cs
@@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.SignalR
///
/// Gets the protocol used by this connection.
///
- public virtual IHubProtocol Protocol { get; internal set; }
+ public virtual IHubProtocol Protocol { get; set; }
// Currently used only for streaming methods
internal ConcurrentDictionary ActiveRequestCancellationSources { get; } = new ConcurrentDictionary(StringComparer.Ordinal);
diff --git a/src/Microsoft.AspNetCore.SignalR.Specification.Tests/HubLifetimeManagerTestBase.cs b/src/Microsoft.AspNetCore.SignalR.Specification.Tests/HubLifetimeManagerTestBase.cs
new file mode 100644
index 0000000000..1313911d09
--- /dev/null
+++ b/src/Microsoft.AspNetCore.SignalR.Specification.Tests/HubLifetimeManagerTestBase.cs
@@ -0,0 +1,142 @@
+// 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 Microsoft.AspNetCore.SignalR.Protocol;
+using Microsoft.AspNetCore.SignalR.Tests;
+using Xunit;
+
+namespace Microsoft.AspNetCore.SignalR.Specification.Tests
+{
+ public abstract class HubLifetimeManagerTestsBase where THub : Hub
+ {
+ public HubLifetimeManager Manager { get; set; }
+
+ public abstract HubLifetimeManager CreateNewHubLifetimeManager();
+
+ [Fact]
+ public async Task SendAllAsyncWritesToAllConnectionsOutput()
+ {
+ using (var client1 = new TestClient())
+ using (var client2 = new TestClient())
+ {
+ var manager = CreateNewHubLifetimeManager();
+ var connection1 = HubConnectionContextUtils.Create(client1.Connection);
+ var connection2 = HubConnectionContextUtils.Create(client2.Connection);
+
+ await manager.OnConnectedAsync(connection1).OrTimeout();
+ await manager.OnConnectedAsync(connection2).OrTimeout();
+
+ await manager.SendAllAsync("Hello", new object[] { "World" }).OrTimeout();
+
+ var message = Assert.IsType(client1.TryRead());
+ Assert.Equal("Hello", message.Target);
+ Assert.Single(message.Arguments);
+ Assert.Equal("World", (string)message.Arguments[0]);
+
+ message = Assert.IsType(client2.TryRead());
+ Assert.Equal("Hello", message.Target);
+ Assert.Single(message.Arguments);
+ Assert.Equal("World", (string)message.Arguments[0]);
+ }
+ }
+
+ [Fact]
+ public async Task SendAllAsyncDoesNotWriteToDisconnectedConnectionsOutput()
+ {
+ using (var client1 = new TestClient())
+ using (var client2 = new TestClient())
+ {
+ var manager = CreateNewHubLifetimeManager();
+ var connection1 = HubConnectionContextUtils.Create(client1.Connection);
+ var connection2 = HubConnectionContextUtils.Create(client2.Connection);
+
+ await manager.OnConnectedAsync(connection1).OrTimeout();
+ await manager.OnConnectedAsync(connection2).OrTimeout();
+
+ await manager.OnDisconnectedAsync(connection2).OrTimeout();
+
+ await manager.SendAllAsync("Hello", new object[] { "World" }).OrTimeout();
+
+ var message = Assert.IsType(client1.TryRead());
+ Assert.Equal("Hello", message.Target);
+ Assert.Single(message.Arguments);
+ Assert.Equal("World", (string)message.Arguments[0]);
+
+ Assert.Null(client2.TryRead());
+ }
+ }
+
+ [Fact]
+ public async Task SendGroupAsyncWritesToAllConnectionsInGroupOutput()
+ {
+ using (var client1 = new TestClient())
+ using (var client2 = new TestClient())
+ {
+ var manager = CreateNewHubLifetimeManager();
+ var connection1 = HubConnectionContextUtils.Create(client1.Connection);
+ var connection2 = HubConnectionContextUtils.Create(client2.Connection);
+
+ await manager.OnConnectedAsync(connection1).OrTimeout();
+ await manager.OnConnectedAsync(connection2).OrTimeout();
+
+ await manager.AddToGroupAsync(connection1.ConnectionId, "group").OrTimeout();
+
+ await manager.SendGroupAsync("group", "Hello", new object[] { "World" }).OrTimeout();
+
+ var message = Assert.IsType(client1.TryRead());
+ Assert.Equal("Hello", message.Target);
+ Assert.Single(message.Arguments);
+ Assert.Equal("World", (string)message.Arguments[0]);
+
+ Assert.Null(client2.TryRead());
+ }
+ }
+
+ [Fact]
+ public async Task SendGroupExceptAsyncDoesNotWriteToExcludedConnections()
+ {
+ using (var client1 = new TestClient())
+ using (var client2 = new TestClient())
+ {
+ var manager = CreateNewHubLifetimeManager();
+ var connection1 = HubConnectionContextUtils.Create(client1.Connection);
+ var connection2 = HubConnectionContextUtils.Create(client2.Connection);
+
+ await manager.OnConnectedAsync(connection1).OrTimeout();
+ await manager.OnConnectedAsync(connection2).OrTimeout();
+
+ await manager.AddToGroupAsync(connection1.ConnectionId, "group1").OrTimeout();
+ await manager.AddToGroupAsync(connection2.ConnectionId, "group1").OrTimeout();
+
+ await manager.SendGroupExceptAsync("group1", "Hello", new object[] { "World" }, new[] { connection2.ConnectionId }).OrTimeout();
+
+ var message = Assert.IsType(client1.TryRead());
+ Assert.Equal("Hello", message.Target);
+ Assert.Single(message.Arguments);
+ Assert.Equal("World", (string)message.Arguments[0]);
+
+ Assert.Null(client2.TryRead());
+ }
+ }
+
+ [Fact]
+ public async Task SendConnectionAsyncWritesToConnectionOutput()
+ {
+ using (var client = new TestClient())
+ {
+ var manager = CreateNewHubLifetimeManager();
+ var connection = HubConnectionContextUtils.Create(client.Connection);
+
+ await manager.OnConnectedAsync(connection).OrTimeout();
+
+ await manager.SendConnectionAsync(connection.ConnectionId, "Hello", new object[] { "World" }).OrTimeout();
+
+ var message = Assert.IsType(client.TryRead());
+ Assert.Equal("Hello", message.Target);
+ Assert.Single(message.Arguments);
+ Assert.Equal("World", (string)message.Arguments[0]);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.SignalR.Specification.Tests/Microsoft.AspNetCore.SignalR.Specification.Tests.csproj b/src/Microsoft.AspNetCore.SignalR.Specification.Tests/Microsoft.AspNetCore.SignalR.Specification.Tests.csproj
new file mode 100644
index 0000000000..d17ca08264
--- /dev/null
+++ b/src/Microsoft.AspNetCore.SignalR.Specification.Tests/Microsoft.AspNetCore.SignalR.Specification.Tests.csproj
@@ -0,0 +1,40 @@
+
+
+
+ Tests for users to verify their own implementations of SignalR types
+ netcoreapp2.2
+ $(DeveloperBuildTestTfms)
+ $(StandardTestTfms);net461
+ win7-x86
+
+
+
+ $(StandardTestTfms)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.AspNetCore.SignalR.Specification.Tests/ScaleoutHubLifetimeManagerTests.cs b/src/Microsoft.AspNetCore.SignalR.Specification.Tests/ScaleoutHubLifetimeManagerTests.cs
new file mode 100644
index 0000000000..a8fbb1fc45
--- /dev/null
+++ b/src/Microsoft.AspNetCore.SignalR.Specification.Tests/ScaleoutHubLifetimeManagerTests.cs
@@ -0,0 +1,399 @@
+// 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.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.SignalR.Protocol;
+using Microsoft.AspNetCore.SignalR.Tests;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.SignalR.Specification.Tests
+{
+ public abstract class ScaleoutHubLifetimeManagerTests : HubLifetimeManagerTestsBase
+ {
+ public abstract TBackplane CreateBackplane();
+ public abstract HubLifetimeManager CreateNewHubLifetimeManager(TBackplane backplane);
+
+ private async Task AssertMessageAsync(TestClient client)
+ {
+ var message = Assert.IsType(await client.ReadAsync().OrTimeout());
+ Assert.Equal("Hello", message.Target);
+ Assert.Single(message.Arguments);
+ Assert.Equal("World", (string)message.Arguments[0]);
+ }
+
+ [Fact]
+ public async Task InvokeAllAsyncWithMultipleServersWritesToAllConnectionsOutput()
+
+ {
+ var backplane = CreateBackplane();
+ var manager1 = CreateNewHubLifetimeManager(backplane);
+ var manager2 = CreateNewHubLifetimeManager(backplane);
+
+ using (var client1 = new TestClient())
+ using (var client2 = new TestClient())
+ {
+ var connection1 = HubConnectionContextUtils.Create(client1.Connection);
+ var connection2 = HubConnectionContextUtils.Create(client2.Connection);
+
+ await manager1.OnConnectedAsync(connection1).OrTimeout();
+ await manager2.OnConnectedAsync(connection2).OrTimeout();
+
+ await manager1.SendAllAsync("Hello", new object[] { "World" }).OrTimeout();
+
+ await AssertMessageAsync(client1);
+ await AssertMessageAsync(client2);
+ }
+ }
+
+ [Fact]
+ public async Task InvokeAllAsyncWithMultipleServersDoesNotWriteToDisconnectedConnectionsOutput()
+ {
+ var backplane = CreateBackplane();
+ var manager1 = CreateNewHubLifetimeManager(backplane);
+ var manager2 = CreateNewHubLifetimeManager(backplane);
+
+ using (var client1 = new TestClient())
+ using (var client2 = new TestClient())
+ {
+ var connection1 = HubConnectionContextUtils.Create(client1.Connection);
+ var connection2 = HubConnectionContextUtils.Create(client2.Connection);
+
+ await manager1.OnConnectedAsync(connection1).OrTimeout();
+ await manager2.OnConnectedAsync(connection2).OrTimeout();
+
+ await manager2.OnDisconnectedAsync(connection2).OrTimeout();
+
+ await manager2.SendAllAsync("Hello", new object[] { "World" }).OrTimeout();
+
+ await AssertMessageAsync(client1);
+
+ Assert.Null(client2.TryRead());
+ }
+ }
+
+ [Fact]
+ public async Task InvokeConnectionAsyncOnServerWithoutConnectionWritesOutputToConnection()
+ {
+ var backplane = CreateBackplane();
+
+ var manager1 = CreateNewHubLifetimeManager(backplane);
+ var manager2 = CreateNewHubLifetimeManager(backplane);
+
+ using (var client = new TestClient())
+ {
+ var connection = HubConnectionContextUtils.Create(client.Connection);
+
+ await manager1.OnConnectedAsync(connection).OrTimeout();
+
+ await manager2.SendConnectionAsync(connection.ConnectionId, "Hello", new object[] { "World" }).OrTimeout();
+
+ await AssertMessageAsync(client);
+ }
+ }
+
+ [Fact]
+ public async Task InvokeGroupAsyncOnServerWithoutConnectionWritesOutputToGroupConnection()
+ {
+ var backplane = CreateBackplane();
+
+ var manager1 = CreateNewHubLifetimeManager(backplane);
+ var manager2 = CreateNewHubLifetimeManager(backplane);
+
+ using (var client = new TestClient())
+ {
+ var connection = HubConnectionContextUtils.Create(client.Connection);
+
+ await manager1.OnConnectedAsync(connection).OrTimeout();
+
+ await manager1.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
+
+ await manager2.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
+
+ await AssertMessageAsync(client);
+ }
+ }
+
+ [Fact]
+ public async Task DisconnectConnectionRemovesConnectionFromGroup()
+ {
+ var backplane = CreateBackplane();
+ var manager = CreateNewHubLifetimeManager(backplane);
+
+ using (var client = new TestClient())
+ {
+ var connection = HubConnectionContextUtils.Create(client.Connection);
+
+ await manager.OnConnectedAsync(connection).OrTimeout();
+
+ await manager.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
+
+ await manager.OnDisconnectedAsync(connection).OrTimeout();
+
+ await manager.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
+
+ Assert.Null(client.TryRead());
+ }
+ }
+
+ [Fact]
+ public async Task RemoveGroupFromLocalConnectionNotInGroupDoesNothing()
+ {
+ var backplane = CreateBackplane();
+ var manager = CreateNewHubLifetimeManager(backplane);
+
+ using (var client = new TestClient())
+ {
+ var connection = HubConnectionContextUtils.Create(client.Connection);
+
+ await manager.OnConnectedAsync(connection).OrTimeout();
+
+ await manager.RemoveFromGroupAsync(connection.ConnectionId, "name").OrTimeout();
+ }
+ }
+
+ [Fact]
+ public async Task RemoveGroupFromConnectionOnDifferentServerNotInGroupDoesNothing()
+ {
+ var backplane = CreateBackplane();
+ var manager1 = CreateNewHubLifetimeManager(backplane);
+ var manager2 = CreateNewHubLifetimeManager(backplane);
+
+ using (var client = new TestClient())
+ {
+ var connection = HubConnectionContextUtils.Create(client.Connection);
+
+ await manager1.OnConnectedAsync(connection).OrTimeout();
+
+ await manager2.RemoveFromGroupAsync(connection.ConnectionId, "name").OrTimeout();
+ }
+ }
+
+ [Fact]
+ public async Task AddGroupAsyncForConnectionOnDifferentServerWorks()
+ {
+ var backplane = CreateBackplane();
+ var manager1 = CreateNewHubLifetimeManager(backplane);
+ var manager2 = CreateNewHubLifetimeManager(backplane);
+
+ using (var client = new TestClient())
+ {
+ var connection = HubConnectionContextUtils.Create(client.Connection);
+
+ await manager1.OnConnectedAsync(connection).OrTimeout();
+
+ await manager2.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
+
+ await manager2.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
+
+ await AssertMessageAsync(client);
+ }
+ }
+
+ [Fact]
+ public async Task AddGroupAsyncForLocalConnectionAlreadyInGroupDoesNothing()
+ {
+ var backplane = CreateBackplane();
+ var manager = CreateNewHubLifetimeManager(backplane);
+
+ using (var client = new TestClient())
+ {
+ var connection = HubConnectionContextUtils.Create(client.Connection);
+
+ await manager.OnConnectedAsync(connection).OrTimeout();
+
+ await manager.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
+ await manager.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
+
+ await manager.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
+
+ await AssertMessageAsync(client);
+ Assert.Null(client.TryRead());
+ }
+ }
+
+ [Fact]
+ public async Task AddGroupAsyncForConnectionOnDifferentServerAlreadyInGroupDoesNothing()
+ {
+ var backplane = CreateBackplane();
+ var manager1 = CreateNewHubLifetimeManager(backplane);
+ var manager2 = CreateNewHubLifetimeManager(backplane);
+
+ using (var client = new TestClient())
+ {
+ var connection = HubConnectionContextUtils.Create(client.Connection);
+
+ await manager1.OnConnectedAsync(connection).OrTimeout();
+
+ await manager1.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
+ await manager2.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
+
+ await manager2.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
+
+ await AssertMessageAsync(client);
+ Assert.Null(client.TryRead());
+ }
+ }
+
+ [Fact]
+ public async Task RemoveGroupAsyncForConnectionOnDifferentServerWorks()
+ {
+ var backplane = CreateBackplane();
+ var manager1 = CreateNewHubLifetimeManager(backplane);
+ var manager2 = CreateNewHubLifetimeManager(backplane);
+
+ using (var client = new TestClient())
+ {
+ var connection = HubConnectionContextUtils.Create(client.Connection);
+
+ await manager1.OnConnectedAsync(connection).OrTimeout();
+
+ await manager1.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
+
+ await manager2.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
+
+ await AssertMessageAsync(client);
+
+ await manager2.RemoveFromGroupAsync(connection.ConnectionId, "name").OrTimeout();
+
+ await manager2.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
+
+ Assert.Null(client.TryRead());
+ }
+ }
+
+ [Fact]
+ public async Task InvokeConnectionAsyncForLocalConnectionDoesNotPublishToBackplane()
+ {
+ var backplane = CreateBackplane();
+ var manager1 = CreateNewHubLifetimeManager(backplane);
+ var manager2 = CreateNewHubLifetimeManager(backplane);
+
+ using (var client = new TestClient())
+ {
+ var connection = HubConnectionContextUtils.Create(client.Connection);
+
+ // Add connection to both "servers" to see if connection receives message twice
+ await manager1.OnConnectedAsync(connection).OrTimeout();
+ await manager2.OnConnectedAsync(connection).OrTimeout();
+
+ await manager1.SendConnectionAsync(connection.ConnectionId, "Hello", new object[] { "World" }).OrTimeout();
+
+ await AssertMessageAsync(client);
+ Assert.Null(client.TryRead());
+ }
+ }
+
+ [Fact]
+ public async Task WritingToRemoteConnectionThatFailsDoesNotThrow()
+ {
+ var backplane = CreateBackplane();
+ var manager1 = CreateNewHubLifetimeManager(backplane);
+ var manager2 = CreateNewHubLifetimeManager(backplane);
+
+ using (var client = new TestClient())
+ {
+ // Force an exception when writing to connection
+ var connectionMock = HubConnectionContextUtils.CreateMock(client.Connection);
+ connectionMock.Setup(m => m.WriteAsync(It.IsAny(), It.IsAny())).Throws(new Exception());
+ var connection = connectionMock.Object;
+
+ await manager2.OnConnectedAsync(connection).OrTimeout();
+
+ // This doesn't throw because there is no connection.ConnectionId on this server so it has to publish to the backplane.
+ // And once that happens there is no way to know if the invocation was successful or not.
+ await manager1.SendConnectionAsync(connection.ConnectionId, "Hello", new object[] { "World" }).OrTimeout();
+ }
+ }
+
+ [Fact]
+ public async Task WritingToGroupWithOneConnectionFailingSecondConnectionStillReceivesMessage()
+ {
+ var backplane = CreateBackplane();
+ var manager = CreateNewHubLifetimeManager(backplane);
+
+ using (var client1 = new TestClient())
+ using (var client2 = new TestClient())
+ {
+ // Force an exception when writing to connection
+ var connectionMock = HubConnectionContextUtils.CreateMock(client1.Connection);
+ connectionMock.Setup(m => m.WriteAsync(It.IsAny(), It.IsAny())).Throws(new Exception());
+
+ var connection1 = connectionMock.Object;
+ var connection2 = HubConnectionContextUtils.Create(client2.Connection);
+
+ await manager.OnConnectedAsync(connection1).OrTimeout();
+ await manager.AddToGroupAsync(connection1.ConnectionId, "group");
+ await manager.OnConnectedAsync(connection2).OrTimeout();
+ await manager.AddToGroupAsync(connection2.ConnectionId, "group");
+
+ await manager.SendGroupAsync("group", "Hello", new object[] { "World" }).OrTimeout();
+ // connection1 will throw when receiving a group message, we are making sure other connections
+ // are not affected by another connection throwing
+ await AssertMessageAsync(client2);
+
+ // Repeat to check that group can still be sent to
+ await manager.SendGroupAsync("group", "Hello", new object[] { "World" }).OrTimeout();
+ await AssertMessageAsync(client2);
+ }
+ }
+
+ [Fact]
+ public async Task InvokeUserSendsToAllConnectionsForUser()
+ {
+ var backplane = CreateBackplane();
+ var manager = CreateNewHubLifetimeManager(backplane);
+
+ using (var client1 = new TestClient())
+ using (var client2 = new TestClient())
+ using (var client3 = new TestClient())
+ {
+ var connection1 = HubConnectionContextUtils.Create(client1.Connection, userIdentifier: "userA");
+ var connection2 = HubConnectionContextUtils.Create(client2.Connection, userIdentifier: "userA");
+ var connection3 = HubConnectionContextUtils.Create(client3.Connection, userIdentifier: "userB");
+
+ await manager.OnConnectedAsync(connection1).OrTimeout();
+ await manager.OnConnectedAsync(connection2).OrTimeout();
+ await manager.OnConnectedAsync(connection3).OrTimeout();
+
+ await manager.SendUserAsync("userA", "Hello", new object[] { "World" }).OrTimeout();
+ await AssertMessageAsync(client1);
+ await AssertMessageAsync(client2);
+ }
+ }
+
+ [Fact]
+ public async Task StillSubscribedToUserAfterOneOfMultipleConnectionsAssociatedWithUserDisconnects()
+ {
+ var backplane = CreateBackplane();
+ var manager = CreateNewHubLifetimeManager(backplane);
+
+ using (var client1 = new TestClient())
+ using (var client2 = new TestClient())
+ using (var client3 = new TestClient())
+ {
+ var connection1 = HubConnectionContextUtils.Create(client1.Connection, userIdentifier: "userA");
+ var connection2 = HubConnectionContextUtils.Create(client2.Connection, userIdentifier: "userA");
+ var connection3 = HubConnectionContextUtils.Create(client3.Connection, userIdentifier: "userB");
+
+ await manager.OnConnectedAsync(connection1).OrTimeout();
+ await manager.OnConnectedAsync(connection2).OrTimeout();
+ await manager.OnConnectedAsync(connection3).OrTimeout();
+
+ await manager.SendUserAsync("userA", "Hello", new object[] { "World" }).OrTimeout();
+ await AssertMessageAsync(client1);
+ await AssertMessageAsync(client2);
+
+ // Disconnect one connection for the user
+ await manager.OnDisconnectedAsync(connection1).OrTimeout();
+ await manager.SendUserAsync("userA", "Hello", new object[] { "World" }).OrTimeout();
+ await AssertMessageAsync(client2);
+ }
+ }
+ }
+ public class MyHub : Hub
+ {
+ }
+}
diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj
index 585c330599..39827c10d7 100644
--- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj
+++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj
@@ -20,4 +20,4 @@
-
+
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.SignalR.Redis.Tests/Microsoft.AspNetCore.SignalR.Redis.Tests.csproj b/test/Microsoft.AspNetCore.SignalR.Redis.Tests/Microsoft.AspNetCore.SignalR.Redis.Tests.csproj
index 3b4fd5daa1..24d6cebb93 100644
--- a/test/Microsoft.AspNetCore.SignalR.Redis.Tests/Microsoft.AspNetCore.SignalR.Redis.Tests.csproj
+++ b/test/Microsoft.AspNetCore.SignalR.Redis.Tests/Microsoft.AspNetCore.SignalR.Redis.Tests.csproj
@@ -15,6 +15,7 @@
+
diff --git a/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisHubLifetimeManagerTests.cs b/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisHubLifetimeManagerTests.cs
index 17ebd19c50..39dc6acb66 100644
--- a/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisHubLifetimeManagerTests.cs
+++ b/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisHubLifetimeManagerTests.cs
@@ -1,558 +1,58 @@
// 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.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
-using Moq;
+using Microsoft.AspNetCore.SignalR.Specification.Tests;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Xunit;
namespace Microsoft.AspNetCore.SignalR.Redis.Tests
{
- public class RedisHubLifetimeManagerTests
+ public class RedisHubLifetimeManagerTests : ScaleoutHubLifetimeManagerTests
{
- [Fact]
- public async Task InvokeAllAsyncWritesToAllConnectionsOutput()
+ private TestRedisServer _server;
+
+ public override HubLifetimeManager CreateNewHubLifetimeManager(TestRedisServer backplane)
{
- var server = new TestRedisServer();
-
- using (var client1 = new TestClient())
- using (var client2 = new TestClient())
- {
- var manager = CreateLifetimeManager(server);
- var connection1 = HubConnectionContextUtils.Create(client1.Connection);
- var connection2 = HubConnectionContextUtils.Create(client2.Connection);
-
- await manager.OnConnectedAsync(connection1).OrTimeout();
- await manager.OnConnectedAsync(connection2).OrTimeout();
-
- await manager.SendAllAsync("Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client1);
- await AssertMessageAsync(client2);
- }
+ return CreateLifetimeManager(backplane);
}
- [Fact]
- public async Task InvokeAllExceptAsyncExcludesSpecifiedConnections()
+ public override TestRedisServer CreateBackplane()
{
- var server = new TestRedisServer();
-
- using (var client1 = new TestClient())
- using (var client2 = new TestClient())
- using (var client3 = new TestClient())
- {
- var manager1 = CreateLifetimeManager(server);
- var manager2 = CreateLifetimeManager(server);
- var manager3 = CreateLifetimeManager(server);
-
- var connection1 = HubConnectionContextUtils.Create(client1.Connection);
- var connection2 = HubConnectionContextUtils.Create(client2.Connection);
- var connection3 = HubConnectionContextUtils.Create(client3.Connection);
-
- await manager1.OnConnectedAsync(connection1).OrTimeout();
- await manager2.OnConnectedAsync(connection2).OrTimeout();
- await manager3.OnConnectedAsync(connection3).OrTimeout();
-
- await manager1.SendAllExceptAsync("Hello", new object[] { "World" }, new[] { client3.Connection.ConnectionId }).OrTimeout();
-
- await AssertMessageAsync(client1);
- await AssertMessageAsync(client2);
- Assert.Null(client3.TryRead());
- }
+ return new TestRedisServer();
}
- [Fact]
- public async Task InvokeAllAsyncDoesNotWriteToDisconnectedConnectionsOutput()
+ public override HubLifetimeManager CreateNewHubLifetimeManager()
{
- var server = new TestRedisServer();
+ _server = new TestRedisServer();
- using (var client1 = new TestClient())
- using (var client2 = new TestClient())
- {
- var manager = CreateLifetimeManager(server);
- var connection1 = HubConnectionContextUtils.Create(client1.Connection);
- var connection2 = HubConnectionContextUtils.Create(client2.Connection);
-
- await manager.OnConnectedAsync(connection1).OrTimeout();
- await manager.OnConnectedAsync(connection2).OrTimeout();
-
- await manager.OnDisconnectedAsync(connection2).OrTimeout();
-
- await manager.SendAllAsync("Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client1);
-
- Assert.Null(client2.TryRead());
- }
+ return CreateLifetimeManager(_server);
}
- [Fact]
- public async Task InvokeGroupAsyncWritesToAllConnectionsInGroupOutput()
+ public class TestObject
{
- var server = new TestRedisServer();
-
- using (var client1 = new TestClient())
- using (var client2 = new TestClient())
- {
- var manager = CreateLifetimeManager(server);
- var connection1 = HubConnectionContextUtils.Create(client1.Connection);
- var connection2 = HubConnectionContextUtils.Create(client2.Connection);
-
- await manager.OnConnectedAsync(connection1).OrTimeout();
- await manager.OnConnectedAsync(connection2).OrTimeout();
-
- await manager.AddToGroupAsync(connection1.ConnectionId, "gunit").OrTimeout();
-
- await manager.SendGroupAsync("gunit", "Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client1);
- Assert.Null(client2.TryRead());
- }
+ public string TestProperty { get; set; }
}
- [Fact]
- public async Task InvokeGroupExceptAsyncWritesToAllValidConnectionsInGroupOutput()
+ private RedisHubLifetimeManager CreateLifetimeManager(TestRedisServer server, MessagePackHubProtocolOptions messagePackOptions = null, JsonHubProtocolOptions jsonOptions = null)
{
- var server = new TestRedisServer();
-
- using (var client1 = new TestClient())
- using (var client2 = new TestClient())
- {
- var manager = CreateLifetimeManager(server);
- var connection1 = HubConnectionContextUtils.Create(client1.Connection);
- var connection2 = HubConnectionContextUtils.Create(client2.Connection);
-
- await manager.OnConnectedAsync(connection1).OrTimeout();
- await manager.OnConnectedAsync(connection2).OrTimeout();
-
- await manager.AddToGroupAsync(connection1.ConnectionId, "gunit").OrTimeout();
- await manager.AddToGroupAsync(connection2.ConnectionId, "gunit").OrTimeout();
-
- var excludedConnectionIds = new List { client2.Connection.ConnectionId };
- await manager.SendGroupExceptAsync("gunit", "Hello", new object[] { "World" }, excludedConnectionIds).OrTimeout();
-
- await AssertMessageAsync(client1);
- Assert.Null(client2.TryRead());
- }
- }
-
- [Fact]
- public async Task InvokeConnectionAsyncWritesToConnectionOutput()
- {
- var server = new TestRedisServer();
-
- using (var client = new TestClient())
- {
- var manager = CreateLifetimeManager(server);
- var connection = HubConnectionContextUtils.Create(client.Connection);
-
- await manager.OnConnectedAsync(connection).OrTimeout();
-
- await manager.SendConnectionAsync(connection.ConnectionId, "Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client);
- }
- }
-
- [Fact]
- public async Task InvokeConnectionAsyncOnNonExistentConnectionDoesNotThrow()
- {
- var server = new TestRedisServer();
-
- var manager = CreateLifetimeManager(server);
- await manager.SendConnectionAsync("NotARealConnectionId", "Hello", new object[] { "World" }).OrTimeout();
- }
-
- [Fact]
- public async Task InvokeAllAsyncWithMultipleServersWritesToAllConnectionsOutput()
- {
- var server = new TestRedisServer();
-
- var manager1 = CreateLifetimeManager(server);
- var manager2 = CreateLifetimeManager(server);
-
- using (var client1 = new TestClient())
- using (var client2 = new TestClient())
- {
- var connection1 = HubConnectionContextUtils.Create(client1.Connection);
- var connection2 = HubConnectionContextUtils.Create(client2.Connection);
-
- await manager1.OnConnectedAsync(connection1).OrTimeout();
- await manager2.OnConnectedAsync(connection2).OrTimeout();
-
- await manager1.SendAllAsync("Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client1);
- await AssertMessageAsync(client2);
- }
- }
-
- [Fact]
- public async Task InvokeAllAsyncWithMultipleServersDoesNotWriteToDisconnectedConnectionsOutput()
- {
- var server = new TestRedisServer();
-
- var manager1 = CreateLifetimeManager(server);
- var manager2 = CreateLifetimeManager(server);
-
- using (var client1 = new TestClient())
- using (var client2 = new TestClient())
- {
- var connection1 = HubConnectionContextUtils.Create(client1.Connection);
- var connection2 = HubConnectionContextUtils.Create(client2.Connection);
-
- await manager1.OnConnectedAsync(connection1).OrTimeout();
- await manager2.OnConnectedAsync(connection2).OrTimeout();
-
- await manager2.OnDisconnectedAsync(connection2).OrTimeout();
-
- await manager2.SendAllAsync("Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client1);
-
- Assert.Null(client2.TryRead());
- }
- }
-
- [Fact]
- public async Task InvokeConnectionAsyncOnServerWithoutConnectionWritesOutputToConnection()
- {
- var server = new TestRedisServer();
-
- var manager1 = CreateLifetimeManager(server);
- var manager2 = CreateLifetimeManager(server);
-
- using (var client = new TestClient())
- {
- var connection = HubConnectionContextUtils.Create(client.Connection);
-
- await manager1.OnConnectedAsync(connection).OrTimeout();
-
- await manager2.SendConnectionAsync(connection.ConnectionId, "Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client);
- }
- }
-
- [Fact]
- public async Task InvokeGroupAsyncOnServerWithoutConnectionWritesOutputToGroupConnection()
- {
- var server = new TestRedisServer();
-
- var manager1 = CreateLifetimeManager(server);
- var manager2 = CreateLifetimeManager(server);
-
- using (var client = new TestClient())
- {
- var connection = HubConnectionContextUtils.Create(client.Connection);
-
- await manager1.OnConnectedAsync(connection).OrTimeout();
-
- await manager1.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
-
- await manager2.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client);
- }
- }
-
- [Fact]
- public async Task DisconnectConnectionRemovesConnectionFromGroup()
- {
- var server = new TestRedisServer();
-
- var manager = CreateLifetimeManager(server);
-
- using (var client = new TestClient())
- {
- var connection = HubConnectionContextUtils.Create(client.Connection);
-
- await manager.OnConnectedAsync(connection).OrTimeout();
-
- await manager.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
-
- await manager.OnDisconnectedAsync(connection).OrTimeout();
-
- await manager.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
-
- Assert.Null(client.TryRead());
- }
- }
-
- [Fact]
- public async Task RemoveGroupFromLocalConnectionNotInGroupDoesNothing()
- {
- var server = new TestRedisServer();
-
- var manager = CreateLifetimeManager(server);
-
- using (var client = new TestClient())
- {
- var connection = HubConnectionContextUtils.Create(client.Connection);
-
- await manager.OnConnectedAsync(connection).OrTimeout();
-
- await manager.RemoveFromGroupAsync(connection.ConnectionId, "name").OrTimeout();
- }
- }
-
- [Fact]
- public async Task RemoveGroupFromConnectionOnDifferentServerNotInGroupDoesNothing()
- {
- var server = new TestRedisServer();
-
- var manager1 = CreateLifetimeManager(server);
- var manager2 = CreateLifetimeManager(server);
-
- using (var client = new TestClient())
- {
- var connection = HubConnectionContextUtils.Create(client.Connection);
-
- await manager1.OnConnectedAsync(connection).OrTimeout();
-
- await manager2.RemoveFromGroupAsync(connection.ConnectionId, "name").OrTimeout();
- }
- }
-
- [Fact]
- public async Task AddGroupAsyncForConnectionOnDifferentServerWorks()
- {
- var server = new TestRedisServer();
-
- var manager1 = CreateLifetimeManager(server);
- var manager2 = CreateLifetimeManager(server);
-
- using (var client = new TestClient())
- {
- var connection = HubConnectionContextUtils.Create(client.Connection);
-
- await manager1.OnConnectedAsync(connection).OrTimeout();
-
- await manager2.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
-
- await manager2.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client);
- }
- }
-
- [Fact]
- public async Task AddGroupAsyncForLocalConnectionAlreadyInGroupDoesNothing()
- {
- var server = new TestRedisServer();
-
- var manager = CreateLifetimeManager(server);
-
- using (var client = new TestClient())
- {
- var connection = HubConnectionContextUtils.Create(client.Connection);
-
- await manager.OnConnectedAsync(connection).OrTimeout();
-
- await manager.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
- await manager.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
-
- await manager.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client);
- Assert.Null(client.TryRead());
- }
- }
-
- [Fact]
- public async Task AddGroupAsyncForConnectionOnDifferentServerAlreadyInGroupDoesNothing()
- {
- var server = new TestRedisServer();
-
- var manager1 = CreateLifetimeManager(server);
- var manager2 = CreateLifetimeManager(server);
-
- using (var client = new TestClient())
- {
- var connection = HubConnectionContextUtils.Create(client.Connection);
-
- await manager1.OnConnectedAsync(connection).OrTimeout();
-
- await manager1.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
- await manager2.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
-
- await manager2.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client);
- Assert.Null(client.TryRead());
- }
- }
-
- [Fact]
- public async Task RemoveGroupAsyncForConnectionOnDifferentServerWorks()
- {
- var server = new TestRedisServer();
-
- var manager1 = CreateLifetimeManager(server);
- var manager2 = CreateLifetimeManager(server);
-
- using (var client = new TestClient())
- {
- var connection = HubConnectionContextUtils.Create(client.Connection);
-
- await manager1.OnConnectedAsync(connection).OrTimeout();
-
- await manager1.AddToGroupAsync(connection.ConnectionId, "name").OrTimeout();
-
- await manager2.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client);
-
- await manager2.RemoveFromGroupAsync(connection.ConnectionId, "name").OrTimeout();
-
- await manager2.SendGroupAsync("name", "Hello", new object[] { "World" }).OrTimeout();
-
- Assert.Null(client.TryRead());
- }
- }
-
- [Fact]
- public async Task InvokeConnectionAsyncForLocalConnectionDoesNotPublishToRedis()
- {
- var server = new TestRedisServer();
-
- var manager1 = CreateLifetimeManager(server);
- var manager2 = CreateLifetimeManager(server);
-
- using (var client = new TestClient())
- {
- var connection = HubConnectionContextUtils.Create(client.Connection);
-
- // Add connection to both "servers" to see if connection receives message twice
- await manager1.OnConnectedAsync(connection).OrTimeout();
- await manager2.OnConnectedAsync(connection).OrTimeout();
-
- await manager1.SendConnectionAsync(connection.ConnectionId, "Hello", new object[] { "World" }).OrTimeout();
-
- await AssertMessageAsync(client);
- Assert.Null(client.TryRead());
- }
- }
-
- [Fact]
- public async Task WritingToRemoteConnectionThatFailsDoesNotThrow()
- {
- var server = new TestRedisServer();
-
- var manager1 = CreateLifetimeManager(server);
- var manager2 = CreateLifetimeManager(server);
-
- using (var client = new TestClient())
- {
- // Force an exception when writing to connection
- var connectionMock = HubConnectionContextUtils.CreateMock(client.Connection);
- connectionMock.Setup(m => m.WriteAsync(It.IsAny(), It.IsAny())).Throws(new Exception());
- var connection = connectionMock.Object;
-
- await manager2.OnConnectedAsync(connection).OrTimeout();
-
- // This doesn't throw because there is no connection.ConnectionId on this server so it has to publish to redis.
- // And once that happens there is no way to know if the invocation was successful or not.
- await manager1.SendConnectionAsync(connection.ConnectionId, "Hello", new object[] { "World" }).OrTimeout();
- }
- }
-
- [Fact]
- public async Task WritingToGroupWithOneConnectionFailingSecondConnectionStillReceivesMessage()
- {
- var server = new TestRedisServer();
-
- var manager = CreateLifetimeManager(server);
-
- using (var client1 = new TestClient())
- using (var client2 = new TestClient())
- {
- // Force an exception when writing to connection
- var connectionMock = HubConnectionContextUtils.CreateMock(client1.Connection);
- connectionMock.Setup(m => m.WriteAsync(It.IsAny(), It.IsAny())).Throws(new Exception());
-
- var connection1 = connectionMock.Object;
- var connection2 = HubConnectionContextUtils.Create(client2.Connection);
-
- await manager.OnConnectedAsync(connection1).OrTimeout();
- await manager.AddToGroupAsync(connection1.ConnectionId, "group");
- await manager.OnConnectedAsync(connection2).OrTimeout();
- await manager.AddToGroupAsync(connection2.ConnectionId, "group");
-
- await manager.SendGroupAsync("group", "Hello", new object[] { "World" }).OrTimeout();
- // connection1 will throw when receiving a group message, we are making sure other connections
- // are not affected by another connection throwing
- await AssertMessageAsync(client2);
-
- // Repeat to check that group can still be sent to
- await manager.SendGroupAsync("group", "Hello", new object[] { "World" }).OrTimeout();
- await AssertMessageAsync(client2);
- }
- }
-
- [Fact]
- public async Task InvokeUserSendsToAllConnectionsForUser()
- {
- var server = new TestRedisServer();
-
- var manager = CreateLifetimeManager(server);
-
- using (var client1 = new TestClient())
- using (var client2 = new TestClient())
- using (var client3 = new TestClient())
- {
- var connection1 = HubConnectionContextUtils.Create(client1.Connection, userIdentifier: "userA");
- var connection2 = HubConnectionContextUtils.Create(client2.Connection, userIdentifier: "userA");
- var connection3 = HubConnectionContextUtils.Create(client3.Connection, userIdentifier: "userB");
-
- await manager.OnConnectedAsync(connection1).OrTimeout();
- await manager.OnConnectedAsync(connection2).OrTimeout();
- await manager.OnConnectedAsync(connection3).OrTimeout();
-
- await manager.SendUserAsync("userA", "Hello", new object[] { "World" }).OrTimeout();
- await AssertMessageAsync(client1);
- await AssertMessageAsync(client2);
- }
- }
-
- [Fact]
- public async Task StillSubscribedToUserAfterOneOfMultipleConnectionsAssociatedWithUserDisconnects()
- {
- var server = new TestRedisServer();
-
- var manager = CreateLifetimeManager(server);
-
- using (var client1 = new TestClient())
- using (var client2 = new TestClient())
- using (var client3 = new TestClient())
- {
- var connection1 = HubConnectionContextUtils.Create(client1.Connection, userIdentifier: "userA");
- var connection2 = HubConnectionContextUtils.Create(client2.Connection, userIdentifier: "userA");
- var connection3 = HubConnectionContextUtils.Create(client3.Connection, userIdentifier: "userB");
-
- await manager.OnConnectedAsync(connection1).OrTimeout();
- await manager.OnConnectedAsync(connection2).OrTimeout();
- await manager.OnConnectedAsync(connection3).OrTimeout();
-
- await manager.SendUserAsync("userA", "Hello", new object[] { "World" }).OrTimeout();
- await AssertMessageAsync(client1);
- await AssertMessageAsync(client2);
-
- // Disconnect one connection for the user
- await manager.OnDisconnectedAsync(connection1).OrTimeout();
- await manager.SendUserAsync("userA", "Hello", new object[] { "World" }).OrTimeout();
- await AssertMessageAsync(client2);
- }
+ var options = new RedisOptions() { ConnectionFactory = async (t) => await Task.FromResult(new TestConnectionMultiplexer(server)) };
+ messagePackOptions = messagePackOptions ?? new MessagePackHubProtocolOptions();
+ jsonOptions = jsonOptions ?? new JsonHubProtocolOptions();
+ return new RedisHubLifetimeManager(
+ NullLogger>.Instance,
+ Options.Create(options),
+ new DefaultHubProtocolResolver(new IHubProtocol[]
+ {
+ new JsonHubProtocol(Options.Create(jsonOptions)),
+ new MessagePackHubProtocol(Options.Create(messagePackOptions)),
+ }, NullLogger.Instance));
}
[Fact]
@@ -598,46 +98,5 @@ namespace Microsoft.AspNetCore.SignalR.Redis.Tests
});
}
}
-
- public class TestObject
- {
- public string TestProperty { get; set; }
- }
-
- private RedisHubLifetimeManager CreateLifetimeManager(TestRedisServer server, MessagePackHubProtocolOptions messagePackOptions = null, JsonHubProtocolOptions jsonOptions = null)
- {
- var options = new RedisOptions() { ConnectionFactory = async (t) => await Task.FromResult(new TestConnectionMultiplexer(server)) };
- messagePackOptions = messagePackOptions ?? new MessagePackHubProtocolOptions();
- jsonOptions = jsonOptions ?? new JsonHubProtocolOptions();
-
- return new RedisHubLifetimeManager(
- NullLogger>.Instance,
- Options.Create(options),
- new DefaultHubProtocolResolver(new IHubProtocol[]
- {
- new JsonHubProtocol(Options.Create(jsonOptions)),
- new MessagePackHubProtocol(Options.Create(messagePackOptions)),
- }, NullLogger.Instance));
- }
-
- private async Task AssertMessageAsync(TestClient client)
- {
- var message = Assert.IsType(await client.ReadAsync().OrTimeout());
- Assert.Equal("Hello", message.Target);
- Assert.Single(message.Arguments);
- Assert.Equal("World", (string)message.Arguments[0]);
- }
-
- private class MyHub : Hub
- {
- }
-
- private class MockChannel : Channel
- {
- public MockChannel(ChannelWriter writer = null)
- {
- Writer = writer;
- }
- }
}
}
diff --git a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/HubConnectionContextUtils.cs b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/HubConnectionContextUtils.cs
index 304d9332e1..6cb265fe74 100644
--- a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/HubConnectionContextUtils.cs
+++ b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/HubConnectionContextUtils.cs
@@ -9,7 +9,12 @@ using Moq;
namespace Microsoft.AspNetCore.SignalR.Tests
{
- public static class HubConnectionContextUtils
+#if TESTUTILS
+ public
+#else
+ internal
+#endif
+ static class HubConnectionContextUtils
{
public static HubConnectionContext Create(ConnectionContext connection, IHubProtocol protocol = null, string userIdentifier = null)
{
diff --git a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj
index 761d80b1ad..b6884a9fdc 100644
--- a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj
+++ b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/Microsoft.AspNetCore.SignalR.Tests.Utils.csproj
@@ -1,8 +1,9 @@
-
+
$(StandardTestTfms)
Microsoft.AspNetCore.SignalR.Tests
+ $(DefineConstants);TESTUTILS
diff --git a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TaskExtensions.cs b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TaskExtensions.cs
index 2ff2279245..e64be4fbc7 100644
--- a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TaskExtensions.cs
+++ b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TaskExtensions.cs
@@ -7,7 +7,12 @@ using System.Runtime.CompilerServices;
namespace System.Threading.Tasks
{
- public static class TaskExtensions
+#if TESTUTILS
+ public
+#else
+ internal
+#endif
+ static class TaskExtensions
{
private const int DefaultTimeout = 5000;
diff --git a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs
index 534e83cf2b..371307737f 100644
--- a/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs
+++ b/test/Microsoft.AspNetCore.SignalR.Tests.Utils/TestClient.cs
@@ -14,7 +14,12 @@ using Microsoft.AspNetCore.SignalR.Protocol;
namespace Microsoft.AspNetCore.SignalR.Tests
{
- public class TestClient : ITransferFormatFeature, IConnectionHeartbeatFeature, IDisposable
+#if TESTUTILS
+ public
+#else
+ internal
+#endif
+ class TestClient : ITransferFormatFeature, IConnectionHeartbeatFeature, IDisposable
{
private readonly object _heartbeatLock = new object();
private List<(Action