public async Task ClientConnectionWithDiagnosticClientTagTest()
        {
            using (StartVerifiableLog(out var loggerFactory))
            {
                var            ccm                 = new TestClientConnectionManager();
                var            ccf                 = new ClientConnectionFactory();
                var            protocol            = new ServiceProtocol();
                TestConnection transportConnection = null;
                var            connectionFactory   = new TestConnectionFactory(conn =>
                {
                    transportConnection = conn;
                    return(Task.CompletedTask);
                });

                var diagnosticClientConnectionId = "diagnosticClient";
                var normalClientConnectionId     = "normalClient";

                var services          = new ServiceCollection();
                var connectionHandler = new DiagnosticClientConnectionHandler(diagnosticClientConnectionId);
                services.AddSingleton(connectionHandler);
                var builder = new ConnectionBuilder(services.BuildServiceProvider());
                builder.UseConnectionHandler <DiagnosticClientConnectionHandler>();
                ConnectionDelegate handler = builder.Build();

                var connection = new ServiceConnection(protocol, ccm, connectionFactory, loggerFactory, handler, ccf,
                                                       "serverId", Guid.NewGuid().ToString("N"), null, null, closeTimeOutMilliseconds: 500);

                var connectionTask = connection.StartAsync();

                // completed handshake
                await connection.ConnectionInitializedTask.OrTimeout();

                Assert.Equal(ServiceConnectionStatus.Connected, connection.Status);

                await transportConnection.Application.Output.WriteAsync(
                    protocol.GetMessageBytes(new OpenConnectionMessage(diagnosticClientConnectionId, null, new Dictionary <string, StringValues>
                {
                    { Constants.AsrsIsDiagnosticClient, "true" }
                }, null)));

                await transportConnection.Application.Output.WriteAsync(
                    protocol.GetMessageBytes(new OpenConnectionMessage(normalClientConnectionId, null)));

                var connections = await Task.WhenAll(ccm.WaitForClientConnectionAsync(normalClientConnectionId).OrTimeout(),
                                                     ccm.WaitForClientConnectionAsync(diagnosticClientConnectionId).OrTimeout());

                await Task.WhenAll(from c in connections select c.LifetimeTask.OrTimeout());

                // complete reading to end the connection
                transportConnection.Application.Output.Complete();

                // 1s for application task to timeout
                await connectionTask.OrTimeout(1000);

                Assert.Equal(ServiceConnectionStatus.Disconnected, connection.Status);
                Assert.Empty(ccm.ClientConnections);
            }
        }
        public async Task TestServiceConnectionWithNormalApplicationTask()
        {
            using (StartVerifiableLog(out var loggerFactory, LogLevel.Debug))
            {
                var            ccm                 = new TestClientConnectionManager();
                var            ccf                 = new ClientConnectionFactory();
                var            protocol            = new ServiceProtocol();
                TestConnection transportConnection = null;
                var            connectionFactory   = new TestConnectionFactory(conn =>
                {
                    transportConnection = conn;
                    return(Task.CompletedTask);
                });
                var services = new ServiceCollection();
                var builder  = new ConnectionBuilder(services.BuildServiceProvider());
                builder.UseConnectionHandler <TestConnectionHandler>();
                ConnectionDelegate handler = builder.Build();
                var connection             = new ServiceConnection(protocol, ccm, connectionFactory, loggerFactory, handler, ccf,
                                                                   "serverId", Guid.NewGuid().ToString("N"), null, null);

                var connectionTask = connection.StartAsync();

                // completed handshake
                await connection.ConnectionInitializedTask.OrTimeout();

                Assert.Equal(ServiceConnectionStatus.Connected, connection.Status);
                var clientConnectionId = Guid.NewGuid().ToString();

                await transportConnection.Application.Output.WriteAsync(
                    protocol.GetMessageBytes(new OpenConnectionMessage(clientConnectionId, new Claim[] { })));

                var clientConnection = await ccm.WaitForClientConnectionAsync(clientConnectionId).OrTimeout();

                await transportConnection.Application.Output.WriteAsync(
                    protocol.GetMessageBytes(new CloseConnectionMessage(clientConnectionId)));

                // Normal end with close message
                await ccm.WaitForClientConnectionRemovalAsync(clientConnectionId).OrTimeout();

                // another connection comes in
                clientConnectionId = Guid.NewGuid().ToString();

                await transportConnection.Application.Output.WriteAsync(
                    protocol.GetMessageBytes(new OpenConnectionMessage(clientConnectionId, new Claim[] { })));

                clientConnection = await ccm.WaitForClientConnectionAsync(clientConnectionId).OrTimeout();

                // complete reading to end the connection
                transportConnection.Application.Output.Complete();

                await connectionTask.OrTimeout();

                Assert.Equal(ServiceConnectionStatus.Disconnected, connection.Status);
                Assert.Empty(ccm.ClientConnections);
            }
        }
        public async Task ClientConnectionOutgoingAbortCanEndLifeTime()
        {
            using (StartVerifiableLog(out var loggerFactory, LogLevel.Warning, expectedErrors: c => true,
                                      logChecker: logs =>
            {
                Assert.Equal(2, logs.Count);
                Assert.Equal("SendLoopStopped", logs[0].Write.EventId.Name);
                Assert.Equal("ApplicationTaskFailed", logs[1].Write.EventId.Name);
                return(true);
            }))
            {
                var            ccm                 = new TestClientConnectionManager();
                var            ccf                 = new ClientConnectionFactory();
                var            protocol            = new ServiceProtocol();
                TestConnection transportConnection = null;
                var            connectionFactory   = new TestConnectionFactory(conn =>
                {
                    transportConnection = conn;
                    return(Task.CompletedTask);
                });
                var services          = new ServiceCollection();
                var connectionHandler = new EndlessConnectionHandler();
                services.AddSingleton(connectionHandler);
                var builder = new ConnectionBuilder(services.BuildServiceProvider());
                builder.UseConnectionHandler <EndlessConnectionHandler>();
                ConnectionDelegate handler = builder.Build();
                var connection             = new ServiceConnection(protocol, ccm, connectionFactory, loggerFactory, handler, ccf,
                                                                   "serverId", Guid.NewGuid().ToString("N"), null, null,
                                                                   closeTimeOutMilliseconds: 500);

                var connectionTask = connection.StartAsync();

                // completed handshake
                await connection.ConnectionInitializedTask.OrTimeout();

                Assert.Equal(ServiceConnectionStatus.Connected, connection.Status);
                var clientConnectionId = Guid.NewGuid().ToString();

                await transportConnection.Application.Output.WriteAsync(
                    protocol.GetMessageBytes(new OpenConnectionMessage(clientConnectionId, new Claim[] { })));

                var clientConnection = await ccm.WaitForClientConnectionAsync(clientConnectionId).OrTimeout();

                clientConnection.CancelOutgoing();

                await clientConnection.LifetimeTask.OrTimeout();

                // complete reading to end the connection
                transportConnection.Application.Output.Complete();

                // 1s for application task to timeout
                await connectionTask.OrTimeout(1000);

                Assert.Equal(ServiceConnectionStatus.Disconnected, connection.Status);
                Assert.Empty(ccm.ClientConnections);

                connectionHandler.CancellationToken.Cancel();
            }
        }
        public async Task TestServiceConnectionWithEndlessApplicationTaskNeverEnds()
        {
            var clientConnectionId = Guid.NewGuid().ToString();

            using (StartVerifiableLog(out var loggerFactory, LogLevel.Warning, expectedErrors: c => true,
                                      logChecker: logs =>
            {
                Assert.Single(logs);
                Assert.Equal("DetectedLongRunningApplicationTask", logs[0].Write.EventId.Name);
                Assert.Equal($"The connection {clientConnectionId} has a long running application logic that prevents the connection from complete.", logs[0].Write.Message);
                return(true);
            }))
            {
                var            ccm                 = new TestClientConnectionManager();
                var            ccf                 = new ClientConnectionFactory();
                var            protocol            = new ServiceProtocol();
                TestConnection transportConnection = null;
                var            connectionFactory   = new TestConnectionFactory(conn =>
                {
                    transportConnection = conn;
                    return(Task.CompletedTask);
                });
                var services          = new ServiceCollection();
                var connectionHandler = new EndlessConnectionHandler();
                services.AddSingleton(connectionHandler);
                var builder = new ConnectionBuilder(services.BuildServiceProvider());
                builder.UseConnectionHandler <EndlessConnectionHandler>();
                ConnectionDelegate handler = builder.Build();
                var connection             = new ServiceConnection(protocol, ccm, connectionFactory, loggerFactory, handler, ccf,
                                                                   "serverId", Guid.NewGuid().ToString("N"),
                                                                   null, null, null, closeTimeOutMilliseconds: 1);

                var connectionTask = connection.StartAsync();

                // completed handshake
                await connection.ConnectionInitializedTask.OrTimeout();

                Assert.Equal(ServiceConnectionStatus.Connected, connection.Status);

                await transportConnection.Application.Output.WriteAsync(
                    protocol.GetMessageBytes(new OpenConnectionMessage(clientConnectionId, new Claim[] { })));

                var clientConnection = await ccm.WaitForClientConnectionAsync(clientConnectionId).OrTimeout();

                // complete reading to end the connection
                transportConnection.Application.Output.Complete();

                // Assert timeout
                var lifetime = clientConnection.LifetimeTask;
                var task     = await Task.WhenAny(lifetime, Task.Delay(1000));

                Assert.NotEqual(lifetime, task);

                Assert.Equal(ServiceConnectionStatus.Disconnected, connection.Status);

                // since the service connection ends, the client connection is cleaned up from the collection...
                Assert.Empty(ccm.ClientConnections);
            }
        }
        public async Task ClientConnectionContextAbortCanSendOutCloseMessage()
        {
            using (StartVerifiableLog(out var loggerFactory, LogLevel.Trace, expectedErrors: c => true,
                                      logChecker: logs =>
            {
                return(true);
            }))
            {
                var            ccm                 = new TestClientConnectionManager();
                var            ccf                 = new ClientConnectionFactory();
                var            protocol            = new ServiceProtocol();
                TestConnection transportConnection = null;
                var            connectionFactory   = new TestConnectionFactory(conn =>
                {
                    transportConnection = conn;
                    return(Task.CompletedTask);
                });

                var services          = new ServiceCollection();
                var lastWill          = "This is the last will";
                var connectionHandler = new LastWillConnectionHandler(lastWill);
                services.AddSingleton(connectionHandler);
                var builder = new ConnectionBuilder(services.BuildServiceProvider());
                builder.UseConnectionHandler <LastWillConnectionHandler>();
                ConnectionDelegate handler = builder.Build();

                var connection = new ServiceConnection(protocol, ccm, connectionFactory, loggerFactory, handler, ccf,
                                                       "serverId", Guid.NewGuid().ToString("N"), null, null, closeTimeOutMilliseconds: 500);

                var connectionTask = connection.StartAsync();

                // completed handshake
                await connection.ConnectionInitializedTask.OrTimeout();

                Assert.Equal(ServiceConnectionStatus.Connected, connection.Status);
                var clientConnectionId = Guid.NewGuid().ToString();

                await transportConnection.Application.Output.WriteAsync(
                    protocol.GetMessageBytes(new OpenConnectionMessage(clientConnectionId, new Claim[] { })));

                var clientConnection = await ccm.WaitForClientConnectionAsync(clientConnectionId).OrTimeout();

                await clientConnection.LifetimeTask.OrTimeout();

                transportConnection.Transport.Output.Complete();
                var input = await transportConnection.Application.Input.ReadAsync();

                var buffer   = input.Buffer;
                var canParse = protocol.TryParseMessage(ref buffer, out var msg);
                Assert.True(canParse);
                var message = msg as ConnectionDataMessage;
                Assert.NotNull(message);

                Assert.Equal(clientConnectionId, message.ConnectionId);
                Assert.Equal(lastWill, Encoding.UTF8.GetString(message.Payload.First.ToArray()));

                // complete reading to end the connection
                transportConnection.Application.Output.Complete();

                // 1s for application task to timeout
                await connectionTask.OrTimeout(1000);

                Assert.Equal(ServiceConnectionStatus.Disconnected, connection.Status);
                Assert.Empty(ccm.ClientConnections);
            }
        }
        public async Task TestServiceConnectionWithErrorApplicationTask()
        {
            using (StartVerifiableLog(out var loggerFactory, LogLevel.Warning, expectedErrors: c => true,
                                      logChecker: logs =>
            {
                Assert.Equal(2, logs.Count);
                Assert.Equal("SendLoopStopped", logs[0].Write.EventId.Name);
                Assert.Equal("ApplicationTaskFailed", logs[1].Write.EventId.Name);
                return(true);
            }))
            {
                var            ccm                 = new TestClientConnectionManager();
                var            ccf                 = new ClientConnectionFactory();
                var            protocol            = new ServiceProtocol();
                TestConnection transportConnection = null;
                var            connectionFactory   = new TestConnectionFactory(conn =>
                {
                    transportConnection = conn;
                    return(Task.CompletedTask);
                });
                var services          = new ServiceCollection();
                var errorTcs          = new TaskCompletionSource <Exception>();
                var connectionHandler = new ErrorConnectionHandler(errorTcs);
                services.AddSingleton(connectionHandler);
                var builder = new ConnectionBuilder(services.BuildServiceProvider());

                builder.UseConnectionHandler <ErrorConnectionHandler>();
                ConnectionDelegate handler = builder.Build();

                var connection = new ServiceConnection(protocol, ccm, connectionFactory, loggerFactory, handler, ccf,
                                                       "serverId", Guid.NewGuid().ToString("N"), null, null);

                var connectionTask = connection.StartAsync();

                // completed handshake
                await connection.ConnectionInitializedTask.OrTimeout();

                Assert.Equal(ServiceConnectionStatus.Connected, connection.Status);
                var clientConnectionId = Guid.NewGuid().ToString();

                await transportConnection.Application.Output.WriteAsync(
                    protocol.GetMessageBytes(new OpenConnectionMessage(clientConnectionId, new Claim[] { })));

                var clientConnection = await ccm.WaitForClientConnectionAsync(clientConnectionId).OrTimeout();

                errorTcs.SetException(new InvalidOperationException("error operation"));

                await clientConnection.LifetimeTask.OrTimeout();

                // Should complete the connection when application throws
                await ccm.WaitForClientConnectionRemovalAsync(clientConnectionId).OrTimeout();

                // Application task should not affect the underlying service connection
                Assert.Equal(ServiceConnectionStatus.Connected, connection.Status);

                // complete reading to end the connection
                transportConnection.Application.Output.Complete();

                await connectionTask.OrTimeout();

                Assert.Equal(ServiceConnectionStatus.Disconnected, connection.Status);
                Assert.Empty(ccm.ClientConnections);
            }
        }