use std::sync::Arc;
use std::time::Duration;
use chrono::{Duration as ChronoDuration, Timelike, Utc};
use quickfix_tokio::{Application, Engine, MemoryStoreFactory, NullLogFactory, Settings};
#[derive(Default)]
struct App;
impl Application for App {}
fn free_port() -> u16 {
std::net::TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port()
}
fn hms(t: chrono::DateTime<Utc>) -> String {
t.format("%H:%M:%S").to_string()
}
async fn wait_logged_on(h: &quickfix_tokio::SessionHandle, secs: u64) -> bool {
let deadline = tokio::time::Instant::now() + Duration::from_secs(secs);
while tokio::time::Instant::now() < deadline {
if h.is_logged_on().await {
return true;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
false
}
#[tokio::test]
async fn scheduled_logout_when_window_closes() {
let now = Utc::now();
let sod = now.time().num_seconds_from_midnight();
if !(20..86_350).contains(&sod) {
eprintln!("skipping scheduled_logout: too close to midnight");
return;
}
let start = hms(now - ChronoDuration::seconds(15));
let end = hms(now + ChronoDuration::seconds(3));
let port = free_port();
let server = Engine::start(
&Settings::parse(&format!(
"[SESSION]\nConnectionType=acceptor\nBeginString=FIX.4.4\nSenderCompID=SERVER\n\
TargetCompID=CLIENT\nSocketAcceptPort={port}\nHeartBtInt=30\n\
StartTime={start}\nEndTime={end}\n"
))
.unwrap(),
Arc::new(App),
Arc::new(MemoryStoreFactory::new()),
Arc::new(NullLogFactory),
)
.await
.unwrap();
let client = Engine::start(
&Settings::parse(&format!(
"[SESSION]\nConnectionType=initiator\nBeginString=FIX.4.4\nSenderCompID=CLIENT\n\
TargetCompID=SERVER\nSocketConnectHost=127.0.0.1\nSocketConnectPort={port}\n\
HeartBtInt=30\nReconnectInterval=1\nStartTime={start}\nEndTime={end}\n"
))
.unwrap(),
Arc::new(App),
Arc::new(MemoryStoreFactory::new()),
Arc::new(NullLogFactory),
)
.await
.unwrap();
let cs = client.session("FIX.4.4", "CLIENT", "SERVER").unwrap();
let ss = server.session("FIX.4.4", "SERVER", "CLIENT").unwrap();
assert!(wait_logged_on(&cs, 5).await, "did not log on inside the window");
assert!(ss.is_logged_on().await, "server did not log on inside the window");
tokio::time::sleep(Duration::from_secs(5)).await;
assert!(!cs.is_logged_on().await, "client still logged on after window closed");
assert!(!ss.is_logged_on().await, "server still logged on after window closed");
client.stop().await;
server.stop().await;
}
#[tokio::test]
async fn no_logon_outside_session_time() {
let sod = Utc::now().time().num_seconds_from_midnight();
if sod < 10 {
eprintln!("skipping no_logon_outside_session_time: too close to midnight");
return;
}
let port = free_port();
let schedule = "StartTime=00:00:01\nEndTime=00:00:02\n";
let server = Engine::start(
&Settings::parse(&format!(
"[SESSION]\nConnectionType=acceptor\nBeginString=FIX.4.4\nSenderCompID=SERVER\n\
TargetCompID=CLIENT\nSocketAcceptPort={port}\nHeartBtInt=30\n{schedule}"
))
.unwrap(),
Arc::new(App),
Arc::new(MemoryStoreFactory::new()),
Arc::new(NullLogFactory),
)
.await
.unwrap();
let client = Engine::start(
&Settings::parse(&format!(
"[SESSION]\nConnectionType=initiator\nBeginString=FIX.4.4\nSenderCompID=CLIENT\n\
TargetCompID=SERVER\nSocketConnectHost=127.0.0.1\nSocketConnectPort={port}\n\
HeartBtInt=30\nReconnectInterval=1\n{schedule}"
))
.unwrap(),
Arc::new(App),
Arc::new(MemoryStoreFactory::new()),
Arc::new(NullLogFactory),
)
.await
.unwrap();
let cs = client.session("FIX.4.4", "CLIENT", "SERVER").unwrap();
assert!(
!wait_logged_on(&cs, 3).await,
"logged on despite being outside the session window"
);
client.stop().await;
server.stop().await;
}