Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 9 additions & 0 deletions crates/hotfix/tests/common/assertions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use crate::common::mock_counterparty::MockCounterparty;
use crate::common::test_messages::TestMessage;
use hotfix::session::SessionRef;
use hotfix::session::Status;
use hotfix_message::fix44::{MSG_TYPE, MsgType};
use hotfix_message::message::Message;
use hotfix_message::{FieldType, Part};
use std::time::Duration;

pub const DEFAULT_TIMEOUT: Duration = Duration::from_millis(500);
Expand Down Expand Up @@ -77,3 +79,10 @@ impl Then<&mut MockCounterparty<TestMessage>> {
.await;
}
}

pub fn assert_msg_type(msg: &Message, msg_type: MsgType) {
assert_eq!(
msg.header().get::<&str>(MSG_TYPE).unwrap(),
msg_type.to_string()
)
}
3 changes: 2 additions & 1 deletion crates/hotfix/tests/session_test_cases/business_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::common::assertions::then;
use crate::common::setup::given_an_active_session;
use crate::common::test_messages::TestMessage;
use hotfix::message::FixMessage;
use hotfix_message::{FieldType, fix44::MsgType};

#[tokio::test]
async fn test_new_order_single() {
Expand All @@ -15,7 +16,7 @@ async fn test_new_order_single() {
then(&mut mock_counterparty)
.receives(|msg| {
let parsed = TestMessage::parse(msg);
assert_eq!(parsed.message_type(), "D");
assert_eq!(parsed.message_type(), MsgType::OrderSingle.to_string());
})
.await;

Expand Down
33 changes: 28 additions & 5 deletions crates/hotfix/tests/session_test_cases/heartbeat_tests.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::common::actions::when;
use crate::common::assertions::then;
use crate::common::assertions::{assert_msg_type, then};
use crate::common::setup::{HEARTBEAT_INTERVAL, given_an_active_session};
use hotfix::message::test_request::TestRequest;
use hotfix_message::Part;
use hotfix_message::fix44::MSG_TYPE;
use hotfix_message::fix44::{MsgType, TEST_REQ_ID};
use std::time::Duration;

/// Tests the automatic heartbeat mechanism in an active FIX session:
Expand All @@ -23,7 +24,7 @@ async fn test_heartbeats() {
.elapses()
.await;
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "0"))
.receives(|msg| assert_msg_type(msg, MsgType::Heartbeat))
.await;

when(&session).requests_disconnect().await;
Expand All @@ -49,7 +50,7 @@ async fn test_peer_timeout() {
.elapses()
.await;
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "0"))
.receives(|msg| assert_msg_type(msg, MsgType::Heartbeat))
.await;

// we wait enough time for the peer deadline to pass
Expand All @@ -58,10 +59,32 @@ async fn test_peer_timeout() {
.await;
// a TestRequest (type '1') is sent to the counterparty
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "1"))
.receives(|msg| assert_msg_type(msg, MsgType::TestRequest))
.await;

// we wait even longer and the counterparty never responds, so we disconnect from the counterparty
when(Duration::from_secs(peer_interval)).elapses().await;
then(&mut mock_counterparty).gets_disconnected().await;
}

/// Tests that we send a heartbeat in response to Test Requests.
///
/// The `TestReqID` of the heartbeat (field 112) should match that of the request.
#[tokio::test(start_paused = true)]
async fn test_heartbeat_in_response_to_test_request() {
let (session, mut mock_counterparty) = given_an_active_session().await;

let test_request = TestRequest::new("abc-123".to_string());
when(&mut mock_counterparty)
.sends_message(test_request)
.await;
then(&mut mock_counterparty)
.receives(|msg| {
assert_msg_type(msg, MsgType::Heartbeat);
assert_eq!(msg.get::<&str>(TEST_REQ_ID).unwrap(), "abc-123");
})
.await;

when(&session).requests_disconnect().await;
then(&mut mock_counterparty).gets_disconnected().await;
}
26 changes: 13 additions & 13 deletions crates/hotfix/tests/session_test_cases/invalid_message_tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::common::actions::when;
use crate::common::assertions::then;
use crate::common::assertions::{assert_msg_type, then};
use crate::common::setup::{COUNTERPARTY_COMP_ID, OUR_COMP_ID, given_an_active_session};
use crate::common::test_messages::{
ExecutionReportWithInvalidField, TestMessage, build_execution_report_with_comp_id,
Expand All @@ -11,7 +11,7 @@ use crate::common::test_messages::{
};
use hotfix::session::Status;
use hotfix_message::Part;
use hotfix_message::fix44::{MSG_TYPE, SESSION_REJECT_REASON, SessionRejectReason};
use hotfix_message::fix44::{MsgType, SESSION_REJECT_REASON, SessionRejectReason};

/// Tests that when a counterparty sends a message containing an invalid/unrecognised field,
/// the session rejects the message by sending a Reject (MsgType=3) message back.
Expand All @@ -23,7 +23,7 @@ async fn test_message_with_invalid_field_gets_rejected() {
.sends_message(ExecutionReportWithInvalidField::default())
.await;
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "3"))
.receives(|msg| assert_msg_type(msg, MsgType::Reject))
.await;

