public async Task SendToGroupFromOutsideOfHub() { using (var host = new MemoryHost()) { IHubContext<IBasicClient> hubContext = null; host.Configure(app => { var configuration = new HubConfiguration { Resolver = new DefaultDependencyResolver() }; app.MapSignalR(configuration); hubContext = configuration.Resolver.Resolve<IConnectionManager>().GetHubContext<SendToSome, IBasicClient>(); }); var connection1 = new HubConnection("http://foo/"); using (connection1) { var wh1 = new AsyncManualResetEvent(initialState: false); var hub1 = connection1.CreateHubProxy("SendToSome"); await connection1.Start(host); hub1.On("send", wh1.Set); hubContext.Groups.Add(connection1.ConnectionId, "Foo").Wait(); hubContext.Clients.Group("Foo").send(); Assert.True(await wh1.WaitAsync(TimeSpan.FromSeconds(10))); } } }
public MemoryTestHost(MemoryHost host, string logPath) { _host = host; _listener = new TextWriterTraceListener(logPath + ".transports.log"); Disposables = new List<IDisposable>(); ExtraData = new Dictionary<string, string>(); }
public void ConnectionsWithTheSameConnectionIdLongPollingCloseGracefully() { using (var host = new MemoryHost()) { host.Configure(app => { var config = new ConnectionConfiguration { Resolver = new DefaultDependencyResolver() }; app.MapSignalR<MyGroupEchoConnection>("/echo", config); config.Resolver.Register(typeof(IProtectedData), () => new EmptyProtectedData()); }); string id = Guid.NewGuid().ToString("d"); var tasks = new List<Task>(); for (int i = 0; i < 1000; i++) { tasks.Add(ProcessRequest(host, "longPolling", id)); } ProcessRequest(host, "longPolling", id); Task.WaitAll(tasks.ToArray()); Assert.True(tasks.All(t => !t.IsFaulted)); } }
public static IDisposable StressGroups(int max = 100) { var host = new MemoryHost(); host.Configure(app => { var config = new HubConfiguration() { Resolver = new DefaultDependencyResolver() }; app.MapSignalR(config); var configuration = config.Resolver.Resolve<IConfigurationManager>(); // The below effectively sets the heartbeat interval to five seconds. configuration.KeepAlive = TimeSpan.FromSeconds(10); }); var countDown = new CountDownRange<int>(Enumerable.Range(0, max)); var connection = new HubConnection("http://foo"); var proxy = connection.CreateHubProxy("HubWithGroups"); proxy.On<int>("Do", i => { if (!countDown.Mark(i)) { Debugger.Break(); } }); try { connection.Start(new Client.Transports.LongPollingTransport(host)).Wait(); proxy.Invoke("Join", "foo").Wait(); for (int i = 0; i < max; i++) { proxy.Invoke("Send", "foo", i).Wait(); } proxy.Invoke("Leave", "foo").Wait(); for (int i = max + 1; i < max + 50; i++) { proxy.Invoke("Send", "foo", i).Wait(); } if (!countDown.Wait(TimeSpan.FromSeconds(10))) { Console.WriteLine("Didn't receive " + max + " messages. Got " + (max - countDown.Count) + " missed " + String.Join(",", countDown.Left.Select(i => i.ToString()))); Debugger.Break(); } } finally { connection.Stop(); } return host; }
public async Task AuthenticatedUserCanReceiveHubMessagesByDefault() { using (var host = new MemoryHost()) { host.Configure(app => { var configuration = new HubConfiguration { Resolver = new DefaultDependencyResolver() }; WithUser(app, new GenericPrincipal(new GenericIdentity("test"), new string[] { })); app.MapSignalR("/signalr", configuration); }); var connection = CreateHubConnection("http://foo/"); using (connection) { var hub = connection.CreateHubProxy("NoAuthHub"); var wh = new ManualResetEvent(false); hub.On<string, string, object>("joined", (id, time, authInfo) => { Assert.NotNull(id); wh.Set(); }); await connection.Start(host); Assert.True(wh.WaitOne(TimeSpan.FromSeconds(3))); } } }
public void AuthenticatedAndAuthorizedUserCanInvokeMethodsInHubsAuthorizedWithRoles() { using (var host = new MemoryHost()) { host.MapHubs(); var connection = new Client.Hubs.HubConnection("http://foo/"); host.User = new GenericPrincipal(new GenericIdentity("test"), new string[] { "Admin" }); var hub = connection.CreateHubProxy("AdminAuthHub"); var wh = new ManualResetEvent(false); hub.On<string, string>("invoked", (id, time) => { Assert.NotNull(id); wh.Set(); }); connection.Start(host).Wait(); hub.InvokeWithTimeout("InvokedFromClient"); Assert.True(wh.WaitOne(TimeSpan.FromSeconds(3))); connection.Stop(); } }
public void AddingToMultipleGroups() { var host = new MemoryHost(); host.MapHubs(); int max = 10; var countDown = new CountDownRange<int>(Enumerable.Range(0, max)); var connection = new Client.Hubs.HubConnection("http://foo"); var proxy = connection.CreateProxy("MultGroupHub"); proxy.On<User>("onRoomJoin", user => { Assert.True(countDown.Mark(user.Index)); }); connection.Start(host).Wait(); for (int i = 0; i < max; i++) { var user = new User { Index = i, Name = "tester", Room = "test" + i }; proxy.Invoke("login", user).Wait(); proxy.Invoke("joinRoom", user).Wait(); } Assert.True(countDown.Wait(TimeSpan.FromSeconds(30)), "Didn't receive " + max + " messages. Got " + (max - countDown.Count) + " missed " + String.Join(",", countDown.Left.Select(i => i.ToString()))); connection.Stop(); }
public static IDisposable ManyUniqueGroups(int concurrency) { var host = new MemoryHost(); var threads = new List<Thread>(); var cancellationTokenSource = new CancellationTokenSource(); host.MapHubs(); for (int i = 0; i < concurrency; i++) { var thread = new Thread(_ => { while (!cancellationTokenSource.IsCancellationRequested) { RunOne(host); } }); threads.Add(thread); thread.Start(); } return new DisposableAction(() => { cancellationTokenSource.Cancel(); threads.ForEach(t => t.Join()); host.Dispose(); }); }
public void AuthenticatedAndAuthorizedUserCanInvokeMethodsInHubsAuthorizedSpecifyingUserAndRole() { using (var host = new MemoryHost()) { host.Configure(app => { var configuration = new HubConfiguration { Resolver = new DefaultDependencyResolver() }; WithUser(app, new GenericPrincipal(new GenericIdentity("User"), new string[] { "Admin" })); app.MapHubs("/signalr", configuration); }); var connection = CreateHubConnection("http://foo/"); var hub = connection.CreateHubProxy("UserAndRoleAuthHub"); var wh = new ManualResetEvent(false); hub.On<string, string>("invoked", (id, time) => { Assert.NotNull(id); wh.Set(); }); connection.Start(host).Wait(); hub.InvokeWithTimeout("InvokedFromClient"); Assert.True(wh.WaitOne(TimeSpan.FromSeconds(3))); connection.Stop(); } }
public void ChangeHubUrl() { using (var host = new MemoryHost()) { host.MapHubs("/foo"); var connection = new Client.Hubs.HubConnection("http://site/foo", useDefaultUrl: false); var hub = connection.CreateHubProxy("demo"); var wh = new ManualResetEventSlim(false); hub.On("signal", id => { Assert.NotNull(id); wh.Set(); }); connection.Start(host).Wait(); hub.Invoke("DynamicTask").Wait(); Assert.True(wh.Wait(TimeSpan.FromSeconds(10))); connection.Stop(); } }
public static IDisposable RunConnectDisconnect(int connections) { var host = new MemoryHost(); host.MapHubs(); for (int i = 0; i < connections; i++) { var connection = new Client.Hubs.HubConnection("http://foo"); var proxy = connection.CreateHubProxy("EchoHub"); var wh = new ManualResetEventSlim(false); proxy.On("echo", _ => wh.Set()); try { connection.Start(host).Wait(); proxy.Invoke("Echo", "foo").Wait(); if (!wh.Wait(TimeSpan.FromSeconds(10))) { Debugger.Break(); } } finally { connection.Stop(); } } return host; }
public async Task SendToUsersFromOutsideOfHub() { using (var host = new MemoryHost()) { IHubContext<IBasicClient> hubContext = HubFacts.InitializeUserByQuerystring(host); var wh1 = new AsyncManualResetEvent(); var wh2 = new AsyncManualResetEvent(); var connection1 = HubFacts.GetUserConnection("myuser"); var connection2 = HubFacts.GetUserConnection("myuser2"); using (connection1) using (connection2) { var proxy1 = connection1.CreateHubProxy("SendToSome"); var proxy2 = connection2.CreateHubProxy("SendToSome"); await connection1.Start(host); await connection2.Start(host); proxy1.On("send", wh1.Set); proxy2.On("send", wh2.Set); hubContext.Clients.Users(new List<string> { "myuser", "myuser2" }).send(); Assert.True(await wh1.WaitAsync(TimeSpan.FromSeconds(10))); Assert.True(await wh2.WaitAsync(TimeSpan.FromSeconds(10))); } } }
protected ITestHost CreateHost(HostType hostType, TransportType transportType) { ITestHost host = null; switch (hostType) { case HostType.IISExpress: host = new IISExpressTestHost(); host.TransportFactory = () => CreateTransport(transportType); host.Transport = host.TransportFactory(); break; case HostType.Memory: var mh = new MemoryHost(); host = new MemoryTestHost(mh); host.TransportFactory = () => CreateTransport(transportType, mh); host.Transport = host.TransportFactory(); break; case HostType.Owin: host = new OwinTestHost(); host.TransportFactory = () => CreateTransport(transportType); host.Transport = host.TransportFactory(); break; default: break; } return host; }
public void DisconnectFiresForHubsWhenConnectionGoesAway() { using (var host = new MemoryHost()) { host.MapHubs(); host.Configuration.DisconnectTimeout = TimeSpan.Zero; host.Configuration.HeartbeatInterval = TimeSpan.FromSeconds(5); var connectWh = new ManualResetEventSlim(); var disconnectWh = new ManualResetEventSlim(); host.DependencyResolver.Register(typeof(MyHub), () => new MyHub(connectWh, disconnectWh)); var connection = new Client.Hubs.HubConnection("http://foo/"); connection.CreateHubProxy("MyHub"); // Maximum wait time for disconnect to fire (3 heart beat intervals) var disconnectWait = TimeSpan.FromTicks(host.Configuration.HeartbeatInterval.Ticks * 3); connection.Start(host).Wait(); Assert.True(connectWh.Wait(TimeSpan.FromSeconds(10)), "Connect never fired"); connection.Stop(); Assert.True(disconnectWh.Wait(disconnectWait), "Disconnect never fired"); } }
public static IDisposable BrodcastFromServer() { var host = new MemoryHost(); IHubContext context = null; host.Configure(app => { var config = new HubConfiguration() { Resolver = new DefaultDependencyResolver() }; app.MapHubs(config); var configuration = config.Resolver.Resolve<IConfigurationManager>(); // The below effectively sets the heartbeat interval to five seconds. configuration.KeepAlive = TimeSpan.FromSeconds(10); var connectionManager = config.Resolver.Resolve<IConnectionManager>(); context = connectionManager.GetHubContext("EchoHub"); }); var cancellationTokenSource = new CancellationTokenSource(); var thread = new Thread(() => { while (!cancellationTokenSource.IsCancellationRequested) { context.Clients.All.echo(); } }); thread.Start(); var connection = new Client.Hubs.HubConnection("http://foo"); var proxy = connection.CreateHubProxy("EchoHub"); try { connection.Start(host).Wait(); Thread.Sleep(1000); } finally { connection.Stop(); } return new DisposableAction(() => { cancellationTokenSource.Cancel(); thread.Join(); host.Dispose(); }); }
public void NoReconnectsAfterFallback() { // There was a regression where the SSE transport would try to reconnect after it times out. // This test ensures that no longer happens. // #2180 using (var host = new MemoryHost()) { var myReconnect = new MyReconnect(); host.Configure(app => { Func<AppFunc, AppFunc> middleware = (next) => { return env => { var request = new OwinRequest(env); var response = new OwinResponse(env); if (!request.Path.Value.Contains("negotiate") && !request.QueryString.Value.Contains("longPolling")) { response.Body = new MemoryStream(); } return next(env); }; }; app.Use(middleware); var config = new ConnectionConfiguration { Resolver = new DefaultDependencyResolver() }; config.Resolver.Register(typeof(MyReconnect), () => myReconnect); app.MapSignalR<MyReconnect>("/echo", config); }); var connection = new Connection("http://foo/echo"); using (connection) { connection.Start(host).Wait(); // Give SSE an opportunity to reconnect Thread.Sleep(TimeSpan.FromSeconds(5)); Assert.Equal(connection.State, ConnectionState.Connected); Assert.Equal(connection.Transport.Name, "longPolling"); Assert.Equal(0, myReconnect.Reconnects); } } }
public static IDisposable Run(int connections, int senders, string payload, string transport) { var host = new MemoryHost(); host.Configure(app => { app.MapConnection<StressConnection>("/echo"); }); var countDown = new CountdownEvent(senders); var cancellationTokenSource = new CancellationTokenSource(); for (int i = 0; i < connections; i++) { if (transport.Equals("longPolling", StringComparison.OrdinalIgnoreCase)) { ThreadPool.QueueUserWorkItem(state => { string connectionId = state.ToString(); LongPollingLoop(host, connectionId); }, i); } else { string connectionId = i.ToString(); ProcessRequest(host, transport, connectionId); } } for (var i = 0; i < senders; i++) { ThreadPool.QueueUserWorkItem(_ => { while (!cancellationTokenSource.IsCancellationRequested) { string connectionId = i.ToString(); ProcessSendRequest(host, transport, connectionId, payload); } countDown.Signal(); }); } return new DisposableAction(() => { cancellationTokenSource.Cancel(); // Wait for all senders to stop countDown.Wait(TimeSpan.FromMilliseconds(1000 * senders)); host.Dispose(); }); }
public static IDisposable StressGroups(int max = 100) { using (var host = new MemoryHost()) { host.HubPipeline.EnableAutoRejoiningGroups(); host.MapHubs(); host.Configuration.HeartBeatInterval = TimeSpan.FromSeconds(5); host.Configuration.KeepAlive = TimeSpan.FromSeconds(5); var countDown = new CountDownRange<int>(Enumerable.Range(0, max)); var connection = new Client.Hubs.HubConnection("http://foo"); var proxy = connection.CreateHubProxy("HubWithGroups"); proxy.On<int>("Do", i => { if (!countDown.Mark(i)) { Debugger.Break(); } }); try { connection.Start(host).Wait(); proxy.Invoke("Join", "foo").Wait(); for (int i = 0; i < max; i++) { proxy.Invoke("Send", "foo", i).Wait(); } proxy.Invoke("Leave", "foo").Wait(); for (int i = max + 1; i < max + 50; i++) { proxy.Invoke("Send", "foo", i).Wait(); } if (!countDown.Wait(TimeSpan.FromSeconds(10))) { Console.WriteLine("Didn't receive " + max + " messages. Got " + (max - countDown.Count) + " missed " + String.Join(",", countDown.Left.Select(i => i.ToString()))); Debugger.Break(); } } finally { connection.Stop(); } } return new DisposableAction(() => { }); }
public static IDisposable ClientGroupsSyncWithServerGroupsOnReconnectLongPolling() { var host = new MemoryHost(); host.Configuration.KeepAlive = null; host.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(5); host.Configuration.HeartbeatInterval = TimeSpan.FromSeconds(2); host.MapConnection<MyRejoinGroupConnection>("/groups"); var connection = new Client.Connection("http://foo/groups"); var inGroupOnReconnect = new List<bool>(); var wh = new ManualResetEventSlim(); connection.Received += message => { Console.WriteLine(message); wh.Set(); }; connection.Reconnected += () => { var inGroup = connection.Groups.Contains(typeof(MyRejoinGroupConnection).FullName + ".test"); if (!inGroup) { Debugger.Break(); } inGroupOnReconnect.Add(inGroup); connection.Send(new { type = 3, group = "test", message = "Reconnected" }).Wait(); }; connection.Start(new Client.Transports.LongPollingTransport(host)).Wait(); // Join the group connection.Send(new { type = 1, group = "test" }).Wait(); Thread.Sleep(TimeSpan.FromSeconds(10)); if (!wh.Wait(TimeSpan.FromSeconds(10))) { Debugger.Break(); } Console.WriteLine(inGroupOnReconnect.Count > 0); Console.WriteLine(String.Join(", ", inGroupOnReconnect.Select(b => b.ToString()))); connection.Stop(); return host; }
public void UnableToCreateHubThrowsError() { using (var host = new MemoryHost()) { host.MapHubs(); var hubConnection = new HubConnection("http://fake"); IHubProxy proxy = hubConnection.CreateHubProxy("MyHub2"); hubConnection.Start(host).Wait(); Assert.Throws<MissingMethodException>(() => proxy.Invoke("Send", "hello").Wait()); } }
public async Task ConnectionCanStartWithAuthenicatedUserAndQueryString() { using (var host = new MemoryHost()) { host.Configure(app => { Func<AppFunc, AppFunc> middleware = (next) => { return env => { if (((string)env["owin.RequestQueryString"]).IndexOf("access_token") == -1) { return next(env); } var user = new CustomPrincipal { Name = "Bob", IsAuthenticated = true, Roles = new[] { "User" } }; env["server.User"] = user; return next(env); }; }; app.Use(middleware); var config = new ConnectionConfiguration { Resolver = new DefaultDependencyResolver() }; app.MapSignalR<MyAuthenticatedConnection>("/authenticatedConnection", config); }); var connection = new Connection("http://foo/authenticatedConnection", "access_token=1234"); using (connection) { await connection.Start(host); Assert.Equal(connection.State, ConnectionState.Connected); } } }
private static void LongPollingLoop(MemoryHost host, string connectionId) { LongPoll: var task = ProcessRequest(host, "longPolling", connectionId); if (task.IsCompleted) { task.Wait(); goto LongPoll; } task.ContinueWith(t => LongPollingLoop(host, connectionId)); }
public void ComplexPersonState() { using (var host = new MemoryHost()) { host.MapHubs(); var connection = new Client.Hubs.HubConnection("http://site/"); var hub = connection.CreateHubProxy("demo"); var wh = new ManualResetEvent(false); connection.Start(host).Wait(); var person = new SignalR.Samples.Hubs.DemoHub.DemoHub.Person { Address = new SignalR.Samples.Hubs.DemoHub.DemoHub.Address { Street = "Redmond", Zip = "98052" }, Age = 25, Name = "David" }; var person1 = hub.Invoke<SignalR.Samples.Hubs.DemoHub.DemoHub.Person>("ComplexType", person).Result; var person2 = hub.GetValue<SignalR.Samples.Hubs.DemoHub.DemoHub.Person>("person"); JObject obj = ((dynamic)hub).person; var person3 = obj.ToObject<SignalR.Samples.Hubs.DemoHub.DemoHub.Person>(); Assert.NotNull(person1); Assert.NotNull(person2); Assert.NotNull(person3); Assert.Equal("David", person1.Name); Assert.Equal("David", person2.Name); Assert.Equal("David", person3.Name); Assert.Equal(25, person1.Age); Assert.Equal(25, person2.Age); Assert.Equal(25, person3.Age); Assert.Equal("Redmond", person1.Address.Street); Assert.Equal("Redmond", person2.Address.Street); Assert.Equal("Redmond", person3.Address.Street); Assert.Equal("98052", person1.Address.Zip); Assert.Equal("98052", person2.Address.Zip); Assert.Equal("98052", person3.Address.Zip); connection.Stop(); } }
public void ThrownWebExceptionShouldBeUnwrapped() { var host = new MemoryHost(); host.MapConnection<MyBadConnection>("/ErrorsAreFun"); var connection = new Client.Connection("http://test/ErrorsAreFun"); // Expecting 404 var aggEx = Assert.Throws<AggregateException>(() => connection.Start(host).Wait()); connection.Stop(); using (var ser = aggEx.GetError()) { Assert.Equal(ser.StatusCode, HttpStatusCode.NotFound); Assert.NotNull(ser.ResponseBody); Assert.NotNull(ser.Exception); } }
public async Task InitMessageSentToFallbackTransports() { using (var host = new MemoryHost()) { host.Configure(app => { Func<AppFunc, AppFunc> middleware = (next) => { return env => { var request = new OwinRequest(env); var response = new OwinResponse(env); if (!request.Path.Value.Contains("negotiate") && !request.QueryString.Value.Contains("longPolling")) { response.Body = new MemoryStream(); } return next(env); }; }; app.Use(middleware); var config = new ConnectionConfiguration { Resolver = new DefaultDependencyResolver() }; app.MapSignalR<MyConnection>("/echo", config); }); var connection = new Connection("http://foo/echo"); using (connection) { await connection.Start(host); Assert.Equal(connection.State, ConnectionState.Connected); Assert.Equal(connection.Transport.Name, "longPolling"); } } }
public void HubNamesAreNotCaseSensitive() { var host = new MemoryHost(); host.MapHubs(); var hubConnection = new HubConnection("http://fake"); IHubProxy proxy = hubConnection.CreateProxy("chatHub"); var wh = new ManualResetEvent(false); proxy.On("addMessage", data => { Assert.Equal("hello", data); wh.Set(); }); hubConnection.Start(host).Wait(); proxy.Invoke("Send", "hello").Wait(); Assert.True(wh.WaitOne(TimeSpan.FromSeconds(5))); }
public async Task SendToUserFromOutsideOfHub() { using (var host = new MemoryHost()) { IHubContext<IBasicClient> hubContext = HubFacts.InitializeUserByQuerystring(host); var wh = new AsyncManualResetEvent(); using (var connection = HubFacts.GetUserConnection("myuser")) { var hub = connection.CreateHubProxy("SendToSome"); await connection.Start(host); hub.On("send", wh.Set); hubContext.Clients.User("myuser").send(); Assert.True(await wh.WaitAsync(TimeSpan.FromSeconds(10))); } } }
public void DisconnectFiresForPersistentConnectionWhenClientGoesAway() { var host = new MemoryHost(); host.MapConnection<MyConnection>("/echo"); host.Configuration.DisconnectTimeout = TimeSpan.Zero; host.Configuration.HeartBeatInterval = TimeSpan.FromSeconds(5); var connectWh = new ManualResetEventSlim(); var disconnectWh = new ManualResetEventSlim(); host.DependencyResolver.Register(typeof(MyConnection), () => new MyConnection(connectWh, disconnectWh)); var connection = new Client.Connection("http://foo/echo"); // Maximum wait time for disconnect to fire (3 heart beat intervals) var disconnectWait = TimeSpan.FromTicks(host.Configuration.HeartBeatInterval.Ticks * 3); connection.Start(host).Wait(); Assert.True(connectWh.Wait(TimeSpan.FromSeconds(10)), "Connect never fired"); connection.Stop(); Assert.True(disconnectWh.Wait(disconnectWait), "Disconnect never fired"); }
public void DisconnectFiresForHubsWhenConnectionGoesAway() { using (var host = new MemoryHost()) { var dr = new DefaultDependencyResolver(); var configuration = dr.Resolve<IConfigurationManager>(); var connectWh = new ManualResetEventSlim(); var disconnectWh = new ManualResetEventSlim(); host.Configure(app => { var config = new HubConfiguration { Resolver = dr }; app.MapHubs("/signalr", config); configuration.DisconnectTimeout = TimeSpan.FromSeconds(6); dr.Register(typeof(MyHub), () => new MyHub(connectWh, disconnectWh)); }); var connection = new Client.Hubs.HubConnection("http://foo/"); connection.CreateHubProxy("MyHub"); // Maximum wait time for disconnect to fire (3 heart beat intervals) var disconnectWait = TimeSpan.FromTicks(configuration.HeartbeatInterval().Ticks * 3); connection.Start(host).Wait(); Assert.True(connectWh.Wait(TimeSpan.FromSeconds(10)), "Connect never fired"); connection.Stop(); Assert.True(disconnectWh.Wait(disconnectWait), "Disconnect never fired"); } }
public async Task DisconnectFiresForPersistentConnectionWhenClientCallsStop() { using (var host = new MemoryHost()) { var connectWh = new AsyncManualResetEvent(); var disconnectWh = new AsyncManualResetEvent(); var dr = new DefaultDependencyResolver(); var configuration = dr.Resolve<IConfigurationManager>(); host.Configure(app => { var config = new ConnectionConfiguration { Resolver = dr }; app.MapSignalR<MyConnection>("/echo", config); configuration.DisconnectTimeout = TimeSpan.FromSeconds(6); dr.Register(typeof(MyConnection), () => new MyConnection(connectWh, disconnectWh)); }); var connection = new Client.Connection("http://foo/echo"); // Maximum wait time for disconnect to fire (3 heart beat intervals) var disconnectWait = TimeSpan.FromTicks(configuration.HeartbeatInterval().Ticks * 3); await connection.Start(host); Assert.True(await connectWh.WaitAsync(TimeSpan.FromSeconds(10)), "Connect never fired"); connection.Stop(); Assert.True(await disconnectWh.WaitAsync(disconnectWait), "Disconnect never fired"); } }