Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions quickfixj-core/src/test/java/quickfix/SessionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1398,6 +1398,110 @@ public void testResendMessagesWithIncorrectChecksum() throws Exception {
}
}

// Test for issue #597 - demonstrates current behavior where Reject messages are NOT resent
// This test documents the bug before PR #1124's fix
@Test
public void testRejectMessagesNotResentInCurrentCode() throws Exception {
final UnitTestApplication application = new UnitTestApplication();
final SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, "SENDER", "TARGET");

// Create session with default settings (ForceResendWhenCorruptedStore=false)
try (Session session = SessionFactoryTestSupport.createSession(sessionID, application, false, false, true, true, null)) {
// Use a responder that captures all sent messages
FailingResponder responder = new FailingResponder(100); // Allow many successful sends
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Please replace the FailingResponder by a normal Responder, since we do not need the functionality of the FailingResponder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced FailingResponder with mock Responder and ArgumentCaptor (commit fad489e). Also removed the incorrectly added UnitTestResponder.java file.

session.setResponder(responder);
final SessionState state = getSessionState(session);

// Logon
final Logon logonToSend = new Logon();
setUpHeader(session.getSessionID(), logonToSend, true, 1);
logonToSend.setInt(HeartBtInt.FIELD, 30);
logonToSend.setInt(EncryptMethod.FIELD, EncryptMethod.NONE_OTHER);
logonToSend.toString(); // calculate length/checksum
session.next(logonToSend);

// Send messages that will be stored:
// seq 2: application message
session.send(createAppMessage(2));
// seq 3: Reject message (session-level but should be resent per FIX spec)
Message rejectMsg = createReject(3, 1);
session.send(rejectMsg);
// seq 4: application message
session.send(createAppMessage(4));
// seq 5: Heartbeat message (session-level, should NOT be resent)
Message heartbeatMsg = createHeartbeatMessage(5);
session.send(heartbeatMsg);

// Clear the sent messages from initial send
int initialMessageCount = responder.sentMessages.size();
responder.sentMessages.clear();
responder.sendCallCount = 0;

// Request resend of messages 1-5
Message resendRequest = createResendRequest(100, 1);
resendRequest.toString(); // calculate length/checksum
processMessage(session, resendRequest);

// Verify messages sent during resend
List<Message> resentMessages = new ArrayList<>();
for (String msgData : responder.sentMessages) {
resentMessages.add(new Message(msgData));
}

// Current behavior verification:
// 1. Should have sent: SequenceReset(1-2), AppMsg(2), SequenceReset(3-4), AppMsg(4), SequenceReset(5-6)
// The Reject message should be included in a gap fill (bug)

boolean foundSeqReset1 = false; // Gap fill for Logon (seq 1)
boolean foundAppMsg2 = false;
boolean foundSeqReset3 = false; // Gap fill that includes Reject (seq 3) - this is the bug
boolean foundAppMsg4 = false;
boolean foundSeqReset5 = false; // Gap fill for Heartbeat (seq 5)
boolean foundRejectResend = false;

for (Message msg : resentMessages) {
String msgType = msg.getHeader().getString(MsgType.FIELD);
int msgSeqNum = msg.getHeader().getInt(MsgSeqNum.FIELD);

if (msgType.equals(SequenceReset.MSGTYPE)) {
boolean isGapFill = msg.isSetField(GapFillFlag.FIELD) && msg.getBoolean(GapFillFlag.FIELD);
int newSeqNo = msg.getInt(NewSeqNo.FIELD);

if (isGapFill && msgSeqNum == 1 && newSeqNo == 2) {
foundSeqReset1 = true; // Gap fill over Logon
} else if (isGapFill && msgSeqNum == 3 && newSeqNo == 4) {
foundSeqReset3 = true; // Gap fill over Reject (bug - should not gap fill)
} else if (isGapFill && msgSeqNum == 5 && newSeqNo == 6) {
foundSeqReset5 = true; // Gap fill over Heartbeat
}
} else if (msgType.equals(News.MSGTYPE)) {
// Application message
boolean isPossDup = msg.getHeader().isSetField(PossDupFlag.FIELD)
&& msg.getHeader().getBoolean(PossDupFlag.FIELD);
assertTrue("Application message should have PossDupFlag set", isPossDup);

if (msgSeqNum == 2) {
foundAppMsg2 = true;
} else if (msgSeqNum == 4) {
foundAppMsg4 = true;
}
} else if (msgType.equals(Reject.MSGTYPE)) {
foundRejectResend = true;
}
}

// Verify current behavior (documents the bug)
assertTrue("Should send gap fill for Logon (seq 1)", foundSeqReset1);
assertTrue("Should resend application message (seq 2)", foundAppMsg2);
assertTrue("Should send gap fill for Reject (seq 3) - this is the bug", foundSeqReset3);
assertTrue("Should resend application message (seq 4)", foundAppMsg4);
assertTrue("Should send gap fill for Heartbeat (seq 5)", foundSeqReset5);
assertFalse("Reject message should NOT be resent in current code (bug)", foundRejectResend);

assertFalse(state.isResendRequested());
}
}

// QFJ-493
@Test
public void testGapFillSatisfiesResendRequest() throws Exception {
Expand Down
18 changes: 18 additions & 0 deletions quickfixj-core/src/test/java/quickfix/UnitTestResponder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package quickfix;

import java.util.ArrayList;
import java.util.List;

public class UnitTestResponder extends Responder {
List<String> sentMessages = new ArrayList<>();

@Override
public void send(String message) {
sentMessages.add(message);
super.send(message);
}

public List<String> getSentMessages() {
return sentMessages;
}
}
Loading