• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wboayue / rust-ibapi / 25713636856

12 May 2026 04:36AM UTC coverage: 91.517% (+0.06%) from 91.46%
25713636856

push

github

web-flow
display_groups/sync: cover Deref + IntoIterator impls (#562)

* display_groups/sync: cover Deref + IntoIterator impls

Adds three tests exercising the previously-uncovered trait impls
on DisplayGroupSubscription:
- Subscription<T>::next reached via Deref::deref
- (&sub).into_iter (the borrowed IntoIterator impl)
- sub.into_iter (the owned, consuming IntoIterator impl)

Extracts a stubbed_subscription helper + display_group_update_response
encoder so the four tests share fixture construction.

Coverage 76% → 96% lines, 82% → 96% regions, 50% → 100% functions.
The 3 remaining lines are panic! arms inside let-else patterns,
unreachable when tests pass.

* /simplify pass: named constants, MessageBusStub::with_responses, dedupe

- stubbed_subscription: replace inline MessageBusStub literal with
  MessageBusStub::with_responses (existing constructor)
- display_group_update_response: derive from IncomingMessages::DisplayGroupUpdated
  and TEST_REQ_ID_FIRST instead of magic 68 / 9000
- Client::stubbed: server_versions::LINKING (the actual gate per C#
  EClient.cs:3449) instead of magic 176
- Drop the now-redundant WHAT comment on display_group_update_response
- Collapse 3 near-identical test bodies via assert_first_data_eq helper
- Hoist top-of-mod use for SubscriptionItem (was triplicated per test)

Side-effect: coverage 96% → 98.6% lines because helper consolidation
reduced instrumented sites.

Async sibling display_groups/async.rs has the same 176/9000/68 warts
but is out of scope; can be aligned in a separate sweep.

27 of 28 new or added lines in 1 file covered. (96.43%)

1 existing line in 1 file now uncovered.

18803 of 20546 relevant lines covered (91.52%)

28.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.28
/src/display_groups/sync.rs
1
//! Synchronous implementation of display groups functionality
2

3
use std::ops::Deref;
4
use std::sync::Arc;
5

6
use crate::client::blocking::{ClientRequestBuilders, Subscription};
7
use crate::client::sync::Client;
8
use crate::transport::MessageBus;
9
use crate::Error;
10

11
use super::common::stream_decoders::DisplayGroupUpdate;
12
use super::encoders;
13

14
/// A subscription to display group events with the ability to update the displayed contract.
15
///
16
/// Created by [`Client::subscribe_to_group_events`](crate::client::blocking::Client::subscribe_to_group_events).
17
/// Derefs to `Subscription<DisplayGroupUpdate>` for `next()`, `cancel()`, `iter()`, etc.
18
pub struct DisplayGroupSubscription {
19
    inner: Subscription<DisplayGroupUpdate>,
20
    message_bus: Arc<dyn MessageBus>,
21
}
22

23
impl DisplayGroupSubscription {
24
    /// Updates the contract displayed in the TWS display group.
25
    ///
26
    /// # Arguments
27
    /// * `contract_info` - Contract to display:
28
    ///   - `"contractID@exchange"` for individual contracts (e.g., "265598@SMART")
29
    ///   - `"none"` for empty selection
30
    ///   - `"combo"` for combination contracts
31
    pub fn update(&self, contract_info: &str) -> Result<(), Error> {
1✔
32
        let request_id = self.inner.request_id().expect("subscription has no request ID");
1✔
33
        let request = encoders::encode_update_display_group(request_id, contract_info)?;
1✔
34
        self.message_bus.send_message(&request)
1✔
35
    }
1✔
36
}
37

38
impl Deref for DisplayGroupSubscription {
39
    type Target = Subscription<DisplayGroupUpdate>;
40
    fn deref(&self) -> &Self::Target {
1✔
41
        &self.inner
1✔
42
    }
1✔
43
}
44

45
impl<'a> IntoIterator for &'a DisplayGroupSubscription {
46
    type Item = Result<crate::subscriptions::SubscriptionItem<DisplayGroupUpdate>, crate::Error>;
47
    type IntoIter = <&'a Subscription<DisplayGroupUpdate> as IntoIterator>::IntoIter;
48

49
    fn into_iter(self) -> Self::IntoIter {
1✔
50
        (&self.inner).into_iter()
1✔
51
    }
1✔
52
}
53

54
impl IntoIterator for DisplayGroupSubscription {
55
    type Item = Result<crate::subscriptions::SubscriptionItem<DisplayGroupUpdate>, crate::Error>;
56
    type IntoIter = <Subscription<DisplayGroupUpdate> as IntoIterator>::IntoIter;
57

58
    fn into_iter(self) -> Self::IntoIter {
1✔
59
        self.inner.into_iter()
1✔
60
    }
1✔
61
}
62

63
impl Client {
64
    /// Subscribes to display group events for the specified group.
65
    ///
66
    /// Display Groups are a TWS-only feature (not available in IB Gateway).
67
    /// They allow organizing contracts into color-coded groups in the TWS UI.
68
    /// When subscribed, you receive updates whenever the user changes the contract
69
    /// displayed in that group within TWS.
70
    ///
71
    /// # Arguments
72
    /// * `group_id` - The ID of the group to subscribe to (1-9)
73
    ///
74
    /// # Examples
75
    ///
76
    /// ```no_run
77
    /// use ibapi::client::blocking::Client;
78
    ///
79
    /// let client = Client::connect("127.0.0.1:7497", 100).expect("connection failed");
80
    ///
81
    /// let subscription = client.subscribe_to_group_events(1).expect("subscription failed");
82
    ///
83
    /// // Update the displayed contract
84
    /// subscription.update("265598@SMART").expect("update failed");
85
    ///
86
    /// for event in &subscription {
87
    ///     println!("group event: {:?}", event);
88
    /// }
89
    /// ```
90
    pub fn subscribe_to_group_events(&self, group_id: i32) -> Result<DisplayGroupSubscription, Error> {
4✔
91
        let builder = self.request();
4✔
92
        let request = encoders::encode_subscribe_to_group_events(builder.request_id(), group_id)?;
4✔
93
        let inner = builder.send(request)?;
4✔
94
        Ok(DisplayGroupSubscription {
4✔
95
            inner,
4✔
96
            message_bus: self.message_bus.clone(),
4✔
97
        })
4✔
98
    }
4✔
99
}
100

101
#[cfg(test)]
102
mod tests {
103
    use super::*;
104
    use crate::common::test_utils::helpers::TEST_REQ_ID_FIRST;
105
    use crate::messages::IncomingMessages;
106
    use crate::server_versions;
107
    use crate::stubs::MessageBusStub;
108
    use crate::subscriptions::SubscriptionItem;
109
    use std::sync::Arc;
110

111
    fn display_group_update_response(contract_info: &str) -> String {
4✔
112
        format!(
4✔
113
            "{}\x001\x00{TEST_REQ_ID_FIRST}\x00{contract_info}\x00",
114
            IncomingMessages::DisplayGroupUpdated as i32,
4✔
115
        )
116
    }
4✔
117

118
    fn stubbed_subscription(responses: Vec<String>) -> (Arc<MessageBusStub>, DisplayGroupSubscription) {
4✔
119
        let message_bus = Arc::new(MessageBusStub::with_responses(responses));
4✔
120
        let client = Client::stubbed(message_bus.clone(), server_versions::LINKING);
4✔
121
        let subscription = client.subscribe_to_group_events(1).expect("failed to subscribe");
4✔
122
        (message_bus, subscription)
4✔
123
    }
4✔
124

125
    fn assert_first_data_eq(item: Option<Result<SubscriptionItem<DisplayGroupUpdate>, Error>>, expected_contract_info: &str) {
3✔
126
        let Some(Ok(SubscriptionItem::Data(update))) = item else {
3✔
NEW
127
            panic!("expected Data");
×
128
        };
129
        assert_eq!(update.contract_info, expected_contract_info);
3✔
130
    }
3✔
131

132
    #[test]
133
    fn test_update_display_group() {
1✔
134
        use crate::common::test_utils::helpers::assert_proto_msg_id;
135
        use crate::messages::OutgoingMessages;
136

137
        let (message_bus, subscription) = stubbed_subscription(vec![display_group_update_response("265598@SMART")]);
1✔
138
        subscription.update("265598@SMART").expect("update failed");
1✔
139

140
        let requests = message_bus.request_messages.read().unwrap();
1✔
141
        // First request is subscribe, second is update
142
        assert_eq!(requests.len(), 2);
1✔
143

144
        assert_proto_msg_id(&requests[0], OutgoingMessages::SubscribeToGroupEvents);
1✔
145
        assert_proto_msg_id(&requests[1], OutgoingMessages::UpdateDisplayGroup);
1✔
146
    }
1✔
147

148
    #[test]
149
    fn test_subscription_derefs_to_inner_for_next() {
1✔
150
        let (_bus, subscription) = stubbed_subscription(vec![display_group_update_response("265598@SMART")]);
1✔
151
        // `.next()` is Subscription<T>::next reached via Deref::deref.
152
        assert_first_data_eq(subscription.next(), "265598@SMART");
1✔
153
    }
1✔
154

155
    #[test]
156
    fn test_borrowed_into_iter_yields_subscription_items() {
1✔
157
        let (_bus, subscription) = stubbed_subscription(vec![display_group_update_response("265598@SMART")]);
1✔
158
        assert_first_data_eq((&subscription).into_iter().next(), "265598@SMART");
1✔
159
    }
1✔
160

161
    #[test]
162
    fn test_owned_into_iter_consumes_subscription() {
1✔
163
        let (_bus, subscription) = stubbed_subscription(vec![display_group_update_response("265598@SMART")]);
1✔
164
        assert_first_data_eq(subscription.into_iter().next(), "265598@SMART");
1✔
165
    }
1✔
166
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc