Skip to content

Commit 11d327c

Browse files
committed
Parse messages in session tests so assertions can be made on their details
1 parent 6e7ea11 commit 11d327c

2 files changed

Lines changed: 88 additions & 49 deletions

File tree

Lines changed: 79 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
1-
use hotfix::message::FixMessage;
1+
use hotfix::message::{FixMessage, RawFixMessage};
22
use hotfix::session::SessionRef;
33
use hotfix::transport::FixConnection;
44
use hotfix::transport::reader::ReaderRef;
55
use hotfix::transport::writer::{WriterMessage, WriterRef};
6+
use hotfix_message::dict::Dictionary;
7+
use hotfix_message::message::{Config as MessageConfig, Message};
8+
use hotfix_message::parsed_message::ParsedMessage;
69
use std::time::Duration;
10+
use tokio::sync::mpsc::Receiver;
711
use tokio::sync::{mpsc, oneshot};
812

9-
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(1);
13+
const DEFAULT_TIMEOUT: Duration = Duration::from_millis(10);
1014

1115
pub struct MockCounterparty {
12-
// Receiver-End of the channel
13-
receiver: mpsc::UnboundedReceiver<WriterMessage>,
14-
// History of Received messages from the client
15-
messages: Vec<WriterMessage>,
16+
receiver: Receiver<WriterMessage>,
17+
// History of received messages from the session
18+
messages: Vec<Message>,
19+
dictionary: Dictionary,
20+
message_config: MessageConfig,
1621
_connection: FixConnection,
1722
_dc_sender: oneshot::Sender<()>,
1823
}
1924

