ibapi 4.0.1

A Rust implementation of the Interactive Brokers TWS API, providing a reliable and user friendly interface for TWS and IB Gateway. Designed with a focus on simplicity and performance.
Documentation
use super::*;
use crate::common::test_utils::helpers::{assert_request, proto_response, TEST_REQ_ID_FIRST};
use crate::messages::IncomingMessages;
use crate::stubs::MessageBusStub;
use crate::subscriptions::StreamDecoder;
use crate::testdata::builders::wsh::{
    cancel_wsh_event_data_request, cancel_wsh_metadata_request, wsh_event_data_request, wsh_event_data_response, wsh_metadata_request,
    wsh_metadata_response,
};
use crate::testdata::builders::ResponseProtoEncoder;
use crate::wsh::common::test_data::{self, json_responses};
use std::sync::Arc;

/// Apply a table row's optional filters to the builder. The row shape predates
/// #752's builders and keeps its `Option` fields, which is what makes this the
/// adapter rather than a second API.
fn by_contract(
    client: &Client,
    contract_id: i32,
    start_date: Option<time::Date>,
    end_date: Option<time::Date>,
    limit: Option<i32>,
    auto_fill: Option<AutoFill>,
) -> Result<WshEventData, Error> {
    let mut builder = client.wsh_event_data_by_contract(contract_id);
    if let Some(start_date) = start_date {
        builder = builder.starting(start_date);
    }
    if let Some(end_date) = end_date {
        builder = builder.ending(end_date);
    }
    if let Some(limit) = limit {
        builder = builder.limit(limit);
    }
    if let Some(auto_fill) = auto_fill {
        builder = builder.auto_fill(auto_fill);
    }
    builder.fetch()
}

fn by_filter(client: &Client, filter: &str, limit: Option<i32>, auto_fill: Option<AutoFill>) -> Result<Subscription<WshEventData>, Error> {
    let mut builder = client.wsh_event_data_by_filter(filter);
    if let Some(limit) = limit {
        builder = builder.limit(limit);
    }
    if let Some(auto_fill) = auto_fill {
        builder = builder.auto_fill(auto_fill);
    }
    builder.subscribe()
}

#[test]
fn test_wsh_metadata_table() {
    use crate::wsh::common::test_tables::{wsh_metadata_test_cases, ApiExpectedResult};

    for test_case in wsh_metadata_test_cases() {
        let message_bus = Arc::new(MessageBusStub::with_ordered_responses(test_case.response_messages));

        let client = Client::stubbed(message_bus, test_case.server_version);
        let result = client.wsh_metadata();

        match test_case.expected_result {
            ApiExpectedResult::Success { json } => {
                assert!(result.is_ok(), "Test '{}' failed: {:?}", test_case.name, result.err());
                assert_eq!(result.unwrap().data_json, json, "Test '{}' json mismatch", test_case.name);
            }
            ApiExpectedResult::ServerVersionError => {
                assert!(result.is_err(), "Test '{}' should have failed", test_case.name);
                assert!(
                    matches!(result.unwrap_err(), Error::ServerVersion(_, _, _)),
                    "Test '{}' wrong error type",
                    test_case.name
                );
            }
        }
    }
}

#[test]
fn test_wsh_metadata_request_body() {
    let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![proto_response(
        IncomingMessages::WshMetaData,
        wsh_metadata_response()
            .request_id(TEST_REQ_ID_FIRST)
            .data_json(json_responses::METADATA_SIMPLE)
            .encode_proto(),
    )]));

    let client = Client::stubbed(message_bus.clone(), crate::server_versions::WSHE_CALENDAR);
    client.wsh_metadata().expect("metadata request failed");

    assert_request(&message_bus, 0, &wsh_metadata_request().request_id(TEST_REQ_ID_FIRST));
}

