Skip to main content

nautilus_model/events/order/spec/
pending_update.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use nautilus_core::{UUID4, UnixNanos};
17
18use crate::{
19    events::OrderPendingUpdate,
20    identifiers::{AccountId, ClientOrderId, InstrumentId, StrategyId, TraderId, VenueOrderId},
21    stubs::{TestDefault, test_uuid},
22};
23
24/// Test-only fluent spec for [`OrderPendingUpdate`].
25///
26/// All fields carry sensible defaults so callers only set what differs.
27/// `build()` constructs the event through [`OrderPendingUpdate::new`] so any future invariants
28/// added to the production constructor are exercised by tests built on this spec.
29#[derive(Debug, Clone, bon::Builder)]
30#[builder(finish_fn = into_spec)]
31pub struct OrderPendingUpdateSpec {
32    #[builder(default = TraderId::test_default())]
33    pub trader_id: TraderId,
34    #[builder(default = StrategyId::test_default())]
35    pub strategy_id: StrategyId,
36    #[builder(default = InstrumentId::test_default())]
37    pub instrument_id: InstrumentId,
38    #[builder(default = ClientOrderId::test_default())]
39    pub client_order_id: ClientOrderId,
40    pub account_id: Option<AccountId>,
41    #[builder(default = test_uuid())]
42    pub event_id: UUID4,
43    #[builder(default = UnixNanos::default())]
44    pub ts_event: UnixNanos,
45    #[builder(default = UnixNanos::default())]
46    pub ts_init: UnixNanos,
47    #[builder(default = false)]
48    pub reconciliation: bool,
49    pub venue_order_id: Option<VenueOrderId>,
50}
51
52impl<S: order_pending_update_spec_builder::IsComplete> OrderPendingUpdateSpecBuilder<S> {
53    /// Builds the spec and constructs an [`OrderPendingUpdate`] through its production constructor.
54    #[must_use]
55    pub fn build(self) -> OrderPendingUpdate {
56        let spec = self.into_spec();
57        OrderPendingUpdate::new(
58            spec.trader_id,
59            spec.strategy_id,
60            spec.instrument_id,
61            spec.client_order_id,
62            spec.account_id,
63            spec.event_id,
64            spec.ts_event,
65            spec.ts_init,
66            spec.reconciliation,
67            spec.venue_order_id,
68        )
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use rstest::rstest;
75
76    use super::*;
77    use crate::stubs::reset_test_uuid_rng;
78
79    #[rstest]
80    fn defaults_are_sensible() {
81        // Pin the spec's no-arg defaults so accidental drift in any individual default surfaces here,
82        // rather than as silent behavior change in downstream tests.
83        let event = OrderPendingUpdateSpec::builder().build();
84        assert_eq!(event.trader_id, TraderId::test_default());
85        assert_eq!(event.strategy_id, StrategyId::test_default());
86        assert_eq!(event.instrument_id, InstrumentId::test_default());
87        assert_eq!(event.client_order_id, ClientOrderId::test_default());
88        assert_eq!(event.account_id, None);
89        assert_eq!(event.ts_event, UnixNanos::default());
90        assert_eq!(event.ts_init, UnixNanos::default());
91        assert!(!event.reconciliation);
92        assert_eq!(event.venue_order_id, None);
93    }
94
95    #[rstest]
96    fn overrides_apply_through_constructor() {
97        let event = OrderPendingUpdateSpec::builder()
98            .account_id(AccountId::from("SIM-002"))
99            .venue_order_id(VenueOrderId::from("V-1"))
100            .reconciliation(true)
101            .build();
102
103        assert_eq!(event.account_id, Some(AccountId::from("SIM-002")));
104        assert_eq!(event.venue_order_id, Some(VenueOrderId::from("V-1")));
105        assert!(event.reconciliation);
106        assert_eq!(event.trader_id, TraderId::test_default());
107    }
108
109    #[rstest]
110    fn accepts_an_absent_account_id() {
111        let event = OrderPendingUpdateSpec::builder()
112            .maybe_account_id(None)
113            .build();
114
115        assert_eq!(event.account_id, None);
116    }
117
118    #[rstest]
119    fn event_ids_are_unique_within_a_run() {
120        reset_test_uuid_rng();
121        let a = OrderPendingUpdateSpec::builder().build();
122        let b = OrderPendingUpdateSpec::builder().build();
123        let c = OrderPendingUpdateSpec::builder().build();
124        assert_ne!(a.event_id, b.event_id);
125        assert_ne!(b.event_id, c.event_id);
126        assert_ne!(a.event_id, c.event_id);
127    }
128
129    #[rstest]
130    fn event_id_sequence_is_reproducible() {
131        // Reset before each draw so the comparison is run-order independent.
132        reset_test_uuid_rng();
133        let first_run: Vec<_> = (0..3)
134            .map(|_| OrderPendingUpdateSpec::builder().build().event_id)
135            .collect();
136
137        reset_test_uuid_rng();
138        let second_run: Vec<_> = (0..3)
139            .map(|_| OrderPendingUpdateSpec::builder().build().event_id)
140            .collect();
141
142        assert_eq!(first_run, second_run);
143    }
144}