mirror of
https://dev.azure.com/schwarzit/schwarzit.stackit-public/_git/audit-go
synced 2026-02-13 11:27:25 +00:00
So far the SDK provided a messaging API that was not thread-safe (i.e. goroutine-safe). Additionally the SDK provided a MutexAPI which made it thread-safe at the cost of removed concurrency possibilities. The changes implemented in this commit replace both implementations with a thread-safe connection pool based solution. The api gateway is a SDK user that requires reliable high performance send capabilities with a limit amount of amqp connections. These changes in the PR try address their requirements by moving the responsibility of connection management into the SDK. From this change other SDK users will benefit as well. Security-concept-update-needed: false. JIRA Work Item: STACKITALO-62
578 lines
18 KiB
Go
578 lines
18 KiB
Go
package messaging
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
type connectionProviderMock struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (p *connectionProviderMock) NewAmqpConnection(config AmqpConnectionConfig, connectionName string) *AmqpConnection {
|
|
args := p.Called(config, connectionName)
|
|
return args.Get(0).(*AmqpConnection)
|
|
}
|
|
|
|
var _ connectionProvider = (*connectionProviderMock)(nil)
|
|
|
|
func Test_AmqpConnectionPool_GetHandle(t *testing.T) {
|
|
|
|
t.Run("next handle", func(t *testing.T) {
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
}
|
|
|
|
handle := pool.NewHandle()
|
|
assert.NotNil(t, handle)
|
|
assert.Equal(t, 0, handle.connectionOffset)
|
|
assert.Equal(t, 1, pool.handleOffset)
|
|
})
|
|
|
|
t.Run("next handle high offset", func(t *testing.T) {
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 13,
|
|
lock: sync.RWMutex{},
|
|
}
|
|
|
|
handle := pool.NewHandle()
|
|
assert.NotNil(t, handle)
|
|
assert.Equal(t, 3, handle.connectionOffset)
|
|
assert.Equal(t, 14, pool.handleOffset)
|
|
})
|
|
}
|
|
|
|
func Test_AmqpConnectionPool_internalAddConnection(t *testing.T) {
|
|
|
|
t.Run("internal add connection", func(t *testing.T) {
|
|
conn := &amqpConnMock{}
|
|
|
|
dialer := &amqpDialMock{}
|
|
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
|
|
|
|
connection := &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
dialer: dialer,
|
|
}
|
|
|
|
connectionProvider := &connectionProviderMock{}
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connectionProvider: connectionProvider,
|
|
}
|
|
|
|
err := pool.internalAddConnection()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, 1, len(pool.connections))
|
|
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1)
|
|
dialer.AssertNumberOfCalls(t, "Dial", 1)
|
|
})
|
|
|
|
t.Run("dialer error", func(t *testing.T) {
|
|
conn := &amqpConnMock{}
|
|
|
|
dialer := &amqpDialMock{}
|
|
var c *amqpConnMock = nil
|
|
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error")).Once()
|
|
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
|
|
|
|
connection := &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
dialer: dialer,
|
|
}
|
|
|
|
connectionProvider := &connectionProviderMock{}
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connectionProvider: connectionProvider,
|
|
}
|
|
|
|
err := pool.internalAddConnection()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, 1, len(pool.connections))
|
|
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1)
|
|
dialer.AssertNumberOfCalls(t, "Dial", 2)
|
|
})
|
|
|
|
t.Run("repetitive dialer error", func(t *testing.T) {
|
|
dialer := &amqpDialMock{}
|
|
var c *amqpConnMock = nil
|
|
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error"))
|
|
|
|
connection := &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
dialer: dialer,
|
|
}
|
|
|
|
connectionProvider := &connectionProviderMock{}
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connectionProvider: connectionProvider,
|
|
}
|
|
|
|
err := pool.internalAddConnection()
|
|
assert.EqualError(t, err, "new connection: new internal connection: internal connect: dial: test error")
|
|
|
|
assert.Equal(t, 0, len(pool.connections))
|
|
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 1)
|
|
dialer.AssertNumberOfCalls(t, "Dial", 2)
|
|
})
|
|
}
|
|
|
|
func Test_AmqpConnectionPool_initializeConnections(t *testing.T) {
|
|
|
|
t.Run("initialize connections successfully", func(t *testing.T) {
|
|
|
|
conn := &amqpConnMock{}
|
|
dialer := &amqpDialMock{}
|
|
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
|
|
|
|
connection := &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
dialer: dialer,
|
|
}
|
|
|
|
connectionProvider := &connectionProviderMock{}
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connectionProvider: connectionProvider,
|
|
}
|
|
|
|
err := pool.initializeConnections()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, 5, len(pool.connections))
|
|
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 5)
|
|
})
|
|
|
|
t.Run("fail initialization of connections", func(t *testing.T) {
|
|
|
|
var c *amqpConnMock = nil
|
|
failingDialer := &amqpDialMock{}
|
|
failingDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error"))
|
|
|
|
failingConnection := &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
dialer: failingDialer,
|
|
}
|
|
|
|
conn := &amqpConnMock{}
|
|
successfulDialer := &amqpDialMock{}
|
|
successfulDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
|
|
|
|
successfulConnection := &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
dialer: successfulDialer,
|
|
}
|
|
|
|
connectionProvider := &connectionProviderMock{}
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(4)
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(failingConnection)
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connectionProvider: connectionProvider,
|
|
}
|
|
|
|
err := pool.initializeConnections()
|
|
assert.EqualError(t, err, "new connection: new internal connection: internal connect: dial: test error")
|
|
|
|
assert.Equal(t, 4, len(pool.connections))
|
|
connectionProvider.AssertNumberOfCalls(t, "NewAmqpConnection", 5)
|
|
})
|
|
}
|
|
|
|
func Test_AmqpConnectionPool_Close(t *testing.T) {
|
|
|
|
t.Run("close connection successfully", func(t *testing.T) {
|
|
// add 5 connections to the pool
|
|
conn := &amqpConnMock{}
|
|
conn.On("Close").Return(nil)
|
|
|
|
dialer := &amqpDialMock{}
|
|
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, nil)
|
|
|
|
connection := &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
dialer: dialer,
|
|
}
|
|
|
|
connectionProvider := &connectionProviderMock{}
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connectionProvider: connectionProvider,
|
|
}
|
|
|
|
err := pool.initializeConnections()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, 5, len(pool.connections))
|
|
|
|
// close the pool
|
|
err = pool.Close()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5, len(pool.connections))
|
|
for _, c := range pool.connections {
|
|
assert.Nil(t, c)
|
|
}
|
|
})
|
|
|
|
t.Run("close connection fail", func(t *testing.T) {
|
|
// add 5 connections to the pool
|
|
failingConn := &amqpConnMock{}
|
|
failingConn.On("Close").Return(errors.New("test error"))
|
|
|
|
failingDialer := &amqpDialMock{}
|
|
failingDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(failingConn, nil)
|
|
|
|
failingConnection := &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
dialer: failingDialer,
|
|
}
|
|
|
|
successfulConn := &amqpConnMock{}
|
|
successfulConn.On("Close").Return(nil)
|
|
successfulDialer := &amqpDialMock{}
|
|
successfulDialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(successfulConn, nil)
|
|
|
|
successfulConnection := &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
dialer: successfulDialer,
|
|
}
|
|
|
|
connectionProvider := &connectionProviderMock{}
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(2)
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(failingConnection).Times(2)
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(successfulConnection).Times(1)
|
|
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connectionProvider: connectionProvider,
|
|
}
|
|
|
|
err := pool.initializeConnections()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, 5, len(pool.connections))
|
|
|
|
// close the pool
|
|
err = pool.Close()
|
|
assert.EqualError(t, err, "pooled connection: internal close: connection close: test error\npooled connection: internal close: connection close: test error")
|
|
assert.Equal(t, 5, len(pool.connections))
|
|
for _, c := range pool.connections {
|
|
assert.Nil(t, c)
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test_AmqpConnectionPool_nextConnectionForHandle(t *testing.T) {
|
|
channelReceiver := func(channel chan struct{}) <-chan struct{} {
|
|
return channel
|
|
}
|
|
|
|
newActiveConnection := func() *AmqpConnection {
|
|
channel := make(chan struct{})
|
|
conn := &amqpConnMock{}
|
|
conn.On("Done", mock.Anything).Return(channelReceiver(channel))
|
|
return &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
conn: conn,
|
|
}
|
|
}
|
|
|
|
newClosedConnection := func() *AmqpConnection {
|
|
channel := make(chan struct{})
|
|
close(channel)
|
|
|
|
conn := &amqpConnMock{}
|
|
conn.On("Done", mock.Anything).Return(channelReceiver(channel))
|
|
return &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
conn: conn,
|
|
}
|
|
}
|
|
|
|
t.Run("next connection for requested handle", func(t *testing.T) {
|
|
connections := make([]*AmqpConnection, 0)
|
|
for i := 0; i < 5; i++ {
|
|
connections = append(connections, newActiveConnection())
|
|
}
|
|
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connections: connections,
|
|
}
|
|
|
|
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1})
|
|
assert.NotNil(t, connection)
|
|
assert.False(t, addConnection)
|
|
})
|
|
|
|
t.Run("nil connection for requested handle", func(t *testing.T) {
|
|
connections := make([]*AmqpConnection, 0)
|
|
connections = append(connections, newActiveConnection())
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, newActiveConnection())
|
|
connections = append(connections, newActiveConnection())
|
|
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connections: connections,
|
|
}
|
|
|
|
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1})
|
|
assert.NotNil(t, connection)
|
|
assert.True(t, addConnection)
|
|
})
|
|
|
|
t.Run("closed connection for requested handle", func(t *testing.T) {
|
|
connections := make([]*AmqpConnection, 0)
|
|
connections = append(connections, newActiveConnection())
|
|
connections = append(connections, newClosedConnection())
|
|
connections = append(connections, newClosedConnection())
|
|
connections = append(connections, newActiveConnection())
|
|
connections = append(connections, newActiveConnection())
|
|
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connections: connections,
|
|
}
|
|
|
|
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1})
|
|
assert.NotNil(t, connection)
|
|
assert.True(t, addConnection)
|
|
})
|
|
|
|
t.Run("no connection for requested handle", func(t *testing.T) {
|
|
connections := make([]*AmqpConnection, 0)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connections: connections,
|
|
}
|
|
|
|
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 1})
|
|
assert.Nil(t, connection)
|
|
assert.True(t, addConnection)
|
|
})
|
|
|
|
t.Run("connection for requested handle with large index", func(t *testing.T) {
|
|
connections := make([]*AmqpConnection, 0)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, newActiveConnection())
|
|
connections = append(connections, nil)
|
|
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connections: connections,
|
|
}
|
|
|
|
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 23})
|
|
assert.NotNil(t, connection)
|
|
assert.False(t, addConnection)
|
|
})
|
|
|
|
t.Run("connection for requested handle nil with large index", func(t *testing.T) {
|
|
connections := make([]*AmqpConnection, 0)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, newActiveConnection())
|
|
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connections: connections,
|
|
}
|
|
|
|
connection, addConnection := pool.nextConnectionForHandle(&ConnectionPoolHandle{connectionOffset: 23})
|
|
assert.NotNil(t, connection)
|
|
assert.True(t, addConnection)
|
|
})
|
|
}
|
|
|
|
func Test_AmqpConnectionPool_GetConnection(t *testing.T) {
|
|
channelReceiver := func(channel chan struct{}) <-chan struct{} {
|
|
return channel
|
|
}
|
|
|
|
newActiveConnection := func() *AmqpConnection {
|
|
channel := make(chan struct{})
|
|
conn := &amqpConnMock{}
|
|
conn.On("Done", mock.Anything).Return(channelReceiver(channel))
|
|
return &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
conn: conn,
|
|
}
|
|
}
|
|
|
|
t.Run("get connection for requested handle", func(t *testing.T) {
|
|
connections := make([]*AmqpConnection, 0)
|
|
for i := 0; i < 5; i++ {
|
|
connections = append(connections, newActiveConnection())
|
|
}
|
|
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connections: connections,
|
|
}
|
|
|
|
connection, err := pool.GetConnection(&ConnectionPoolHandle{connectionOffset: 1})
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, connection)
|
|
assert.Equal(t, connections[1], connection)
|
|
assert.Equal(t, 5, len(connections))
|
|
})
|
|
|
|
t.Run("add connection if missing", func(t *testing.T) {
|
|
connections := make([]*AmqpConnection, 5)
|
|
|
|
connectionProvider := &connectionProviderMock{}
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(newActiveConnection())
|
|
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connections: connections,
|
|
connectionProvider: connectionProvider,
|
|
}
|
|
|
|
connection, err := pool.GetConnection(&ConnectionPoolHandle{connectionOffset: 1})
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, connection)
|
|
assert.Equal(t, connections[1], connection)
|
|
assert.Equal(t, 5, len(connections))
|
|
})
|
|
|
|
t.Run("add connection fails returns alternative connection", func(t *testing.T) {
|
|
connections := make([]*AmqpConnection, 0)
|
|
connections = append(connections, newActiveConnection())
|
|
connections = append(connections, nil)
|
|
connections = append(connections, newActiveConnection())
|
|
connections = append(connections, newActiveConnection())
|
|
connections = append(connections, newActiveConnection())
|
|
|
|
connectionProvider := &connectionProviderMock{}
|
|
|
|
dialer := &amqpDialMock{}
|
|
var c *amqpConnMock = nil
|
|
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, fmt.Errorf("dial error"))
|
|
connection := &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
dialer: dialer,
|
|
}
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
|
|
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connections: connections,
|
|
connectionProvider: connectionProvider,
|
|
}
|
|
|
|
connection, err := pool.GetConnection(&ConnectionPoolHandle{connectionOffset: 1})
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, connection)
|
|
assert.Nil(t, connections[1])
|
|
assert.Equal(t, connections[2], connection)
|
|
assert.Equal(t, 5, len(connections))
|
|
})
|
|
|
|
t.Run("add connection fails", func(t *testing.T) {
|
|
connections := make([]*AmqpConnection, 0)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
connections = append(connections, nil)
|
|
|
|
connectionProvider := &connectionProviderMock{}
|
|
|
|
dialer := &amqpDialMock{}
|
|
var c *amqpConnMock = nil
|
|
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, fmt.Errorf("dial error"))
|
|
connection := &AmqpConnection{
|
|
connectionName: "test",
|
|
lock: sync.RWMutex{},
|
|
dialer: dialer,
|
|
}
|
|
connectionProvider.On("NewAmqpConnection", mock.Anything, mock.Anything).Return(connection)
|
|
|
|
pool := AmqpConnectionPool{
|
|
config: AmqpConnectionPoolConfig{PoolSize: 5},
|
|
handleOffset: 0,
|
|
lock: sync.RWMutex{},
|
|
connections: connections,
|
|
connectionProvider: connectionProvider,
|
|
}
|
|
|
|
connection, err := pool.GetConnection(&ConnectionPoolHandle{connectionOffset: 1})
|
|
assert.EqualError(t, err, "renew connection: new internal connection: internal connect: dial: dial error")
|
|
assert.Nil(t, connection)
|
|
assert.Equal(t, 5, len(connections))
|
|
})
|
|
}
|