From f93464016270bc843745aaa335d32cd289d057f2 Mon Sep 17 00:00:00 2001
From: Chris Busbey <cbusbey@connamara.com>
Date: Wed, 17 Aug 2016 15:38:04 -0500
Subject: [PATCH] reject logon support

---
 errors.go           | 20 ++++++++++++++++++--
 logon_state.go      | 19 ++++++++++++++++++-
 logon_state_test.go | 32 ++++++++++++++++++++++++++++----
 session.go          |  8 +++++++-
 session_test.go     |  8 ++++----
 5 files changed, 75 insertions(+), 12 deletions(-)

diff --git a/errors.go b/errors.go
index c384211c..af949ebd 100644
--- a/errors.go
+++ b/errors.go
@@ -5,8 +5,8 @@ import (
 	"fmt"
 )
 
-//DoNotSend is a convenience error to indicate a DoNotSend in ToApp
-var DoNotSend = errors.New("Do Not Send")
+//ErrDoNotSend is a convenience error to indicate a DoNotSend in ToApp
+var ErrDoNotSend = errors.New("Do Not Send")
 
 //rejectReason enum values.
 const (
@@ -37,6 +37,22 @@ type MessageRejectError interface {
 	IsBusinessReject() bool
 }
 
+//RejectLogon indicates the application is rejecting permission to logon. Implements MessageRejectError
+type RejectLogon struct {
+	Text string
+}
+
+func (e RejectLogon) Error() string { return e.Text }
+
+//RefTagID implements MessageRejectError
+func (RejectLogon) RefTagID() *Tag { return nil }
+
+//RejectReason implements MessageRejectError
+func (RejectLogon) RejectReason() int { return 0 }
+
+//IsBusinessReject implements MessageRejectError
+func (RejectLogon) IsBusinessReject() bool { return false }
+
 type messageRejectError struct {
 	rejectReason     int
 	text             string
diff --git a/logon_state.go b/logon_state.go
index b70e4f5a..88e69bda 100644
--- a/logon_state.go
+++ b/logon_state.go
@@ -21,7 +21,24 @@ func (s logonState) FixMsgIn(session *session, msg Message) (nextState sessionSt
 	}
 
 	if err := session.handleLogon(msg); err != nil {
-		return handleStateError(session, err)
+		switch err := err.(type) {
+		case RejectLogon:
+			session.log.OnEvent(err.Text)
+			msg := session.buildLogout(err.Text)
+
+			if err := session.dropAndSend(msg, false); err != nil {
+				session.logError(err)
+			}
+
+			if err := session.store.IncrNextTargetMsgSeqNum(); err != nil {
+				session.logError(err)
+			}
+
+			return latentState{}
+
+		default:
+			return handleStateError(session, err)
+		}
 	}
 	return inSession{}
 }
diff --git a/logon_state_test.go b/logon_state_test.go
index 19f1915d..30f56cfd 100644
--- a/logon_state_test.go
+++ b/logon_state_test.go
@@ -66,9 +66,9 @@ func (s *LogonStateTestSuite) TestFixMsgInNotLogon() {
 }
 
 func (s *LogonStateTestSuite) TestFixMsgInLogon() {
-	s.store.IncrNextSenderMsgSeqNum()
+	s.Require().Nil(s.store.IncrNextSenderMsgSeqNum())
 	s.MessageFactory.seqNum = 1
-	s.store.IncrNextTargetMsgSeqNum()
+	s.Require().Nil(s.store.IncrNextTargetMsgSeqNum())
 
 	logon := s.Logon()
 	logon.Body.SetField(tagHeartBtInt, FIXInt(32))
@@ -93,9 +93,9 @@ func (s *LogonStateTestSuite) TestFixMsgInLogon() {
 
 func (s *LogonStateTestSuite) TestFixMsgInLogonInitiateLogon() {
 	s.session.InitiateLogon = true
-	s.store.IncrNextSenderMsgSeqNum()
+	s.Require().Nil(s.store.IncrNextSenderMsgSeqNum())
 	s.MessageFactory.seqNum = 1
-	s.store.IncrNextTargetMsgSeqNum()
+	s.Require().Nil(s.store.IncrNextTargetMsgSeqNum())
 
 	logon := s.Logon()
 	logon.Body.SetField(tagHeartBtInt, FIXInt(32))
@@ -150,3 +150,27 @@ func (s *LogonStateTestSuite) TestStop() {
 		s.Stopped()
 	}
 }
+
+func (s *LogonStateTestSuite) TestFixMsgInLogonRejectLogon() {
+	s.Require().Nil(s.store.IncrNextSenderMsgSeqNum())
+	s.MessageFactory.seqNum = 1
+	s.Require().Nil(s.store.IncrNextTargetMsgSeqNum())
+
+	logon := s.Logon()
+	logon.Body.SetField(tagHeartBtInt, FIXInt(32))
+
+	s.MockApp.On("FromAdmin").Return(RejectLogon{"reject message"})
+	s.MockApp.On("ToAdmin")
+	s.fixMsgIn(s.session, logon)
+
+	s.MockApp.AssertExpectations(s.T())
+
+	s.State(latentState{})
+
+	s.LastToAdminMessageSent()
+	s.MessageType(enum.MsgType_LOGOUT, s.MockApp.lastToAdmin)
+	s.FieldEquals(tagText, "reject message", s.MockApp.lastToAdmin.Body)
+
+	s.NextTargetMsgSeqNum(3)
+	s.NextSenderMsgSeqNum(3)
+}
diff --git a/session.go b/session.go
index d161a6a4..a159ff7e 100644
--- a/session.go
+++ b/session.go
@@ -128,7 +128,7 @@ func (s *session) sendLogon(resetStore, setResetSeqNum bool) error {
 	return nil
 }
 
-func (s *session) sendLogout(reason string) error {
+func (s *session) buildLogout(reason string) Message {
 	logout := NewMessage()
 	logout.Header.SetField(tagMsgType, FIXString("5"))
 	logout.Header.SetField(tagBeginString, FIXString(s.sessionID.BeginString))
@@ -137,6 +137,12 @@ func (s *session) sendLogout(reason string) error {
 	if reason != "" {
 		logout.Body.SetField(tagText, FIXString(reason))
 	}
+
+	return logout
+}
+
+func (s *session) sendLogout(reason string) error {
+	logout := s.buildLogout(reason)
 	return s.send(logout)
 }
 
diff --git a/session_test.go b/session_test.go
index a2485ddf..b1cb64c4 100644
--- a/session_test.go
+++ b/session_test.go
@@ -611,8 +611,8 @@ func (suite *SessionSendTestSuite) TestQueueForSendAppMessage() {
 }
 
 func (suite *SessionSendTestSuite) TestQueueForSendDoNotSendAppMessage() {
-	suite.MockApp.On("ToApp").Return(DoNotSend)
-	suite.Equal(DoNotSend, suite.queueForSend(suite.NewOrderSingle()))
+	suite.MockApp.On("ToApp").Return(ErrDoNotSend)
+	suite.Equal(ErrDoNotSend, suite.queueForSend(suite.NewOrderSingle()))
 
 	suite.MockApp.AssertExpectations(suite.T())
 	suite.NoMessagePersisted(1)
@@ -649,8 +649,8 @@ func (suite *SessionSendTestSuite) TestSendAppMessage() {
 }
 
 func (suite *SessionSendTestSuite) TestSendAppDoNotSendMessage() {
-	suite.MockApp.On("ToApp").Return(DoNotSend)
-	suite.Equal(DoNotSend, suite.send(suite.NewOrderSingle()))
+	suite.MockApp.On("ToApp").Return(ErrDoNotSend)
+	suite.Equal(ErrDoNotSend, suite.send(suite.NewOrderSingle()))
 
 	suite.MockApp.AssertExpectations(suite.T())
 	suite.NextSenderMsgSeqNum(1)
-- 
GitLab