Skip to content

Commit 5bef56b

Browse files
committed
Flesh out structure for session flow tests
1 parent 4272c07 commit 5bef56b

8 files changed

Lines changed: 171 additions & 10 deletions

File tree

crates/hotfix/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ pub(crate) mod error;
2929
pub mod initiator;
3030
pub mod message;
3131
mod message_utils;
32-
pub(crate) mod session;
32+
pub mod session;
3333
mod session_schedule;
3434
pub mod store;
35-
pub(crate) mod transport;
35+
pub mod transport;
3636

3737
pub use application::Application;
3838
pub use hotfix_message::field_types;

crates/hotfix/src/message.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub(crate) mod resend_request;
1313
pub(crate) mod sequence_reset;
1414
pub(crate) mod test_request;
1515

16+
pub use parser::RawFixMessage;
17+
1618
pub trait FixMessage: Clone + Send + 'static {
1719
fn write(&self, msg: &mut Message);
1820

crates/hotfix/src/transport/connection.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,19 @@ use crate::transport::reader::ReaderRef;
22
use crate::transport::writer::WriterRef;
33

44
pub struct FixConnection {
5-
_writer: WriterRef,
6-
_reader: ReaderRef,
5+
writer: WriterRef,
6+
reader: ReaderRef,
77
}
88

