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 handler, object state)> _heartbeatHandlers; diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/DefaultHubLifetimeManagerTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/DefaultHubLifetimeManagerTests.cs index 6fcce1872d..0e00c9a9ab 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/DefaultHubLifetimeManagerTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/DefaultHubLifetimeManagerTests.cs @@ -1,172 +1,17 @@ -using System.Threading.Channels; -using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR.Protocol; +// 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 Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Xunit; +using Microsoft.AspNetCore.SignalR.Specification.Tests; namespace Microsoft.AspNetCore.SignalR.Tests { - public class DefaultHubLifetimeManagerTests + public class DefaultHubLifetimeManagerTests : HubLifetimeManagerTestsBase { - [Fact] - public async Task SendAllAsyncWritesToAllConnectionsOutput() + public override HubLifetimeManager CreateNewHubLifetimeManager() { - using (var client1 = new TestClient()) - using (var client2 = new TestClient()) - { - var manager = new DefaultHubLifetimeManager(new Logger>(NullLoggerFactory.Instance)); - 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 = new DefaultHubLifetimeManager(new Logger>(NullLoggerFactory.Instance)); - 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 = new DefaultHubLifetimeManager(new Logger>(NullLoggerFactory.Instance)); - 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(); - - 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 = new DefaultHubLifetimeManager(new Logger>(NullLoggerFactory.Instance)); - 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(); - - await manager.SendGroupExceptAsync("gunit", "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 = new DefaultHubLifetimeManager(new Logger>(NullLoggerFactory.Instance)); - 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]); - } - } - - [Fact] - public async Task SendConnectionAsyncOnNonExistentConnectionNoops() - { - var manager = new DefaultHubLifetimeManager(new Logger>(NullLoggerFactory.Instance)); - await manager.SendConnectionAsync("NotARealConnectionId", "Hello", new object[] { "World" }).OrTimeout(); - } - - [Fact] - public async Task AddGroupOnNonExistentConnectionNoops() - { - var manager = new DefaultHubLifetimeManager(new Logger>(NullLoggerFactory.Instance)); - await manager.AddToGroupAsync("NotARealConnectionId", "MyGroup").OrTimeout(); - } - - [Fact] - public async Task RemoveGroupOnNonExistentConnectionNoops() - { - var manager = new DefaultHubLifetimeManager(new Logger>(NullLoggerFactory.Instance)); - await manager.RemoveFromGroupAsync("NotARealConnectionId", "MyGroup").OrTimeout(); - } - - private class MyHub : Hub - { - - } - - private class MockChannel: Channel - { - - public MockChannel(ChannelWriter writer = null) - { - Writer = writer; - } + return new DefaultHubLifetimeManager(new Logger>(NullLoggerFactory.Instance)); } } } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj b/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj index e964aa34f1..0796e5430a 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) @@ -24,6 +24,7 @@ +