@@ -23,6 +23,37 @@ mod tests_read_options {
23
23
reencoded : Option < & ' a str > ,
24
24
}
25
25
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
+
26
57
#[ tokio:: test]
27
58
async fn test_topic_index ( ) {
28
59
let folder = tempfile:: tempdir ( ) . unwrap ( ) ;
@@ -493,6 +524,32 @@ mod tests_context {
493
524
use super :: * ;
494
525
use tempfile:: TempDir ;
495
526
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
+
496
553
#[ tokio:: test]
497
554
async fn test_context_operations ( ) {
498
555
let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
@@ -891,6 +948,68 @@ mod tests_context {
891
948
let frames_ctx2: Vec < _ > = store. iter_frames ( Some ( ctx2) , None ) . collect ( ) ;
892
949
assert_eq ! ( frames_ctx2, vec![ ctx2_frame1, ctx2_frame2] ) ;
893
950
}
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
+ }
894
1013
}
895
1014
896
1015
mod tests_ttl_expire {
0 commit comments