99
impl FixConnection {
1010
pub fn new(writer: WriterRef, reader: ReaderRef) -> Self {
11-
Self {
12-
_writer: writer,
13-
_reader: reader,
14-
}
11+
Self { writer, reader }
1512
}
1613
pub fn get_writer(&self) -> WriterRef {
17-
self._writer.clone()
14+
self.writer.clone()
1815
}
1916

2017
pub async fn run_until_disconnect(self) {
21-
self._reader.wait_for_disconnect().await
18+
self.reader.wait_for_disconnect().await
2219
}
2320
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use crate::common::test_messages::TestMessage;
2+
use hotfix::Application;
3+
4+
pub struct MockApplication {}
5+
6+
#[async_trait::async_trait]
7+
impl Application<TestMessage> for MockApplication {
8+
async fn on_message_from_app(&self, _msg: TestMessage) {}
9+
10+
async fn on_message_to_app(&self, _msg: TestMessage) {}
11+
12+
async fn on_logout(&mut self, _reason: &str) {}
13+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use hotfix::message::{FixMessage, RawFixMessage};
2+
use hotfix::session::SessionRef;
3+
use hotfix::transport::FixConnection;
4+
use hotfix::transport::reader::ReaderRef;
5+
use hotfix::transport::writer::{WriterMessage, WriterRef};
6+
use std::sync::Arc;
7+
use std::time::{Duration, Instant};
8+
use tokio::sync::mpsc::Receiver;
9+
use tokio::sync::{Mutex, mpsc, oneshot};
10+
11+
pub struct MockCounterparty {
12+
received_messages: Arc<Mutex<Vec<RawFixMessage>>>,
13+
_connection: FixConnection,
14+
_dc_sender: oneshot::Sender<()>,
15+
}
16+
17+
type MessageStore = Arc<Mutex<Vec<RawFixMessage>>>;
18+
19+
impl MockCounterparty {
20+
pub async fn start(session_ref: SessionRef<impl FixMessage>) -> Self {
21+
let (writer_ref, received_messages) = Self::spawn_writer();
22+
let (reader_ref, dc_sender) = Self::create_reader();
23+
let connection = FixConnection::new(writer_ref, reader_ref);
24+
25+
session_ref.register_writer(connection.get_writer()).await;
26+
27+
Self {
28+
received_messages,
29+
_connection: connection,
30+
_dc_sender: dc_sender,
31+
}
32+
}
33+
34+
pub async fn assert_message_count(&self, expected_count: usize, timeout_secs: f32) {
35+
let timeout_duration = Duration::from_secs_f32(timeout_secs);
36+
let start_time = Instant::now();
37+
38+
loop {
39+
{
40+
let messages = self.received_messages.lock().await;
41+
if messages.len() >= expected_count {
42+
return;
43+
}
44+
}
45+
46+
if start_time.elapsed() >= timeout_duration {
47+
let current_count = self.received_messages.lock().await.len();
48+
panic!(
49+
"Expected {expected_count} messages, but only received {current_count} within {timeout_secs} seconds"
50+
);
51+
}
52+
53+
tokio::time::sleep(Duration::from_millis(1)).await;
54+
}
55+
}
56+
57+
fn spawn_writer() -> (WriterRef, MessageStore) {
58+
// mock implementation to receive messages from the session
59+
// and hold on to them for test assertions
60+
let received_messages: MessageStore = Arc::new(Mutex::new(vec![]));
61+
let (sender, mailbox) = mpsc::channel(10);
62+
tokio::spawn(Self::run_writer(mailbox, received_messages.clone()));
63+
64+
(WriterRef::new(sender), received_messages)
65+
}
66+
67+
fn create_reader() -> (ReaderRef, oneshot::Sender<()>) {
68+
let (dc_sender, dc_receiver) = oneshot::channel();
69+
(ReaderRef::new(dc_receiver), dc_sender)
70+
}
71+
72+
async fn run_writer(
73+
mut mailbox: Receiver<WriterMessage>,
74+
received_messages: Arc<Mutex<Vec<RawFixMessage>>>,
75+
) {
76+
while let Some(msg) = mailbox.recv().await {
77+
match msg {
78+
WriterMessage::SendMessage(fix_message) => {
79+
println!("Received message from session: {fix_message:?}");
80+
received_messages.lock().await.push(fix_message);
81+
}
82+
WriterMessage::Disconnect => {
83+
println!("Disconnecting");
84+
break;
85+
}
86+
}
87+
}
88+
}
89+
}

crates/hotfix/tests/common/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod mock_application;
2+
pub mod mock_counterparty;
3+
pub mod test_messages;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use hotfix::Message as HotfixMessage;
2+
use hotfix::message::FixMessage;
3+
4+
#[derive(Debug, Clone)]
5+
pub struct TestMessage;
6+
7+
impl FixMessage for TestMessage {
8+
fn write(&self, _msg: &mut HotfixMessage) {}
9+
10+
fn message_type(&self) -> &str {
11+
unimplemented!()
12+
}
13+
14+
fn parse(_msg: &HotfixMessage) -> Self {
15+
TestMessage
16+
}
17+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use crate::common::mock_application::MockApplication;
2+
use crate::common::mock_counterparty::MockCounterparty;
3+
use crate::common::test_messages::TestMessage;
4+
use hotfix::application::ApplicationRef;
5+
use hotfix::config::SessionConfig;
6+
use hotfix::session::SessionRef;
7+
use hotfix::store::in_memory::InMemoryMessageStore;
8+
9+
mod common;
10+
11+
#[tokio::test]
12+
async fn test_happy_login_flow() {
13+
let session = create_session();
14+
let mock_counterparty = MockCounterparty::start(session).await;
15+
mock_counterparty.assert_message_count(1, 0.5).await;
16+
}
17+
18+
fn create_session() -> SessionRef<TestMessage> {
19+
let config = create_session_config();
20+
let application_ref = ApplicationRef::new(MockApplication {});
21+
let message_store = InMemoryMessageStore::default();
22+
23+
SessionRef::new(config, application_ref, message_store)
24+
}
25+
26+
fn create_session_config() -> SessionConfig {
27+
SessionConfig {
28+
begin_string: "FIX.4.4".to_string(),
29+
sender_comp_id: "dummy-initiator".to_string(),
30+
target_comp_id: "dummy-acceptor".to_string(),
31+
data_dictionary_path: None,
32+
connection_host: "".to_string(),
33+
connection_port: 0,
34+
tls_config: None,
35+
heartbeat_interval: 30,
36+
reconnect_interval: 30,
37+
reset_on_logon: false,
38+
schedule: None,
39+
}
40+
}

0 commit comments

Comments
 (0)