// Copyright (c) quickfixengine.org All rights reserved. // // This file may be distributed under the terms of the quickfixengine.org // license as defined by quickfixengine.org and appearing in the file // LICENSE included in the packaging of this file. // // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING // THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A // PARTICULAR PURPOSE. // // See http://www.quickfixengine.org/LICENSE for licensing information. // // Contact ask@quickfixengine.org if any conditions of this licensing // are not clear to you. package quickfix import ( "bytes" "testing" "time" "quantex.com/qfixdpl/quickfix/internal" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) func newFIXString(val string) *FIXString { s := FIXString(val) return &s } type SessionSuite struct { SessionSuiteRig } func TestSessionSuite(t *testing.T) { suite.Run(t, new(SessionSuite)) } func (s *SessionSuite) SetupTest() { s.Init() s.Require().Nil(s.session.store.Reset()) s.session.State = latentState{} } func (s *SessionSuite) TestFillDefaultHeader() { s.session.sessionID.BeginString = "FIX.4.2" s.session.sessionID.TargetCompID = "TAR" s.session.sessionID.SenderCompID = "SND" msg := NewMessage() s.session.fillDefaultHeader(msg, nil) s.FieldEquals(tagBeginString, "FIX.4.2", msg.Header) s.FieldEquals(tagTargetCompID, "TAR", msg.Header) s.FieldEquals(tagSenderCompID, "SND", msg.Header) s.False(msg.Header.Has(tagSenderSubID)) s.False(msg.Header.Has(tagSenderLocationID)) s.False(msg.Header.Has(tagTargetSubID)) s.False(msg.Header.Has(tagTargetLocationID)) s.session.sessionID.BeginString = "FIX.4.3" s.session.sessionID.TargetCompID = "TAR" s.session.sessionID.TargetSubID = "TARS" s.session.sessionID.TargetLocationID = "TARL" s.session.sessionID.SenderCompID = "SND" s.session.sessionID.SenderSubID = "SNDS" s.session.sessionID.SenderLocationID = "SNDL" msg = NewMessage() s.session.fillDefaultHeader(msg, nil) s.FieldEquals(tagBeginString, "FIX.4.3", msg.Header) s.FieldEquals(tagTargetCompID, "TAR", msg.Header) s.FieldEquals(tagTargetSubID, "TARS", msg.Header) s.FieldEquals(tagTargetLocationID, "TARL", msg.Header) s.FieldEquals(tagSenderCompID, "SND", msg.Header) s.FieldEquals(tagSenderSubID, "SNDS", msg.Header) s.FieldEquals(tagSenderLocationID, "SNDL", msg.Header) } func (s *SessionSuite) TestInsertSendingTime() { var tests = []struct { BeginString string Precision TimestampPrecision ExpectedPrecision TimestampPrecision }{ {BeginStringFIX40, Millis, Seconds}, // Config is ignored for fix < 4.2. {BeginStringFIX41, Millis, Seconds}, {BeginStringFIX42, Millis, Millis}, {BeginStringFIX42, Micros, Micros}, {BeginStringFIX42, Nanos, Nanos}, {BeginStringFIX43, Nanos, Nanos}, {BeginStringFIX44, Nanos, Nanos}, {BeginStringFIXT11, Nanos, Nanos}, } for _, test := range tests { s.session.sessionID.BeginString = test.BeginString s.timestampPrecision = test.Precision msg := NewMessage() s.session.insertSendingTime(msg) var f FIXUTCTimestamp s.Nil(msg.Header.GetField(tagSendingTime, &f)) s.Equal(f.Precision, test.ExpectedPrecision) } } func (s *SessionSuite) TestCheckCorrectCompID() { s.session.sessionID.TargetCompID = "TAR" s.session.sessionID.SenderCompID = "SND" var testCases = []struct { senderCompID *FIXString targetCompID *FIXString returnsError bool rejectReason int }{ {returnsError: true, rejectReason: rejectReasonRequiredTagMissing}, {senderCompID: newFIXString("TAR"), returnsError: true, rejectReason: rejectReasonRequiredTagMissing}, {senderCompID: newFIXString("TAR"), targetCompID: newFIXString("JCD"), returnsError: true, rejectReason: rejectReasonCompIDProblem}, {senderCompID: newFIXString("JCD"), targetCompID: newFIXString("SND"), returnsError: true, rejectReason: rejectReasonCompIDProblem}, {senderCompID: newFIXString("TAR"), targetCompID: newFIXString("SND"), returnsError: false}, } for _, tc := range testCases { msg := NewMessage() if tc.senderCompID != nil { msg.Header.SetField(tagSenderCompID, tc.senderCompID) } if tc.targetCompID != nil { msg.Header.SetField(tagTargetCompID, tc.targetCompID) } rej := s.session.checkCompID(msg) if !tc.returnsError { s.Require().Nil(rej) continue } s.NotNil(rej) s.Equal(tc.rejectReason, rej.RejectReason()) } } func (s *SessionSuite) TestCheckBeginString() { msg := NewMessage() msg.Header.SetField(tagBeginString, FIXString("FIX.4.4")) err := s.session.checkBeginString(msg) s.Require().NotNil(err, "wrong begin string should return error") s.IsType(incorrectBeginString{}, err) msg.Header.SetField(tagBeginString, FIXString(s.session.sessionID.BeginString)) s.Nil(s.session.checkBeginString(msg)) } func (s *SessionSuite) TestCheckTargetTooHigh() { msg := NewMessage() s.Require().Nil(s.session.store.SetNextTargetMsgSeqNum(45)) err := s.session.checkTargetTooHigh(msg) s.Require().NotNil(err, "missing sequence number should return error") s.Equal(rejectReasonRequiredTagMissing, err.RejectReason()) msg.Header.SetField(tagMsgSeqNum, FIXInt(47)) err = s.session.checkTargetTooHigh(msg) s.Require().NotNil(err, "sequence number too high should return an error") s.IsType(targetTooHigh{}, err) // Spot on. msg.Header.SetField(tagMsgSeqNum, FIXInt(45)) s.Nil(s.session.checkTargetTooHigh(msg)) } func (s *SessionSuite) TestCheckSendingTime() { s.session.MaxLatency = time.Duration(120) * time.Second msg := NewMessage() err := s.session.checkSendingTime(msg) s.Require().NotNil(err, "sending time is a required field") s.Equal(rejectReasonRequiredTagMissing, err.RejectReason()) sendingTime := time.Now().Add(time.Duration(-200) * time.Second) msg.Header.SetField(tagSendingTime, FIXUTCTimestamp{Time: sendingTime}) err = s.session.checkSendingTime(msg) s.Require().NotNil(err, "sending time too late should give error") s.Equal(rejectReasonSendingTimeAccuracyProblem, err.RejectReason()) sendingTime = time.Now().Add(time.Duration(200) * time.Second) msg.Header.SetField(tagSendingTime, FIXUTCTimestamp{Time: sendingTime}) err = s.session.checkSendingTime(msg) s.Require().NotNil(err, "future sending time should give error") s.Equal(rejectReasonSendingTimeAccuracyProblem, err.RejectReason()) sendingTime = time.Now() msg.Header.SetField(tagSendingTime, FIXUTCTimestamp{Time: sendingTime}) s.Nil(s.session.checkSendingTime(msg), "sending time should be ok") s.session.SkipCheckLatency = true sendingTime = time.Now().Add(time.Duration(-200) * time.Second) msg.Header.SetField(tagSendingTime, FIXUTCTimestamp{Time: sendingTime}) err = s.session.checkSendingTime(msg) s.Require().Nil(err, "should skip latency check") } func (s *SessionSuite) TestCheckTargetTooLow() { msg := NewMessage() s.Require().Nil(s.session.store.SetNextTargetMsgSeqNum(45)) err := s.session.checkTargetTooLow(msg) s.Require().NotNil(err, "sequence number is required") s.Equal(rejectReasonRequiredTagMissing, err.RejectReason()) // Too low. msg.Header.SetField(tagMsgSeqNum, FIXInt(43)) err = s.session.checkTargetTooLow(msg) s.NotNil(err, "sequence number too low should return error") s.IsType(targetTooLow{}, err) // Spot on. msg.Header.SetField(tagMsgSeqNum, FIXInt(45)) s.Nil(s.session.checkTargetTooLow(msg)) } func (s *SessionSuite) TestShouldSendReset() { var tests = []struct { BeginString string ResetOnLogon bool ResetOnDisconnect bool ResetOnLogout bool NextSenderMsgSeqNum int NextTargetMsgSeqNum int Expected bool }{ {BeginStringFIX40, true, false, false, 1, 1, false}, // ResetSeqNumFlag not available < fix41. {BeginStringFIX41, true, false, false, 1, 1, true}, // Session must be configured to reset on logon. {BeginStringFIX42, true, false, false, 1, 1, true}, {BeginStringFIX43, true, false, false, 1, 1, true}, {BeginStringFIX44, true, false, false, 1, 1, true}, {BeginStringFIXT11, true, false, false, 1, 1, true}, {BeginStringFIX41, false, true, false, 1, 1, true}, // Or disconnect. {BeginStringFIX42, false, true, false, 1, 1, true}, {BeginStringFIX43, false, true, false, 1, 1, true}, {BeginStringFIX44, false, true, false, 1, 1, true}, {BeginStringFIXT11, false, true, false, 1, 1, true}, {BeginStringFIX41, false, false, true, 1, 1, true}, // Or logout. {BeginStringFIX42, false, false, true, 1, 1, true}, {BeginStringFIX43, false, false, true, 1, 1, true}, {BeginStringFIX44, false, false, true, 1, 1, true}, {BeginStringFIXT11, false, false, true, 1, 1, true}, {BeginStringFIX41, true, true, false, 1, 1, true}, // Or combo. {BeginStringFIX42, false, true, true, 1, 1, true}, {BeginStringFIX43, true, false, true, 1, 1, true}, {BeginStringFIX44, true, true, true, 1, 1, true}, {BeginStringFIX41, false, false, false, 1, 1, false}, // Or will not be set. {BeginStringFIX41, true, false, false, 1, 10, false}, // Session seq numbers should be reset at the time of check. {BeginStringFIX42, true, false, false, 2, 1, false}, {BeginStringFIX43, true, false, false, 14, 100, false}, } for _, test := range tests { s.session.sessionID.BeginString = test.BeginString s.session.ResetOnLogon = test.ResetOnLogon s.session.ResetOnDisconnect = test.ResetOnDisconnect s.session.ResetOnLogout = test.ResetOnLogout s.Require().Nil(s.MockStore.SetNextSenderMsgSeqNum(test.NextSenderMsgSeqNum)) s.Require().Nil(s.MockStore.SetNextTargetMsgSeqNum(test.NextTargetMsgSeqNum)) s.Equal(s.shouldSendReset(), test.Expected) } } func (s *SessionSuite) TestCheckSessionTimeNoStartTimeEndTime() { var tests = []struct { before, after sessionState }{ {before: latentState{}}, {before: logonState{}}, {before: logoutState{}}, {before: inSession{}}, {before: resendState{}}, {before: pendingTimeout{resendState{}}}, {before: pendingTimeout{inSession{}}}, {before: notSessionTime{}, after: latentState{}}, } for _, test := range tests { s.SetupTest() s.session.SessionTime = nil s.session.State = test.before s.session.CheckSessionTime(s.session, time.Now()) if test.after != nil { s.State(test.after) } else { s.State(test.before) } } } func (s *SessionSuite) TestCheckSessionTimeInRange() { var tests = []struct { before, after sessionState expectReset bool }{ {before: latentState{}}, {before: logonState{}}, {before: logoutState{}}, {before: inSession{}}, {before: resendState{}}, {before: pendingTimeout{resendState{}}}, {before: pendingTimeout{inSession{}}}, {before: notSessionTime{}, after: latentState{}, expectReset: true}, } for _, test := range tests { s.SetupTest() s.session.State = test.before now := time.Now().UTC() memStore, memErr := NewMemoryStoreFactory().Create(s.sessionID) s.Require().Nil(memErr) if test.before.IsSessionTime() { s.Require().Nil(memStore.Reset()) } else { memStore.SetCreationTime(now.Add(time.Duration(-1) * time.Minute)) } s.session.store = memStore s.IncrNextSenderMsgSeqNum() s.IncrNextTargetMsgSeqNum() sessionTime, err := internal.NewUTCTimeRange( internal.NewTimeOfDay(now.Clock()), internal.NewTimeOfDay(now.Add(time.Hour).Clock()), []time.Weekday{}, ) s.Nil(err) s.session.SessionTime = sessionTime s.session.CheckSessionTime(s.session, now) if test.after != nil { s.State(test.after) } else { s.State(test.before) } if test.expectReset { s.ExpectStoreReset() } else { s.NextSenderMsgSeqNum(2) s.NextSenderMsgSeqNum(2) } } } func (s *SessionSuite) TestCheckSessionTimeNotInRange() { var tests = []struct { before sessionState initiateLogon bool expectOnLogout bool expectSendLogout bool }{ {before: latentState{}}, {before: logonState{}}, {before: logonState{}, initiateLogon: true, expectOnLogout: true}, {before: logoutState{}, expectOnLogout: true}, {before: inSession{}, expectOnLogout: true, expectSendLogout: true}, {before: resendState{}, expectOnLogout: true, expectSendLogout: true}, {before: pendingTimeout{resendState{}}, expectOnLogout: true, expectSendLogout: true}, {before: pendingTimeout{inSession{}}, expectOnLogout: true, expectSendLogout: true}, {before: notSessionTime{}}, } for _, test := range tests { s.SetupTest() s.session.State = test.before s.session.InitiateLogon = test.initiateLogon s.IncrNextSenderMsgSeqNum() s.IncrNextTargetMsgSeqNum() now := time.Now().UTC() sessionTime, err := internal.NewUTCTimeRange( internal.NewTimeOfDay(now.Add(time.Hour).Clock()), internal.NewTimeOfDay(now.Add(time.Duration(2)*time.Hour).Clock()), []time.Weekday{}, ) s.Nil(err) s.session.SessionTime = sessionTime if test.expectOnLogout { s.MockApp.On("OnLogout") } if test.expectSendLogout { s.MockApp.On("ToAdmin") } s.session.CheckSessionTime(s.session, now) s.MockApp.AssertExpectations(s.T()) s.State(notSessionTime{}) s.NextTargetMsgSeqNum(2) if test.expectSendLogout { s.LastToAdminMessageSent() s.MessageType(string(msgTypeLogout), s.MockApp.lastToAdmin) s.NextSenderMsgSeqNum(3) } else { s.NextSenderMsgSeqNum(2) } } } func (s *SessionSuite) TestCheckSessionTimeInRangeButNotSameRangeAsStore() { var tests = []struct { before sessionState initiateLogon bool expectOnLogout bool expectSendLogout bool }{ {before: latentState{}}, {before: logonState{}}, {before: logonState{}, initiateLogon: true, expectOnLogout: true}, {before: logoutState{}, expectOnLogout: true}, {before: inSession{}, expectOnLogout: true, expectSendLogout: true}, {before: resendState{}, expectOnLogout: true, expectSendLogout: true}, {before: pendingTimeout{resendState{}}, expectOnLogout: true, expectSendLogout: true}, {before: pendingTimeout{inSession{}}, expectOnLogout: true, expectSendLogout: true}, {before: notSessionTime{}}, } for _, test := range tests { s.SetupTest() s.session.State = test.before s.session.InitiateLogon = test.initiateLogon s.Require().Nil(s.store.Reset()) s.IncrNextSenderMsgSeqNum() s.IncrNextTargetMsgSeqNum() now := time.Now().UTC() sessionTime, err := internal.NewUTCTimeRange( internal.NewTimeOfDay(now.Add(time.Duration(-1)*time.Hour).Clock()), internal.NewTimeOfDay(now.Add(time.Hour).Clock()), []time.Weekday{}, ) s.Nil(err) s.session.SessionTime = sessionTime if test.expectOnLogout { s.MockApp.On("OnLogout") } if test.expectSendLogout { s.MockApp.On("ToAdmin") } s.session.CheckSessionTime(s.session, now.AddDate(0, 0, 1)) s.MockApp.AssertExpectations(s.T()) s.State(latentState{}) if test.expectSendLogout { s.LastToAdminMessageSent() s.MessageType(string(msgTypeLogout), s.MockApp.lastToAdmin) s.FieldEquals(tagMsgSeqNum, 2, s.MockApp.lastToAdmin.Header) } s.ExpectStoreReset() } } func (s *SessionSuite) TestIncomingNotInSessionTime() { var tests = []struct { before sessionState initiateLogon bool expectOnLogout bool expectSendLogout bool }{ {before: logonState{}}, {before: logonState{}, initiateLogon: true, expectOnLogout: true}, {before: logoutState{}, expectOnLogout: true}, {before: inSession{}, expectOnLogout: true, expectSendLogout: true}, {before: resendState{}, expectOnLogout: true, expectSendLogout: true}, {before: pendingTimeout{resendState{}}, expectOnLogout: true, expectSendLogout: true}, {before: pendingTimeout{inSession{}}, expectOnLogout: true, expectSendLogout: true}, } for _, test := range tests { s.SetupTest() s.session.State = test.before s.session.InitiateLogon = test.initiateLogon s.IncrNextSenderMsgSeqNum() s.IncrNextTargetMsgSeqNum() now := time.Now().UTC() sessionTime, err := internal.NewUTCTimeRange( internal.NewTimeOfDay(now.Add(time.Hour).Clock()), internal.NewTimeOfDay(now.Add(time.Duration(2)*time.Hour).Clock()), []time.Weekday{}, ) s.Nil(err) s.session.SessionTime = sessionTime if test.expectOnLogout { s.MockApp.On("OnLogout") } if test.expectSendLogout { s.MockApp.On("ToAdmin") } msg := s.NewOrderSingle() msgBytes := msg.build() s.session.Incoming(s.session, fixIn{bytes: bytes.NewBuffer(msgBytes)}) s.MockApp.AssertExpectations(s.T()) s.State(notSessionTime{}) } } func (s *SessionSuite) TestSendAppMessagesNotInSessionTime() { var tests = []struct { before sessionState initiateLogon bool expectOnLogout bool expectSendLogout bool }{ {before: logonState{}}, {before: logonState{}, initiateLogon: true, expectOnLogout: true}, {before: logoutState{}, expectOnLogout: true}, {before: inSession{}, expectOnLogout: true, expectSendLogout: true}, {before: resendState{}, expectOnLogout: true, expectSendLogout: true}, {before: pendingTimeout{resendState{}}, expectOnLogout: true, expectSendLogout: true}, {before: pendingTimeout{inSession{}}, expectOnLogout: true, expectSendLogout: true}, } for _, test := range tests { s.SetupTest() s.session.State = test.before s.session.InitiateLogon = test.initiateLogon s.IncrNextSenderMsgSeqNum() s.IncrNextTargetMsgSeqNum() s.MockApp.On("ToApp").Return(nil) s.Require().Nil(s.queueForSend(s.NewOrderSingle())) s.MockApp.AssertExpectations(s.T()) now := time.Now().UTC() sessionTime, err := internal.NewUTCTimeRange( internal.NewTimeOfDay(now.Add(time.Hour).Clock()), internal.NewTimeOfDay(now.Add(time.Duration(2)*time.Hour).Clock()), []time.Weekday{}, ) s.Nil(err) s.session.SessionTime = sessionTime if test.expectOnLogout { s.MockApp.On("OnLogout") } if test.expectSendLogout { s.MockApp.On("ToAdmin") } s.session.SendAppMessages(s.session) s.MockApp.AssertExpectations(s.T()) s.State(notSessionTime{}) } } func (s *SessionSuite) TestTimeoutNotInSessionTime() { var tests = []struct { before sessionState initiateLogon bool expectOnLogout bool expectSendLogout bool }{ {before: logonState{}}, {before: logonState{}, initiateLogon: true, expectOnLogout: true}, {before: logoutState{}, expectOnLogout: true}, {before: inSession{}, expectOnLogout: true, expectSendLogout: true}, {before: resendState{}, expectOnLogout: true, expectSendLogout: true}, {before: pendingTimeout{resendState{}}, expectOnLogout: true, expectSendLogout: true}, {before: pendingTimeout{inSession{}}, expectOnLogout: true, expectSendLogout: true}, } var events = []internal.Event{internal.PeerTimeout, internal.NeedHeartbeat, internal.LogonTimeout, internal.LogoutTimeout} for _, test := range tests { for _, event := range events { s.SetupTest() s.session.State = test.before s.session.InitiateLogon = test.initiateLogon s.IncrNextSenderMsgSeqNum() s.IncrNextTargetMsgSeqNum() now := time.Now().UTC() sessionTime, err := internal.NewUTCTimeRange( internal.NewTimeOfDay(now.Add(time.Hour).Clock()), internal.NewTimeOfDay(now.Add(time.Duration(2)*time.Hour).Clock()), []time.Weekday{}, ) s.Nil(err) s.session.SessionTime = sessionTime if test.expectOnLogout { s.MockApp.On("OnLogout") } if test.expectSendLogout { s.MockApp.On("ToAdmin") } s.session.Timeout(s.session, event) s.MockApp.AssertExpectations(s.T()) s.State(notSessionTime{}) } } } func (s *SessionSuite) TestOnAdminConnectInitiateLogon() { adminMsg := connect{ messageOut: s.Receiver.sendChannel, } s.session.State = latentState{} s.session.HeartBtInt = time.Duration(45) * time.Second s.IncrNextSenderMsgSeqNum() s.session.InitiateLogon = true s.MockApp.On("ToAdmin") s.session.onAdmin(adminMsg) s.MockApp.AssertExpectations(s.T()) s.True(s.session.InitiateLogon) s.False(s.sentReset) s.State(logonState{}) s.LastToAdminMessageSent() s.MessageType(string(msgTypeLogon), s.MockApp.lastToAdmin) s.FieldEquals(tagHeartBtInt, 45, s.MockApp.lastToAdmin.Body) s.FieldEquals(tagMsgSeqNum, 2, s.MockApp.lastToAdmin.Header) s.NextSenderMsgSeqNum(3) } func (s *SessionSuite) TestInitiateLogonResetSeqNumFlag() { adminMsg := connect{ messageOut: s.Receiver.sendChannel, } s.session.State = latentState{} s.session.HeartBtInt = time.Duration(45) * time.Second s.Require().Nil(s.store.Reset()) s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() s.session.ResetOnLogon = true s.session.InitiateLogon = true s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.MockApp.On("ToAdmin") s.session.onAdmin(adminMsg) s.MockApp.AssertExpectations(s.T()) s.True(s.session.InitiateLogon) s.True(s.sentReset) s.State(logonState{}) s.LastToAdminMessageSent() s.MessageType(string(msgTypeLogon), s.MockApp.lastToAdmin) s.FieldEquals(tagMsgSeqNum, 1, s.MockApp.lastToAdmin.Header) s.FieldEquals(tagResetSeqNumFlag, true, s.MockApp.lastToAdmin.Body) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(1) } func (s *SessionSuite) TestOnAdminConnectInitiateLogonFIXT11() { s.session.sessionID.BeginString = string(BeginStringFIXT11) s.session.DefaultApplVerID = "8" s.session.InitiateLogon = true adminMsg := connect{ messageOut: s.Receiver.sendChannel, } s.session.State = latentState{} s.MockApp.On("ToAdmin") s.session.onAdmin(adminMsg) s.MockApp.AssertExpectations(s.T()) s.True(s.session.InitiateLogon) s.State(logonState{}) s.LastToAdminMessageSent() s.MessageType(string(msgTypeLogon), s.MockApp.lastToAdmin) s.FieldEquals(tagDefaultApplVerID, "8", s.MockApp.lastToAdmin.Body) } func (s *SessionSuite) TestOnAdminConnectRefreshOnLogon() { var tests = []bool{true, false} for _, doRefresh := range tests { s.SetupTest() s.session.RefreshOnLogon = doRefresh adminMsg := connect{ messageOut: s.Receiver.sendChannel, } s.session.State = latentState{} s.session.InitiateLogon = true if doRefresh { s.MockStore.On("Refresh").Return(nil) } s.MockApp.On("ToAdmin") s.session.onAdmin(adminMsg) s.MockStore.AssertExpectations(s.T()) } } func (s *SessionSuite) TestOnAdminConnectAccept() { adminMsg := connect{ messageOut: s.Receiver.sendChannel, } s.session.State = latentState{} s.IncrNextSenderMsgSeqNum() s.session.onAdmin(adminMsg) s.False(s.session.InitiateLogon) s.State(logonState{}) s.NoMessageSent() s.NextSenderMsgSeqNum(2) } func (s *SessionSuite) TestOnAdminConnectNotInSession() { var tests = []bool{true, false} for _, doInitiateLogon := range tests { s.SetupTest() s.session.State = notSessionTime{} s.IncrNextSenderMsgSeqNum() s.session.InitiateLogon = doInitiateLogon adminMsg := connect{ messageOut: s.Receiver.sendChannel, } s.session.onAdmin(adminMsg) s.State(notSessionTime{}) s.NoMessageSent() s.Disconnected() s.NextSenderMsgSeqNum(2) } } func (s *SessionSuite) TestOnAdminStop() { s.session.State = logonState{} s.session.onAdmin(stopReq{}) s.Disconnected() s.Stopped() } func (s *SessionSuite) TestResetOnDisconnect() { s.IncrNextSenderMsgSeqNum() s.IncrNextTargetMsgSeqNum() s.session.ResetOnDisconnect = false s.session.onDisconnect() s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.session.ResetOnDisconnect = true s.session.onDisconnect() s.ExpectStoreReset() } type SessionSendTestSuite struct { SessionSuiteRig } func TestSessionSendTestSuite(t *testing.T) { suite.Run(t, new(SessionSendTestSuite)) } func (suite *SessionSendTestSuite) SetupTest() { suite.Init() suite.session.State = inSession{} } func (suite *SessionSendTestSuite) TestQueueForSendAppMessage() { suite.MockApp.On("ToApp").Return(nil) require.Nil(suite.T(), suite.queueForSend(suite.NewOrderSingle())) suite.MockApp.AssertExpectations(suite.T()) suite.NoMessageSent() suite.MessagePersisted(suite.MockApp.lastToApp) suite.FieldEquals(tagMsgSeqNum, 1, suite.MockApp.lastToApp.Header) suite.NextSenderMsgSeqNum(2) } func (suite *SessionSendTestSuite) TestQueueForSendDoNotSendAppMessage() { suite.MockApp.On("ToApp").Return(ErrDoNotSend) suite.Equal(ErrDoNotSend, suite.queueForSend(suite.NewOrderSingle())) suite.MockApp.AssertExpectations(suite.T()) 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.LastToAdminMessageSent() suite.MessagePersisted(suite.MockApp.lastToAdmin) suite.NextSenderMsgSeqNum(2) } func (suite *SessionSendTestSuite) TestQueueForSendAdminMessage() { suite.MockApp.On("ToAdmin") require.Nil(suite.T(), suite.queueForSend(suite.Heartbeat())) suite.MockApp.AssertExpectations(suite.T()) suite.MessagePersisted(suite.MockApp.lastToAdmin) suite.NoMessageSent() suite.NextSenderMsgSeqNum(2) } func (suite *SessionSendTestSuite) TestSendAppMessage() { suite.MockApp.On("ToApp").Return(nil) require.Nil(suite.T(), suite.send(suite.NewOrderSingle())) suite.MockApp.AssertExpectations(suite.T()) suite.MessagePersisted(suite.MockApp.lastToApp) suite.LastToAppMessageSent() suite.NextSenderMsgSeqNum(2) } func (suite *SessionSendTestSuite) TestSendAppDoNotSendMessage() { suite.MockApp.On("ToApp").Return(ErrDoNotSend) suite.Equal(ErrDoNotSend, suite.send(suite.NewOrderSingle())) suite.MockApp.AssertExpectations(suite.T()) suite.NextSenderMsgSeqNum(1) suite.NoMessageSent() } func (suite *SessionSendTestSuite) TestSendAdminMessage() { suite.MockApp.On("ToAdmin") require.Nil(suite.T(), suite.send(suite.Heartbeat())) suite.MockApp.AssertExpectations(suite.T()) suite.LastToAdminMessageSent() suite.MessagePersisted(suite.MockApp.lastToAdmin) } func (suite *SessionSendTestSuite) TestSendFlushesQueue() { suite.MockApp.On("ToApp").Return(nil) suite.MockApp.On("ToAdmin") 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.NoMessageSent() suite.MockApp.On("ToApp").Return(nil) require.Nil(suite.T(), suite.send(suite.NewOrderSingle())) suite.MockApp.AssertExpectations(suite.T()) order2 := suite.MockApp.lastToApp suite.MessageSentEquals(order1) suite.MessageSentEquals(heartbeat) suite.MessageSentEquals(order2) suite.NoMessageSent() } func (suite *SessionSendTestSuite) TestSendNotLoggedOn() { suite.MockApp.On("ToApp").Return(nil) suite.MockApp.On("ToAdmin") require.Nil(suite.T(), suite.queueForSend(suite.NewOrderSingle())) require.Nil(suite.T(), suite.queueForSend(suite.Heartbeat())) suite.MockApp.AssertExpectations(suite.T()) suite.NoMessageSent() var tests = []sessionState{logoutState{}, latentState{}, logonState{}} for _, test := range tests { suite.MockApp.On("ToApp").Return(nil) suite.session.State = test require.Nil(suite.T(), suite.send(suite.NewOrderSingle())) suite.MockApp.AssertExpectations(suite.T()) suite.NoMessageSent() } } func (suite *SessionSendTestSuite) TestSendEnableLastMsgSeqNumProcessed() { suite.session.State = inSession{} suite.session.EnableLastMsgSeqNumProcessed = true suite.Require().Nil(suite.session.store.SetNextTargetMsgSeqNum(45)) suite.MockApp.On("ToApp").Return(nil) require.Nil(suite.T(), suite.send(suite.NewOrderSingle())) suite.MockApp.AssertExpectations(suite.T()) suite.LastToAppMessageSent() suite.FieldEquals(tagLastMsgSeqNumProcessed, 44, suite.MockApp.lastToApp.Header) } func (suite *SessionSendTestSuite) TestSendDisableMessagePersist() { suite.session.State = inSession{} suite.session.DisableMessagePersist = true suite.MockApp.On("ToApp").Return(nil) require.Nil(suite.T(), suite.send(suite.NewOrderSingle())) suite.MockApp.AssertExpectations(suite.T()) suite.LastToAppMessageSent() suite.NoMessagePersisted(1) suite.NextSenderMsgSeqNum(2) } func (suite *SessionSendTestSuite) TestDropAndSendAdminMessage() { suite.MockApp.On("ToAdmin") suite.Require().Nil(suite.dropAndSend(suite.Heartbeat())) suite.MockApp.AssertExpectations(suite.T()) suite.MessagePersisted(suite.MockApp.lastToAdmin) suite.LastToAdminMessageSent() } func (suite *SessionSendTestSuite) TestDropAndSendDropsQueue() { suite.MockApp.On("ToApp").Return(nil) suite.MockApp.On("ToAdmin") require.Nil(suite.T(), suite.queueForSend(suite.NewOrderSingle())) require.Nil(suite.T(), suite.queueForSend(suite.Heartbeat())) suite.MockApp.AssertExpectations(suite.T()) suite.NoMessageSent() suite.MockApp.On("ToAdmin") require.Nil(suite.T(), suite.dropAndSend(suite.Logon())) suite.MockApp.AssertExpectations(suite.T()) msg := suite.MockApp.lastToAdmin suite.MessageType(string(msgTypeLogon), msg) suite.FieldEquals(tagMsgSeqNum, 3, msg.Header) // Only one message sent. suite.LastToAdminMessageSent() suite.NoMessageSent() } func (suite *SessionSendTestSuite) TestDropAndSendDropsQueueWithReset() { suite.MockApp.On("ToApp").Return(nil) suite.MockApp.On("ToAdmin") require.Nil(suite.T(), suite.queueForSend(suite.NewOrderSingle())) require.Nil(suite.T(), suite.queueForSend(suite.Heartbeat())) suite.MockApp.AssertExpectations(suite.T()) suite.NoMessageSent() suite.MockApp.On("ToAdmin") suite.Require().Nil(suite.MockStore.Reset()) require.Nil(suite.T(), suite.dropAndSend(suite.Logon())) suite.MockApp.AssertExpectations(suite.T()) msg := suite.MockApp.lastToAdmin suite.MessageType(string(msgTypeLogon), msg) suite.FieldEquals(tagMsgSeqNum, 1, msg.Header) // Only one message sent. suite.LastToAdminMessageSent() suite.NoMessageSent() } func (s *SessionSuite) TestSeqNumResetTime() { s.MockApp.On("ToAdmin") s.SetupTest() now := time.Now().UTC() s.session.ResetSeqTime = now s.session.EnableResetSeqTime = true s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextSenderMsgSeqNum() s.IncrNextTargetMsgSeqNum() s.MockApp.On("ToAdmin") s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.IncrNextSenderMsgSeqNum() s.IncrNextTargetMsgSeqNum() s.MockApp.On("ToAdmin") s.session.CheckResetTime(s.session, now) s.NextSenderMsgSeqNum(3) s.NextSenderMsgSeqNum(3) } func (s *SessionSuite) TestSeqNumResetTimeDisconnected() { s.session.State = logonState{} s.session.ResetSeqTime = time.Now().UTC().Add(time.Second * 2) s.session.EnableResetSeqTime = true s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.session.onAdmin(stopReq{}) s.Disconnected() s.Stopped() // Wait for reset time to pass. time.Sleep(time.Second * 3) s.MockApp.On("ToAdmin") // Disconnected so the seq numbers should not be reset. s.session.CheckResetTime(s.session, time.Now().UTC()) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) } func (s *SessionSuite) TestSeqNumResetTimeAfterElapse() { s.session.State = logonState{} before := time.Now().UTC() resetTime := time.Now().UTC().Add(time.Second * 2) after := resetTime.Add(time.Second * 1) s.session.ResetSeqTime = resetTime s.session.EnableResetSeqTime = true s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.MockApp.On("ToAdmin") s.session.CheckResetTime(s.session, before) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.session.lastCheckedResetSeqTime = before s.session.CheckResetTime(s.session, after) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(1) } func (s *SessionSuite) TestSeqNumResetTimeNotAfterDisconnect() { s.session.State = logonState{} before := time.Now().UTC() resetTime := before.Add(time.Second * 2) after := resetTime.Add(time.Second * 1) s.session.ResetSeqTime = resetTime s.session.EnableResetSeqTime = true s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.MockApp.On("ToAdmin") s.session.CheckResetTime(s.session, before) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.session.onAdmin(stopReq{}) s.Disconnected() s.Stopped() s.session.lastCheckedResetSeqTime = before s.session.CheckResetTime(s.session, after) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) } func (s *SessionSuite) TestSeqNumResetTimeDisconnected_LocalTZ() { s.session.State = logonState{} tz, err := time.LoadLocation("America/Chicago") if err != nil { s.T().Fatal(err) } s.session.TimeZone = tz s.session.ResetSeqTime = time.Now().In(tz).Add(time.Second * 2) s.session.EnableResetSeqTime = true s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.session.onAdmin(stopReq{}) s.Disconnected() s.Stopped() // Wait for reset time to pass. time.Sleep(time.Second * 3) s.MockApp.On("ToAdmin") // Disconnected so the seq numbers should not be reset. s.session.CheckResetTime(s.session, time.Now().UTC()) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) } func (s *SessionSuite) TestSeqNumResetTimeAfterElapse_LocalTZ() { s.session.State = logonState{} tz, err := time.LoadLocation("America/Chicago") if err != nil { s.T().Fatal(err) } s.session.TimeZone = tz before := time.Now().In(tz) resetTime := before.Add(time.Second * 2) after := resetTime.Add(time.Second * 1) s.session.ResetSeqTime = resetTime s.session.EnableResetSeqTime = true s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.MockApp.On("ToAdmin") s.session.CheckResetTime(s.session, before) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.session.lastCheckedResetSeqTime = before s.session.CheckResetTime(s.session, after) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(1) } func (s *SessionSuite) TestSeqNumResetTimeNotAfterDisconnect_LocalTZ() { s.session.State = logonState{} tz, err := time.LoadLocation("America/Chicago") if err != nil { s.T().Fatal(err) } s.session.TimeZone = tz before := time.Now().In(tz) resetTime := before.Add(time.Second * 2) after := resetTime.Add(time.Second * 1) s.session.ResetSeqTime = resetTime s.session.EnableResetSeqTime = true s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.MockApp.On("ToAdmin") s.session.CheckResetTime(s.session, before) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.session.onAdmin(stopReq{}) s.Disconnected() s.Stopped() s.session.lastCheckedResetSeqTime = before s.session.CheckResetTime(s.session, after) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) } func (s *SessionSuite) TestSeqNumResetTimeAfterElapse_MultipleDaysLater() { s.session.State = logonState{} // Example Dates: // ResetTime = 2025-06-08 10:15:30 // Before = 2025-07-10 10:15:29 // After = 2025-07-10 10:15:32 resetTime := time.Now() before := resetTime.AddDate(0, 1, 2).Add(time.Second * -1) after := resetTime.AddDate(0, 1, 2).Add(time.Second * 2) s.session.ResetSeqTime = resetTime s.session.EnableResetSeqTime = true s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.MockApp.On("ToAdmin") s.session.CheckResetTime(s.session, before) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.session.lastCheckedResetSeqTime = before s.session.CheckResetTime(s.session, after) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(1) } func (s *SessionSuite) TestSeqNumResetTimeAtExactTime() { s.session.State = logonState{} resetTime := time.Now() before := resetTime.Add(time.Second * -1) s.session.ResetSeqTime = resetTime s.session.EnableResetSeqTime = true s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) // We are before the reset time, so we should not reset the seq numbers s.MockApp.On("ToAdmin") s.session.CheckResetTime(s.session, before) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) // We check the reset time exactly at the configured time, so we reset s.session.lastCheckedResetSeqTime = before s.session.CheckResetTime(s.session, resetTime) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(1) } func (s *SessionSuite) TestSeqNumResetTimePreviousCheck() { s.session.State = logonState{} resetTime := time.Now() before := resetTime.Add(time.Second * -1) after := resetTime.Add(time.Second * 1) s.session.ResetSeqTime = resetTime s.session.EnableResetSeqTime = true s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) // We are before the reset time, so we should not reset the seq numbers s.MockApp.On("ToAdmin") s.session.CheckResetTime(s.session, before) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) // We just checked the reset time at the configured time, so we do not reset the seq num for the next check s.session.lastCheckedResetSeqTime = resetTime s.session.CheckResetTime(s.session, after) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) } func (s *SessionSuite) TestSeqNumResetTime_NanoSeconds() { s.session.State = logonState{} resetTime := time.Now() // Nanoseconds are not used in the resetSeqTime, but are referenced in the comparison of before/after before := resetTime.Add(time.Nanosecond * -1) after := resetTime.Add(time.Nanosecond * 1) s.session.ResetSeqTime = resetTime s.session.EnableResetSeqTime = true s.NextSenderMsgSeqNum(1) s.NextTargetMsgSeqNum(1) s.IncrNextTargetMsgSeqNum() s.IncrNextSenderMsgSeqNum() s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.MockApp.On("ToAdmin") s.session.CheckResetTime(s.session, before) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) s.session.lastCheckedResetSeqTime = before s.session.CheckResetTime(s.session, after) s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(1) }