@@ -23,6 +23,37 @@ mod tests_read_options {
2323 reencoded : Option < & ' a str > ,
2424 }
2525
26+ #[ tokio:: test]
27+ async fn test_topic_index_order ( ) {
28+ let folder = tempfile:: tempdir ( ) . unwrap ( ) ;
29+
30+ let store = Store :: new ( folder. path ( ) . to_path_buf ( ) ) ;
31+
32+ let frame1 = Frame {
33+ id : scru128:: new ( ) ,
34+ topic : "ab" . to_owned ( ) ,
35+ ..Default :: default ( )
36+ } ;
37+ let frame1 = store. append ( frame1) . unwrap ( ) ;
38+
39+ let frame2 = Frame {
40+ id : scru128:: new ( ) ,
41+ topic : "abc" . to_owned ( ) ,
42+ ..Default :: default ( )
43+ } ;
44+ let frame2 = store. append ( frame2) . unwrap ( ) ;
45+
46+ let keys = store. idx_topic . keys ( ) . flatten ( ) . collect :: < Vec < _ > > ( ) ;
47+
48+ assert_eq ! (
49+ & [
50+ fjall:: Slice :: from( idx_topic_key_from_frame( & frame1) . unwrap( ) ) ,
51+ fjall:: Slice :: from( idx_topic_key_from_frame( & frame2) . unwrap( ) ) ,
52+ ] ,
53+ & * keys,
54+ ) ;
55+ }
56+
2657 #[ tokio:: test]
2758 async fn test_topic_index ( ) {
2859 let folder = tempfile:: tempdir ( ) . unwrap ( ) ;
@@ -493,6 +524,32 @@ mod tests_context {
493524 use super :: * ;
494525 use tempfile:: TempDir ;
495526
527+ #[ tokio:: test]
528+ async fn test_reject_null_byte_in_topic ( ) {
529+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
530+ let store = Store :: new ( temp_dir. path ( ) . to_path_buf ( ) ) ;
531+
532+ // Try to create a frame with a topic containing a null byte
533+ let frame = Frame {
534+ id : scru128:: new ( ) ,
535+ topic : "test\0 topic" . to_owned ( ) ,
536+ context_id : ZERO_CONTEXT ,
537+ hash : None ,
538+ meta : None ,
539+ ttl : None ,
540+ } ;
541+
542+ // Creating the index key should fail
543+ let result = idx_topic_key_from_frame ( & frame) ;
544+ assert ! ( result. is_err( ) ) ;
545+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "null byte" ) ) ;
546+
547+ // Trying to append the frame should also fail
548+ let result = store. append ( frame) ;
549+ assert ! ( result. is_err( ) ) ;
550+ assert ! ( result. unwrap_err( ) . to_string( ) . contains( "null byte" ) ) ;
551+ }
552+
496553 #[ tokio:: test]
497554 async fn test_context_operations ( ) {
498555 let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
@@ -891,6 +948,68 @@ mod tests_context {
891948 let frames_ctx2: Vec < _ > = store. iter_frames ( Some ( ctx2) , None ) . collect ( ) ;
892949 assert_eq ! ( frames_ctx2, vec![ ctx2_frame1, ctx2_frame2] ) ;
893950 }
951+
952+ #[ test]
953+ fn test_idx_context_key_range_end ( ) {
954+ // Test 1: Normal case - verify basic increment works
955+ let context_id = Scru128Id :: from_u128 ( 100 ) ;
956+ let next_id = Scru128Id :: from_u128 ( 101 ) ;
957+ let result = idx_context_key_range_end ( context_id) ;
958+ assert_eq ! ( result, next_id. as_bytes( ) . to_vec( ) ) ;
959+
960+ // Test 2: Test with a complex key that's not all 0xFF
961+ let complex_id = Scru128Id :: from_u128 ( 0x8000_FFFF_0000_AAAA_1234_5678_9ABC_DEF0 ) ;
962+ let expected_next = Scru128Id :: from_u128 ( 0x8000_FFFF_0000_AAAA_1234_5678_9ABC_DEF1 ) ;
963+ assert_eq ! (
964+ idx_context_key_range_end( complex_id) ,
965+ expected_next. as_bytes( ) . to_vec( )
966+ ) ;
967+
968+ // Test 3: Boundary case - near maximum value
969+ let near_max = Scru128Id :: from_u128 ( u128:: MAX - 1 ) ;
970+ let max = Scru128Id :: from_u128 ( u128:: MAX ) ;
971+ assert_eq ! ( idx_context_key_range_end( near_max) , max. as_bytes( ) . to_vec( ) ) ;
972+
973+ // Test 4: Boundary case - at maximum value (saturating_add should prevent overflow)
974+ let at_max = Scru128Id :: from_u128 ( u128:: MAX ) ;
975+ assert_eq ! (
976+ idx_context_key_range_end( at_max) ,
977+ at_max. as_bytes( ) . to_vec( ) ,
978+ "When at u128::MAX, saturating_add should keep the same value"
979+ ) ;
980+
981+ // Test 5: Integration test - make sure it works in range queries
982+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
983+ let store = Store :: new ( temp_dir. path ( ) . to_path_buf ( ) ) ;
984+
985+ // Create first context normally
986+ let ctx1_frame = store
987+ . append ( Frame :: builder ( "xs.context" , ZERO_CONTEXT ) . build ( ) )
988+ . unwrap ( ) ;
989+ let ctx1 = ctx1_frame. id ;
990+
991+ // For ctx2, we need to manually create and register it
992+ let ctx2 = Scru128Id :: from_u128 ( ctx1. to_u128 ( ) + 1 ) ;
993+ let ctx2_frame = Frame :: builder ( "xs.context" , ZERO_CONTEXT )
994+ . id ( ctx2)
995+ . ttl ( TTL :: Forever )
996+ . build ( ) ;
997+
998+ // Manually insert the frame and register the context
999+ store. insert_frame ( & ctx2_frame) . unwrap ( ) ;
1000+ store. contexts . write ( ) . unwrap ( ) . insert ( ctx2) ;
1001+
1002+ // Add frames to both contexts
1003+ let frame1 = store. append ( Frame :: builder ( "test" , ctx1) . build ( ) ) . unwrap ( ) ;
1004+ let frame2 = store. append ( Frame :: builder ( "test" , ctx2) . build ( ) ) . unwrap ( ) ;
1005+
1006+ // Test that range query correctly separates the contexts
1007+ let frames1: Vec < _ > = store. read_sync ( None , None , Some ( ctx1) ) . collect ( ) ;
1008+ assert_eq ! ( frames1, vec![ frame1] , "Should only return frames from ctx1" ) ;
1009+
1010+ let frames2: Vec < _ > = store. read_sync ( None , None , Some ( ctx2) ) . collect ( ) ;
1011+ assert_eq ! ( frames2, vec![ frame2] , "Should only return frames from ctx2" ) ;
1012+ }
8941013}
8951014
8961015mod tests_ttl_expire {
0 commit comments