when(&session).requests_disconnect().await;
Expand Down Expand Up @@ -51,7 +51,7 @@ async fn test_garbled_message_with_invalid_target_comp_id_gets_ignored() {

// we then initiate a resend, having skipped the garbled message
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "2"))
.receives(|msg| assert_msg_type(msg, MsgType::ResendRequest))
.await;
then(&session)
.status_changes_to(Status::AwaitingResend {
Expand Down Expand Up @@ -81,7 +81,7 @@ async fn test_message_with_invalid_begin_string() {

// then we log out and disconnect
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "5"))
.receives(|msg| assert_msg_type(msg, MsgType::Logout))
.await;
then(&mut mock_counterparty).gets_disconnected().await;
}
Expand All @@ -104,10 +104,10 @@ async fn test_message_with_invalid_target_comp_id() {

// then we send a reject, log out and disconnect
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "3"))
.receives(|msg| assert_msg_type(msg, MsgType::Reject))
.await;
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "5"))
.receives(|msg| assert_msg_type(msg, MsgType::Logout))
.await;
then(&mut mock_counterparty).gets_disconnected().await;
}
Expand All @@ -130,10 +130,10 @@ async fn test_message_with_invalid_sender_comp_id() {

// then we send a reject, log out and disconnect
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "3"))
.receives(|msg| assert_msg_type(msg, MsgType::Reject))
.await;
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "5"))
.receives(|msg| assert_msg_type(msg, MsgType::Logout))
.await;
then(&mut mock_counterparty).gets_disconnected().await;
}
Expand All @@ -154,7 +154,7 @@ async fn test_message_with_invalid_msg_type() {
// then we send a reject
then(&mut mock_counterparty)
.receives(|msg| {
assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "3");
assert_msg_type(msg, MsgType::Reject);
assert_eq!(msg.get::<u32>(SESSION_REJECT_REASON).unwrap(), 11);
})
.await;
Expand Down Expand Up @@ -189,7 +189,7 @@ async fn test_message_with_sequence_number_too_low() {
then(&mut mock_counterparty)
.receives(|msg| {
// we log them out
assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "5");
assert_msg_type(msg, MsgType::Logout);
})
.await;
then(&mut mock_counterparty).gets_disconnected().await;
Expand Down Expand Up @@ -253,7 +253,7 @@ async fn test_message_with_incorrect_orig_sending_time_is_rejected() {
.await;
then(&mut mock_counterparty)
.receives(|msg| {
assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "3");
assert_msg_type(msg, MsgType::Reject);
assert_eq!(
msg.get::<SessionRejectReason>(SESSION_REJECT_REASON)
.unwrap(),
Expand Down Expand Up @@ -290,7 +290,7 @@ async fn test_message_with_missing_orig_sending_time_is_rejected() {
.await;
then(&mut mock_counterparty)
.receives(|msg| {
assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "3");
assert_msg_type(msg, MsgType::Reject);
assert_eq!(
msg.get::<SessionRejectReason>(SESSION_REJECT_REASON)
.unwrap(),
Expand Down
19 changes: 9 additions & 10 deletions crates/hotfix/tests/session_test_cases/logon_tests.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use crate::common::actions::when;
use crate::common::assertions::then;
use crate::common::assertions::{assert_msg_type, then};
use crate::common::setup::{
LOGON_TIMEOUT, given_a_connected_session, given_a_connected_session_with_store,
};
use crate::common::test_messages::TestMessage;
use hotfix::session::Status;
use hotfix::store::MessageStore;
use hotfix::store::in_memory::InMemoryMessageStore;
use hotfix_message::Part;
use hotfix_message::fix44::MSG_TYPE;
use hotfix_message::fix44::MsgType;
use std::time::Duration;

/// Tests successful FIX session establishment via logon message exchange.
Expand All @@ -20,7 +19,7 @@ async fn test_happy_logon() {

// assert that a logon message is received (type 'A')
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "A"))
.receives(|msg| assert_msg_type(msg, MsgType::Logon))
.await;
then(&session)
.status_changes_to(Status::AwaitingLogon)
Expand All @@ -43,7 +42,7 @@ async fn test_non_logon_response_to_logon() {

// assert that a logon message is received (type 'A')
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "A"))
.receives(|msg| assert_msg_type(msg, MsgType::Logon))
.await;
then(&session)
.status_changes_to(Status::AwaitingLogon)
Expand Down Expand Up @@ -75,7 +74,7 @@ async fn test_logon_response_with_sequence_number_too_low() {

// assert that a logon message is received (type 'A')
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "A"))
.receives(|msg| assert_msg_type(msg, MsgType::Logon))
.await;
then(&session)
.status_changes_to(Status::AwaitingLogon)
Expand All @@ -85,7 +84,7 @@ async fn test_logon_response_with_sequence_number_too_low() {
when(&mut mock_counterparty).sends_logon().await;
// the counterparty then receives a logout message (type '5') and gets disconnected
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "5"))
.receives(|msg| assert_msg_type(msg, MsgType::Logout))
.await;
then(&mut mock_counterparty).gets_disconnected().await;
}
Expand All @@ -106,7 +105,7 @@ async fn test_logon_response_with_sequence_number_too_high() {

// assert that a logon message is received (type 'A')
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "A"))
.receives(|msg| assert_msg_type(msg, MsgType::Logon))
.await;
then(&session)
.status_changes_to(Status::AwaitingLogon)
Expand All @@ -123,7 +122,7 @@ async fn test_logon_response_with_sequence_number_too_high() {
})
.await;
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "2"))
.receives(|msg| assert_msg_type(msg, MsgType::ResendRequest))
.await;

