@@ -183,6 +183,90 @@ mod tests {
183183 RouteStatus :: Active ,
184184 ) )
185185 }
186+
187+ #[ test]
188+ fn missing_required_router_still_rejects_replay_capture ( ) {
189+ let registry = Register :: default ( ) ;
190+ registry. update_info ( 7 , peer ( 65001 ) . with_parent_ingress ( 3u32 ) ) ;
191+ assert ! ( SnapshotCache :: default ( )
192+ . capture( & route( 7 ) , & registry)
193+ . is_none( ) ) ;
194+ }
195+
196+ #[ tokio:: test]
197+ async fn replay_stops_at_router_before_opaque_unit_parent ( ) {
198+ // BMP input reserves a unit ID without an IngressInfo entry. The
199+ // router points at it for internal ownership; it is not wire identity.
200+ for source in [ 7 , 8 ] {
201+ let registry = Register :: default ( ) ;
202+ let unit = registry. register ( ) ;
203+ registry. update_info (
204+ 3 ,
205+ IngressInfo :: new ( )
206+ . with_ingress_type ( IngressType :: Bmp )
207+ . with_parent_ingress ( unit)
208+ . with_name ( "example-router" ) ,
209+ ) ;
210+ registry. update_info ( 7 , peer ( 65001 ) . with_parent_ingress ( 3u32 ) ) ;
211+ registry. update_info (
212+ 8 ,
213+ IngressInfo :: new ( )
214+ . with_ingress_type ( IngressType :: BgpPath )
215+ . with_parent_ingress ( 7u32 )
216+ . with_path_id ( 33u32 ) ,
217+ ) ;
218+ assert ! ( registry. get( unit) . is_none( ) ) ;
219+
220+ let ( tx, mut rx) = mpsc:: channel ( 8 ) ;
221+ let client = Arc :: new ( ClientState :: new (
222+ "127.0.0.1:1234" . parse ( ) . unwrap ( ) ,
223+ tx,
224+ 10 ,
225+ 1_000_000 ,
226+ ) ) ;
227+ assert ! ( matches!(
228+ client
229+ . buffer_update_if_dumping( route( source) , & registry)
230+ . await ,
231+ super :: super :: client_state:: BufferUpdateResult :: Buffered
232+ ) ) ;
233+ for id in [ 8 , 7 , 3 ] {
234+ registry. remove ( id) ;
235+ }
236+ let queued = client. take_buffered_updates ( ) . await ;
237+ assert_eq ! ( queued. len( ) , 1 ) ;
238+ let identities = & queued[ 0 ] . identities ;
239+ assert_eq ! ( identities. session_identity( source) , Some ( ( 7 , 0 ) ) ) ;
240+ assert ! ( identities. get( unit) . is_none( ) ) ;
241+ assert_eq ! (
242+ identities. get( 3 ) . unwrap( ) . name. as_deref( ) ,
243+ Some ( "example-router" )
244+ ) ;
245+ assert ! (
246+ send_update_to_client(
247+ & client,
248+ & queued[ 0 ] . update,
249+ identities,
250+ true ,
251+ FanInPeerDistinguisher :: default ( ) ,
252+ false ,
253+ )
254+ . await
255+ ) ;
256+ let up = rx. try_recv ( ) . unwrap ( ) ;
257+ assert_eq ! ( up[ 5 ] , 3 ) ;
258+ assert_eq ! (
259+ u32 :: from_be_bytes( up[ 32 ..36 ] . try_into( ) . unwrap( ) ) ,
260+ 65001
261+ ) ;
262+ assert ! ( up
263+ . windows( b"example-router" . len( ) )
264+ . any( |part| part == b"example-router" ) ) ;
265+ assert_eq ! ( rx. try_recv( ) . unwrap( ) [ 5 ] , 0 ) ;
266+ assert ! ( rx. try_recv( ) . is_err( ) ) ;
267+ }
268+ }
269+
186270 #[ tokio:: test]
187271 async fn replay_keeps_original_headers_across_retirement_and_same_id_reconnect (
188272 ) {
0 commit comments