ibapi 5.3.0

An unofficial Rust client for the Interactive Brokers TWS API: market data, orders, and account data, with async (Tokio) and blocking clients for TWS and IB Gateway.
Documentation
use super::*;

/// Round-trip a frame body through the async `MemoryStream`. Either ordering is
/// correct — if the consumer reaches the queue first it parks on `Notify` and
/// the producer's `notify_one` wakes it; if the producer pushes first the
/// consumer returns immediately. EOF surfaces as `Io(UnexpectedEof)` after
/// `hang_up`.
///
/// Pinned to `current_thread` so the producer task cannot run before the
/// consumer arms its notify; that ordering is what exercises the wait path.
#[tokio::test(flavor = "current_thread")]
async fn round_trip_frame() {
    let stream = MemoryStream::default();

    let producer = stream.clone();
    let push = tokio::spawn(async move {
        tokio::task::yield_now().await;
        producer.push_inbound(b"hello".to_vec());
    });

    let body = stream.read_message().await.unwrap();
    assert_eq!(body, b"hello");
    push.await.unwrap();

    stream.write_all(b"out").await.unwrap();
    assert_eq!(stream.captured(), b"out");

    stream.hang_up();
    let err = stream.read_message().await.unwrap_err();
    assert!(
        matches!(err, Error::Io(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof),
        "unexpected error: {err:?}"
    );
}

/// The transport close hook must wake an adapter read that is already waiting.
#[tokio::test(flavor = "current_thread")]
async fn trait_close_wakes_a_pending_reader() {
    let stream = MemoryStream::default();
    let reading = stream.clone();
    let read = tokio::spawn(async move { reading.read_message().await });
    tokio::task::yield_now().await;
    stream.close().await;
    let result = tokio::time::timeout(Duration::from_secs(1), read)
        .await
        .unwrap_or_else(|_| panic!("transport close did not wake {stream:?}"))
        .unwrap();
    assert!(matches!(result, Err(Error::Shutdown)), "got {result:?}");
}

/// Unlike `hang_up`, the transport close survives `reopen`.
#[tokio::test]
async fn trait_close_is_terminal() {
    let stream = MemoryStream::default();
    stream.close().await;
    stream.reopen();
    stream.push_inbound(b"late".to_vec());
    assert!(matches!(stream.read_message().await, Err(Error::Shutdown)));
    assert!(matches!(stream.write_all(b"late").await, Err(Error::Shutdown)));
    assert!(matches!(stream.reconnect().await, Err(Error::Shutdown)));
    assert!(stream.captured().is_empty());
}