// the counterparty then completes the resend sequence and the session transitions to Active
Expand All @@ -145,7 +144,7 @@ async fn test_logon_timeout() {

// assert that a logon message is received (type 'A')
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "A"))
.receives(|msg| assert_msg_type(msg, MsgType::Logon))
.await;
then(&session)
.status_changes_to(Status::AwaitingLogon)
Expand Down
11 changes: 5 additions & 6 deletions crates/hotfix/tests/session_test_cases/resend_tests.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use crate::common::actions::when;
use crate::common::assertions::then;
use crate::common::assertions::{assert_msg_type, then};
use crate::common::setup::given_an_active_session;
use crate::common::test_messages::{
TestMessage, build_execution_report_with_incorrect_body_length,
};
use hotfix::session::Status;
use hotfix_message::Part;
use hotfix_message::fix44::MSG_TYPE;
use hotfix_message::fix44::MsgType;

#[tokio::test]
async fn test_message_sequence_number_too_high() {
Expand All @@ -31,7 +30,7 @@ async fn test_message_sequence_number_too_high() {
})
.await;
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "2"))
.receives(|msg| assert_msg_type(msg, MsgType::ResendRequest))
.await;

// the first message is the logon message, which doesn't need to be resent
Expand Down Expand Up @@ -64,7 +63,7 @@ async fn test_infinite_resend_requests_are_prevented() {

// we then initiate a resend, having skipped the garbled message
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "2"))
.receives(|msg| assert_msg_type(msg, MsgType::ResendRequest))
.await;
then(&session)
.status_changes_to(Status::AwaitingResend {
Expand All @@ -90,7 +89,7 @@ async fn test_infinite_resend_requests_are_prevented() {
})
.await;
then(&mut mock_counterparty)
.receives(|msg| assert_eq!(msg.header().get::<&str>(MSG_TYPE).unwrap(), "2"))
.receives(|msg| assert_msg_type(msg, MsgType::ResendRequest))
.await;
}

Expand Down