@@ -39,6 +39,35 @@ impl SnapshotCryptoProvider for TestCryptoProvider {
3939 }
4040}
4141
42+ #[ derive( Debug ) ]
43+ struct FailingCryptoProvider {
44+ fail_label : & ' static [ u8 ] ,
45+ }
46+
47+ impl SnapshotCryptoProvider for FailingCryptoProvider {
48+ fn label ( & self ) -> & ' static str {
49+ "failing-test-provider"
50+ }
51+
52+ fn compliance_capable ( & self ) -> bool {
53+ false
54+ }
55+
56+ fn sha256 ( & self , chunks : & [ & [ u8 ] ] ) -> Result < [ u8 ; 32 ] , String > {
57+ TestCryptoProvider . sha256 ( chunks)
58+ }
59+
60+ fn hmac_sha256 ( & self , key : & [ u8 ] , chunks : & [ & [ u8 ] ] ) -> Result < [ u8 ; 32 ] , String > {
61+ if chunks
62+ . first ( )
63+ . is_some_and ( |label| * label == self . fail_label )
64+ {
65+ return Err ( "injected cryptographic provider failure" . to_owned ( ) ) ;
66+ }
67+ TestCryptoProvider . hmac_sha256 ( key, chunks)
68+ }
69+ }
70+
4271#[ cfg( unix) ]
4372#[ test]
4473fn doctor_rechecks_integrity_key_permissions ( ) {
@@ -103,17 +132,118 @@ fn authenticated_prune_boundary_rejects_tampering() {
103132 assert ! ( !store. doctor( ) . unwrap( ) . healthy) ;
104133}
105134
135+ #[ test]
136+ fn snapshot_generation_provider_failure_is_returned_without_abort ( ) {
137+ let dir = TestDir :: new ( "snapshot-provider-generation-failure" ) ;
138+ let store = store_with_provider (
139+ & dir,
140+ Arc :: new ( FailingCryptoProvider {
141+ fail_label : b"fluxheim-snapshot-generation-v1\0 " ,
142+ } ) ,
143+ ) ;
144+
145+ assert ! ( matches!(
146+ store. snapshot_config( & Config :: default ( ) , None ) ,
147+ Err ( SnapshotError :: CryptoProvider ( _) )
148+ ) ) ;
149+ assert ! ( store. list( ) . unwrap( ) . is_empty( ) ) ;
150+ }
151+
152+ #[ test]
153+ fn snapshot_manifest_provider_failure_is_returned_without_abort ( ) {
154+ let dir = TestDir :: new ( "snapshot-provider-manifest-failure" ) ;
155+ let store = store_with_provider (
156+ & dir,
157+ Arc :: new ( FailingCryptoProvider {
158+ fail_label : b"fluxheim-snapshot-v1\0 " ,
159+ } ) ,
160+ ) ;
161+
162+ assert ! ( matches!(
163+ store. snapshot_config( & Config :: default ( ) , None ) ,
164+ Err ( SnapshotError :: CryptoProvider ( _) )
165+ ) ) ;
166+ assert ! ( store. list( ) . unwrap( ) . is_empty( ) ) ;
167+ assert ! ( store. current_id( ) . unwrap( ) . is_none( ) ) ;
168+ }
169+
170+ #[ test]
171+ fn recovery_provider_failure_is_returned_without_abort ( ) {
172+ let dir = TestDir :: new ( "snapshot-provider-recovery-failure" ) ;
173+ let store = store_with_provider (
174+ & dir,
175+ Arc :: new ( FailingCryptoProvider {
176+ fail_label : b"fluxheim-snapshot-recovery-v1\0 " ,
177+ } ) ,
178+ ) ;
179+ store. snapshot_config ( & Config :: default ( ) , None ) . unwrap ( ) ;
180+
181+ assert ! ( matches!(
182+ store. save_runtime_state( & crate :: SnapshotRuntimeState :: default ( ) ) ,
183+ Err ( SnapshotError :: CryptoProvider ( _) )
184+ ) ) ;
185+ assert ! ( !store. root( ) . join( "self-healing.toml" ) . exists( ) ) ;
186+ }
187+
188+ #[ test]
189+ fn prune_provider_failure_preserves_snapshots_without_abort ( ) {
190+ let dir = TestDir :: new ( "snapshot-provider-prune-failure" ) ;
191+ let store = store_with_provider (
192+ & dir,
193+ Arc :: new ( FailingCryptoProvider {
194+ fail_label : b"fluxheim-snapshot-prune-boundary-v1\0 " ,
195+ } ) ,
196+ ) ;
197+ store. snapshot_config ( & Config :: default ( ) , None ) . unwrap ( ) ;
198+ store. snapshot_config ( & Config :: default ( ) , None ) . unwrap ( ) ;
199+ store. snapshot_config ( & Config :: default ( ) , None ) . unwrap ( ) ;
200+
201+ assert ! ( matches!(
202+ store. prune( & SnapshotPruneOptions {
203+ keep: Some ( 0 ) ,
204+ older_than: None ,
205+ protected_ids: Vec :: new( ) ,
206+ } ) ,
207+ Err ( SnapshotError :: CryptoProvider ( _) )
208+ ) ) ;
209+ assert_eq ! ( store. list( ) . unwrap( ) . len( ) , 3 ) ;
210+ }
211+
212+ #[ test]
213+ fn generation_state_replay_and_removal_fail_before_publication ( ) {
214+ let dir = TestDir :: new ( "snapshot-generation-replay" ) ;
215+ let ( store, _key) = authenticated_store ( & dir) ;
216+ store. snapshot_config ( & Config :: default ( ) , None ) . unwrap ( ) ;
217+ let generation_path = safe_child_path ( store. root ( ) , "generation.toml" ) ;
218+ let generation_one = std:: fs:: read ( & generation_path) . unwrap ( ) ;
219+ store. snapshot_config ( & Config :: default ( ) , None ) . unwrap ( ) ;
220+
221+ std:: fs:: write ( & generation_path, & generation_one) . unwrap ( ) ;
222+ assert ! ( matches!(
223+ store. snapshot_config( & Config :: default ( ) , None ) ,
224+ Err ( SnapshotError :: GenerationStateInvalid )
225+ ) ) ;
226+ assert_eq ! ( store. list( ) . unwrap( ) . len( ) , 2 ) ;
227+ assert ! ( !store. doctor( ) . unwrap( ) . healthy) ;
228+
229+ std:: fs:: remove_file ( & generation_path) . unwrap ( ) ;
230+ assert ! ( matches!(
231+ store. snapshot_config( & Config :: default ( ) , None ) ,
232+ Err ( SnapshotError :: GenerationStateInvalid )
233+ ) ) ;
234+ assert_eq ! ( store. list( ) . unwrap( ) . len( ) , 2 ) ;
235+ }
236+
106237fn authenticated_store ( dir : & TestDir ) -> ( SnapshotStore , std:: path:: PathBuf ) {
238+ let store = store_with_provider ( dir, Arc :: new ( TestCryptoProvider ) ) ;
239+ ( store, dir. child ( "snapshot.key" ) )
240+ }
241+
242+ fn store_with_provider ( dir : & TestDir , provider : Arc < dyn SnapshotCryptoProvider > ) -> SnapshotStore {
107243 let key = dir. child ( "snapshot.key" ) ;
108244 std:: fs:: write ( & key, [ 11u8 ; 32 ] ) . unwrap ( ) ;
109245 set_private_file ( & key) ;
110- let store = SnapshotStore :: with_integrity_key_file (
111- dir. child ( "store" ) ,
112- & key,
113- Arc :: new ( TestCryptoProvider ) ,
114- )
115- . unwrap ( ) ;
116- ( store, key)
246+ SnapshotStore :: with_integrity_key_file ( dir. child ( "store" ) , & key, provider) . unwrap ( )
117247}
118248
119249fn set_private_file ( path : & std:: path:: Path ) {
0 commit comments