Skip to main content

nautilus_model/events/order/spec/
rejected.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};
17use ustr::Ustr;
18
19use crate::{
20    events::OrderRejected,
21    identifiers::{AccountId, ClientOrderId, InstrumentId, StrategyId, TraderId},
22    stubs::{TestDefault, test_uuid},
23};
24
25/// Test-only fluent spec for [`OrderRejected`].
26///
27/// All fields carry sensible defaults so callers only set what differs.
28/// `build()` constructs the event through [`OrderRejected::new`] so any future invariants
29/// added to the production constructor are exercised by tests built on this spec.
30#[derive(Debug, Clone, bon::Builder)]
31#[builder(finish_fn = into_spec)]
32pub struct OrderRejectedSpec {
33    #[builder(default = TraderId::test_default())]
34    pub trader_id: TraderId,
35    #[builder(default = StrategyId::test_default())]
36    pub strategy_id: StrategyId,
37    #[builder(default = InstrumentId::test_default())]
38    pub instrument_id: InstrumentId,
39    #[builder(default = ClientOrderId::test_default())]
40    pub client_order_id: ClientOrderId,
41    #[builder(default = AccountId::test_default())]
42    pub account_id: AccountId,
43    #[builder(default = Ustr::from("TEST"))]
44    pub reason: Ustr,
45    #[builder(default = test_uuid())]
46    pub event_id: UUID4,
47    #[builder(default = UnixNanos::default())]
48    pub ts_event: UnixNanos,
49    #[builder(default = UnixNanos::default())]
50    pub ts_init: UnixNanos,
51    #[builder(default = false)]
52    pub reconciliation: bool,
53    #[builder(default = false)]
54    pub due_post_only: bool,
55}
56
57impl<S: order_rejected_spec_builder::IsComplete> OrderRejectedSpecBuilder<S> {
58    /// Builds the spec and constructs an [`OrderRejected`] through its production constructor.
59    #[must_use]
60    pub fn build(self) -> OrderRejected {
61        let spec = self.into_spec();
62        OrderRejected::new(
63            spec.trader_id,
64            spec.strategy_id,
65            spec.instrument_id,
66            spec.client_order_id,
67            spec.account_id,
68            spec.reason,
69            spec.event_id,
70            spec.ts_event,
71            spec.ts_init,
72            spec.reconciliation,
73            spec.due_post_only,
74        )
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use rstest::rstest;
81
82    use super::*;
83    use crate::stubs::reset_test_uuid_rng;
84
85    #[rstest]
86    fn defaults_are_sensible() {
87        // Pin the spec's no-arg defaults so accidental drift in any individual default surfaces here,
88        // rather than as silent behavior change in downstream tests.
89        let event = OrderRejectedSpec::builder().build();
90        assert_eq!(event.trader_id, TraderId::test_default());
91        assert_eq!(event.strategy_id, StrategyId::test_default());
92        assert_eq!(event.instrument_id, InstrumentId::test_default());
93        assert_eq!(event.client_order_id, ClientOrderId::test_default());
94        assert_eq!(event.account_id, AccountId::test_default());
95        assert_eq!(event.reason, Ustr::from("TEST"));
96        assert_eq!(event.ts_event, UnixNanos::default());
97        assert_eq!(event.ts_init, UnixNanos::default());
98        assert_eq!(event.reconciliation, 0);
99        assert_eq!(event.due_post_only, 0);
100    }
101
102    #[rstest]
103    fn overrides_apply_through_constructor() {
104        let event = OrderRejectedSpec::builder()
105            .reason(Ustr::from("INSUFFICIENT_MARGIN"))
106            .reconciliation(true)
107            .due_post_only(true)
108            .build();
109
110        assert_eq!(event.reason, Ustr::from("INSUFFICIENT_MARGIN"));
111        // Production constructor stores the bools as u8; assert against encoded values.
112        assert_eq!(event.reconciliation, 1);
113        assert_eq!(event.due_post_only, 1);
114        assert_eq!(event.trader_id, TraderId::test_default());
115    }
116
117    #[rstest]
118    fn event_ids_are_unique_within_a_run() {
119        reset_test_uuid_rng();
120        let a = OrderRejectedSpec::builder().build();
121        let b = OrderRejectedSpec::builder().build();
122        let c = OrderRejectedSpec::builder().build();
123        assert_ne!(a.event_id, b.event_id);
124        assert_ne!(b.event_id, c.event_id);
125        assert_ne!(a.event_id, c.event_id);
126    }
127
128    #[rstest]
129    fn event_id_sequence_is_reproducible() {
130        // Reset before each draw so the comparison is run-order independent.
131        reset_test_uuid_rng();
132        let first_run: Vec<_> = (0..3)
133            .map(|_| OrderRejectedSpec::builder().build().event_id)
134            .collect();
135
136        reset_test_uuid_rng();
137        let second_run: Vec<_> = (0..3)
138            .map(|_| OrderRejectedSpec::builder().build().event_id)
139            .collect();
140
141        assert_eq!(first_run, second_run);
142    }
143}