#[test]
fn test_wsh_event_data_by_contract_table() {
    use crate::wsh::common::test_tables::{event_data_by_contract_test_cases, ApiExpectedResult};

    for test_case in event_data_by_contract_test_cases() {
        let message_bus = Arc::new(MessageBusStub::with_ordered_responses(test_case.response_messages));

        let client = Client::stubbed(message_bus.clone(), test_case.server_version);
        let result = by_contract(
            &client,
            test_case.contract_id,
            test_case.start_date,
            test_case.end_date,
            test_case.limit,
            test_case.auto_fill,
        );

        match test_case.expected_result {
            ApiExpectedResult::Success { json } => {
                assert!(result.is_ok(), "Test '{}' failed: {:?}", test_case.name, result.err());
                assert_eq!(result.unwrap().data_json, json, "Test '{}' json mismatch", test_case.name);
                assert_request(
                    &message_bus,
                    0,
                    &wsh_event_data_request()
                        .request_id(TEST_REQ_ID_FIRST)
                        .contract_id(Some(test_case.contract_id))
                        .start_date(test_case.start_date)
                        .end_date(test_case.end_date)
                        .limit(test_case.limit)
                        .auto_fill(test_case.auto_fill),
                );
            }
            ApiExpectedResult::ServerVersionError => {
                assert!(result.is_err(), "Test '{}' should have failed", test_case.name);
                assert!(
                    matches!(result.unwrap_err(), Error::ServerVersion(_, _, _)),
                    "Test '{}' wrong error type",
                    test_case.name
                );
            }
        }
    }
}

#[test]
fn test_wsh_event_data_by_filter_subscription_sync() {
    let event_response = |data| {
        proto_response(
            IncomingMessages::WshEventData,
            wsh_event_data_response()
                .request_id(test_data::REQUEST_ID_FILTER)
                .data_json(data)
                .encode_proto(),
        )
    };
    let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![
        event_response(json_responses::EVENT_DATA_EARNINGS),
        event_response(json_responses::EVENT_DATA_DIVIDEND),
    ]));

    let client = Client::stubbed(message_bus.clone(), crate::server_versions::WSH_EVENT_DATA_FILTERS_DATE);
    let subscription = by_filter(&client, test_data::TEST_FILTER, Some(50), None).expect("filter request failed");

    assert_request(
        &message_bus,
        0,
        &wsh_event_data_request()
            .request_id(TEST_REQ_ID_FIRST)
            .filter(Some(test_data::TEST_FILTER))
            .limit(Some(50)),
    );

    let event = subscription.next_data().expect("first event").expect("subscription error");
    assert_eq!(event.data_json, json_responses::EVENT_DATA_EARNINGS);

    let event = subscription.next_data().expect("second event").expect("subscription error");
    assert_eq!(event.data_json, json_responses::EVENT_DATA_DIVIDEND);

    assert!(subscription.next_data().is_none());
}

#[test]
fn test_wsh_event_data_by_filter_integration_table() {
    use crate::wsh::common::test_tables::{event_data_by_filter_integration_test_cases, IntegrationExpectedResult};

    for test_case in event_data_by_filter_integration_test_cases() {
        let message_bus = Arc::new(MessageBusStub::with_ordered_responses(test_case.response_messages));

        let client = Client::stubbed(message_bus.clone(), test_case.server_version);

        let result = match test_case.name {
            "successful filter request with autofill" => {
                let filter = "filter=value";
                let auto_fill = AutoFill {
                    competitors: true,
                    portfolio: false,
                    watchlist: true,
                };
                let result = by_filter(&client, filter, Some(100), Some(auto_fill));
                if result.is_ok() {
                    assert_request(
                        &message_bus,
                        0,
                        &wsh_event_data_request()
                            .request_id(TEST_REQ_ID_FIRST)
                            .filter(Some(filter))
                            .limit(Some(100))
                            .auto_fill(Some(auto_fill)),
                    );
                }
                result
            }
            "successful filter request without autofill" => {
                let filter = "filter=value";
                let result = by_filter(&client, filter, None, None);
                if result.is_ok() {
                    assert_request(
                        &message_bus,
                        0,
                        &wsh_event_data_request().request_id(TEST_REQ_ID_FIRST).filter(Some(filter)),
                    );
                }
                result
            }
            _ => by_filter(&client, "filter", None, None),
        };

        match test_case.expected_result {
            IntegrationExpectedResult::Success => {
                assert!(result.is_ok(), "Test '{}' failed: {:?}", test_case.name, result.err());
            }
            IntegrationExpectedResult::ServerVersionError => {
                assert!(result.is_err(), "Test '{}' should have failed", test_case.name);
                assert!(
                    matches!(result.as_ref().err(), Some(Error::ServerVersion(_, _, _))),
                    "Test '{}' wrong error type",
                    test_case.name
                );
            }
        }
    }
}