2025
impl MockCounterparty {
2126
pub async fn start(session_ref: SessionRef<impl FixMessage>) -> Self {
22-
let (writer_ref, receiver) = Self::spawn_writer();
27+
let (writer_ref, receiver) = Self::create_writer();
2328
let (reader_ref, dc_sender) = Self::create_reader();
2429
let connection = FixConnection::new(writer_ref, reader_ref);
2530

@@ -28,65 +33,92 @@ impl MockCounterparty {
2833
Self {
2934
receiver,
3035
messages: vec![],
36+
dictionary: Dictionary::fix44(),
37+
message_config: MessageConfig::default(),
3138
_connection: connection,
3239
_dc_sender: dc_sender,
3340
}
3441
}
3542

36-
/// Listen to the next message on the channel
37-
pub async fn get_next(&mut self, timeout: Option<Duration>) -> &WriterMessage {
38-
let timeout = timeout.unwrap_or(DEFAULT_TIMEOUT);
39-
let msg = tokio::time::timeout(timeout, self.receiver.recv())
43+
/// Waits for and returns the next message received from the session.
44+
///
45+
/// A `None` response indicates we have been disconnected, either through the channel
46+
/// dropping on the session's side, or through an explicit `Disconnect` message.
47+
async fn get_next(&mut self) -> Option<&Message> {
48+
self.receiver
49+
.recv()
4050
.await
41-
.unwrap_or_else(|_| panic!("Message not received in less than {timeout:?}"))
42-
.unwrap_or_else(|| panic!("Received message is None"));
43-
self.messages.push(msg);
44-
self.messages.last().unwrap()
51+
.and_then(|writer_message| match writer_message {
52+
WriterMessage::SendMessage(raw_message) => {
53+
let message = self.parse_message(&raw_message);
54+
self.messages.push(message);
55+
self.messages.last()
56+
}
57+
WriterMessage::Disconnect => None,
58+
})
4559
}
4660

47-
pub async fn assert_next<F>(&mut self, timeout: Option<Duration>, assertion: F)
61+
fn parse_message(&self, raw_message: &RawFixMessage) -> Message {
62+
match Message::from_bytes(
63+
&self.message_config,
64+
&self.dictionary,
65+
raw_message.as_bytes(),
66+
) {
67+
ParsedMessage::Valid(valid_message) => valid_message,
68+
_ => {
69+
panic!("only valid messages are supported in the mock counterparty")
70+
}
71+
}
72+
}
73+
74+
pub async fn assert_next<F>(&mut self, assertion: F)
4875
where
49-
F: FnOnce(&WriterMessage) -> bool,
76+
F: FnOnce(&Message) -> bool,
5077
{
51-
let msg = self.get_next(timeout).await;
52-
assert!(assertion(msg));
78+
self.assert_next_with_timeout(assertion, DEFAULT_TIMEOUT)
79+
.await;
5380
}
5481

55-
pub async fn assert_disconnected(&mut self, timeout: Option<Duration>) {
56-
self.assert_next(timeout, |msg| matches!(msg, &WriterMessage::Disconnect))
57-
.await;
58-
assert!(self.receiver.is_closed());
82+
pub async fn assert_next_with_timeout<F>(&mut self, assertion: F, timeout: Duration)
83+
where
84+
F: FnOnce(&Message) -> bool,
85+
{
86+
match tokio::time::timeout(timeout, self.get_next()).await {
87+
Ok(Some(message)) => {
88+
assertion(message);
89+
}
90+
Ok(None) => {
91+
panic!("disconnected before receiving any message");
92+
}
93+
Err(_) => {
94+
panic!("timeout expired before receiving any message");
95+
}
96+
}
97+
}
98+
99+
pub async fn assert_disconnected(&mut self) {
100+
self.assert_disconnected_with_timeout(DEFAULT_TIMEOUT).await;
59101
}
60102

61-
fn spawn_writer() -> (WriterRef, mpsc::UnboundedReceiver<WriterMessage>) {
62-
// mock implementation to receive messages from the session
63-
// and hold on to them for test assertions
64-
let (tx, rx) = mpsc::unbounded_channel();
65-
let (sender, mailbox) = mpsc::channel(10);
66-
tokio::spawn(Self::run_writer(mailbox, tx));
103+
pub async fn assert_disconnected_with_timeout(&mut self, timeout: Duration) {
104+
if tokio::time::timeout(timeout, async {
105+
// keep consuming messages until a disconnect occurs
106+
while self.get_next().await.is_some() {}
107+
})
108+
.await
109+
.is_err()
110+
{
111+
panic!("timeout expired before a disconnect occurred");
112+
}
113+
}
67114

68-
(WriterRef::new(sender), rx)
115+
fn create_writer() -> (WriterRef, Receiver<WriterMessage>) {
116+
let (sender, receiver) = mpsc::channel(10);
117+
(WriterRef::new(sender), receiver)
69118
}
70119

71120
fn create_reader() -> (ReaderRef, oneshot::Sender<()>) {
72121
let (dc_sender, dc_receiver) = oneshot::channel();
73122
(ReaderRef::new(dc_receiver), dc_sender)
74123
}
75-
76-
async fn run_writer(
77-
mut mailbox: mpsc::Receiver<WriterMessage>,
78-
received_messages: mpsc::UnboundedSender<WriterMessage>,
79-
) {
80-
while let Some(msg) = mailbox.recv().await {
81-
println!("Received message from session: {msg:?}");
82-
let disconnect = matches!(msg, WriterMessage::Disconnect);
83-
if let Err(e) = received_messages.send(msg) {
84-
panic!("Failed to send message. Error: {e:?}");
85-
}
86-
if disconnect {
87-
println!("Disconnecting");
88-
break;
89-
}
90-
}
91-
}
92124
}

crates/hotfix/tests/session_tests.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,25 @@ use hotfix::application::ApplicationRef;
55
use hotfix::config::SessionConfig;
66
use hotfix::session::SessionRef;
77
use hotfix::store::in_memory::InMemoryMessageStore;
8+
use hotfix_message::Part;
9+
use hotfix_message::fix44::MSG_TYPE;
810

911
mod common;
1012

1113
#[tokio::test]
1214
async fn test_happy_login_flow() {
1315
let session = create_session();
1416
let mut mock_counterparty = MockCounterparty::start(session.clone()).await;
15-
mock_counterparty.assert_next(None, |_msg| true).await;
17+
18+
// assert that a logon message is received (type 'A')
19+
mock_counterparty
20+
.assert_next(|msg| msg.header().get::<&str>(MSG_TYPE).unwrap() == "A")
21+
.await;
22+
1623
session
1724
.disconnect("Test Session Finished".to_string())
1825
.await;
19-
mock_counterparty.assert_disconnected(None).await;
26+
mock_counterparty.assert_disconnected().await;
2027
}
2128

2229
fn create_session() -> SessionRef<TestMessage> {

0 commit comments

Comments
 (0)