audit-go/audit/messaging/amqp_connection_test.go
2025-01-16 10:54:08 +01:00

415 lines
12 KiB
Go

package messaging
import (
"context"
"errors"
"github.com/Azure/go-amqp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"sync"
"testing"
)
type amqpConnMock struct {
mock.Mock
}
func (m *amqpConnMock) Done() <-chan struct{} {
args := m.Called()
return args.Get(0).(<-chan struct{})
}
func (m *amqpConnMock) NewSession(ctx context.Context, opts *amqp.SessionOptions) (amqpSession, error) {
args := m.Called(ctx, opts)
return args.Get(0).(amqpSession), args.Error(1)
}
func (m *amqpConnMock) Close() error {
args := m.Called()
return args.Error(0)
}
var _ amqpConn = (*amqpConnMock)(nil)
type amqpDialMock struct {
mock.Mock
}
func (m *amqpDialMock) Dial(ctx context.Context, addr string, opts *amqp.ConnOptions) (amqpConn, error) {
args := m.Called(ctx, addr, opts)
return args.Get(0).(amqpConn), args.Error(1)
}
var _ amqpDial = (*amqpDialMock)(nil)
type amqpSessionMock struct {
mock.Mock
}
func (m *amqpSessionMock) NewSender(ctx context.Context, target string, opts *amqp.SenderOptions) (amqpSender, error) {
args := m.Called(ctx, target, opts)
return args.Get(0).(amqpSender), args.Error(1)
}
func (m *amqpSessionMock) Close(ctx context.Context) error {
args := m.Called(ctx)
return args.Error(0)
}
var _ amqpSession = (*amqpSessionMock)(nil)
func Test_AmqpConnection_IsClosed(t *testing.T) {
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
}
channelReceiver := func(channel chan struct{}) <-chan struct{} {
return channel
}
t.Run("is closed - connection nil", func(t *testing.T) {
assert.True(t, connection.IsClosed())
})
t.Run("is closed", func(t *testing.T) {
channel := make(chan struct{})
close(channel)
amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Done").Return(channelReceiver(channel))
connection.conn = amqpConnMock
assert.True(t, connection.IsClosed())
})
t.Run("is not closed", func(t *testing.T) {
channel := make(chan struct{})
amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Done").Return(channelReceiver(channel))
connection.conn = amqpConnMock
assert.False(t, connection.IsClosed())
})
}
func Test_AmqpConnection_Close(t *testing.T) {
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
}
t.Run("already closed", func(t *testing.T) {
assert.NoError(t, connection.Close())
})
t.Run("close error", func(t *testing.T) {
err := errors.New("test error")
amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Close").Return(err)
connection.conn = amqpConnMock
assert.EqualError(t, connection.Close(), "close: internal connection close: test error")
assert.NotNil(t, connection.conn)
amqpConnMock.AssertNumberOfCalls(t, "Close", 1)
})
t.Run("close without error", func(t *testing.T) {
amqpConnMock := &amqpConnMock{}
amqpConnMock.On("Close").Return(nil)
connection.conn = amqpConnMock
assert.Nil(t, connection.Close())
assert.Nil(t, connection.conn)
amqpConnMock.AssertNumberOfCalls(t, "Close", 1)
})
}
func Test_AmqpConnection_Connect(t *testing.T) {
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
}
t.Run("already connected", func(t *testing.T) {
connection.conn = &amqpConnMock{}
assert.NoError(t, connection.Connect())
})
t.Run("dial error", func(t *testing.T) {
connection.conn = nil
connection.username = "user"
connection.password = "pass"
amqpDialMock := &amqpDialMock{}
var c *amqpConnMock = nil
amqpDialMock.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(c, errors.New("test error"))
connection.dialer = amqpDialMock
assert.EqualError(t, connection.Connect(), "internal connection connect: dial: test error")
assert.Nil(t, connection.conn)
})
t.Run("connect without error", func(t *testing.T) {
connection.conn = nil
amqpDialMock := &amqpDialMock{}
amqpConn := &amqpConnMock{}
amqpDialMock.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(amqpConn, nil)
connection.dialer = amqpDialMock
assert.NoError(t, connection.Connect())
assert.Equal(t, amqpConn, connection.conn)
})
}
func Test_AmqpConnection_ResetConnection(t *testing.T) {
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
}
t.Run("reset connection - connect on first attempt", func(t *testing.T) {
connection.conn = nil
dialer := &amqpDialMock{}
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(&amqpConnMock{}, nil)
connection.dialer = dialer
assert.NoError(t, connection.ResetConnection(context.Background()))
assert.NotNil(t, connection.conn)
dialer.AssertNumberOfCalls(t, "Dial", 1)
})
t.Run("reset connection - connect on second attempt", func(t *testing.T) {
connection.conn = nil
dialer := &amqpDialMock{}
var conn *amqpConnMock = nil
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, errors.New("test error")).Once()
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(&amqpConnMock{}, nil)
connection.dialer = dialer
assert.NoError(t, connection.ResetConnection(context.Background()))
assert.NotNil(t, connection.conn)
dialer.AssertNumberOfCalls(t, "Dial", 2)
})
t.Run("reset connection - fail continuously", func(t *testing.T) {
connection.conn = nil
dialer := &amqpDialMock{}
var conn *amqpConnMock = nil
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, errors.New("test error"))
connection.dialer = dialer
assert.EqualError(t, connection.ResetConnection(context.Background()), "connect: dial: test error")
assert.Nil(t, connection.conn)
dialer.AssertNumberOfCalls(t, "Dial", 2)
})
}
type ResettableFunction struct {
mock.Mock
}
func (f *ResettableFunction) Run(ctx context.Context) (any, error) {
args := f.Called(ctx)
return args.Get(0), args.Error(1)
}
func Test_AmqpConnection_ResetConnectionAndRetryIfErrorWithReturnValue(t *testing.T) {
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
}
t.Run("no error", func(t *testing.T) {
value, err := connection.ResetConnectionAndRetryIfErrorWithReturnValue("test", func(ctx context.Context) (any, error) {
return struct{}{}, nil
})
assert.NoError(t, err)
assert.Equal(t, struct{}{}, value)
})
t.Run("reset - success", func(t *testing.T) {
connection.conn = nil
dialer := &amqpDialMock{}
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(&amqpConnMock{}, nil)
connection.dialer = dialer
resettableFunction := &ResettableFunction{}
resettableFunction.On("Run", mock.Anything).Return(struct{}{}, errors.New("test error")).Once()
resettableFunction.On("Run", mock.Anything).Return(struct{}{}, nil)
value, err := connection.ResetConnectionAndRetryIfErrorWithReturnValue("test", resettableFunction.Run)
assert.NoError(t, err)
assert.Equal(t, struct{}{}, value)
resettableFunction.AssertNumberOfCalls(t, "Run", 2)
})
t.Run("reset - fail", func(t *testing.T) {
connection.conn = nil
dialer := &amqpDialMock{}
var conn *amqpConnMock = nil
dialer.On("Dial", mock.Anything, mock.Anything, mock.Anything).Return(conn, errors.New("test error"))
connection.dialer = dialer
resettableFunction := &ResettableFunction{}
resettableFunction.On("Run", mock.Anything).Return(struct{}{}, errors.New("test error"))
value, err := connection.ResetConnectionAndRetryIfErrorWithReturnValue("test", resettableFunction.Run)
assert.EqualError(t, err, "reset connection: connect: dial: test error")
assert.Nil(t, value)
resettableFunction.AssertNumberOfCalls(t, "Run", 1)
})
}
func Test_AmqpConnection_NewSender(t *testing.T) {
connection := &AmqpConnection{
connectionName: "test",
lock: sync.RWMutex{},
}
channelReceiver := func(channel chan struct{}) <-chan struct{} {
return channel
}
t.Run("connection not initialized", func(t *testing.T) {
sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "connection is not initialized")
assert.Nil(t, sender)
})
t.Run("connection is closed", func(t *testing.T) {
channel := make(chan struct{})
close(channel)
conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel))
connection.conn = conn
sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "amqp connection is closed")
assert.Nil(t, sender)
})
t.Run("session error", func(t *testing.T) {
channel := make(chan struct{})
var session *amqpSessionMock = nil
conn := &amqpConnMock{}
conn.On("NewSession", mock.Anything, mock.Anything).Return(session, errors.New("test error"))
conn.On("Done").Return(channelReceiver(channel))
connection.conn = conn
sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "new session: test error")
assert.Nil(t, sender)
})
t.Run("sender error", func(t *testing.T) {
channel := make(chan struct{})
sessionMock := &amqpSessionMock{}
var amqpSender *amqp.Sender = nil
sessionMock.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(amqpSender, errors.New("test error"))
sessionMock.On("Close", mock.Anything).Return(nil)
conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel))
conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil)
connection.conn = conn
sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "new internal sender: test error")
assert.Nil(t, sender)
})
t.Run("session close error", func(t *testing.T) {
channel := make(chan struct{})
sessionMock := &amqpSessionMock{}
var amqpSender *amqp.Sender = nil
sessionMock.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(amqpSender, errors.New("test error"))
sessionMock.On("Close", mock.Anything).Return(errors.New("close error"))
conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel))
conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil)
connection.conn = conn
sender, err := connection.NewSender(context.Background(), "topic")
assert.EqualError(t, err, "new internal sender: test error\nclose session: close error")
assert.Nil(t, sender)
})
t.Run("get sender", func(t *testing.T) {
channel := make(chan struct{})
amqpSender := &amqp.Sender{}
sessionMock := &amqpSessionMock{}
sessionMock.On("NewSender", mock.Anything, mock.Anything, mock.Anything).Return(amqpSender, nil)
conn := &amqpConnMock{}
conn.On("Done").Return(channelReceiver(channel))
conn.On("NewSession", mock.Anything, mock.Anything).Return(sessionMock, nil)
connection.conn = conn
sender, err := connection.NewSender(context.Background(), "topic")
assert.NoError(t, err)
assert.NotNil(t, sender)
assert.Equal(t, amqpSender, sender.sender)
assert.Equal(t, sessionMock, sender.session)
})
}
func Test_AmqpConnection_NewAmqpConnection(t *testing.T) {
config := AmqpConnectionConfig{
BrokerUrl: "brokerUrl",
Username: "username",
Password: "password",
}
connection := NewAmqpConnection(&config, "connectionName")
assert.NotNil(t, connection)
assert.Equal(t, connection.connectionName, "connectionName")
assert.Equal(t, connection.brokerUrl, "brokerUrl")
assert.Equal(t, connection.username, "username")
assert.Equal(t, connection.password, "password")
assert.NotNil(t, connection.dialer)
}
func Test_As(t *testing.T) {
t.Run("error", func(t *testing.T) {
value, err := As[amqp.Message](nil, errors.New("test error"))
assert.EqualError(t, err, "test error")
assert.Nil(t, value)
})
t.Run("value nil", func(t *testing.T) {
value, err := As[amqp.Message](nil, nil)
assert.NoError(t, err)
assert.Nil(t, value)
})
t.Run("value not not type", func(t *testing.T) {
value, err := As[amqp.Message](struct{}{}, nil)
assert.EqualError(t, err, "could not cast value: struct {}")
assert.Nil(t, value)
})
t.Run("cast", func(t *testing.T) {
var sessionAny any = &amqpSessionMock{}
value, err := As[amqpSessionMock](sessionAny, nil)
assert.NoError(t, err)
assert.NotNil(t, value)
})
}