@@ -2,7 +2,7 @@ use crate::common::actions::when;
22use crate :: common:: assertions:: { assert_msg_type, then} ;
33use crate :: common:: cleanup:: finally;
44use crate :: common:: setup:: given_an_active_session;
5- use hotfix:: session:: Status ;
5+ use hotfix:: session:: { SetNextTargetSeqNumError , Status } ;
66use hotfix_message:: Part ;
77use hotfix_message:: fix44:: { MsgType , RESET_SEQ_NUM_FLAG } ;
88use std:: num:: NonZeroU64 ;
@@ -94,3 +94,211 @@ async fn test_set_next_target_seq_num_while_disconnected() {
9494 . expect ( "session info" ) ;
9595 assert_eq ! ( info. next_target_seq_number, 42 ) ;
9696}
97+
98+ /// Rejection: while `Active`, SetNextTargetSeqNum is refused and the store is
99+ /// untouched.
100+ #[ tokio:: test]
101+ async fn test_set_next_target_seq_num_rejected_while_active ( ) {
102+ let ( session, mut counterparty) = crate :: common:: setup:: given_an_active_session ( ) . await ;
103+
104+ let info_before = session
105+ . session_handle ( )
106+ . get_session_info ( )
107+ . await
108+ . expect ( "session info" ) ;
109+
110+ let result = session
111+ . session_handle ( )
112+ . set_next_target_seq_num ( NonZeroU64 :: new ( 42 ) . expect ( "42 is non-zero" ) )
113+ . await ;
114+
115+ assert ! (
116+ matches!(
117+ result,
118+ Err ( SetNextTargetSeqNumError :: InvalidState { current: Status :: Active } )
119+ ) ,
120+ "expected InvalidState{{ current: Active }}, got: {result:?}"
121+ ) ;
122+
123+ let info_after = session
124+ . session_handle ( )
125+ . get_session_info ( )
126+ . await
127+ . expect ( "session info" ) ;
128+ assert_eq ! (
129+ info_after. next_target_seq_number,
130+ info_before. next_target_seq_number,
131+ "target sequence number should not change on rejection"
132+ ) ;
133+
134+ crate :: common:: cleanup:: finally ( & session, & mut counterparty)
135+ . disconnect ( )
136+ . await ;
137+ }
138+
139+ /// Rejection: while `AwaitingLogon` (we've sent our Logon, peer hasn't responded),
140+ /// SetNextTargetSeqNum is refused and the store is untouched.
141+ #[ tokio:: test]
142+ async fn test_set_next_target_seq_num_rejected_while_awaiting_logon ( ) {
143+ let ( session, mut counterparty) =
144+ crate :: common:: setup:: given_a_connected_session ( ) . await ;
145+
146+ // wait for our outbound Logon so we're deterministically in AwaitingLogon
147+ crate :: common:: assertions:: then ( & mut counterparty)
148+ . receives ( |msg| crate :: common:: assertions:: assert_msg_type ( msg, hotfix_message:: fix44:: MsgType :: Logon ) )
149+ . await ;
150+
151+ let info_before = session
152+ . session_handle ( )
153+ . get_session_info ( )
154+ . await
155+ . expect ( "session info" ) ;
156+
157+ let result = session
158+ . session_handle ( )
159+ . set_next_target_seq_num ( NonZeroU64 :: new ( 42 ) . expect ( "42 is non-zero" ) )
160+ . await ;
161+
162+ assert ! (
163+ matches!(
164+ result,
165+ Err ( SetNextTargetSeqNumError :: InvalidState { current: Status :: AwaitingLogon } )
166+ ) ,
167+ "expected InvalidState{{ current: AwaitingLogon }}, got: {result:?}"
168+ ) ;
169+
170+ let info_after = session
171+ . session_handle ( )
172+ . get_session_info ( )
173+ . await
174+ . expect ( "session info" ) ;
175+ assert_eq ! (
176+ info_after. next_target_seq_number,
177+ info_before. next_target_seq_number
178+ ) ;
179+
180+ crate :: common:: cleanup:: finally ( & session, & mut counterparty)
181+ . disconnect ( )
182+ . await ;
183+ }
184+
185+ /// Rejection: while `AwaitingLogout` (we've sent our Logout and are waiting
186+ /// for the peer's reply), SetNextTargetSeqNum is refused and the store is
187+ /// untouched.
188+ #[ tokio:: test]
189+ async fn test_set_next_target_seq_num_rejected_while_awaiting_logout ( ) {
190+ use crate :: common:: actions:: when;
191+ use crate :: common:: assertions:: { assert_msg_type, then} ;
192+ use hotfix:: message:: logout:: Logout ;
193+ use hotfix_message:: fix44:: MsgType ;
194+
195+ let ( session, mut counterparty) =
196+ crate :: common:: setup:: given_an_active_session ( ) . await ;
197+
198+ // initiate logout from our side — we stay in AwaitingLogout until the peer replies
199+ when ( & session) . requests_disconnect ( ) . await ;
200+ then ( & mut counterparty)
201+ . receives ( |msg| assert_msg_type ( msg, MsgType :: Logout ) )
202+ . await ;
203+
204+ let info_before = session
205+ . session_handle ( )
206+ . get_session_info ( )
207+ . await
208+ . expect ( "session info" ) ;
209+
210+ let result = session
211+ . session_handle ( )
212+ . set_next_target_seq_num ( NonZeroU64 :: new ( 42 ) . expect ( "42 is non-zero" ) )
213+ . await ;
214+
215+ assert ! (
216+ matches!(
217+ result,
218+ Err ( SetNextTargetSeqNumError :: InvalidState { current: Status :: AwaitingLogout } )
219+ ) ,
220+ "expected InvalidState{{ current: AwaitingLogout }}, got: {result:?}"
221+ ) ;
222+
223+ let info_after = session
224+ . session_handle ( )
225+ . get_session_info ( )
226+ . await
227+ . expect ( "session info" ) ;
228+ assert_eq ! (
229+ info_after. next_target_seq_number,
230+ info_before. next_target_seq_number
231+ ) ;
232+
233+ // let the peer reply so the session cleans up (do NOT call finally().disconnect()
234+ // — we're already in logout).
235+ when ( & mut counterparty) . sends_message ( Logout :: default ( ) ) . await ;
236+ then ( & mut counterparty) . gets_disconnected ( ) . await ;
237+ }
238+
239+ /// Rejection: while `AwaitingResend` (we detected a gap and asked the peer to
240+ /// resend), SetNextTargetSeqNum is refused and the store is untouched.
241+ #[ tokio:: test]
242+ async fn test_set_next_target_seq_num_rejected_while_awaiting_resend ( ) {
243+ use crate :: common:: actions:: when;
244+ use crate :: common:: test_messages:: TestMessage ;
245+
246+ let ( mut session, mut counterparty) =
247+ crate :: common:: setup:: given_an_active_session ( ) . await ;
248+
249+ // create a gap so the session transitions to AwaitingResend
250+ when ( & mut counterparty)
251+ . has_previously_sent ( TestMessage :: dummy_execution_report ( ) )
252+ . await ;
253+ when ( & mut counterparty)
254+ . sends_message ( TestMessage :: dummy_execution_report ( ) )
255+ . await ;
256+
257+ crate :: common:: assertions:: then ( & mut session)
258+ . status_changes_to ( Status :: AwaitingResend {
259+ begin : 2 ,
260+ end : 3 ,
261+ attempts : 1 ,
262+ } )
263+ . await ;
264+ crate :: common:: assertions:: then ( & mut counterparty)
265+ . receives ( |msg| {
266+ crate :: common:: assertions:: assert_msg_type ( msg, hotfix_message:: fix44:: MsgType :: ResendRequest )
267+ } )
268+ . await ;
269+
270+ let info_before = session
271+ . session_handle ( )
272+ . get_session_info ( )
273+ . await
274+ . expect ( "session info" ) ;
275+
276+ let result = session
277+ . session_handle ( )
278+ . set_next_target_seq_num ( NonZeroU64 :: new ( 42 ) . expect ( "42 is non-zero" ) )
279+ . await ;
280+
281+ assert ! (
282+ matches!(
283+ result,
284+ Err ( SetNextTargetSeqNumError :: InvalidState {
285+ current: Status :: AwaitingResend { .. }
286+ } )
287+ ) ,
288+ "expected InvalidState{{ current: AwaitingResend{{..}} }}, got: {result:?}"
289+ ) ;
290+
291+ let info_after = session
292+ . session_handle ( )
293+ . get_session_info ( )
294+ . await
295+ . expect ( "session info" ) ;
296+ assert_eq ! (
297+ info_after. next_target_seq_number,
298+ info_before. next_target_seq_number
299+ ) ;
300+
301+ crate :: common:: cleanup:: finally ( & session, & mut counterparty)
302+ . disconnect ( )
303+ . await ;
304+ }
0 commit comments