Files
qfixdpl/quickfix/session_test.go
2026-03-09 15:35:32 -03:00

1329 lines
37 KiB
Go

// 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)
}