|
1 | 1 | use crate::common::actions::when; |
2 | 2 | use crate::common::assertions::{assert_msg_type, then}; |
3 | | -use crate::common::setup::given_an_active_session; |
| 3 | +use crate::common::setup::{HEARTBEAT_INTERVAL, given_an_active_session}; |
4 | 4 | use crate::common::test_messages::{ |
5 | 5 | TestMessage, build_execution_report_with_incorrect_body_length, build_invalid_resend_request, |
6 | 6 | }; |
7 | | -use hotfix::message::FixMessage; |
| 7 | +use hotfix::message::{FixMessage, ResendRequest}; |
8 | 8 | use hotfix::session::Status; |
9 | | -use hotfix_message::FieldType; |
10 | | -use hotfix_message::fix44::MsgType; |
| 9 | +use hotfix_message::fix44::{GAP_FILL_FLAG, MsgType, NEW_SEQ_NO}; |
| 10 | +use hotfix_message::{FieldType, Part}; |
| 11 | +use std::time::Duration; |
11 | 12 |
|
12 | 13 | #[tokio::test] |
13 | 14 | async fn test_message_sequence_number_too_high() { |
@@ -168,3 +169,55 @@ async fn test_invalid_resend_request_gets_rejected() { |
168 | 169 | then(&mut counterparty).gets_disconnected().await; |
169 | 170 | } |
170 | 171 | } |
| 172 | + |
| 173 | +/// Tests that when a counterparty requests a resend of both admin and business messages, |
| 174 | +/// the session gap fills admin messages and resends business messages as expected. |
| 175 | +#[tokio::test(start_paused = true)] |
| 176 | +async fn test_resend_request_with_gap_fill_for_admin_messages() { |
| 177 | + let (session, mut counterparty) = given_an_active_session().await; |
| 178 | + |
| 179 | + // wait for a heartbeat to be sent automatically (this will be message sequence number 2) |
| 180 | + when(Duration::from_secs(HEARTBEAT_INTERVAL + 1)) |
| 181 | + .elapses() |
| 182 | + .await; |
| 183 | + then(&mut counterparty) |
| 184 | + .receives(|msg| assert_msg_type(msg, MsgType::Heartbeat)) |
| 185 | + .await; |
| 186 | + |
| 187 | + // send an execution report from the session (this will be message sequence number 3) |
| 188 | + when(&session) |
| 189 | + .sends_message(TestMessage::dummy_execution_report()) |
| 190 | + .await; |
| 191 | + then(&mut counterparty) |
| 192 | + .receives(|msg| assert_msg_type(msg, MsgType::ExecutionReport)) |
| 193 | + .await; |
| 194 | + |
| 195 | + // counterparty requests a resend of messages 2 and 3 |
| 196 | + let resend_request = ResendRequest::new(2, 3); |
| 197 | + when(&mut counterparty).sends_message(resend_request).await; |
| 198 | + |
| 199 | + // the session should send a SequenceReset-GapFill for the heartbeat (message 2) |
| 200 | + then(&mut counterparty) |
| 201 | + .receives(|msg| { |
| 202 | + assert_msg_type(msg, MsgType::SequenceReset); |
| 203 | + assert_eq!(msg.get::<&str>(GAP_FILL_FLAG).unwrap(), "Y"); |
| 204 | + // the gap fill's MsgSeqNum indicates the beginning of the gap |
| 205 | + assert_eq!( |
| 206 | + msg.header() |
| 207 | + .get::<u64>(hotfix_message::fix44::MSG_SEQ_NUM) |
| 208 | + .unwrap(), |
| 209 | + 2 |
| 210 | + ); |
| 211 | + // NewSeqNo indicates the next sequence number after the gap |
| 212 | + assert_eq!(msg.get::<u64>(NEW_SEQ_NO).unwrap(), 3); |
| 213 | + }) |
| 214 | + .await; |
| 215 | + |
| 216 | + // the session should resend the execution report (message 3) |
| 217 | + then(&mut counterparty) |
| 218 | + .receives(|msg| assert_msg_type(msg, MsgType::ExecutionReport)) |
| 219 | + .await; |
| 220 | + |
| 221 | + when(&session).requests_disconnect().await; |
| 222 | + then(&mut counterparty).gets_disconnected().await; |
| 223 | +} |
0 commit comments