private TestServiceConnection MockServiceConnection(IConnectionFactory serviceConnectionFactory      = null,
                                                            IClientConnectionFactory clientConnectionFactory = null,
                                                            ILoggerFactory loggerFactory = null,
                                                            GracefulShutdownMode mode    = GracefulShutdownMode.Off)
        {
            clientConnectionFactory ??= new ClientConnectionFactory();
            serviceConnectionFactory ??= new TestConnectionFactory(conn => Task.CompletedTask);
            loggerFactory ??= NullLoggerFactory.Instance;

            var services          = new ServiceCollection();
            var connectionHandler = new EndlessConnectionHandler();

            services.AddSingleton(connectionHandler);
            var builder = new ConnectionBuilder(services.BuildServiceProvider());

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

            return(new TestServiceConnection(
                       serviceConnectionFactory,
                       clientConnectionFactory,
                       loggerFactory,
                       handler,
                       mode: mode
                       ));
        }
        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);
            }
        }