@@ -402,6 +402,8 @@ pub struct LSHStats {
402402#[ allow( clippy:: unwrap_used, clippy:: expect_used) ]
403403mod tests {
404404 use super :: * ;
405+ use serde_json:: Value ;
406+ use std:: path:: Path ;
405407
406408 /// Helper: create clustered test data.
407409 fn clustered_data ( n_clusters : usize , points_per_cluster : usize , dim : usize ) -> Vec < f32 > {
@@ -436,6 +438,42 @@ mod tests {
436438 dists
437439 }
438440
441+ fn lsh_snapshot_dir ( ) -> tempfile:: TempDir {
442+ let dim = 8 ;
443+ let data = clustered_data ( 3 , 8 , dim) ;
444+ let params = LSHParams {
445+ num_tables : 4 ,
446+ num_probes : 2 ,
447+ seed : Some ( 42 ) ,
448+ } ;
449+
450+ let mut index = CrossPolytopeLSHIndex :: new ( dim, params) . unwrap ( ) ;
451+ index. add_vectors ( & data) . unwrap ( ) ;
452+ index. build ( ) . unwrap ( ) ;
453+
454+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
455+ index. save_to_dir ( dir. path ( ) ) . unwrap ( ) ;
456+ dir
457+ }
458+
459+ fn mutate_lsh_manifest ( dir : & Path , mutator : impl FnOnce ( & mut Value ) ) {
460+ let path = dir. join ( "manifest.json" ) ;
461+ let mut value: Value = serde_json:: from_slice ( & std:: fs:: read ( & path) . unwrap ( ) ) . unwrap ( ) ;
462+ mutator ( & mut value) ;
463+ std:: fs:: write ( & path, serde_json:: to_vec_pretty ( & value) . unwrap ( ) ) . unwrap ( ) ;
464+ }
465+
466+ fn assert_lsh_snapshot_load_fails ( mutator : impl FnOnce ( & Path ) ) {
467+ let dir = lsh_snapshot_dir ( ) ;
468+ mutator ( dir. path ( ) ) ;
469+ let error = CrossPolytopeLSHIndex :: load_from_dir ( dir. path ( ) )
470+ . expect_err ( "corrupt LSH snapshot loaded successfully" ) ;
471+ assert ! (
472+ matches!( error, RetrieveError :: FormatError ( _) ) ,
473+ "expected LSH corrupt snapshot to fail as a format error, got {error:?}"
474+ ) ;
475+ }
476+
439477 #[ test]
440478 fn test_build_and_search ( ) {
441479 let dim = 16 ;
@@ -641,6 +679,37 @@ mod tests {
641679 ) ;
642680 }
643681
682+ #[ test]
683+ fn load_rejects_bad_snapshot_magic ( ) {
684+ assert_lsh_snapshot_load_fails ( |dir| {
685+ mutate_lsh_manifest ( dir, |value| value[ "magic" ] [ 0 ] = 0u32 . into ( ) ) ;
686+ } ) ;
687+ }
688+
689+ #[ test]
690+ fn load_rejects_future_snapshot_version ( ) {
691+ assert_lsh_snapshot_load_fails ( |dir| {
692+ mutate_lsh_manifest ( dir, |value| value[ "version" ] = 999u32 . into ( ) ) ;
693+ } ) ;
694+ }
695+
696+ #[ test]
697+ fn load_rejects_zero_snapshot_dimension ( ) {
698+ assert_lsh_snapshot_load_fails ( |dir| {
699+ mutate_lsh_manifest ( dir, |value| value[ "dimension" ] = 0u32 . into ( ) ) ;
700+ } ) ;
701+ }
702+
703+ #[ test]
704+ fn load_rejects_truncated_vector_payload ( ) {
705+ assert_lsh_snapshot_load_fails ( |dir| {
706+ let path = dir. join ( "vectors.bin" ) ;
707+ let mut bytes = std:: fs:: read ( & path) . unwrap ( ) ;
708+ bytes. pop ( ) . unwrap ( ) ;
709+ std:: fs:: write ( path, bytes) . unwrap ( ) ;
710+ } ) ;
711+ }
712+
644713 #[ test]
645714 fn test_hash_determinism ( ) {
646715 let dim = 8 ;
0 commit comments