use crate::client::handshake_state::{HandshakeState, ServerVersion};
use crate::common::test_utils::helpers::order_status_frame;
use crate::orders::OrderStatusKind;
use std::sync::Arc;
use std::time::Duration;
use super::*;
use crate::client::ids::{OrderId, RequestId};
use crate::common::test_utils::helpers;
use crate::common::test_utils::helpers::{
binary_proto, body, error_frame, execution_data_frame, farm_ok_frame_42, farm_ok_frame_unrouted, handshake_frames, NoticeTestData, FARM_OK_MSG,
};
use crate::connection::common::ConnectionProtocol;
use crate::connection::r#async::AsyncConnection;
use crate::messages::{encode_raw_length, OutgoingMessages, TRANSPORT_RECONNECT_CODE};
use crate::server_versions;
use crate::testdata::builders::contracts::contract_data;
use crate::testdata::builders::orders::order_bound;
use crate::testdata::builders::ResponseProtoEncoder;
use crate::transport::common::poison_with;
const CLIENT_ID: i32 = 28;
fn make_bus() -> (MemoryStream, Arc<AsyncTcpMessageBus<MemoryStream>>) {
let stream = MemoryStream::default();
let connection = AsyncConnection::stubbed(stream.clone(), CLIENT_ID);
connection.set_server_version_for_test(server_versions::PROTOBUF_REST_MESSAGES_3);
let bus = Arc::new(AsyncTcpMessageBus::new(connection));
(stream, bus)
}
const TICK: Duration = Duration::from_millis(100);
#[tokio::test]
async fn with_channel_capacity_bounds_request_channels() {
let stream = MemoryStream::default();
let connection = AsyncConnection::stubbed(stream.clone(), 28);
connection.set_server_version_for_test(server_versions::PROTOBUF_REST_MESSAGES_3);
let bus = Arc::new(AsyncTcpMessageBus::with_channel_capacity(connection, 2));
let _sub = bus.send_request(RequestId::nth(1), vec![]).await.unwrap();
let sender = bus.requests.sender(&RequestId::nth(1)).unwrap();
for _ in 0..3 {
sender.send(RoutedItem::Error(Error::Cancelled)).unwrap();
}
assert_eq!(sender.len(), 2, "capacity-2 channel retains only the newest 2 frames");
}
async fn assert_capacity_and_class(name: &str, subscription: &mut AsyncInternalSubscription, send: impl Fn(), capacity: usize, class: ChannelClass) {
for _ in 0..=capacity {
send();
}
let expected = match class {
ChannelClass::MarketData => crate::messages::subscription_lag_notice(1),
ChannelClass::Order | ChannelClass::OrderStream => crate::messages::order_lag_notice(1),
};
match subscription.next_routed().await {
Some(RoutedItem::Notice(notice)) => assert_eq!(notice, expected, "{name}"),
other => panic!("{name}: expected a one-frame lag notice, got {other:?}"),
}
}
#[tokio::test]
async fn channel_classes_under_small_channel_capacity() {
let stream = MemoryStream::default();
let connection = AsyncConnection::stubbed(stream.clone(), 28);
connection.set_server_version_for_test(server_versions::PROTOBUF_REST_MESSAGES_3);
let bus = Arc::new(AsyncTcpMessageBus::with_channel_capacity(connection, 1));
let cancelled = || RoutedItem::Error(Error::Cancelled);
let mut market = bus.send_request(RequestId::nth(1), vec![]).await.unwrap();
let sender = bus.requests.sender(&RequestId::nth(1)).unwrap();
let send = || drop(sender.send(cancelled()));
assert_capacity_and_class("request", &mut market, send, 1, ChannelClass::MarketData).await;
let mut executions = bus.send_executions_request(RequestId::nth(2), vec![]).await.unwrap();
let sender = bus.requests.sender(&RequestId::nth(2)).unwrap();
let send = || drop(sender.send(cancelled()));
assert_capacity_and_class("executions", &mut executions, send, ORDER_CAPACITY, ChannelClass::Order).await;
let mut order = bus.send_order_request(OrderId::new(3), vec![]).await.unwrap();
let sender = bus.orders.sender(&OrderId::new(3)).unwrap();
let send = || drop(sender.send(cancelled()));
assert_capacity_and_class("order", &mut order, send, ORDER_CAPACITY, ChannelClass::Order).await;
let mut updates = bus.create_order_update_subscription().await.unwrap();
let sender = bus.order_taps.updates_sender().unwrap();
let send = || drop(sender.send(cancelled()));
assert_capacity_and_class(
"order update stream",
&mut updates,
send,
ORDER_STREAM_CAPACITY,
ChannelClass::OrderStream,
)
.await;
let notify = || bus.shared_channels.notify_all(cancelled);
let mut positions = bus.send_shared_request(OutgoingMessages::RequestPositions, vec![]).await.unwrap();
assert_capacity_and_class("positions", &mut positions, notify, 1, ChannelClass::MarketData).await;
let mut open_orders = bus.send_shared_request(OutgoingMessages::RequestOpenOrders, vec![]).await.unwrap();
assert_capacity_and_class("open orders", &mut open_orders, notify, ORDER_STREAM_CAPACITY, ChannelClass::OrderStream).await;
}
#[test]
fn channel_class_capacity() {
assert_eq!(ChannelClass::MarketData.capacity(4), 4);
assert_eq!(ChannelClass::Order.capacity(4), ORDER_CAPACITY);
assert_eq!(ChannelClass::OrderStream.capacity(4), ORDER_STREAM_CAPACITY);
let large = ORDER_STREAM_CAPACITY * 2;
assert_eq!(ChannelClass::Order.capacity(large), large);
assert_eq!(ChannelClass::OrderStream.capacity(large), large);
}
#[test]
fn shared_order_channels_are_order_class() {
for request in [
OutgoingMessages::RequestOpenOrders,
OutgoingMessages::RequestAllOpenOrders,
OutgoingMessages::RequestAutoOpenOrders,
OutgoingMessages::RequestCompletedOrders,
] {
assert_eq!(ChannelClass::of_shared(request), ChannelClass::OrderStream, "{request:?}");
}
assert_eq!(ChannelClass::of_shared(OutgoingMessages::RequestPositions), ChannelClass::MarketData);
}
#[tokio::test]
async fn order_class_lag_surfaces_order_lag_notice() {
let (sender, receiver) = broadcast::channel(1);
let mut subscription = AsyncInternalSubscription::new(receiver).class(ChannelClass::Order);
let mut clone = subscription.clone();
for _ in 0..4 {
sender.send(RoutedItem::Error(Error::Cancelled)).unwrap();
}
for sub in [&mut subscription, &mut clone] {
match sub.next_routed().await {
Some(RoutedItem::Notice(notice)) => assert_eq!(notice, crate::messages::order_lag_notice(3)),
other => panic!("expected order lag notice, got {other:?}"),
}
}
}
async fn next_message(sub: &mut AsyncInternalSubscription) -> ResponseMessage {
tokio::time::timeout(TICK, sub.next())
.await
.expect("subscription got no message before timeout")
.expect("subscription closed")
.expect("subscription error")
}
#[tokio::test]
async fn request_id_correlation_with_interleaved_responses() {
let (stream, bus) = make_bus();
let (id_a, id_b) = (RequestId::nth(100), RequestId::nth(200));
let mut sub_a = bus.send_request(id_a, vec![]).await.unwrap();
let mut sub_b = bus.send_request(id_b, vec![]).await.unwrap();
stream.push_inbound(body(&format!("89|{id_b}|payload-b|")));
stream.push_inbound(body(&format!("89|{id_a}|payload-a|")));
bus.read_and_route_message().await.unwrap();
bus.read_and_route_message().await.unwrap();
let msg_a = next_message(&mut sub_a).await;
let msg_b = next_message(&mut sub_b).await;
assert_eq!(msg_a.peek_int(1).unwrap(), id_a.raw());
assert_eq!(msg_b.peek_int(1).unwrap(), id_b.raw());
assert!(sub_a.try_next_routed().is_none(), "sub_a received an extra message");
assert!(sub_b.try_next_routed().is_none(), "sub_b received an extra message");
}
#[tokio::test]
async fn order_id_correlation_with_interleaved_responses() {
let (stream, bus) = make_bus();
let mut sub_a = bus.send_order_request(OrderId::from(11), vec![]).await.unwrap();
let mut sub_b = bus.send_order_request(OrderId::from(22), vec![]).await.unwrap();
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::OrderStatus as i32,
&crate::proto::OrderStatus {
order_id: Some(22),
status: Some("Filled".into()),
..Default::default()
},
));
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::OrderStatus as i32,
&crate::proto::OrderStatus {
order_id: Some(11),
status: Some("Submitted".into()),
..Default::default()
},
));
bus.read_and_route_message().await.unwrap();
bus.read_and_route_message().await.unwrap();
let msg_a = next_message(&mut sub_a).await;
let msg_b = next_message(&mut sub_b).await;
assert_eq!(msg_a.order_id(), Some(11));
assert_eq!(msg_b.order_id(), Some(22));
assert!(sub_a.try_next_routed().is_none(), "sub_a received an extra message");
assert!(sub_b.try_next_routed().is_none(), "sub_b received an extra message");
}
#[tokio::test]
async fn shared_channel_fan_out_for_open_orders() {
let (stream, bus) = make_bus();
let mut sub_open = bus.send_shared_request(OutgoingMessages::RequestOpenOrders, vec![]).await.unwrap();
let mut sub_all = bus.send_shared_request(OutgoingMessages::RequestAllOpenOrders, vec![]).await.unwrap();
let mut sub_auto = bus.send_shared_request(OutgoingMessages::RequestAutoOpenOrders, vec![]).await.unwrap();
for order_id in [Some(42), None] {
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::OpenOrder as i32,
&crate::proto::OpenOrder {
order_id,
..Default::default()
},
));
bus.read_and_route_message().await.unwrap();
for (name, sub) in [("open", &mut sub_open), ("all", &mut sub_all), ("auto", &mut sub_auto)] {
let msg = next_message(sub).await;
assert_eq!(msg.message_type(), crate::messages::IncomingMessages::OpenOrder, "sub_{name}");
assert_eq!(msg.order_id(), order_id, "sub_{name}");
}
}
}
#[tokio::test]
async fn routed_order_also_reaches_open_orders() {
use crate::messages::IncomingMessages as M;
let (stream, bus) = make_bus();
let mut order = bus.send_order_request(OrderId::from(42), vec![]).await.unwrap();
let mut open_orders = bus.send_shared_request(OutgoingMessages::RequestOpenOrders, vec![]).await.unwrap();
stream.push_inbound(binary_proto(
M::OpenOrder as i32,
&crate::proto::OpenOrder {
order_id: Some(42),
..Default::default()
},
));
stream.push_inbound(binary_proto(
M::OrderStatus as i32,
&crate::proto::OrderStatus {
order_id: Some(42),
status: Some("Submitted".into()),
..Default::default()
},
));
bus.read_and_route_message().await.unwrap();
bus.read_and_route_message().await.unwrap();
for (name, sub) in [("order", &mut order), ("open orders", &mut open_orders)] {
for expected in [M::OpenOrder, M::OrderStatus] {
let msg = next_message(sub).await;
assert_eq!(msg.message_type(), expected, "{name}");
assert_eq!(msg.order_id(), Some(42), "{name}");
}
assert!(sub.try_next_routed().is_none(), "{name} received an extra message");
}
}
#[tokio::test]
async fn shared_channel_routing_current_time() {
let (stream, bus) = make_bus();
let mut sub = bus.send_shared_request(OutgoingMessages::RequestCurrentTime, vec![]).await.unwrap();
stream.push_inbound(body("49|1|1700000000|"));
bus.read_and_route_message().await.unwrap();
let msg = next_message(&mut sub).await;
assert_eq!(msg.peek_int(0).unwrap(), 49);
assert_eq!(msg.peek_int(2).unwrap(), 1_700_000_000);
}
#[tokio::test]
async fn read_and_route_surfaces_eof() {
let (stream, bus) = make_bus();
stream.hang_up();
let err = bus.read_and_route_message().await.expect_err("dispatch should surface an error");
assert!(
matches!(err, Error::Io(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof),
"unexpected error: {err:?}"
);
}
#[tokio::test]
async fn send_message_writes_through() {
let (stream, bus) = make_bus();
let mb: &dyn AsyncMessageBus = bus.as_ref();
mb.send_message(b"global-cancel-bytes".to_vec()).await.unwrap();
let captured = stream.captured();
assert!(captured.windows(b"global-cancel-bytes".len()).any(|w| w == b"global-cancel-bytes"));
}
#[tokio::test]
async fn create_order_update_subscription_is_unique() {
let (_, bus) = make_bus();
let mb: &dyn AsyncMessageBus = bus.as_ref();
let _first = mb.create_order_update_subscription().await.unwrap();
let err = mb.create_order_update_subscription().await.err().expect("duplicate fails");
assert!(matches!(err, Error::AlreadySubscribed), "got: {err:?}");
}
#[tokio::test]
async fn create_order_update_subscription_after_shutdown_fails() {
let (_, bus) = make_bus();
let mb: &dyn AsyncMessageBus = bus.as_ref();
mb.ensure_shutdown().await;
let err = mb.create_order_update_subscription().await.err().expect("subscribe after shutdown");
assert!(matches!(err, Error::Shutdown), "got: {err:?}");
assert!(bus.order_taps.updates_lease().is_none());
}
#[tokio::test]
async fn notice_stream_ends_on_shutdown() {
let (_, bus) = make_bus();
let mb: &dyn AsyncMessageBus = bus.as_ref();
let mut live = mb.notice_subscribe();
mb.ensure_shutdown().await;
let ended = tokio::time::timeout(Duration::from_millis(500), live.next()).await;
assert!(matches!(ended, Ok(None)), "live stream: {ended:?}");
let mut late = mb.notice_subscribe();
bus.connection.notice_broadcaster.broadcast(Notice::synthesized(-1, "late".into()));
let ended = tokio::time::timeout(Duration::from_millis(500), late.next()).await;
assert!(matches!(ended, Ok(None)), "late stream: {ended:?}");
}
#[tokio::test]
async fn notice_stream_ends_on_request_shutdown_sync() {
let (_, bus) = make_bus();
let mb: &dyn AsyncMessageBus = bus.as_ref();
let mut live = mb.notice_subscribe();
mb.request_shutdown_sync();
let ended = tokio::time::timeout(Duration::from_millis(500), live.next()).await;
assert!(matches!(ended, Ok(None)), "live stream: {ended:?}");
}
#[tokio::test]
async fn order_update_stream_ends_on_request_shutdown_sync() {
let (_, bus) = make_bus();
bus.clone().process_messages().expect("process_messages");
let mut updates = bus.create_order_update_subscription().await.unwrap();
bus.request_shutdown_sync();
let drained = tokio::time::timeout(Duration::from_millis(500), async { while updates.next().await.is_some() {} }).await;
assert!(drained.is_ok(), "order-update stream did not end");
}
#[tokio::test]
async fn shutdown_fails_every_subscription_then_ends_it() {
let (_, bus) = make_bus();
let subscriptions = [
("request", bus.send_request(RequestId::nth(1), vec![]).await.unwrap()),
("order", bus.send_order_request(OrderId::from(7), vec![]).await.unwrap()),
(
"shared",
bus.send_shared_request(OutgoingMessages::RequestPositions, vec![]).await.unwrap(),
),
("order update", bus.create_order_update_subscription().await.unwrap()),
];
bus.request_shutdown_sync();
for (name, mut subscription) in subscriptions {
let first = tokio::time::timeout(TICK, subscription.next_routed()).await.expect(name);
assert!(matches!(first, Some(RoutedItem::Error(Error::Shutdown))), "{name}: {first:?}");
let end = tokio::time::timeout(TICK, subscription.next_routed()).await.expect(name);
assert!(end.is_none(), "{name} did not end: {end:?}");
}
}
#[tokio::test]
async fn subscriptions_with_executions_end_on_request_shutdown_sync() {
let (stream, bus) = make_bus();
let mut order = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
let mut executions = bus.send_request(RequestId::nth(99), vec![]).await.unwrap();
stream.push_inbound(execution_data_frame(CLIENT_ID, 0, 7, "exec-order"));
stream.push_inbound(execution_data_frame(CLIENT_ID, RequestId::nth(99).raw(), 0, "exec-request"));
bus.read_and_route_message().await.unwrap();
bus.read_and_route_message().await.unwrap();
for (name, sub) in [("order", &mut order), ("executions", &mut executions)] {
let message = next_message(sub).await;
assert_eq!(message.message_type(), crate::messages::IncomingMessages::ExecutionData, "{name}");
}
assert_eq!(bus.executions.len(), 2, "both executions mapped");
bus.clone().process_messages().expect("process_messages");
bus.request_shutdown_sync();
for (name, sub) in [("order", &mut order), ("executions", &mut executions)] {
let drained = tokio::time::timeout(Duration::from_millis(500), async { while sub.next().await.is_some() {} }).await;
assert!(drained.is_ok(), "{name} subscription with an execution did not end");
}
assert!(bus.executions.is_empty());
}
#[tokio::test]
async fn shutdown_frame_ends_subscriptions_with_executions() {
let (stream, bus) = make_bus();
let mut order = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
stream.push_inbound(execution_data_frame(CLIENT_ID, 0, 7, "exec-order"));
bus.read_and_route_message().await.unwrap();
let message = next_message(&mut order).await;
assert_eq!(message.message_type(), crate::messages::IncomingMessages::ExecutionData);
bus.clone().process_messages().expect("process_messages");
stream.push_inbound((crate::messages::IncomingMessages::Shutdown as i32).to_be_bytes().to_vec());
let drained = tokio::time::timeout(Duration::from_millis(500), async { while order.next().await.is_some() {} }).await;
assert!(drained.is_ok(), "order subscription with an execution did not end");
assert!(!bus.is_connected());
assert!(bus.executions.is_empty());
}
#[tokio::test]
async fn order_frame_routes_while_a_dropped_order_update_stream_awaits_cleanup() {
let (stream, bus) = make_bus();
let mut order = bus.send_order_request(OrderId::from(22), vec![]).await.unwrap();
let updates = bus.create_order_update_subscription().await.unwrap();
let cleanup_gate = bus.cleanup_gate.lock().await;
drop(updates);
assert!(bus.order_taps.updates_lease().is_some(), "the dropped stream is still registered");
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::OrderStatus as i32,
&crate::proto::OrderStatus {
order_id: Some(22),
status: Some("Filled".into()),
..Default::default()
},
));
tokio::time::timeout(Duration::from_millis(500), bus.read_and_route_message())
.await
.expect("routing blocked")
.unwrap();
assert_eq!(next_message(&mut order).await.order_id(), Some(22));
drop(cleanup_gate);
}
#[tokio::test]
async fn is_connected_reflects_shutdown_flag() {
let (_, bus) = make_bus();
let mb: &dyn AsyncMessageBus = bus.as_ref();
assert!(mb.is_connected());
mb.request_shutdown_sync();
assert!(!mb.is_connected());
}
async fn next_routed(sub: &mut AsyncInternalSubscription) -> RoutedItem {
tokio::time::timeout(TICK, sub.next_routed())
.await
.expect("subscription got no item before timeout")
.expect("subscription closed")
}
#[tokio::test]
async fn warning_with_request_id_delivers_notice() {
let (stream, bus) = make_bus();
let request_id = RequestId::nth(42);
let mut sub = bus.send_request(request_id, vec![]).await.unwrap();
stream.push_inbound(error_frame(request_id.raw(), 2104, FARM_OK_MSG));
bus.read_and_route_message().await.unwrap();
let item = next_routed(&mut sub).await;
match item {
RoutedItem::Notice(notice) => {
assert_eq!(notice.request_id, Some(request_id.raw()));
assert_eq!(notice.code, 2104);
assert_eq!(notice.message, "Market data farm connection is OK:usfarm");
}
other => panic!("expected RoutedItem::Notice, got {other:?}"),
}
stream.push_inbound(body(&format!("89|{request_id}|payload|")));
bus.read_and_route_message().await.unwrap();
let item = next_routed(&mut sub).await;
assert!(matches!(item, RoutedItem::Response(_)), "got: {item:?}");
}
#[tokio::test]
async fn data_advisory_with_request_id_keeps_stream_open() {
let (stream, bus) = make_bus();
let request_id = RequestId::nth(42);
let mut sub = bus.send_request(request_id, vec![]).await.unwrap();
let code = 10167; stream.push_inbound(error_frame(request_id.raw(), code, "Displaying delayed market data."));
bus.read_and_route_message().await.unwrap();
let item = next_routed(&mut sub).await;
match item {
RoutedItem::Notice(notice) => {
assert_eq!(notice.code, code);
assert!(notice.is_data_advisory());
}
other => panic!("expected RoutedItem::Notice, got {other:?}"),
}
stream.push_inbound(body(&format!("89|{request_id}|payload|")));
bus.read_and_route_message().await.unwrap();
let item = next_routed(&mut sub).await;
assert!(matches!(item, RoutedItem::Response(_)), "got: {item:?}");
}
#[tokio::test]
async fn hard_error_with_request_id_terminates_subscription() {
let (stream, bus) = make_bus();
let request_id = RequestId::nth(42);
let mut sub = bus.send_request(request_id, vec![]).await.unwrap();
stream.push_inbound(error_frame(request_id.raw(), 200, "No security definition found"));
bus.read_and_route_message().await.unwrap();
let item = next_routed(&mut sub).await;
match item {
RoutedItem::Error(Error::Notice(notice)) => {
assert_eq!(notice.request_id, Some(request_id.raw()));
assert_eq!(notice.code, 200);
assert_eq!(notice.message, "No security definition found");
}
other => panic!("expected RoutedItem::Error(Notice), got {other:?}"),
}
}
#[tokio::test]
async fn warning_with_unspecified_id_is_log_only() {
let (stream, bus) = make_bus();
let mut sub = bus.send_request(RequestId::nth(42), vec![]).await.unwrap();
stream.push_inbound(error_frame(-1, 2104, FARM_OK_MSG));
bus.read_and_route_message().await.unwrap();
assert!(sub.try_next_routed().is_none(), "unrouted notice must not be delivered to a subscription");
}
#[tokio::test]
async fn request_less_hard_error_fails_one_shot_and_spares_stream() {
let (stream, bus) = make_bus();
let mut notice_stream = bus.notice_subscribe();
let mut one_shot = bus.send_shared_request(OutgoingMessages::RequestIds, vec![]).await.unwrap();
let mut streaming = bus.send_shared_request(OutgoingMessages::RequestPositions, vec![]).await.unwrap();
stream.push_inbound(error_frame(-1, 321, READ_ONLY_MSG));
bus.read_and_route_message().await.unwrap();
let item = tokio::time::timeout(TICK, one_shot.next())
.await
.expect("one-shot got no error before timeout")
.expect("subscription closed");
match item {
Err(Error::Notice(notice)) => {
assert_eq!(notice.code, 321);
assert_eq!(notice.message, READ_ONLY_MSG);
}
other => panic!("expected Err(Notice), got {other:?}"),
}
assert!(
streaming.try_next_routed().is_none(),
"streaming shared sub must not receive the request-less error"
);
let notice = tokio::time::timeout(TICK, notice_stream.next()).await.unwrap().unwrap();
assert_eq!(notice.request_id, None);
assert_eq!(notice.code, 321);
}
#[tokio::test]
async fn request_less_warning_does_not_fail_one_shot() {
let (stream, bus) = make_bus();
let mut one_shot = bus.send_shared_request(OutgoingMessages::RequestIds, vec![]).await.unwrap();
stream.push_inbound(error_frame(-1, 2104, FARM_OK_MSG));
bus.read_and_route_message().await.unwrap();
assert!(one_shot.try_next_routed().is_none(), "warning must not fail a one-shot shared request");
}
#[tokio::test]
async fn request_less_system_message_does_not_fail_one_shot() {
let (stream, bus) = make_bus();
let mut notice_stream = bus.notice_subscribe();
let mut one_shot = bus.send_shared_request(OutgoingMessages::RequestIds, vec![]).await.unwrap();
let code = crate::messages::CONNECTIVITY_RESTORED_DATA_MAINTAINED_CODE;
stream.push_inbound(error_frame(-1, code, CONNECTIVITY_RESTORED_MSG));
bus.read_and_route_message().await.unwrap();
assert!(
one_shot.try_next_routed().is_none(),
"system message must not fail a one-shot shared request"
);
let notice = tokio::time::timeout(TICK, notice_stream.next()).await.unwrap().unwrap();
assert_eq!(notice.code, code);
assert!(notice.is_system_message());
}
#[tokio::test]
async fn warning_with_order_id_routes_to_order_channel() {
let (stream, bus) = make_bus();
let mut sub = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
stream.push_inbound(error_frame(7, 2104, "Order warning"));
bus.read_and_route_message().await.unwrap();
let item = next_routed(&mut sub).await;
match item {
RoutedItem::Notice(notice) => {
assert_eq!(notice.code, 2104);
assert_eq!(notice.message, "Order warning");
}
other => panic!("expected RoutedItem::Notice, got {other:?}"),
}
}
use crate::subscriptions::r#async::Subscription;
use crate::subscriptions::{DecoderContext, StreamDecoder, SubscriptionItem, SubscriptionItemStreamExt};
use futures::StreamExt;
const CONNECTIVITY_RESTORED_MSG: &str = "Connectivity between IB and TWS has been restored - data maintained.";
const READ_ONLY_MSG: &str = "The API interface is currently in Read-Only mode.";
async fn make_request_subscription(request_id: RequestId) -> (MemoryStream, Arc<AsyncTcpMessageBus<MemoryStream>>, Subscription<NoticeTestData>) {
let (stream, bus) = make_bus();
let internal = bus.send_request(request_id, vec![]).await.unwrap();
let sub = Subscription::new_from_internal(internal, bus.clone(), Some(request_id.raw()), None, DecoderContext::default());
(stream, bus, sub)
}
async fn make_order_subscription(order_id: OrderId) -> (MemoryStream, Arc<AsyncTcpMessageBus<MemoryStream>>, Subscription<NoticeTestData>) {
let (stream, bus) = make_bus();
let internal = bus.send_order_request(order_id, vec![]).await.unwrap();
let sub = Subscription::new_from_internal(internal, bus.clone(), None, Some(order_id.value()), DecoderContext::default());
(stream, bus, sub)
}
fn count_frames(captured: &[u8], payload: &[u8]) -> usize {
let mut rest = captured;
let mut count = 0;
while rest.len() >= 4 {
let len = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]) as usize;
let (frame, tail) = rest[4..].split_at(len);
count += usize::from(frame == payload);
rest = tail;
}
count
}
async fn wait_for_frames(stream: &MemoryStream, payload: &[u8], expected: usize) -> usize {
let deadline = std::time::Instant::now() + Duration::from_secs(2);
loop {
let count = count_frames(&stream.captured(), payload);
if count >= expected || std::time::Instant::now() >= deadline {
return count;
}
tokio::time::sleep(Duration::from_millis(1)).await;
}
}
#[tokio::test]
async fn shared_subscription_cancel_waits_for_last_subscriber() {
use crate::accounts::PositionUpdate;
use crate::client::builders::r#async::SubscriptionBuilder;
let (stream, bus) = make_bus();
let cancel = <PositionUpdate as StreamDecoder<PositionUpdate>>::cancel_message(0, None, None).unwrap();
let make = || async {
let message_bus: Arc<dyn AsyncMessageBus> = bus.clone();
SubscriptionBuilder::<PositionUpdate>::new_with_components(DecoderContext::default(), message_bus)
.send_shared(OutgoingMessages::RequestPositions, b"positions".to_vec())
.await
.unwrap()
};
let first = make().await;
let mut second = make().await;
drop(first);
tokio::time::sleep(Duration::from_millis(20)).await;
assert_eq!(
count_frames(&stream.captured(), &cancel),
0,
"cancel written while a subscription is still live"
);
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::PositionEnd as i32,
&crate::proto::PositionEnd {},
));
bus.read_and_route_message().await.unwrap();
let item = next_item(&mut second).await.expect("survivor received nothing").unwrap();
assert!(matches!(item, SubscriptionItem::Data(PositionUpdate::PositionEnd)), "got: {item:?}");
drop(second);
assert_eq!(wait_for_frames(&stream, &cancel, 1).await, 1, "last drop must write exactly one cancel");
}
#[tokio::test]
async fn shared_subscription_without_cancel_message_releases_count() {
use crate::orders::Orders;
let (stream, bus) = make_bus();
let count = || async { bus.shared_channels.live(OutgoingMessages::RequestOpenOrders).await };
let internal = bus
.send_shared_request(OutgoingMessages::RequestOpenOrders, b"open-orders".to_vec())
.await
.unwrap();
let sub = Subscription::<Orders>::new_from_internal_simple(internal, bus.clone(), DecoderContext::default());
assert_eq!(count().await, 1);
drop(sub);
let deadline = std::time::Instant::now() + Duration::from_secs(2);
while count().await != 0 && std::time::Instant::now() < deadline {
tokio::time::sleep(Duration::from_millis(1)).await;
}
assert_eq!(count().await, 0, "count leaked for a type with no cancel message");
assert_eq!(count_frames(&stream.captured(), b"open-orders"), 1, "only the request was written");
}
#[tokio::test]
async fn shared_subscription_ended_natively_still_releases_count() {
use crate::orders::Orders;
let (stream, bus) = make_bus();
let count = || async { bus.shared_channels.live(OutgoingMessages::RequestOpenOrders).await };
let internal = bus
.send_shared_request(OutgoingMessages::RequestOpenOrders, b"open-orders".to_vec())
.await
.unwrap();
let mut sub = Subscription::<Orders>::new_from_internal_simple(internal, bus.clone(), DecoderContext::default());
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::OpenOrderEnd as i32,
&crate::proto::OpenOrdersEnd {},
));
bus.read_and_route_message().await.unwrap();
assert!(next_item(&mut sub).await.is_none(), "OpenOrderEnd ends the stream");
assert!(sub.ended_natively());
drop(sub);
let deadline = std::time::Instant::now() + Duration::from_secs(2);
while count().await != 0 && std::time::Instant::now() < deadline {
tokio::time::sleep(Duration::from_millis(1)).await;
}
assert_eq!(count().await, 0, "count leaked after a natural end");
}
type PositionsSubscription = Subscription<crate::accounts::PositionUpdate>;
async fn positions_subscription(bus: &Arc<AsyncTcpMessageBus<MemoryStream>>) -> PositionsSubscription {
let internal = bus
.send_shared_request(OutgoingMessages::RequestPositions, b"positions".to_vec())
.await
.unwrap();
Subscription::new_from_internal(internal, bus.clone(), None, None, DecoderContext::default())
}
fn positions_cancel() -> Vec<u8> {
<crate::accounts::PositionUpdate as StreamDecoder<crate::accounts::PositionUpdate>>::cancel_message(0, None, None).unwrap()
}
async fn positions_live(bus: &Arc<AsyncTcpMessageBus<MemoryStream>>) -> usize {
bus.shared_channels.live(OutgoingMessages::RequestPositions).await
}
async fn wait_for_positions_live(bus: &Arc<AsyncTcpMessageBus<MemoryStream>>, expected: usize) -> usize {
let deadline = std::time::Instant::now() + Duration::from_secs(2);
loop {
let live = positions_live(bus).await;
if live == expected || std::time::Instant::now() >= deadline {
return live;
}
tokio::time::sleep(Duration::from_millis(1)).await;
}
}
#[tokio::test]
async fn shared_subscription_dropped_outside_runtime_cancels() {
let (stream, bus) = make_bus();
let cancel = positions_cancel();
let sub = positions_subscription(&bus).await;
std::thread::spawn(move || drop(sub)).join().expect("drop outside a runtime panicked");
assert_eq!(wait_for_frames(&stream, &cancel, 1).await, 1, "off-runtime drop must write the cancel");
drop(positions_subscription(&bus).await);
assert_eq!(
wait_for_frames(&stream, &cancel, 2).await,
2,
"count leaked: next drop withheld its cancel"
);
}
#[tokio::test]
async fn shared_count_restarts_after_reset() {
let (stream, bus) = make_bus();
let cancel = positions_cancel();
let old = positions_subscription(&bus).await;
bus.reset_channels().await;
assert_eq!(positions_live(&bus).await, 0);
let new = positions_subscription(&bus).await;
drop(new);
assert_eq!(wait_for_frames(&stream, &cancel, 1).await, 1, "cancel withheld by a dead handle");
drop(old);
tokio::time::sleep(Duration::from_millis(20)).await;
assert_eq!(count_frames(&stream.captured(), &cancel), 1, "dead handle wrote a cancel");
}
#[tokio::test]
async fn stale_shared_handle_neither_cancels_nor_decrements() {
let (stream, bus) = make_bus();
let cancel = positions_cancel();
let old = positions_subscription(&bus).await;
bus.reset_channels().await;
let new = positions_subscription(&bus).await;
drop(old);
tokio::time::sleep(Duration::from_millis(20)).await;
assert_eq!(count_frames(&stream.captured(), &cancel), 0, "dead handle wrote a cancel");
assert_eq!(positions_live(&bus).await, 1);
drop(new);
assert_eq!(wait_for_frames(&stream, &cancel, 1).await, 1);
assert_eq!(wait_for_positions_live(&bus, 0).await, 0);
}
#[tokio::test]
async fn reset_fails_a_shared_channel_once() {
let (_stream, bus) = make_bus();
let mut open_orders = bus.send_shared_request(OutgoingMessages::RequestOpenOrders, vec![]).await.unwrap();
bus.reset_channels().await;
let items: Vec<_> = std::iter::from_fn(|| open_orders.try_next_routed()).collect();
assert!(matches!(items.as_slice(), [RoutedItem::Error(Error::ConnectionReset)]), "{items:?}");
}
type AccountUpdatesSubscription = Subscription<crate::accounts::AccountUpdate>;
fn account(name: &str) -> crate::accounts::types::AccountId {
crate::accounts::types::AccountId(name.to_string())
}
fn account_updates_request(name: &str) -> Vec<u8> {
format!("account-updates {name}").into_bytes()
}
async fn account_updates_subscription(bus: &Arc<AsyncTcpMessageBus<MemoryStream>>, name: &str) -> Result<AccountUpdatesSubscription, Error> {
let internal = bus.send_account_updates_request(&account(name), account_updates_request(name)).await?;
Ok(Subscription::new_from_internal(
internal,
bus.clone(),
None,
None,
DecoderContext::default(),
))
}
fn account_updates_cancel() -> Vec<u8> {
<crate::accounts::AccountUpdate as StreamDecoder<crate::accounts::AccountUpdate>>::cancel_message(0, None, None).unwrap()
}
async fn account_updates_slot(bus: &AsyncTcpMessageBus<MemoryStream>) -> Option<crate::accounts::types::AccountId> {
bus.shared_channels.account_updates().await
}
#[tokio::test]
async fn account_updates_same_account_shares() {
let (stream, bus) = make_bus();
let cancel = account_updates_cancel();
let first = account_updates_subscription(&bus, "DU1").await.unwrap();
let second = account_updates_subscription(&bus, "DU1").await.unwrap();
assert_eq!(count_frames(&stream.captured(), &account_updates_request("DU1")), 2);
assert_eq!(bus.shared_channels.live(OutgoingMessages::RequestAccountData).await, 2);
first.cancel().await;
assert_eq!(
count_frames(&stream.captured(), &cancel),
0,
"cancel written while a subscription is still live"
);
assert_eq!(account_updates_slot(&bus).await, Some(account("DU1")));
second.cancel().await;
assert_eq!(count_frames(&stream.captured(), &cancel), 1);
assert_eq!(account_updates_slot(&bus).await, None);
}
#[tokio::test]
async fn account_updates_other_account_refused() {
use crate::accounts::AccountUpdate;
let (stream, bus) = make_bus();
let mut first = account_updates_subscription(&bus, "DU1").await.unwrap();
let refused = account_updates_subscription(&bus, "DU2").await.map(|_| ());
assert!(
matches!(&refused, Err(Error::AccountUpdatesInUse { active, requested }) if *active == account("DU1") && *requested == account("DU2")),
"got: {refused:?}"
);
assert_eq!(
count_frames(&stream.captured(), &account_updates_request("DU2")),
0,
"refused request was written"
);
assert_eq!(bus.shared_channels.live(OutgoingMessages::RequestAccountData).await, 1);
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::AccountDownloadEnd as i32,
&crate::proto::AccountDataEnd {
account_name: Some("DU1".to_string()),
},
));
bus.read_and_route_message().await.unwrap();
let item = next_item(&mut first).await.expect("live subscription received nothing").unwrap();
assert!(matches!(item, SubscriptionItem::Data(AccountUpdate::End)), "got: {item:?}");
}
#[tokio::test]
async fn account_updates_other_account_after_cancel() {
let (stream, bus) = make_bus();
let first = account_updates_subscription(&bus, "DU1").await.unwrap();
first.cancel().await;
assert_eq!(count_frames(&stream.captured(), &account_updates_cancel()), 1);
let _second = account_updates_subscription(&bus, "DU2").await.unwrap();
assert_eq!(count_frames(&stream.captured(), &account_updates_request("DU2")), 1);
assert_eq!(account_updates_slot(&bus).await, Some(account("DU2")));
}
#[tokio::test]
async fn account_updates_slot_restarts_after_reset() {
let (stream, bus) = make_bus();
let old = account_updates_subscription(&bus, "DU1").await.unwrap();
bus.reset_channels().await;
let _new = account_updates_subscription(&bus, "DU2").await.unwrap();
drop(old);
tokio::time::sleep(Duration::from_millis(20)).await;
assert_eq!(
count_frames(&stream.captured(), &account_updates_cancel()),
0,
"dead handle wrote a cancel"
);
assert_eq!(account_updates_slot(&bus).await, Some(account("DU2")));
}
async fn next_item<T: StreamDecoder<T> + Send + 'static>(sub: &mut Subscription<T>) -> Option<Result<SubscriptionItem<T>, Error>> {
tokio::time::timeout(TICK, sub.next())
.await
.expect("subscription got no item before timeout")
}
#[tokio::test]
async fn subscription_notice_delivery_request_keyed() {
let (stream, bus, mut subscription) = make_request_subscription(RequestId::nth(42)).await;
stream.push_inbound(farm_ok_frame_42());
bus.read_and_route_message().await.unwrap();
match next_item(&mut subscription).await {
Some(Ok(SubscriptionItem::Notice(notice))) => {
assert_eq!(notice.code, 2104);
assert_eq!(notice.message, FARM_OK_MSG);
}
other => panic!("expected SubscriptionItem::Notice, got {other:?}"),
}
stream.push_inbound(body(&format!("89|{}|payload|", RequestId::nth(42))));
bus.read_and_route_message().await.unwrap();
match next_item(&mut subscription).await {
Some(Ok(SubscriptionItem::Data(_))) => {}
other => panic!("expected SubscriptionItem::Data, got {other:?}"),
}
}
#[tokio::test]
async fn subscription_10091_preserves_later_option_computation() {
use crate::contracts::tick_types::TickType;
use crate::market_data::realtime::TickTypes;
use crate::testdata::builders::{market_data::tick_option_computation, ResponseProtoEncoder};
let (stream, bus) = make_bus();
let request_id = RequestId::nth(42);
let internal = bus.send_request(request_id, vec![]).await.unwrap();
let mut subscription = Subscription::new_from_internal(internal, bus.clone(), Some(request_id.raw()), None, DecoderContext::default());
let computation = tick_option_computation()
.request_id(request_id.raw())
.tick_type(TickType::DelayedModelOption as i32)
.tick_attrib(0)
.delta(0.5)
.to_proto();
stream.push_inbound(error_frame(request_id.raw(), 10091, "Synthetic partial-entitlement advisory"));
stream.push_inbound(binary_proto(IncomingMessages::TickOptionComputation as i32, &computation));
bus.read_and_route_message().await.unwrap();
bus.read_and_route_message().await.unwrap();
match next_item(&mut subscription).await {
Some(Ok(SubscriptionItem::Notice(notice))) => {
assert_eq!(notice.request_id, Some(request_id.raw()));
assert_eq!(notice.code, 10091);
assert_eq!(notice.message, "Synthetic partial-entitlement advisory");
assert!(notice.is_data_advisory());
}
other => panic!("expected nonterminal 10091 notice, got {other:?}"),
}
match next_item(&mut subscription).await {
Some(Ok(SubscriptionItem::Data(TickTypes::OptionComputation(greeks)))) => {
assert_eq!(greeks.field, TickType::DelayedModelOption);
assert_eq!(greeks.tick_attribute, Some(0));
assert_eq!(greeks.delta, Some(0.5));
assert_eq!(greeks.implied_volatility, None);
}
other => panic!("option computation after 10091 lost: {other:?}"),
}
}
#[tokio::test]
async fn subscription_317_yields_reset_then_rows() {
use crate::market_data::realtime::MarketDepths;
use crate::testdata::builders::{market_data::market_depth_response, ResponseProtoEncoder};
let (stream, bus) = make_bus();
let request_id = RequestId::nth(42);
let internal = bus.send_request(request_id, vec![]).await.unwrap();
let subscription: Subscription<MarketDepths> =
Subscription::new_from_internal(internal, bus.clone(), Some(request_id.raw()), None, DecoderContext::default());
let row = market_depth_response()
.request_id(request_id.raw())
.position(0)
.operation(0)
.side(1)
.price(101.5)
.size(3.0)
.to_proto();
stream.push_inbound(error_frame(
request_id.raw(),
317,
"Market depth data has been RESET. Please empty deep book contents before applying any new entries.",
));
stream.push_inbound(binary_proto(IncomingMessages::MarketDepth as i32, &row));
bus.read_and_route_message().await.unwrap();
bus.read_and_route_message().await.unwrap();
let mut depths = subscription.filter_data();
match depths.next().await {
Some(Ok(MarketDepths::Reset)) => {}
other => panic!("expected MarketDepths::Reset, got {other:?}"),
}
match depths.next().await {
Some(Ok(MarketDepths::MarketDepth(depth))) => {
assert_eq!(depth.position, 0);
assert_eq!(depth.operation, 0);
assert_eq!(depth.side, 1);
assert_eq!(depth.price, 101.5);
assert_eq!(depth.size, 3.0);
}
other => panic!("market depth row after 317 lost: {other:?}"),
}
}
#[tokio::test]
async fn subscription_hard_error_terminates_stream() {
let (stream, bus, mut subscription) = make_request_subscription(RequestId::nth(42)).await;
let request_id = RequestId::nth(42);
stream.push_inbound(error_frame(request_id.raw(), 200, "No security definition found"));
stream.push_inbound(body(&format!("89|{request_id}|payload|")));
bus.read_and_route_message().await.unwrap();
bus.read_and_route_message().await.unwrap();
match next_item(&mut subscription).await {
Some(Err(Error::Notice(notice))) => {
assert_eq!(notice.code, 200);
assert_eq!(notice.message, "No security definition found");
}
other => panic!("expected Some(Err(Error::Notice)), got {other:?}"),
}
assert!(next_item(&mut subscription).await.is_none(), "terminal error must hide even queued data");
}
#[tokio::test]
async fn subscription_notice_delivery_order_keyed() {
let (stream, bus, mut subscription) = make_order_subscription(OrderId::from(7)).await;
stream.push_inbound(error_frame(7, 2109, "Outside RTH order warning"));
bus.read_and_route_message().await.unwrap();
match next_item(&mut subscription).await {
Some(Ok(SubscriptionItem::Notice(notice))) => {
assert_eq!(notice.code, 2109);
assert_eq!(notice.message, "Outside RTH order warning");
}
other => panic!("expected SubscriptionItem::Notice, got {other:?}"),
}
}
#[tokio::test]
async fn subscription_unspecified_notice_not_delivered() {
let (stream, bus, mut subscription) = make_request_subscription(RequestId::nth(42)).await;
stream.push_inbound(farm_ok_frame_unrouted());
bus.read_and_route_message().await.unwrap();
let item = tokio::time::timeout(TICK, subscription.next()).await;
assert!(item.is_err(), "unrouted notice must not be delivered to a subscription, got {item:?}");
}
#[tokio::test]
async fn subscription_data_stream_filters_notices() {
let (stream, bus, subscription) = make_request_subscription(RequestId::nth(42)).await;
let request_id = RequestId::nth(42);
stream.push_inbound(body(&format!("89|{request_id}|first|")));
stream.push_inbound(farm_ok_frame_42());
stream.push_inbound(body(&format!("89|{request_id}|second|")));
for _ in 0..3 {
bus.read_and_route_message().await.unwrap();
}
let collected: Vec<_> = subscription.filter_data().take(2).collect().await;
assert_eq!(collected.len(), 2, "filter_data() must yield the two data items");
for item in collected {
assert!(matches!(item, Ok(NoticeTestData)), "unexpected stream item");
}
}
#[tokio::test]
async fn notice_stream_receives_unrouted_warning() {
let (stream, bus) = make_bus();
let mut notice_stream = bus.notice_subscribe();
stream.push_inbound(farm_ok_frame_unrouted());
bus.read_and_route_message().await.unwrap();
let notice = tokio::time::timeout(TICK, notice_stream.next())
.await
.expect("notice not delivered before timeout")
.expect("stream closed early");
assert_eq!(notice.code, 2104);
assert_eq!(notice.message, FARM_OK_MSG);
}
#[tokio::test]
async fn notice_stream_fans_out_to_multiple_subscribers() {
let (stream, bus) = make_bus();
let mut s1 = bus.notice_subscribe();
let mut s2 = bus.notice_subscribe();
stream.push_inbound(farm_ok_frame_unrouted());
bus.read_and_route_message().await.unwrap();
let n1 = tokio::time::timeout(TICK, s1.next()).await.unwrap().unwrap();
let n2 = tokio::time::timeout(TICK, s2.next()).await.unwrap().unwrap();
assert_eq!(n1.code, 2104);
assert_eq!(n2.code, 2104);
}
#[tokio::test]
async fn notice_stream_receives_unrouted_hard_error() {
let (stream, bus) = make_bus();
let mut notice_stream = bus.notice_subscribe();
stream.push_inbound(error_frame(-1, 504, "Not connected"));
bus.read_and_route_message().await.unwrap();
let notice = tokio::time::timeout(TICK, notice_stream.next()).await.unwrap().unwrap();
assert_eq!(notice.code, 504);
}
#[tokio::test]
async fn notice_stream_skips_routed_notices() {
let (stream, bus, mut subscription) = make_request_subscription(RequestId::nth(42)).await;
let mut notice_stream = bus.notice_subscribe();
stream.push_inbound(farm_ok_frame_42());
bus.read_and_route_message().await.unwrap();
let item = tokio::time::timeout(TICK, subscription.next()).await.unwrap();
assert!(matches!(item, Some(Ok(SubscriptionItem::Notice(_)))), "owner missed notice");
let leaked = tokio::time::timeout(TICK, notice_stream.next()).await;
assert!(leaked.is_err(), "routed notice leaked to global stream");
}
#[tokio::test]
async fn notice_stream_late_subscriber_misses_prior() {
let (stream, bus) = make_bus();
stream.push_inbound(farm_ok_frame_unrouted());
bus.read_and_route_message().await.unwrap();
let mut late = bus.notice_subscribe();
let leaked = tokio::time::timeout(TICK, late.next()).await;
assert!(leaked.is_err(), "late subscriber should not see prior notices");
}
#[tokio::test]
async fn execution_data_routes_to_order_channel() {
let (stream, bus) = make_bus();
let mut sub = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
stream.push_inbound(execution_data_frame(CLIENT_ID, RequestId::nth(99).raw(), 7, "exec-1"));
bus.read_and_route_message().await.unwrap();
let msg = next_message(&mut sub).await;
assert_eq!(msg.order_id(), Some(7));
}
#[tokio::test]
async fn execution_data_falls_back_to_request_channel() {
let (stream, bus) = make_bus();
let request_id = RequestId::nth(99);
let mut sub = bus.send_request(request_id, vec![]).await.unwrap();
stream.push_inbound(execution_data_frame(CLIENT_ID, request_id.raw(), 7, "exec-1"));
bus.read_and_route_message().await.unwrap();
let msg = next_message(&mut sub).await;
assert_eq!(msg.request_id(), Some(request_id.raw()));
}
#[tokio::test]
async fn execution_data_orphan_dropped() {
let (stream, bus) = make_bus();
let mut unrelated = bus.send_request(RequestId::nth(42), vec![]).await.unwrap();
stream.push_inbound(execution_data_frame(CLIENT_ID, RequestId::nth(99).raw(), 7, "exec-1"));
bus.read_and_route_message().await.unwrap();
assert!(unrelated.try_next_routed().is_none(), "unrelated sub got an orphan message");
}
#[tokio::test]
async fn execution_data_end_routes_to_order_channel() {
let (stream, bus) = make_bus();
let mut sub = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::ExecutionDataEnd as i32,
&crate::proto::ExecutionDetailsEnd { req_id: Some(7) },
));
bus.read_and_route_message().await.unwrap();
next_message(&mut sub).await;
}
#[tokio::test]
async fn execution_data_end_falls_back_to_request_channel() {
let (stream, bus) = make_bus();
let request_id = RequestId::nth(7);
let mut sub = bus.send_request(request_id, vec![]).await.unwrap();
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::ExecutionDataEnd as i32,
&crate::proto::ExecutionDetailsEnd {
req_id: Some(request_id.raw()),
},
));
bus.read_and_route_message().await.unwrap();
next_message(&mut sub).await;
}
#[tokio::test]
async fn execution_data_end_orphan_dropped() {
let (stream, bus) = make_bus();
let mut unrelated = bus.send_request(RequestId::nth(42), vec![]).await.unwrap();
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::ExecutionDataEnd as i32,
&crate::proto::ExecutionDetailsEnd {
req_id: Some(RequestId::nth(999).raw()),
},
));
bus.read_and_route_message().await.unwrap();
assert!(unrelated.try_next_routed().is_none(), "unrelated sub got an orphan end");
}
#[tokio::test]
async fn commission_report_routes_via_execution_id_mapping() {
let (stream, bus) = make_bus();
let mut sub = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
stream.push_inbound(execution_data_frame(CLIENT_ID, RequestId::nth(99).raw(), 7, "exec-abc"));
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::CommissionsReport as i32,
&crate::proto::CommissionAndFeesReport {
exec_id: Some("exec-abc".into()),
..Default::default()
},
));
bus.read_and_route_message().await.unwrap();
bus.read_and_route_message().await.unwrap();
let exec_msg = next_message(&mut sub).await;
assert_eq!(exec_msg.message_type(), crate::messages::IncomingMessages::ExecutionData);
let commission = next_message(&mut sub).await;
assert_eq!(commission.message_type(), crate::messages::IncomingMessages::CommissionsReport);
}
#[tokio::test]
async fn commission_report_without_mapping_dropped() {
let (stream, bus) = make_bus();
let mut unrelated = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::CommissionsReport as i32,
&crate::proto::CommissionAndFeesReport {
exec_id: Some("exec-not-mapped".into()),
..Default::default()
},
));
bus.read_and_route_message().await.unwrap();
assert!(unrelated.try_next_routed().is_none(), "unrelated sub got an unmapped commission");
}
#[tokio::test]
async fn execution_aliases_pruned_when_subscriptions_drop() {
let (stream, bus) = make_bus();
let order = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
let executions = bus.send_request(RequestId::nth(99), vec![]).await.unwrap();
stream.push_inbound(execution_data_frame(CLIENT_ID, 0, 7, "exec-order"));
stream.push_inbound(execution_data_frame(CLIENT_ID, RequestId::nth(99).raw(), 0, "exec-request"));
bus.read_and_route_message().await.unwrap();
bus.read_and_route_message().await.unwrap();
assert_eq!(bus.executions.len(), 2, "both executions mapped");
drop(order);
drain_cleanup_signals(&bus).await;
assert!(!bus.executions.contains(&"exec-order".to_string()), "order alias leaked");
assert!(bus.executions.contains(&"exec-request".to_string()), "live request alias pruned");
drop(executions);
drain_cleanup_signals(&bus).await;
assert!(bus.executions.is_empty(), "request alias leaked");
}
#[tokio::test]
async fn execution_alias_kept_while_a_clone_is_alive() {
let (stream, bus) = make_bus();
let order = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
stream.push_inbound(execution_data_frame(CLIENT_ID, 0, 7, "exec-order"));
bus.read_and_route_message().await.unwrap();
let clone = order.clone();
drop(order);
drain_cleanup_signals(&bus).await;
assert!(bus.executions.contains(&"exec-order".to_string()), "alias pruned while a clone is alive");
drop(clone);
drain_cleanup_signals(&bus).await;
assert!(bus.executions.is_empty(), "alias leaked");
}
#[tokio::test]
async fn stale_cleanup_keeps_newer_execution_aliases() {
let (stream, bus) = make_bus();
let sub_a = bus.send_order_request(OrderId::from(42), vec![]).await.unwrap();
stream.push_inbound(execution_data_frame(CLIENT_ID, 0, 42, "exec-a"));
bus.read_and_route_message().await.unwrap();
let sub_b = bus.send_order_request(OrderId::from(42), vec![]).await.unwrap();
stream.push_inbound(execution_data_frame(CLIENT_ID, 0, 42, "exec-b"));
bus.read_and_route_message().await.unwrap();
drop(sub_a);
drain_cleanup_signals(&bus).await;
assert!(!bus.executions.contains(&"exec-a".to_string()), "stale subscription's alias leaked");
assert!(bus.executions.contains(&"exec-b".to_string()), "newer subscription's alias pruned");
drop(sub_b);
drain_cleanup_signals(&bus).await;
assert!(bus.executions.is_empty());
}
#[tokio::test]
async fn completed_order_routes_to_shared_channel() {
let (stream, bus) = make_bus();
let mut sub = bus.send_shared_request(OutgoingMessages::RequestCompletedOrders, vec![]).await.unwrap();
stream.push_inbound(body("101|265598|AAPL|STK|"));
bus.read_and_route_message().await.unwrap();
let msg = next_message(&mut sub).await;
assert_eq!(msg.peek_int(0).unwrap(), 101);
}
#[tokio::test]
async fn completed_orders_end_routes_to_shared_channel() {
let (stream, bus) = make_bus();
let mut sub = bus.send_shared_request(OutgoingMessages::RequestCompletedOrders, vec![]).await.unwrap();
stream.push_inbound(body("102|"));
bus.read_and_route_message().await.unwrap();
let msg = next_message(&mut sub).await;
assert_eq!(msg.peek_int(0).unwrap(), 102);
}
#[tokio::test]
async fn order_update_stream_receives_open_order() {
let (stream, bus) = make_bus();
let mut order_sub = bus.send_order_request(OrderId::from(42), vec![]).await.unwrap();
let mut stream_sub = bus.create_order_update_subscription().await.unwrap();
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::OpenOrder as i32,
&crate::proto::OpenOrder {
order_id: Some(42),
..Default::default()
},
));
bus.read_and_route_message().await.unwrap();
next_message(&mut order_sub).await;
next_message(&mut stream_sub).await;
}
#[tokio::test]
async fn order_update_stream_receives_order_error_as_notice() {
let (stream, bus) = make_bus();
let mut stream_sub = bus.create_order_update_subscription().await.unwrap();
stream.push_inbound(error_frame(42, 201, "Order rejected"));
bus.read_and_route_message().await.unwrap();
match next_routed(&mut stream_sub).await {
RoutedItem::Notice(notice) => {
assert_eq!(notice.request_id, Some(42));
assert_eq!(notice.code, 201);
assert_eq!(notice.message, "Order rejected");
}
other => panic!("expected RoutedItem::Notice, received {other:?}"),
}
stream.push_inbound(binary_proto(
crate::messages::IncomingMessages::OpenOrder as i32,
&crate::proto::OpenOrder {
order_id: Some(42),
..Default::default()
},
));
bus.read_and_route_message().await.unwrap();
assert!(matches!(next_routed(&mut stream_sub).await, RoutedItem::Response(_)));
}
#[tokio::test]
async fn order_update_stream_skips_data_request_error() {
let (stream, bus) = make_bus();
let mut stream_sub = bus.create_order_update_subscription().await.unwrap();
let request_id = RequestId::nth(42);
let mut sub = bus.send_request(request_id, vec![]).await.unwrap();
stream.push_inbound(error_frame(request_id.raw(), 200, "No security definition found"));
bus.read_and_route_message().await.unwrap();
let item = next_routed(&mut sub).await;
assert!(matches!(item, RoutedItem::Error(Error::Notice(_))), "got: {item:?}");
assert!(
stream_sub.try_next_routed().is_none(),
"order-update stream must not receive a data-request error"
);
}
#[tokio::test]
async fn issue_789_order_error_reaches_order_side() {
let (stream, bus) = make_bus();
let order_id = OrderId::from(7);
let mut request = bus.send_request(RequestId::nth(order_id.value()), vec![]).await.unwrap();
let mut order = bus.send_order_request(order_id, vec![]).await.unwrap();
let mut updates = bus.create_order_update_subscription().await.unwrap();
stream.push_inbound(error_frame(order_id.value(), 202, "Order Canceled"));
stream.push_inbound(error_frame(order_id.value(), 201, "Order rejected"));
bus.read_and_route_message().await.unwrap();
bus.read_and_route_message().await.unwrap();
match next_routed(&mut order).await {
RoutedItem::Notice(notice) => assert_eq!(notice.code, 202),
other => panic!("expected the 202 notice, got {other:?}"),
}
match next_routed(&mut order).await {
RoutedItem::Error(Error::Notice(notice)) => assert_eq!(notice.code, 201),
other => panic!("expected the 201 error, got {other:?}"),
}
for code in [202, 201] {
match next_routed(&mut updates).await {
RoutedItem::Notice(notice) => {
assert_eq!(notice.request_id, Some(order_id.value()));
assert_eq!(notice.code, code);
}
other => panic!("expected the {code} notice on the order-update stream, got {other:?}"),
}
}
assert!(request.try_next_routed().is_none(), "request received an order's error");
}
#[tokio::test]
async fn issue_789_late_request_error_stays_off_order_stream() {
let (stream, bus) = make_bus();
let request_id = RequestId::nth(5);
drop(bus.send_request(request_id, vec![]).await.unwrap());
drain_cleanup_signals(&bus).await;
let mut updates = bus.create_order_update_subscription().await.unwrap();
stream.push_inbound(error_frame(request_id.raw(), 200, "No security definition found"));
bus.read_and_route_message().await.unwrap();
assert!(
try_next_routed(&mut updates).await.is_none(),
"late request error reached the order-update stream"
);
}
#[tokio::test]
async fn warning_with_orphan_request_id_logs() {
let (stream, bus) = make_bus();
let mut unrelated = bus.send_request(RequestId::nth(42), vec![]).await.unwrap();
let mut notice_stream = bus.notice_subscribe();
stream.push_inbound(error_frame(RequestId::nth(99).raw(), 2104, "orphan warning"));
bus.read_and_route_message().await.unwrap();
assert!(unrelated.try_next_routed().is_none(), "unrelated sub got the notice");
let leaked = tokio::time::timeout(TICK, notice_stream.next()).await;
assert!(leaked.is_err(), "global notice stream got a routed-but-orphan notice");
}
pub(super) async fn drain_cleanup_signals<S: AsyncStream>(bus: &Arc<AsyncTcpMessageBus<S>>) {
const MARKER_REQUEST_ID: RequestId = RequestId::nth(987_654);
let marker = bus.send_request(MARKER_REQUEST_ID, vec![]).await.unwrap();
drop(marker);
let deadline = std::time::Instant::now() + Duration::from_secs(2);
while std::time::Instant::now() < deadline {
if !bus.requests.contains(&MARKER_REQUEST_ID) {
return;
}
tokio::time::sleep(Duration::from_millis(1)).await;
}
panic!("cleanup task did not process the marker signal");
}
#[tokio::test]
async fn stale_order_cleanup_preserves_newer_subscription() {
let (_, bus) = make_bus();
let order_id = OrderId::from(42);
let sub_a = bus.send_order_request(order_id, vec![]).await.unwrap();
let mut sub_b = bus.send_order_request(order_id, vec![]).await.unwrap();
drop(sub_a);
drain_cleanup_signals(&bus).await;
let sender = bus.orders.sender(&order_id).expect("stale cleanup removed the newer subscription");
sender
.send(RoutedItem::Error(Error::Cancelled))
.expect("registered channel has no receivers");
let item = tokio::time::timeout(TICK, sub_b.next_routed()).await.expect("sub_b got nothing");
assert!(matches!(item, Some(RoutedItem::Error(Error::Cancelled))), "{item:?}");
drop(sender);
drop(sub_b);
drain_cleanup_signals(&bus).await;
assert!(!bus.orders.contains(&order_id), "order channel leaked");
}
#[tokio::test]
async fn dropping_clone_keeps_order_channel_registered() {
let (_, bus) = make_bus();
let order_id = OrderId::from(7);
let sub = bus.send_order_request(order_id, vec![]).await.unwrap();
let clone = sub.clone();
drop(clone);
drain_cleanup_signals(&bus).await;
assert!(bus.orders.contains(&order_id), "clone drop unregistered a live subscription");
drop(sub);
drain_cleanup_signals(&bus).await;
assert!(!bus.orders.contains(&order_id), "order channel leaked");
}
#[tokio::test]
async fn cleanup_signal_follows_lease_release() {
let (_, bus) = make_bus();
let request_id = RequestId::nth(5);
let mut sub = bus.send_request(request_id, vec![]).await.unwrap();
let is_live = || bus.requests.with_route(&request_id, |route| route.lease.is_live());
assert_eq!(is_live(), Some(true));
sub.send_cleanup_signal();
assert_eq!(is_live(), Some(false), "signal sent while the handle still held the lease");
drain_cleanup_signals(&bus).await;
assert!(!bus.requests.contains(&request_id), "request channel leaked");
drop(sub);
}
#[tokio::test]
async fn drop_then_recreate_order_update_stream() {
let (_, bus) = make_bus();
let s1 = bus.create_order_update_subscription().await.unwrap();
drop(s1);
let mut s2 = bus.create_order_update_subscription().await.expect("immediate recreation failed");
drain_cleanup_signals(&bus).await;
let sender = bus.order_taps.updates_sender().expect("stale cleanup cleared the replacement stream");
sender
.send(RoutedItem::Error(Error::Cancelled))
.expect("replacement stream has no receivers");
let item = tokio::time::timeout(TICK, s2.next_routed()).await.expect("s2 got nothing");
assert!(matches!(item, Some(RoutedItem::Error(Error::Cancelled))), "{item:?}");
drop(sender);
drop(s2);
drain_cleanup_signals(&bus).await;
assert!(bus.order_taps.updates_lease().is_none(), "order update stream leaked");
}
#[tokio::test]
async fn reset_channels_notifies_in_flight_subscriptions() {
let (_, bus) = make_bus();
let mut req = bus.send_request(RequestId::nth(100), vec![]).await.unwrap();
let mut order = bus.send_order_request(OrderId::from(200), vec![]).await.unwrap();
let mut shared = bus.send_shared_request(OutgoingMessages::RequestOpenOrders, vec![]).await.unwrap();
bus.reset_channels().await;
for (name, sub) in [("request", &mut req), ("order", &mut order), ("shared", &mut shared)] {
let item = tokio::time::timeout(TICK, sub.next_routed())
.await
.unwrap_or_else(|_| panic!("{name} got no notification"))
.unwrap_or_else(|| panic!("{name} channel closed early"));
assert!(matches!(item, RoutedItem::Error(Error::ConnectionReset)), "{name}: {item:?}");
}
assert!(bus.requests.is_empty());
assert!(bus.orders.is_empty());
assert!(bus.executions.is_empty());
let mut late = bus.send_shared_request(OutgoingMessages::RequestOpenOrders, vec![]).await.unwrap();
assert!(late.try_next_routed().is_none(), "post-reset shared subscription read a stale reset");
}
#[tokio::test]
async fn reconnect_publishes_reconnect_notice_to_notice_stream() {
let stream = MemoryStream::default();
let connection = AsyncConnection::stubbed(stream.clone(), 28);
connection.set_server_version_for_test(server_versions::PROTOBUF_REST_MESSAGES_3);
stream.push_inbound(b"xx".to_vec());
for frame in handshake_frames(server_versions::PROTOBUF_REST_MESSAGES_3, "EST", 5000) {
stream.push_inbound(frame);
}
let bus = Arc::new(AsyncTcpMessageBus::new(connection));
let mut notices = bus.connection.notice_broadcaster.subscribe();
bus.clone().process_messages().expect("process_messages");
let notice = tokio::time::timeout(Duration::from_secs(2), notices.recv())
.await
.expect("no reconnect notice on the notice stream")
.expect("notice stream closed");
assert_eq!(notice.code, TRANSPORT_RECONNECT_CODE, "{notice:?}");
assert!(bus.is_connected(), "the notice must follow the session going live");
}
#[tokio::test]
async fn reconnect_replays_market_data_type_before_notice() {
let (stream, bus) = make_bus();
let mut notices = bus.connection.notice_broadcaster.subscribe();
let packet = crate::market_data::realtime::common::encode_request_market_data_type(crate::market_data::MarketDataType::Delayed).unwrap();
bus.send_market_data_type(packet.clone()).await.unwrap();
stream.push_inbound(b"xx".to_vec());
for frame in handshake_frames(server_versions::PROTOBUF_REST_MESSAGES_3, "EST", 5000) {
stream.push_inbound(frame);
}
bus.clone().process_messages().expect("process_messages");
let notice = tokio::time::timeout(Duration::from_secs(2), notices.recv())
.await
.expect("no reconnect notice on the notice stream")
.expect("notice stream closed");
assert_eq!(notice.code, TRANSPORT_RECONNECT_CODE, "{notice:?}");
let handler = &bus.connection.connection_handler;
let expected = [
encode_raw_length(&packet),
handler.format_handshake(),
encode_raw_length(&handler.format_start_api(28)),
encode_raw_length(&packet),
]
.concat();
assert_eq!(
stream.captured(),
expected,
"the replay must be on the wire by the time the notice is published"
);
}
#[tokio::test]
async fn ensure_shutdown_joins_processing_task() {
let (_, bus) = make_bus();
bus.clone().process_messages().expect("process_messages");
let mb: &dyn AsyncMessageBus = bus.as_ref();
assert!(lock(&bus.process_task).is_some(), "process_task installed by process_messages");
mb.ensure_shutdown().await;
assert!(!mb.is_connected());
}
#[tokio::test]
async fn reconnect_refreshes_handshake_state() {
let stream = MemoryStream::default();
let connection = AsyncConnection::stubbed(stream.clone(), 28);
connection.set_server_version_for_test(server_versions::PROTOBUF_REST_MESSAGES_3);
stream.push_inbound(b"xx".to_vec());
let new_version = server_versions::CONDITIONS_INCLUDE_OVERNIGHT;
for frame in handshake_frames(new_version, "EST", 5000) {
stream.push_inbound(frame);
}
let bus = Arc::new(AsyncTcpMessageBus::new(connection));
let order_ids = Arc::new(crate::client::id_generator::ClientIdManager::new(100).unwrap());
let server_version = Arc::new(ServerVersion::new(server_versions::PROTOBUF_REST_MESSAGES_3));
bus.set_handshake_state(HandshakeState {
order_ids: order_ids.clone(),
server_version: server_version.clone(),
});
bus.clone().process_messages().expect("process_messages");
let deadline = tokio::time::Instant::now() + Duration::from_secs(2);
while order_ids.current_order_id() < 5000 {
assert!(
tokio::time::Instant::now() < deadline,
"order-id generator was never raised from the reconnect handshake"
);
tokio::time::sleep(Duration::from_millis(5)).await;
}
assert_eq!(server_version.get(), new_version, "server version should be the reconnect handshake's");
}
#[tokio::test]
async fn send_shared_request_unsupported_returns_error() {
let (_, bus) = make_bus();
let mb: &dyn AsyncMessageBus = bus.as_ref();
match mb.send_shared_request(OutgoingMessages::PlaceOrder, b"x".to_vec()).await {
Err(Error::InvalidArgument(_)) => {}
other => panic!("expected Error::InvalidArgument, got {:?}", other.err()),
}
}
#[tokio::test]
async fn unknown_message_id_reaches_the_notice_stream() {
let (stream, bus) = make_bus();
let mut notice_stream = bus.notice_subscribe();
stream.push_inbound(crate::common::test_utils::helpers::unknown_message_frame());
bus.read_and_route_message().await.unwrap();
let notice = tokio::time::timeout(TICK, notice_stream.next())
.await
.expect("unknown frame must raise a notice")
.expect("notice stream closed");
assert_eq!(notice.code, crate::messages::UNKNOWN_MESSAGE_TYPE_CODE);
assert!(
notice.message.contains(&helpers::UNKNOWN_MESSAGE_ID.to_string()),
"notice must name the offending id, got {:?}",
notice.message
);
}
#[tokio::test]
async fn sends_are_refused_while_disconnected() {
let (stream, bus) = make_bus();
let mb: &dyn AsyncMessageBus = bus.as_ref();
bus.connection_state.set_disconnected();
assert!(matches!(
mb.send_request(RequestId::nth(100), b"req-bytes".to_vec()).await,
Err(Error::ConnectionReset)
));
assert!(matches!(
mb.send_order_request(OrderId::from(42), b"order-bytes".to_vec()).await,
Err(Error::ConnectionReset)
));
assert!(matches!(
mb.send_shared_request(OutgoingMessages::RequestManagedAccounts, b"shared-bytes".to_vec())
.await,
Err(Error::ConnectionReset)
));
assert!(matches!(mb.send_message(b"message-bytes".to_vec()).await, Err(Error::ConnectionReset)));
assert!(bus.requests.is_empty(), "a refused request must register nothing");
assert!(bus.orders.is_empty(), "a refused order request must register nothing");
assert!(stream.captured().is_empty(), "a refused send must not reach the socket");
bus.connection_state.set_connected();
assert!(mb.send_request(RequestId::nth(100), b"req-bytes".to_vec()).await.is_ok());
assert!(!stream.captured().is_empty());
}
#[tokio::test]
async fn wait_connected_returns_shutdown_when_the_bus_shuts_down() {
let (_stream, bus) = make_bus();
bus.connection_state.set_disconnected();
let closer = Arc::clone(&bus);
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(20)).await;
closer.request_shutdown_sync();
});
let result = tokio::time::timeout(Duration::from_secs(5), bus.wait_connected())
.await
.expect("wait_connected did not return on shutdown");
assert!(matches!(result, Err(Error::Shutdown)), "got: {result:?}");
}
#[derive(Clone, Debug, Default)]
struct FailingWriteStream(MemoryStream);
#[async_trait::async_trait]
impl AsyncIo for FailingWriteStream {
async fn read_message(&self) -> Result<Vec<u8>, Error> {
self.0.read_message().await
}
async fn write_all(&self, _buf: &[u8]) -> Result<(), Error> {
Err(Error::ConnectionReset)
}
async fn close(&self) {
self.0.close().await;
}
}
#[async_trait::async_trait]
impl AsyncReconnect for FailingWriteStream {
async fn reconnect(&self) -> Result<(), Error> {
self.0.reconnect().await
}
async fn sleep(&self, duration: Duration, shutdown: &ShutdownSignal) {
self.0.sleep(duration, shutdown).await
}
}
impl AsyncStream for FailingWriteStream {}
#[tokio::test]
async fn failed_write_leaves_no_registration() {
let connection = AsyncConnection::stubbed(FailingWriteStream::default(), 28);
connection.set_server_version_for_test(server_versions::PROTOBUF_REST_MESSAGES_3);
let bus = Arc::new(AsyncTcpMessageBus::new(connection));
let mb: &dyn AsyncMessageBus = bus.as_ref();
assert!(mb.send_request(RequestId::nth(100), b"req-bytes".to_vec()).await.is_err());
assert!(bus.requests.is_empty(), "a failed write must leave no request registered");
assert!(mb.send_order_request(OrderId::from(42), b"order-bytes".to_vec()).await.is_err());
assert!(bus.orders.is_empty(), "a failed write must leave no order registered");
}
#[tokio::test]
async fn order_binding_reaches_updates_without_using_raw_order_id() {
let (stream, bus) = make_bus();
let mut order_sub = bus.send_order_request(OrderId::from(42), vec![]).await.unwrap();
let mut update_sub = bus.create_order_update_subscription().await.unwrap();
stream.push_inbound(binary_proto(
IncomingMessages::OrderBound as i32,
&order_bound().client_id(CLIENT_ID).to_proto(),
));
bus.read_and_route_message().await.unwrap();
let message = next_message(&mut update_sub).await;
assert_eq!(message.message_type(), IncomingMessages::OrderBound);
assert!(tokio::time::timeout(TICK, order_sub.next()).await.is_err());
}
pub(super) fn bound(limit: usize) -> BufferBound {
BufferBound {
limit,
end: &[IncomingMessages::ContractDataEnd],
}
}
const CONTRACT_REQUEST_ID: RequestId = RequestId::nth(0);
fn contract_row(contract_id: i32) -> Vec<u8> {
binary_proto(
IncomingMessages::ContractData as i32,
&contract_data().request_id(CONTRACT_REQUEST_ID.raw()).contract_id(contract_id).to_proto(),
)
}
fn contract_end() -> Vec<u8> {
binary_proto(
IncomingMessages::ContractDataEnd as i32,
&crate::proto::ContractDataEnd {
req_id: Some(CONTRACT_REQUEST_ID.raw()),
},
)
}
async fn route_histograms(stream: &MemoryStream, bus: &AsyncTcpMessageBus<MemoryStream>, frames: usize, request_id: RequestId) {
for n in 0..frames {
stream.push_inbound(body(&format!("89|{request_id}|{n}|")));
bus.read_and_route_message().await.unwrap();
}
}
async fn try_next_routed(sub: &mut AsyncInternalSubscription) -> Option<RoutedItem> {
tokio::time::timeout(TICK, sub.next_routed()).await.ok().flatten()
}
#[tokio::test]
async fn bounded_request_fails_after_limit_unread() {
let (stream, bus) = make_bus();
let mut sub = bus.send_request_capped(RequestId::nth(100), vec![], Some(bound(2))).await.unwrap();
route_histograms(&stream, &bus, 4, RequestId::nth(100)).await;
match try_next_routed(&mut sub).await {
Some(RoutedItem::Response(message)) => assert_eq!(message.peek_int(2).unwrap(), 0),
other => panic!("expected the first row, got {other:?}"),
}
assert!(matches!(try_next_routed(&mut sub).await, Some(RoutedItem::Response(_))));
assert!(matches!(
try_next_routed(&mut sub).await,
Some(RoutedItem::Error(Error::BufferLimitExceeded { limit: 2 }))
));
assert!(
try_next_routed(&mut sub).await.is_none(),
"no lag notice, and frames after the overflow are discarded"
);
}
#[tokio::test]
async fn bounded_request_counts_unread_not_total() {
let (stream, bus) = make_bus();
let mut sub = bus.send_request_capped(RequestId::nth(100), vec![], Some(bound(2))).await.unwrap();
for _ in 0..6 {
route_histograms(&stream, &bus, 1, RequestId::nth(100)).await;
assert!(
matches!(try_next_routed(&mut sub).await, Some(RoutedItem::Response(_))),
"a reader that keeps up never overflows"
);
}
}
#[tokio::test]
async fn reset_skips_overflowed_route() {
let (stream, bus) = make_bus();
let mut overflowed = bus.send_request_capped(RequestId::nth(100), vec![], Some(bound(1))).await.unwrap();
let mut at_limit = bus.send_request_capped(RequestId::nth(200), vec![], Some(bound(1))).await.unwrap();
route_histograms(&stream, &bus, 2, RequestId::nth(100)).await;
route_histograms(&stream, &bus, 1, RequestId::nth(200)).await;
bus.reset_channels().await;
assert!(matches!(try_next_routed(&mut overflowed).await, Some(RoutedItem::Response(_))));
assert!(matches!(
try_next_routed(&mut overflowed).await,
Some(RoutedItem::Error(Error::BufferLimitExceeded { .. }))
));
assert!(try_next_routed(&mut overflowed).await.is_none(), "no second terminal error after reset");
assert!(matches!(try_next_routed(&mut at_limit).await, Some(RoutedItem::Response(_))));
assert!(matches!(
try_next_routed(&mut at_limit).await,
Some(RoutedItem::Error(Error::ConnectionReset))
));
}
#[tokio::test]
async fn overflowed_subscription_cancels_on_drop() {
use crate::contracts::ContractDetails;
let (stream, bus) = make_bus();
let internal = bus.send_request_capped(CONTRACT_REQUEST_ID, vec![], Some(bound(1))).await.unwrap();
let mut subscription: Subscription<ContractDetails> = Subscription::new_from_internal(
internal,
bus.clone(),
Some(CONTRACT_REQUEST_ID.raw()),
None,
DecoderContext::new(server_versions::CANCEL_CONTRACT_DATA),
);
for contract_id in [1, 2] {
stream.push_inbound(contract_row(contract_id));
bus.read_and_route_message().await.unwrap();
}
assert!(matches!(next_item(&mut subscription).await, Some(Ok(_))));
assert!(matches!(
next_item(&mut subscription).await,
Some(Err(Error::BufferLimitExceeded { limit: 1 }))
));
drop(subscription);
let cancel = <ContractDetails as StreamDecoder<ContractDetails>>::cancel_message(
server_versions::CANCEL_CONTRACT_DATA,
Some(CONTRACT_REQUEST_ID.raw()),
None,
)
.unwrap();
assert_eq!(wait_for_frames(&stream, &cancel, 1).await, 1, "overflow leaves the cancel to drop");
}
#[tokio::test]
async fn bounded_request_end_marker_at_limit_still_ends() {
let (stream, bus) = make_bus();
let mut sub = bus.send_request_capped(CONTRACT_REQUEST_ID, vec![], Some(bound(1))).await.unwrap();
for frame in [contract_row(1), contract_end(), contract_row(2)] {
stream.push_inbound(frame);
bus.read_and_route_message().await.unwrap();
}
match try_next_routed(&mut sub).await {
Some(RoutedItem::Response(message)) => assert_eq!(message.message_type(), IncomingMessages::ContractData),
other => panic!("expected the row, got {other:?}"),
}
match try_next_routed(&mut sub).await {
Some(RoutedItem::Response(message)) => assert_eq!(message.message_type(), IncomingMessages::ContractDataEnd),
other => panic!("expected the end marker, got {other:?}"),
}
assert!(try_next_routed(&mut sub).await.is_none(), "frames after the end marker are discarded");
}
#[tokio::test]
async fn drain_reports_shutdown() {
use crate::contracts::ContractDetails;
use crate::subscriptions::Drained;
let (_stream, bus) = make_bus();
let internal = bus.send_request(CONTRACT_REQUEST_ID, vec![]).await.unwrap();
let subscription: Subscription<ContractDetails> = Subscription::new_from_internal(
internal,
bus.clone(),
Some(CONTRACT_REQUEST_ID.raw()),
None,
DecoderContext::new(server_versions::CANCEL_CONTRACT_DATA),
);
let drain = tokio::spawn(subscription.cancel_and_drain(tokio::time::Instant::now() + Duration::from_secs(5)));
tokio::time::sleep(Duration::from_millis(20)).await;
bus.request_shutdown();
let outcome: Result<Drained, Error> = drain.await.unwrap();
assert!(matches!(outcome, Err(Error::Shutdown)), "got {outcome:?}");
}
#[tokio::test]
async fn close_ends_dispatch_without_reconnecting() {
let (stream, bus) = make_bus();
let mut internal = bus.send_request(CONTRACT_REQUEST_ID, vec![]).await.unwrap();
bus.clone().process_messages().unwrap();
tokio::task::yield_now().await;
stream.close().await;
let item = tokio::time::timeout(Duration::from_secs(1), internal.next())
.await
.expect("request never ended");
assert!(matches!(item, Some(Err(Error::Shutdown))), "got {item:?}");
}
use super::io::tests::assert_peer_sees_eof;
async fn tcp_client_with_peer() -> (crate::Client, Arc<AsyncTcpMessageBus<AsyncTcpSocket>>, tokio::net::TcpStream) {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let address = listener.local_addr().unwrap().to_string();
let (socket, peer) = tokio::join!(AsyncTcpSocket::connect(&address, true), listener.accept());
let bus = Arc::new(AsyncTcpMessageBus::new(AsyncConnection::stubbed(socket.unwrap(), CLIENT_ID)));
bus.clone().process_messages().unwrap();
let client = crate::Client::stubbed(bus.clone(), server_versions::PROTOBUF_REST_MESSAGES_3);
(client, bus, peer.unwrap().0)
}
#[tokio::test]
async fn disconnect_closes_tcp_with_retained_client_and_subscription() {
let (client, _bus, peer) = tcp_client_with_peer().await;
let _subscription = client.order_update_stream().await.unwrap();
client.disconnect().await;
assert_peer_sees_eof(peer).await;
assert!(!client.is_connected());
client.disconnect().await;
}
#[tokio::test]
async fn client_drop_closes_tcp_with_retained_subscription() {
let (client, bus, peer) = tcp_client_with_peer().await;
let _subscription = client.order_update_stream().await.unwrap();
drop(client);
assert_peer_sees_eof(peer).await;
assert!(!bus.is_connected());
}
async fn read_frame(peer: &mut tokio::net::TcpStream) {
use tokio::io::AsyncReadExt;
let len = peer.read_u32().await.unwrap();
peer.read_exact(&mut vec![0u8; len as usize]).await.unwrap();
}
fn tws_frame(message: IncomingMessages, proto: &impl prost::Message) -> Vec<u8> {
encode_raw_length(&helpers::binary_proto(message as i32, proto))
}
fn open_orders_end_frame() -> Vec<u8> {
tws_frame(
IncomingMessages::OpenOrderEnd,
&crate::testdata::builders::orders::open_order_end().to_proto(),
)
}
fn completed_orders_end_frame() -> Vec<u8> {
tws_frame(
IncomingMessages::CompletedOrdersEnd,
&crate::testdata::builders::orders::completed_orders_end().to_proto(),
)
}
#[tokio::test]
async fn wait_for_fill_lookup_ends_do_not_end_next_requests() {
use crate::orders::Orders;
use crate::testdata::builders::orders::{completed_order, open_order};
use futures::StreamExt;
use tokio::io::AsyncWriteExt;
let order = tws_frame(IncomingMessages::OpenOrder, &open_order().order_id(7).to_proto());
let completed = tws_frame(IncomingMessages::CompletedOrder, &completed_order().order_id(9).to_proto());
let (client, _bus, mut peer) = tcp_client_with_peer().await;
let tws = tokio::spawn(async move {
read_frame(&mut peer).await; tokio::time::sleep(Duration::from_millis(50)).await;
peer.write_all(&open_orders_end_frame()).await.unwrap();
read_frame(&mut peer).await; tokio::time::sleep(Duration::from_millis(50)).await;
peer.write_all(&completed_orders_end_frame()).await.unwrap();
read_frame(&mut peer).await; peer.write_all(&completed).await.unwrap();
peer.write_all(&completed_orders_end_frame()).await.unwrap();
read_frame(&mut peer).await; peer.write_all(&order).await.unwrap();
peer.write_all(&open_orders_end_frame()).await.unwrap();
peer
});
let outcome = client.wait_for_fill(7, Duration::ZERO).await.unwrap();
assert_eq!(outcome, crate::orders::OrderOutcome::TimedOut(None));
let mut orders = client.completed_orders(false).await.unwrap();
let first = tokio::time::timeout(Duration::from_secs(1), orders.next()).await.expect("no reply");
assert!(
matches!(&first, Some(Ok(SubscriptionItem::Data(Orders::OrderData(d)))) if d.order.order_id == 9),
"{first:?}"
);
let mut orders = client.open_orders().await.unwrap();
let first = tokio::time::timeout(Duration::from_secs(1), orders.next()).await.expect("no reply");
assert!(
matches!(&first, Some(Ok(SubscriptionItem::Data(Orders::OrderData(d)))) if d.order.order_id == 7),
"{first:?}"
);
drop(tws.await.unwrap());
}
#[tokio::test]
async fn wait_for_fill_finds_order_among_completed() {
use crate::orders::{OrderOutcome, OrderStatusKind};
use crate::testdata::builders::orders::completed_order;
use tokio::io::AsyncWriteExt;
let (client, _bus, mut peer) = tcp_client_with_peer().await;
let completed = tws_frame(
IncomingMessages::CompletedOrder,
&completed_order()
.client_id(client.client_id())
.order_id(7)
.status(OrderStatusKind::Cancelled)
.total_quantity(60.0)
.filled_quantity(40.0)
.to_proto(),
);
let tws = tokio::spawn(async move {
read_frame(&mut peer).await;
peer.write_all(&open_orders_end_frame()).await.unwrap();
read_frame(&mut peer).await;
peer.write_all(&completed).await.unwrap();
peer.write_all(&completed_orders_end_frame()).await.unwrap();
peer
});
let started = std::time::Instant::now();
let outcome = client.wait_for_fill(7, Duration::from_secs(10)).await.unwrap();
assert!(started.elapsed() < Duration::from_secs(5), "waited {:?}", started.elapsed());
let OrderOutcome::Ended(status) = outcome else {
panic!("{outcome:?}");
};
assert_eq!(status.status, OrderStatusKind::Cancelled);
assert_eq!((status.filled, status.remaining), (40.0, 60.0));
assert_eq!(status.average_fill_price, None);
drop(tws.await.unwrap());
}
#[tokio::test]
async fn wait_for_fill_skips_completed_when_order_is_open() {
use crate::orders::{OrderOutcome, OrderStatusKind};
use crate::testdata::builders::orders::open_order;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let order = tws_frame(IncomingMessages::OpenOrder, &open_order().order_id(7).to_proto());
let status = encode_raw_length(&helpers::order_status_frame(CLIENT_ID, 7, OrderStatusKind::Submitted));
let (client, _bus, mut peer) = tcp_client_with_peer().await;
let tws = tokio::spawn(async move {
read_frame(&mut peer).await;
peer.write_all(&order).await.unwrap();
peer.write_all(&status).await.unwrap();
peer.write_all(&open_orders_end_frame()).await.unwrap();
let next = tokio::time::timeout(Duration::from_millis(300), peer.read_u32()).await;
assert!(next.is_err(), "unexpected request: {next:?}");
peer
});
let outcome = client.wait_for_fill(7, Duration::from_millis(100)).await.unwrap();
assert!(
matches!(&outcome, OrderOutcome::TimedOut(Some(s)) if s.status == OrderStatusKind::Submitted),
"{outcome:?}"
);
drop(tws.await.unwrap());
}
#[tokio::test(start_paused = true)]
async fn rate_limiter_awaits_without_blocking_runtime() -> Result<(), Error> {
let (stream, bus) = make_bus();
bus.set_rate_limiter(crate::RateLimiter::per_second(2));
let ticks = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let ticker = {
let ticks = ticks.clone();
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_millis(10)).await;
ticks.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
})
};
let started = std::time::Instant::now();
for _ in 0..4 {
bus.send_message(b"request".to_vec()).await?;
}
ticker.abort();
assert_eq!(count_frames(&stream.captured(), b"request"), 4);
assert!(ticks.load(std::sync::atomic::Ordering::Relaxed) > 0, "runtime blocked while throttled");
assert!(
started.elapsed() < Duration::from_millis(500),
"slept in real time: {:?}",
started.elapsed()
);
Ok(())
}
#[tokio::test]
async fn order_status_stream_starts_with_earlier_status() {
let (stream, bus) = make_bus();
stream.push_inbound(order_status_frame(CLIENT_ID, 7, OrderStatusKind::Filled));
bus.read_and_route_message().await.unwrap();
let mut statuses = bus.create_order_status_subscription(OrderId::from(7)).await.unwrap();
assert_eq!(next_message(&mut statuses).await.order_id(), Some(7));
}
#[tokio::test]
async fn order_status_stream_starts_with_earlier_rejection() {
let (stream, bus) = make_bus();
stream.push_inbound(helpers::error_frame(7, 110, "price off the tick"));
bus.read_and_route_message().await.unwrap();
let mut statuses = bus.create_order_status_subscription(OrderId::from(7)).await.unwrap();
let item = tokio::time::timeout(TICK, statuses.next_routed()).await.expect("no item");
assert!(matches!(&item, Some(RoutedItem::Error(Error::Notice(n))) if n.code == 110), "{item:?}");
}
#[tokio::test]
async fn order_status_stream_takes_nothing_from_other_routes() {
let (stream, bus) = make_bus();
let mut order = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
let mut updates = bus.create_order_update_subscription().await.unwrap();
let mut statuses = bus.create_order_status_subscription(OrderId::from(7)).await.unwrap();
stream.push_inbound(order_status_frame(CLIENT_ID, 7, OrderStatusKind::Submitted));
bus.read_and_route_message().await.unwrap();
assert_eq!(next_message(&mut statuses).await.order_id(), Some(7));
assert_eq!(next_message(&mut order).await.order_id(), Some(7));
assert_eq!(next_message(&mut updates).await.order_id(), Some(7));
}
#[tokio::test]
async fn order_status_stream_sees_unrouted_status() {
let (stream, bus) = make_bus();
let mut statuses = bus.create_order_status_subscription(OrderId::from(7)).await.unwrap();
stream.push_inbound(order_status_frame(CLIENT_ID, 7, OrderStatusKind::Submitted));
bus.read_and_route_message().await.unwrap();
assert_eq!(next_message(&mut statuses).await.order_id(), Some(7));
}
#[tokio::test]
async fn order_status_stream_ignores_other_clients_status() {
let (stream, bus) = make_bus();
let mut statuses = bus.create_order_status_subscription(OrderId::from(7)).await.unwrap();
stream.push_inbound(order_status_frame(CLIENT_ID + 1, 7, OrderStatusKind::Filled));
bus.read_and_route_message().await.unwrap();
assert!(tokio::time::timeout(TICK, statuses.next_routed()).await.is_err(), "open stream got it");
let mut later = bus.create_order_status_subscription(OrderId::from(7)).await.unwrap();
assert!(
tokio::time::timeout(TICK, later.next_routed()).await.is_err(),
"later stream started with it"
);
}
fn foreign_open_order_frame(order_id: i32) -> Vec<u8> {
let order = crate::testdata::builders::orders::open_order()
.order_id(order_id)
.client_id(CLIENT_ID + 1);
binary_proto(IncomingMessages::OpenOrder as i32, &order.to_proto())
}
fn commission_frame(execution_id: &str) -> Vec<u8> {
binary_proto(
IncomingMessages::CommissionsReport as i32,
&crate::proto::CommissionAndFeesReport {
exec_id: Some(execution_id.into()),
..Default::default()
},
)
}
#[tokio::test]
async fn foreign_order_frames_reach_only_shared_channel() {
let (stream, bus) = make_bus();
let mut order_route = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
let mut updates = bus.create_order_update_subscription().await.unwrap();
let mut all_open = bus.send_shared_request(OutgoingMessages::RequestAllOpenOrders, vec![]).await.unwrap();
stream.push_inbound(foreign_open_order_frame(7));
stream.push_inbound(order_status_frame(CLIENT_ID + 1, 7, OrderStatusKind::Submitted));
bus.read_and_route_message().await.unwrap();
bus.read_and_route_message().await.unwrap();
assert_eq!(next_message(&mut all_open).await.message_type(), IncomingMessages::OpenOrder);
assert_eq!(next_message(&mut all_open).await.message_type(), IncomingMessages::OrderStatus);
assert!(tokio::time::timeout(TICK, order_route.next()).await.is_err(), "order route got it");
assert!(tokio::time::timeout(TICK, updates.next()).await.is_err(), "update stream got it");
}
#[tokio::test]
async fn foreign_execution_answers_requests_only() {
let (stream, bus) = make_bus();
let mut order_route = bus.send_order_request(OrderId::from(7), vec![]).await.unwrap();
let mut updates = bus.create_order_update_subscription().await.unwrap();
let request_id = RequestId::nth(99);
let mut request = bus.send_request(request_id, vec![]).await.unwrap();
stream.push_inbound(execution_data_frame(CLIENT_ID + 1, request_id.raw(), 7, "exec-1"));
stream.push_inbound(commission_frame("exec-1"));
stream.push_inbound(execution_data_frame(CLIENT_ID + 1, -1, 7, "exec-2"));
for _ in 0..3 {
bus.read_and_route_message().await.unwrap();
}
assert_eq!(next_message(&mut request).await.message_type(), IncomingMessages::ExecutionData);
assert_eq!(next_message(&mut request).await.message_type(), IncomingMessages::CommissionsReport);
assert!(
tokio::time::timeout(TICK, request.next()).await.is_err(),
"request got the unprompted one"
);
assert!(tokio::time::timeout(TICK, order_route.next()).await.is_err(), "order route got it");
while let Ok(Some(item)) = tokio::time::timeout(TICK, updates.next()).await {
let message_type = item.unwrap().message_type();
assert_ne!(message_type, IncomingMessages::ExecutionData, "update stream got it");
}
}
#[tokio::test]
async fn foreign_order_bound_is_ignored() {
let (stream, bus) = make_bus();
let mut updates = bus.create_order_update_subscription().await.unwrap();
stream.push_inbound(binary_proto(
IncomingMessages::OrderBound as i32,
&order_bound().client_id(CLIENT_ID + 1).to_proto(),
));
bus.read_and_route_message().await.unwrap();
assert!(tokio::time::timeout(TICK, updates.next()).await.is_err());
}
#[tokio::test]
async fn order_status_stream_sees_snapshot_reply() {
let (stream, bus) = make_bus();
let mut statuses = bus.create_order_status_subscription(OrderId::from(7)).await.unwrap();
let _snapshot = bus
.send_shared_request(OutgoingMessages::RequestOpenOrders, b"open-orders".to_vec())
.await
.unwrap();
stream.push_inbound(order_status_frame(CLIENT_ID, 7, OrderStatusKind::Submitted));
bus.read_and_route_message().await.unwrap();
assert_eq!(next_message(&mut statuses).await.order_id(), Some(7));
}
#[tokio::test]
async fn order_status_stream_ends_on_reset_and_shutdown() {
let (_stream, bus) = make_bus();
let mut statuses = bus.create_order_status_subscription(OrderId::from(7)).await.unwrap();
bus.reset_channels().await;
let item = tokio::time::timeout(TICK, statuses.next_routed()).await.expect("no item");
assert!(matches!(item, Some(RoutedItem::Error(Error::ConnectionReset))), "{item:?}");
let mut statuses = bus.create_order_status_subscription(OrderId::from(7)).await.unwrap();
bus.request_shutdown();
let item = tokio::time::timeout(TICK, statuses.next_routed()).await.expect("no item");
assert!(matches!(item, Some(RoutedItem::Error(Error::Shutdown))), "{item:?}");
assert!(matches!(
bus.create_order_status_subscription(OrderId::from(7)).await,
Err(Error::Shutdown)
));
}
#[tokio::test]
async fn abandoned_order_status_wait_is_released() {
let (_stream, bus) = make_bus();
let wait = async {
let mut statuses = bus.create_order_status_subscription(OrderId::from(7)).await.unwrap();
statuses.next_routed().await
};
assert!(tokio::time::timeout(Duration::from_millis(20), wait).await.is_err(), "nothing was sent");
drain_cleanup_signals(&bus).await;
assert!(!bus.order_taps.has_status_entry(OrderId::from(7)), "status stream outlived its wait");
}
#[tokio::test]
async fn notice_broadcaster_recovers_from_a_poisoned_lock() {
let broadcaster = NoticeBroadcaster::new(broadcast::channel(4).0);
poison_with(|| broadcaster.sender.lock().unwrap());
assert!(broadcaster.sender.is_poisoned());
let mut receiver = broadcaster.subscribe();
broadcaster.broadcast(Notice::synthesized(1, "after poison".into()));
broadcaster.close();
assert_eq!(receiver.recv().await.unwrap().message, "after poison");
assert!(matches!(receiver.recv().await, Err(broadcast::error::RecvError::Closed)));
}
#[tokio::test]
async fn exhausted_reconnect_refuses_later_sends_with_connection_failed() {
let stream = MemoryStream::default();
let mut connection = AsyncConnection::stubbed(stream.clone(), 28);
connection.set_server_version_for_test(server_versions::PROTOBUF_REST_MESSAGES_3);
connection.max_reconnect_attempts = Some(1);
stream.set_reconnect_failures(1);
stream.push_inbound(b"xx".to_vec());
let bus = Arc::new(AsyncTcpMessageBus::new(connection));
let mut in_flight = bus.send_request(RequestId::nth(100), vec![]).await.unwrap();
bus.clone().process_messages().expect("process_messages");
let item = tokio::time::timeout(Duration::from_secs(5), in_flight.next_routed())
.await
.expect("in-flight request got no notification")
.expect("in-flight channel closed early");
assert!(matches!(item, RoutedItem::Error(Error::ConnectionReset)), "in-flight: {item:?}");
let waited = tokio::time::timeout(Duration::from_secs(5), bus.wait_connected())
.await
.expect("wait_connected did not return after the reconnect gave up");
assert!(matches!(waited, Err(Error::ConnectionFailed)), "wait_connected: {waited:?}");
let sent = bus.send_request(RequestId::nth(1), vec![]).await.err();
assert!(matches!(sent, Some(Error::ConnectionFailed)), "send_request: {sent:?}");
let updates = bus.create_order_update_subscription().await.err();
assert!(matches!(updates, Some(Error::ConnectionFailed)), "order updates: {updates:?}");
let status = bus.create_order_status_subscription(OrderId::from(7)).await.err();
assert!(matches!(status, Some(Error::ConnectionFailed)), "order status: {status:?}");
}
#[tokio::test]
async fn send_after_shutdown_fails_with_shutdown() {
let (_, bus) = make_bus();
let mb: &dyn AsyncMessageBus = bus.as_ref();
mb.ensure_shutdown().await;
let sent = bus.send_request(RequestId::nth(1), vec![]).await.err();
assert!(matches!(sent, Some(Error::Shutdown)), "send_request: {sent:?}");
let waited = bus.wait_connected().await;
assert!(matches!(waited, Err(Error::Shutdown)), "wait_connected: {waited:?}");
}
#[tokio::test]
async fn reset_channels_starts_new_session_generation() {
let (_stream, bus) = make_bus();
assert_eq!(bus.session_generation(), 0);
bus.reset_channels().await;
bus.reset_channels().await;
assert_eq!(bus.session_generation(), 2);
}