From f715c287e7acaf787fe5da029ad58dec31e71547 Mon Sep 17 00:00:00 2001 From: Chris Busbey <cbusbey@connamara.com> Date: Sat, 6 Aug 2016 15:06:10 -0500 Subject: [PATCH 1/2] adds time range, necessary for session start/end time features --- Makefile | 2 +- internal/time_range.go | 51 +++++++++++++++++++++++++++++++ internal/time_range_test.go | 60 +++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 internal/time_range.go create mode 100644 internal/time_range_test.go diff --git a/Makefile b/Makefile index f00d1a1b..53cf4737 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ lint: golint . test: get - go test -v -cover . ./datadictionary + go test -v -cover . ./datadictionary ./internal _build_all: get go build -v ./... diff --git a/internal/time_range.go b/internal/time_range.go new file mode 100644 index 00000000..4255b4d9 --- /dev/null +++ b/internal/time_range.go @@ -0,0 +1,51 @@ +package internal + +import ( + "errors" + "time" +) + +//UTCTimeOnly represents the time of day in UTC +type UTCTimeOnly struct { + seconds int +} + +const shortForm = "15:04:05" + +var errParseTime = errors.New("Time must be in the format HH:MM:SS") + +//NewTime returns a newly initialized UTCTimeOnly +func NewTime(hour, minute, second int) UTCTimeOnly { + return UTCTimeOnly{second + minute*60 + hour*60*60} +} + +//ParseTime parses a UTCTimeOnly from a string in the format HH:MM:SS +func ParseTime(str string) (UTCTimeOnly, error) { + t, err := time.Parse(shortForm, str) + if err != nil { + return UTCTimeOnly{}, errParseTime + } + + return NewTime(t.Clock()), nil +} + +//TimeRange represents a time band from StartTime to EndTime +type TimeRange struct { + StartTime, EndTime UTCTimeOnly +} + +//IsInRange returns true if time t is within in the time range +func (r *TimeRange) IsInRange(t time.Time) bool { + if r == nil { + return true + } + + ts := NewTime(t.UTC().Clock()).seconds + start := r.StartTime.seconds + end := r.EndTime.seconds + + if start > end { + return !(end < ts && ts < start) + } + return start <= ts && ts <= end +} diff --git a/internal/time_range_test.go b/internal/time_range_test.go new file mode 100644 index 00000000..0703b849 --- /dev/null +++ b/internal/time_range_test.go @@ -0,0 +1,60 @@ +package internal + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestParseTime(t *testing.T) { + to, err := ParseTime("12:34:04") + assert.Nil(t, err) + assert.Equal(t, NewTime(12, 34, 4), to) + + _, err = ParseTime("0:0:0") + assert.NotNil(t, err) + + _, err = ParseTime("00:00") + assert.NotNil(t, err) + + _, err = ParseTime("0000:00") + assert.NotNil(t, err) +} + +func TestTimeRangeIsInRange(t *testing.T) { + start := NewTime(3, 0, 0) + end := NewTime(18, 0, 0) + + now := time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC) + assert.True(t, (&TimeRange{start, end}).IsInRange(now)) + + now = time.Date(2016, time.August, 10, 18, 0, 0, 0, time.UTC) + assert.True(t, (&TimeRange{start, end}).IsInRange(now)) + + now = time.Date(2016, time.August, 10, 2, 0, 0, 0, time.UTC) + assert.False(t, (&TimeRange{start, end}).IsInRange(now)) + + now = time.Date(2016, time.August, 10, 19, 0, 0, 0, time.UTC) + assert.False(t, (&TimeRange{start, end}).IsInRange(now)) + + now = time.Date(2016, time.August, 10, 18, 0, 1, 0, time.UTC) + assert.False(t, (&TimeRange{start, end}).IsInRange(now)) + + start = NewTime(18, 0, 0) + end = NewTime(3, 0, 0) + now = time.Date(2016, time.August, 10, 18, 0, 0, 0, time.UTC) + assert.True(t, (&TimeRange{start, end}).IsInRange(now)) + + now = time.Date(2016, time.August, 10, 3, 0, 0, 0, time.UTC) + assert.True(t, (&TimeRange{start, end}).IsInRange(now)) + + now = time.Date(2016, time.August, 10, 4, 0, 0, 0, time.UTC) + assert.False(t, (&TimeRange{start, end}).IsInRange(now)) + + now = time.Date(2016, time.August, 10, 17, 0, 0, 0, time.UTC) + assert.False(t, (&TimeRange{start, end}).IsInRange(now)) + + var tr *TimeRange + assert.True(t, tr.IsInRange(now), "always in range if time range is nil") +} -- GitLab From 47a65aeb64797a33d709573a2a8b4a1e197ed938 Mon Sep 17 00:00:00 2001 From: Chris Busbey <cbusbey@connamara.com> Date: Sat, 6 Aug 2016 18:19:16 -0500 Subject: [PATCH 2/2] adds session start/end time configuration, more test util --- config/configuration.go | 2 + quickfix_test.go | 41 ++++++++-- session.go | 29 +++++++ session_test.go | 175 ++++++++++++++++++---------------------- test_helpers.go | 2 + 5 files changed, 147 insertions(+), 102 deletions(-) diff --git a/config/configuration.go b/config/configuration.go index 374eb3da..a3ca80a7 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -12,6 +12,8 @@ const ( SocketCertificateFile string = "SocketCertificateFile" SocketCAFile string = "SocketCAFile" DefaultApplVerID string = "DefaultApplVerID" + StartTime string = "StartTime" + EndTime string = "EndTime" DataDictionary string = "DataDictionary" TransportDataDictionary string = "TransportDataDictionary" AppDataDictionary string = "AppDataDictionary" diff --git a/quickfix_test.go b/quickfix_test.go index d7de0efb..e529c1c2 100644 --- a/quickfix_test.go +++ b/quickfix_test.go @@ -12,6 +12,10 @@ type KnowsFieldMap interface { GetInt(Tag) (int, MessageRejectError) } +func (s *QuickFIXSuite) MessageType(msgType string, msg Message) { + s.FieldEquals(tagMsgType, msgType, msg.Header) +} + func (s *QuickFIXSuite) FieldEquals(tag Tag, expectedValue interface{}, fieldMap KnowsFieldMap) { s.Require().True(fieldMap.Has(tag), "Tag %v not set", tag) @@ -29,6 +33,12 @@ func (s *QuickFIXSuite) FieldEquals(tag Tag, expectedValue interface{}, fieldMap } } +func (s *QuickFIXSuite) MessageEqualsBytes(msgBytes []byte, msg Message) { + _, err := msg.Build() + s.Require().Nil(err) + s.Equal(string(msg.rawMessage), string(msgBytes)) +} + type SessionSuite struct { QuickFIXSuite *messageFactory @@ -50,14 +60,18 @@ func (s *SessionSuite) Init() { } } -func (s *SessionSuite) LastToAdminMessageSent() { +func (s *SessionSuite) MessageSentEquals(msg Message) { msgBytes := s.Receiver.LastMessage() - s.NotNil(msgBytes) + s.NotNil(msgBytes, "Message should have been sent") + s.MessageEqualsBytes(msgBytes, msg) +} - _, err := s.mockApp.lastToAdmin.Build() - s.Nil(err) +func (s *SessionSuite) LastToAppMessageSent() { + s.MessageSentEquals(s.mockApp.lastToApp) +} - s.Equal(string(s.mockApp.lastToAdmin.rawMessage), string(msgBytes)) +func (s *SessionSuite) LastToAdminMessageSent() { + s.MessageSentEquals(s.mockApp.lastToAdmin) } func (s *SessionSuite) NoMessageSent() { @@ -75,3 +89,20 @@ func (s *SessionSuite) NextTargetMsgSeqNum(expected int) { func (s *SessionSuite) NextSenderMsgSeqNum(expected int) { s.Equal(expected, s.session.store.NextSenderMsgSeqNum(), "NextSenderMsgSeqNum should be ", expected) } + +func (s *SessionSuite) NoMessagePersisted(seqNum int) { + persistedMessages, err := s.session.store.GetMessages(seqNum, seqNum) + s.Nil(err) + s.Empty(persistedMessages, "The message should not be persisted") +} + +func (s *SessionSuite) MessagePersisted(msg Message) { + var err error + seqNum, err := msg.Header.GetInt(tagMsgSeqNum) + s.Nil(err, "message should have seq num") + + persistedMessages, err := s.session.store.GetMessages(seqNum, seqNum) + s.Nil(err) + s.Len(persistedMessages, 1, "a message should be stored at %v", seqNum) + s.MessageEqualsBytes(persistedMessages[0], msg) +} diff --git a/session.go b/session.go index fff945b4..04fa60eb 100644 --- a/session.go +++ b/session.go @@ -8,6 +8,7 @@ import ( "github.com/quickfixgo/quickfix/config" "github.com/quickfixgo/quickfix/datadictionary" "github.com/quickfixgo/quickfix/enum" + "github.com/quickfixgo/quickfix/internal" ) //The Session is the primary FIX abstraction for message communication @@ -43,6 +44,8 @@ type session struct { //required on logon for FIX.T.1 messages defaultApplVerID string targetDefaultApplVerID string + + sessionTime *internal.TimeRange } func (s *session) logError(err error) { @@ -133,6 +136,32 @@ func newSession(sessionID SessionID, storeFactory MessageStoreFactory, settings } } + switch { + case !settings.HasSetting(config.StartTime) && !settings.HasSetting(config.EndTime): + //no session times + case settings.HasSetting(config.StartTime) && settings.HasSetting(config.EndTime): + var startTimeStr, endTimeStr string + if startTimeStr, err = settings.Setting(config.StartTime); err != nil { + return session, err + } + + if endTimeStr, err = settings.Setting(config.EndTime); err != nil { + return session, err + } + + session.sessionTime = new(internal.TimeRange) + if session.sessionTime.StartTime, err = internal.ParseTime(startTimeStr); err != nil { + return session, err + } + if session.sessionTime.EndTime, err = internal.ParseTime(endTimeStr); err != nil { + return session, err + } + case settings.HasSetting(config.StartTime): + return session, requiredConfigurationMissing(config.EndTime) + case settings.HasSetting(config.EndTime): + return session, requiredConfigurationMissing(config.StartTime) + } + if session.log, err = logFactory.CreateSessionLog(session.sessionID); err != nil { return session, err } diff --git a/session_test.go b/session_test.go index a3a5ae9a..e661237f 100644 --- a/session_test.go +++ b/session_test.go @@ -6,6 +6,7 @@ import ( "github.com/quickfixgo/quickfix/config" "github.com/quickfixgo/quickfix/enum" + "github.com/quickfixgo/quickfix/internal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -189,6 +190,7 @@ func (s *NewSessionTestSuite) TestDefaults() { s.False(session.resetOnLogon) s.False(session.resetOnLogout) + s.Nil(session.sessionTime, "By default, start and end time unset") } func (s *NewSessionTestSuite) TestResetOnLogon() { @@ -225,78 +227,53 @@ func (s *NewSessionTestSuite) TestResetOnLogout() { } } -type SessionSendTestSuite struct { - suite.Suite +func (s *NewSessionTestSuite) TestStartAndEndTime() { + s.SessionSettings.Set(config.StartTime, "12:00:00") + s.SessionSettings.Set(config.EndTime, "14:00:00") + session, err := newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App) + s.Nil(err) + s.NotNil(session.sessionTime) - *messageFactory - *mockApp - receiver *mockSessionReceiver - *session + s.Equal(internal.NewTime(12, 0, 0), session.sessionTime.StartTime) + s.Equal(internal.NewTime(14, 0, 0), session.sessionTime.EndTime) } -func (suite *SessionSendTestSuite) SetupTest() { - suite.mockApp = new(mockApp) - suite.messageFactory = new(messageFactory) - suite.receiver = newMockSessionReceiver() - suite.session = &session{ - store: new(memoryStore), - application: suite, - log: nullLog{}, - messageOut: suite.receiver.sendChannel, - sessionState: inSession{}, - sessionID: SessionID{BeginString: "FIX.4.2", TargetCompID: "TW", SenderCompID: "ISLD"}, - } -} +func (s *NewSessionTestSuite) TestMissingStartOrEndTime() { + s.SessionSettings.Set(config.StartTime, "12:00:00") + _, err := newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App) + s.NotNil(err) -func TestSessionSendTestSuite(t *testing.T) { - suite.Run(t, new(SessionSendTestSuite)) + s.SetupTest() + s.SessionSettings.Set(config.EndTime, "14:00:00") + _, err = newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App) + s.NotNil(err) } -func (suite *SessionSendTestSuite) shouldNotPersistMessage() { - suite.Equal(1, suite.store.NextSenderMsgSeqNum(), "The next sender sequence number should not be incremented") - persistedMessages, err := suite.store.GetMessages(1, 1) - suite.Nil(err) - suite.Len(persistedMessages, 0, "The message should not be persisted") -} +func (s *NewSessionTestSuite) TestStartOrEndTimeParseError() { + s.SessionSettings.Set(config.StartTime, "1200:00") + s.SessionSettings.Set(config.EndTime, "14:00:00") -func (suite *SessionSendTestSuite) shouldPersistMessage() { - suite.Equal(2, suite.store.NextSenderMsgSeqNum(), "The next sender sequence number should be incremented") - persistedMessages, err := suite.store.GetMessages(1, 1) - suite.Nil(err) - suite.Len(persistedMessages, 1, "The message should be persisted") -} + _, err := newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App) + s.NotNil(err) -func (suite *SessionSendTestSuite) shouldPersistMessageWithSequenceNum(expectedSeqNum int) { - suite.Equal(expectedSeqNum+1, suite.store.NextSenderMsgSeqNum(), "The next sender sequence number should be incremented") - persistedMessages, err := suite.store.GetMessages(expectedSeqNum, expectedSeqNum) - suite.Nil(err) - suite.Len(persistedMessages, 1, "The message should be persisted") -} + s.SessionSettings.Set(config.StartTime, "12:00:00") + s.SessionSettings.Set(config.EndTime, "") -func (suite *SessionSendTestSuite) shouldBeType(msg Message, expectedMsgType string) { - actual, err := msg.Header.GetString(tagMsgType) - suite.Nil(err, "Message doesn't have message type") - suite.Equal(expectedMsgType, actual) + _, err = newSession(s.SessionID, s.MessageStoreFactory, s.SessionSettings, s.LogFactory, s.App) + s.NotNil(err) } -func (suite *SessionSendTestSuite) shouldSendMessage() { - suite.NotNil(suite.receiver.LastMessage(), "The message should have been sent") -} -func (suite *SessionSendTestSuite) shouldNotSendMessage() { - suite.Nil(suite.receiver.LastMessage(), "The message should not have been sent") +type SessionSendTestSuite struct { + SessionSuite } -func (suite *SessionSendTestSuite) shouldSendMessages(cnt int) { - for i := 0; i < cnt; i++ { - suite.shouldSendMessage() - } - - suite.shouldNotSendMessage() +func TestSessionSendTestSuite(t *testing.T) { + suite.Run(t, new(SessionSendTestSuite)) } -func (suite *SessionSendTestSuite) sentMessageShouldBe(sentMsg []byte, msg Message) { - expectedBytes, _ := msg.Build() - suite.Equal(string(expectedBytes), string(sentMsg)) +func (suite *SessionSendTestSuite) SetupTest() { + suite.Init() + suite.session.sessionState = inSession{} } func (suite *SessionSendTestSuite) TestQueueForSendAppMessage() { @@ -304,8 +281,10 @@ func (suite *SessionSendTestSuite) TestQueueForSendAppMessage() { require.Nil(suite.T(), suite.queueForSend(suite.NewOrderSingle())) suite.mockApp.AssertExpectations(suite.T()) - suite.shouldPersistMessage() - suite.shouldNotSendMessage() + suite.NoMessageSent() + suite.MessagePersisted(suite.mockApp.lastToApp) + suite.FieldEquals(tagMsgSeqNum, 1, suite.mockApp.lastToApp.Header) + suite.NextSenderMsgSeqNum(2) } func (suite *SessionSendTestSuite) TestQueueForSendDoNotSendAppMessage() { @@ -313,13 +292,17 @@ func (suite *SessionSendTestSuite) TestQueueForSendDoNotSendAppMessage() { suite.Equal(DoNotSend, suite.queueForSend(suite.NewOrderSingle())) suite.AssertExpectations(suite.T()) - suite.shouldNotPersistMessage() - suite.shouldNotSendMessage() + suite.NoMessagePersisted(1) + suite.NoMessageSent() + suite.NextSenderMsgSeqNum(1) suite.mockApp.On("ToAdmin") require.Nil(suite.T(), suite.send(suite.Heartbeat())) + suite.mockApp.AssertExpectations(suite.T()) - suite.shouldSendMessages(1) + suite.LastToAdminMessageSent() + suite.MessagePersisted(suite.mockApp.lastToAdmin) + suite.NextSenderMsgSeqNum(2) } func (suite *SessionSendTestSuite) TestQueueForSendAdminMessage() { @@ -327,8 +310,9 @@ func (suite *SessionSendTestSuite) TestQueueForSendAdminMessage() { require.Nil(suite.T(), suite.queueForSend(suite.Heartbeat())) suite.mockApp.AssertExpectations(suite.T()) - suite.shouldPersistMessage() - suite.shouldNotSendMessage() + suite.MessagePersisted(suite.mockApp.lastToAdmin) + suite.NoMessageSent() + suite.NextSenderMsgSeqNum(2) } func (suite *SessionSendTestSuite) TestSendAppMessage() { @@ -336,8 +320,9 @@ func (suite *SessionSendTestSuite) TestSendAppMessage() { require.Nil(suite.T(), suite.send(suite.NewOrderSingle())) suite.mockApp.AssertExpectations(suite.T()) - suite.shouldPersistMessage() - suite.shouldSendMessage() + suite.MessagePersisted(suite.mockApp.lastToApp) + suite.LastToAppMessageSent() + suite.NextSenderMsgSeqNum(2) } func (suite *SessionSendTestSuite) TestSendAppDoNotSendMessage() { @@ -345,8 +330,8 @@ func (suite *SessionSendTestSuite) TestSendAppDoNotSendMessage() { suite.Equal(DoNotSend, suite.send(suite.NewOrderSingle())) suite.mockApp.AssertExpectations(suite.T()) - suite.shouldNotPersistMessage() - suite.shouldNotSendMessage() + suite.NextSenderMsgSeqNum(1) + suite.NoMessageSent() } func (suite *SessionSendTestSuite) TestSendAdminMessage() { @@ -354,8 +339,8 @@ func (suite *SessionSendTestSuite) TestSendAdminMessage() { require.Nil(suite.T(), suite.send(suite.Heartbeat())) suite.mockApp.AssertExpectations(suite.T()) - suite.shouldPersistMessage() - suite.shouldSendMessage() + suite.LastToAdminMessageSent() + suite.MessagePersisted(suite.mockApp.lastToAdmin) } func (suite *SessionSendTestSuite) TestSendFlushesQueue() { @@ -364,13 +349,20 @@ func (suite *SessionSendTestSuite) TestSendFlushesQueue() { require.Nil(suite.T(), suite.queueForSend(suite.NewOrderSingle())) require.Nil(suite.T(), suite.queueForSend(suite.Heartbeat())) + order1 := suite.mockApp.lastToApp + heartbeat := suite.mockApp.lastToAdmin + suite.mockApp.AssertExpectations(suite.T()) - suite.shouldNotSendMessage() + suite.NoMessageSent() suite.mockApp.On("ToApp").Return(nil) require.Nil(suite.T(), suite.send(suite.NewOrderSingle())) suite.mockApp.AssertExpectations(suite.T()) - suite.shouldSendMessages(3) + order2 := suite.mockApp.lastToApp + suite.MessageSentEquals(order1) + suite.MessageSentEquals(heartbeat) + suite.MessageSentEquals(order2) + suite.NoMessageSent() } func (suite *SessionSendTestSuite) TestSendNotLoggedOn() { @@ -380,7 +372,7 @@ func (suite *SessionSendTestSuite) TestSendNotLoggedOn() { require.Nil(suite.T(), suite.queueForSend(suite.Heartbeat())) suite.mockApp.AssertExpectations(suite.T()) - suite.shouldNotSendMessage() + suite.NoMessageSent() var tests = []sessionState{logoutState{}, latentState{}, logonState{}} @@ -389,7 +381,7 @@ func (suite *SessionSendTestSuite) TestSendNotLoggedOn() { suite.sessionState = test require.Nil(suite.T(), suite.send(suite.NewOrderSingle())) suite.mockApp.AssertExpectations(suite.T()) - suite.shouldNotSendMessage() + suite.NoMessageSent() } } @@ -398,8 +390,8 @@ func (suite *SessionSendTestSuite) TestDropAndSendAdminMessage() { suite.Require().Nil(suite.dropAndSend(suite.Heartbeat(), false)) suite.mockApp.AssertExpectations(suite.T()) - suite.shouldPersistMessage() - suite.shouldSendMessage() + suite.MessagePersisted(suite.mockApp.lastToAdmin) + suite.LastToAdminMessageSent() } func (suite *SessionSendTestSuite) TestDropAndSendDropsQueue() { @@ -409,25 +401,19 @@ func (suite *SessionSendTestSuite) TestDropAndSendDropsQueue() { require.Nil(suite.T(), suite.queueForSend(suite.Heartbeat())) suite.mockApp.AssertExpectations(suite.T()) - suite.shouldNotSendMessage() + suite.NoMessageSent() suite.mockApp.On("ToAdmin") require.Nil(suite.T(), suite.dropAndSend(suite.Logon(), false)) suite.mockApp.AssertExpectations(suite.T()) msg := suite.mockApp.lastToAdmin - suite.shouldBeType(msg, enum.MsgType_LOGON) - - seqNum, err := msg.Header.GetInt(tagMsgSeqNum) - suite.Nil(err) - suite.Equal(3, seqNum) + suite.MessageType(enum.MsgType_LOGON, msg) + suite.FieldEquals(tagMsgSeqNum, 3, msg.Header) //only one message sent - sentMsgBytes := suite.receiver.LastMessage() - suite.NotNil(sentMsgBytes) - suite.shouldNotSendMessage() - - suite.sentMessageShouldBe(sentMsgBytes, msg) + suite.LastToAdminMessageSent() + suite.NoMessageSent() } func (suite *SessionSendTestSuite) TestDropAndSendDropsQueueWithReset() { @@ -436,22 +422,17 @@ func (suite *SessionSendTestSuite) TestDropAndSendDropsQueueWithReset() { require.Nil(suite.T(), suite.queueForSend(suite.NewOrderSingle())) require.Nil(suite.T(), suite.queueForSend(suite.Heartbeat())) suite.mockApp.AssertExpectations(suite.T()) - suite.shouldNotSendMessage() + suite.NoMessageSent() suite.mockApp.On("ToAdmin") require.Nil(suite.T(), suite.dropAndSend(suite.Logon(), true)) suite.mockApp.AssertExpectations(suite.T()) msg := suite.mockApp.lastToAdmin - suite.shouldBeType(msg, enum.MsgType_LOGON) - seqNum, err := msg.Header.GetInt(tagMsgSeqNum) - suite.Nil(err) - suite.Equal(1, seqNum) + suite.MessageType(enum.MsgType_LOGON, msg) + suite.FieldEquals(tagMsgSeqNum, 1, msg.Header) //only one message sent - sentMsgBytes := suite.receiver.LastMessage() - suite.NotNil(sentMsgBytes) - suite.shouldNotSendMessage() - - suite.sentMessageShouldBe(sentMsgBytes, msg) + suite.LastToAdminMessageSent() + suite.NoMessageSent() } diff --git a/test_helpers.go b/test_helpers.go index 75a9c9f6..093bfc99 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -44,6 +44,7 @@ type mockApp struct { mock.Mock lastToAdmin Message + lastToApp Message } func (e *mockApp) OnCreate(sessionID SessionID) { @@ -70,6 +71,7 @@ func (e *mockApp) ToAdmin(msg Message, sessionID SessionID) { } func (e *mockApp) ToApp(msg Message, sessionID SessionID) (err error) { + e.lastToApp = msg return e.Called().Error(0) } -- GitLab