@@ -239,3 +239,205 @@ impl KeyExprBackend for RmwZenohBackend {
239239 Ok ( ( false , qos) )
240240 }
241241}
242+
243+ #[ cfg( test) ]
244+ mod tests {
245+ use super :: * ;
246+ use crate :: entity:: { EndpointEntity , EntityKind , NodeEntity , TypeInfo } ;
247+ use crate :: qos:: { QosDurability , QosHistory , QosProfile , QosReliability } ;
248+
249+ #[ test]
250+ fn test_mangle_demangle ( ) {
251+ assert_eq ! ( RmwZenohBackend :: mangle_name( "/chatter" ) , "%chatter" ) ;
252+ assert_eq ! (
253+ RmwZenohBackend :: mangle_name( "std_msgs/msg/String" ) ,
254+ "std_msgs%msg%String"
255+ ) ;
256+ assert_eq ! (
257+ RmwZenohBackend :: demangle_name( "std_msgs%msg%String" ) ,
258+ "std_msgs/msg/String"
259+ ) ;
260+ }
261+
262+ #[ test]
263+ fn test_qos_encode_decode ( ) {
264+ let qos = QosProfile :: default ( ) ;
265+ let encoded = RmwZenohBackend :: encode_qos ( & qos, false ) ;
266+
267+ let ( keyless, decoded) = RmwZenohBackend :: decode_qos ( & encoded) . unwrap ( ) ;
268+ assert ! ( !keyless) ;
269+ assert_eq ! ( decoded. reliability, qos. reliability) ;
270+ assert_eq ! ( decoded. durability, qos. durability) ;
271+ }
272+
273+ #[ test]
274+ fn test_qos_reliable_transient ( ) {
275+ let qos = QosProfile {
276+ reliability : QosReliability :: Reliable ,
277+ durability : QosDurability :: TransientLocal ,
278+ history : QosHistory :: from_depth ( 10 ) ,
279+ ..Default :: default ( )
280+ } ;
281+ let encoded = RmwZenohBackend :: encode_qos ( & qos, false ) ;
282+
283+ let ( keyless, decoded) = RmwZenohBackend :: decode_qos ( & encoded) . unwrap ( ) ;
284+ assert ! ( !keyless) ;
285+ assert_eq ! ( decoded. reliability, QosReliability :: Reliable ) ;
286+ assert_eq ! ( decoded. durability, QosDurability :: TransientLocal ) ;
287+ }
288+
289+ /// Test topic key expression format matches rmw_zenoh.
290+ ///
291+ /// rmw_zenoh format: `<domain_id>/<topic>/<type>/<hash>`
292+ #[ test]
293+ fn test_topic_key_expr_format ( ) {
294+ let zid: zenoh:: session:: ZenohId = "1234567890abcdef1234567890abcdef" . parse ( ) . unwrap ( ) ;
295+ let node = NodeEntity :: new ( 0 , zid, 0 , "test_node" . to_string ( ) , "/" . to_string ( ) , String :: new ( ) ) ;
296+
297+ let entity = EndpointEntity {
298+ id : 1 ,
299+ node,
300+ kind : EntityKind :: Publisher ,
301+ topic : "chatter" . to_string ( ) ,
302+ type_info : Some ( TypeInfo :: new ( "std_msgs/msg/String" , TypeHash :: zero ( ) ) ) ,
303+ qos : QosProfile :: default ( ) ,
304+ } ;
305+
306+ let topic_ke = RmwZenohBackend :: topic_key_expr ( & entity) . unwrap ( ) ;
307+ let ke_str = topic_ke. as_str ( ) ;
308+
309+ // rmw_zenoh format: <domain_id>/<topic>/<type>/<hash>
310+ assert ! ( ke_str. starts_with( "0/" ) , "Should start with domain ID '0/', got: {}" , ke_str) ;
311+ assert ! ( ke_str. contains( "/chatter/" ) , "Should contain '/chatter/', got: {}" , ke_str) ;
312+ // Note: rmw_zenoh does NOT escape slashes in topic key expression
313+ assert ! ( ke_str. contains( "std_msgs/msg/String" ) , "Should contain type name, got: {}" , ke_str) ;
314+ }
315+
316+ /// Test liveliness key expression format matches rmw_zenoh.
317+ ///
318+ /// Format: `@ros2_lv/<domain>/<zid>/<nid>/<eid>/<kind>/<enclave>/<namespace>/<node_name>/<topic>/<type>/<hash>/<qos>`
319+ #[ test]
320+ fn test_liveliness_key_expr_format ( ) {
321+ let zid: zenoh:: session:: ZenohId = "1234567890abcdef1234567890abcdef" . parse ( ) . unwrap ( ) ;
322+ let node = NodeEntity :: new ( 0 , zid, 0 , "test_node" . to_string ( ) , "/" . to_string ( ) , String :: new ( ) ) ;
323+
324+ let entity = EndpointEntity {
325+ id : 1 ,
326+ node,
327+ kind : EntityKind :: Publisher ,
328+ topic : "chatter" . to_string ( ) ,
329+ type_info : Some ( TypeInfo :: new ( "std_msgs/msg/String" , TypeHash :: zero ( ) ) ) ,
330+ qos : QosProfile :: default ( ) ,
331+ } ;
332+
333+ let liveliness_ke = RmwZenohBackend :: liveliness_key_expr ( & entity, & zid) . unwrap ( ) ;
334+ let ke_str = liveliness_ke. as_str ( ) ;
335+
336+ // Should start with @ros2_lv
337+ assert ! ( ke_str. starts_with( "@ros2_lv/" ) , "Should start with '@ros2_lv/', got: {}" , ke_str) ;
338+
339+ // Should contain domain ID
340+ assert ! ( ke_str. contains( "/0/" ) , "Should contain domain '/0/', got: {}" , ke_str) ;
341+
342+ // Should contain MP for Publisher
343+ assert ! ( ke_str. contains( "/MP/" ) , "Should contain '/MP/' for Publisher, got: {}" , ke_str) ;
344+
345+ // Should contain node name
346+ assert ! ( ke_str. contains( "/test_node/" ) , "Should contain '/test_node/', got: {}" , ke_str) ;
347+
348+ // Should contain escaped topic name
349+ assert ! ( ke_str. contains( "/chatter/" ) , "Should contain '/chatter/', got: {}" , ke_str) ;
350+
351+ // Should contain escaped type name
352+ assert ! (
353+ ke_str. contains( "std_msgs%msg%String" ) ,
354+ "Should contain 'std_msgs%msg%String', got: {}" ,
355+ ke_str
356+ ) ;
357+ }
358+
359+ /// Test subscriber liveliness key expression
360+ #[ test]
361+ fn test_subscriber_liveliness_key_expr ( ) {
362+ let zid: zenoh:: session:: ZenohId = "1234567890abcdef1234567890abcdef" . parse ( ) . unwrap ( ) ;
363+ let node = NodeEntity :: new ( 0 , zid, 0 , "test_node" . to_string ( ) , "/" . to_string ( ) , String :: new ( ) ) ;
364+
365+ let entity = EndpointEntity {
366+ id : 1 ,
367+ node,
368+ kind : EntityKind :: Subscription ,
369+ topic : "chatter" . to_string ( ) ,
370+ type_info : Some ( TypeInfo :: new ( "std_msgs/msg/String" , TypeHash :: zero ( ) ) ) ,
371+ qos : QosProfile :: default ( ) ,
372+ } ;
373+
374+ let liveliness_ke = RmwZenohBackend :: liveliness_key_expr ( & entity, & zid) . unwrap ( ) ;
375+ let ke_str = liveliness_ke. as_str ( ) ;
376+
377+ // Should contain MS for Subscription
378+ assert ! ( ke_str. contains( "/MS/" ) , "Should contain '/MS/' for Subscription, got: {}" , ke_str) ;
379+ }
380+
381+ /// Test service server liveliness key expression
382+ #[ test]
383+ fn test_service_liveliness_key_expr ( ) {
384+ let zid: zenoh:: session:: ZenohId = "1234567890abcdef1234567890abcdef" . parse ( ) . unwrap ( ) ;
385+ let node = NodeEntity :: new ( 0 , zid, 0 , "test_node" . to_string ( ) , "/" . to_string ( ) , String :: new ( ) ) ;
386+
387+ let entity = EndpointEntity {
388+ id : 1 ,
389+ node,
390+ kind : EntityKind :: Service ,
391+ topic : "add_two_ints" . to_string ( ) ,
392+ type_info : Some ( TypeInfo :: new ( "example_interfaces/srv/AddTwoInts" , TypeHash :: zero ( ) ) ) ,
393+ qos : QosProfile :: default ( ) ,
394+ } ;
395+
396+ let liveliness_ke = RmwZenohBackend :: liveliness_key_expr ( & entity, & zid) . unwrap ( ) ;
397+ let ke_str = liveliness_ke. as_str ( ) ;
398+
399+ // Should contain SS for Service
400+ assert ! ( ke_str. contains( "/SS/" ) , "Should contain '/SS/' for Service, got: {}" , ke_str) ;
401+ }
402+
403+ /// Test client liveliness key expression
404+ #[ test]
405+ fn test_client_liveliness_key_expr ( ) {
406+ let zid: zenoh:: session:: ZenohId = "1234567890abcdef1234567890abcdef" . parse ( ) . unwrap ( ) ;
407+ let node = NodeEntity :: new ( 0 , zid, 0 , "test_node" . to_string ( ) , "/" . to_string ( ) , String :: new ( ) ) ;
408+
409+ let entity = EndpointEntity {
410+ id : 1 ,
411+ node,
412+ kind : EntityKind :: Client ,
413+ topic : "add_two_ints" . to_string ( ) ,
414+ type_info : Some ( TypeInfo :: new ( "example_interfaces/srv/AddTwoInts" , TypeHash :: zero ( ) ) ) ,
415+ qos : QosProfile :: default ( ) ,
416+ } ;
417+
418+ let liveliness_ke = RmwZenohBackend :: liveliness_key_expr ( & entity, & zid) . unwrap ( ) ;
419+ let ke_str = liveliness_ke. as_str ( ) ;
420+
421+ // Should contain SC for Client
422+ assert ! ( ke_str. contains( "/SC/" ) , "Should contain '/SC/' for Client, got: {}" , ke_str) ;
423+ }
424+
425+ /// Test node liveliness key expression
426+ #[ test]
427+ fn test_node_liveliness_key_expr ( ) {
428+ let zid: zenoh:: session:: ZenohId = "1234567890abcdef1234567890abcdef" . parse ( ) . unwrap ( ) ;
429+ let node = NodeEntity :: new ( 0 , zid, 0 , "test_node" . to_string ( ) , "/my_namespace" . to_string ( ) , String :: new ( ) ) ;
430+
431+ let liveliness_ke = RmwZenohBackend :: node_liveliness_key_expr ( & node) . unwrap ( ) ;
432+ let ke_str = liveliness_ke. as_str ( ) ;
433+
434+ // Should start with @ros2_lv
435+ assert ! ( ke_str. starts_with( "@ros2_lv/" ) , "Should start with '@ros2_lv/', got: {}" , ke_str) ;
436+
437+ // Should contain NN for Node
438+ assert ! ( ke_str. contains( "/NN/" ) , "Should contain '/NN/' for Node, got: {}" , ke_str) ;
439+
440+ // Should contain node name
441+ assert ! ( ke_str. contains( "/test_node" ) , "Should contain '/test_node', got: {}" , ke_str) ;
442+ }
443+ }
0 commit comments