#[test]
fn test_server_version_validation_table() {
    use crate::wsh::common::test_tables::server_version_test_cases;

    for test_case in server_version_test_cases() {
        let message_bus = Arc::new(MessageBusStub::with_responses(vec![]));

        let client = Client::stubbed(message_bus, test_case.server_version);

        let result = if let Some(contract_id) = test_case.contract_id {
            by_contract(
                &client,
                contract_id,
                test_case.start_date,
                test_case.end_date,
                test_case.limit,
                test_case.auto_fill,
            )
        } else {
            by_filter(&client, "filter", test_case.limit, test_case.auto_fill).map(|_| WshEventData { data_json: "".to_string() })
        };

        if test_case.expected_error {
            assert!(result.is_err(), "Test '{}' should have failed", test_case.name);
            assert!(
                matches!(result.unwrap_err(), Error::ServerVersion(_, _, _)),
                "Test '{}' wrong error type",
                test_case.name
            );
        } else {
            assert!(result.is_ok(), "Test '{}' failed: {:?}", test_case.name, result.err());
        }
    }
}

#[test]
fn test_subscription_integration_table() {
    use crate::wsh::common::test_tables::subscription_integration_test_cases;

    for test_case in subscription_integration_test_cases() {
        let message_bus = Arc::new(MessageBusStub::with_ordered_responses(test_case.response_messages));

        let client = Client::stubbed(message_bus, test_case.server_version);
        let result = by_filter(&client, "test_filter", None, None);

        assert!(result.is_ok(), "Test '{}' failed to create subscription", test_case.name);
        let subscription = result.unwrap();

        let mut events = vec![];
        while let Some(event) = subscription.next_data() {
            events.push(event.expect("subscription error").data_json);
        }

        assert_eq!(
            events.len(),
            test_case.expected_events.len(),
            "Test '{}' event count mismatch",
            test_case.name
        );

        for (i, (received, expected)) in events.iter().zip(test_case.expected_events.iter()).enumerate() {
            assert_eq!(received, expected, "Test '{}' event {} mismatch", test_case.name, i);
        }
    }
}

#[test]
fn test_data_stream_cancel_message() {
    let request_id = test_data::REQUEST_ID_METADATA;

    let metadata_cancel = WshMetadata::cancel_message(0, Some(request_id), None).unwrap();
    let metadata_expected = cancel_wsh_metadata_request().request_id(request_id);
    use crate::testdata::builders::RequestEncoder;
    assert_eq!(metadata_cancel, metadata_expected.encode_request());

    let event_cancel = WshEventData::cancel_message(0, Some(request_id), None).unwrap();
    let event_expected = cancel_wsh_event_data_request().request_id(request_id);
    assert_eq!(event_cancel, event_expected.encode_request());
}

#[test]
fn test_wsh_metadata_decode_table() {
    use crate::subscriptions::DecoderContext;
    use crate::wsh::common::test_tables::WSH_METADATA_DECODE_TESTS;

    for test_case in WSH_METADATA_DECODE_TESTS {
        let message = test_case.metadata_message();
        let result = WshMetadata::decode(&DecoderContext::default(), &message);

        assert!(result.is_ok(), "Test '{}' failed: {:?}", test_case.name, result.err());
        assert_eq!(result.unwrap().data_json, test_case.data_json, "Test '{}' json mismatch", test_case.name);
    }
}

#[test]
fn test_wsh_event_data_decode_table() {
    use crate::subscriptions::DecoderContext;
    use crate::wsh::common::test_tables::WSH_EVENT_DATA_DECODE_TESTS;

    for test_case in WSH_EVENT_DATA_DECODE_TESTS {
        let message = test_case.event_data_message();
        let result = WshEventData::decode(&DecoderContext::default(), &message);

        assert!(result.is_ok(), "Test '{}' failed: {:?}", test_case.name, result.err());
        assert_eq!(result.unwrap().data_json, test_case.data_json, "Test '{}' json mismatch", test_case.name);
    }
}