44//! - putting `random_entity` and `get_search_key` into a trait
55//! - writing a macro which generates the inner tests
66
7+ use rand:: { Rng , RngCore , distributions:: uniform:: SampleRange } ;
78pub use rstest:: * ;
89pub use rstest_reuse:: { self , * } ;
910use wasm_bindgen_test:: * ;
@@ -12,24 +13,23 @@ mod common;
1213
1314wasm_bindgen_test_configure ! ( run_in_browser) ;
1415
16+ fn random_bytes ( len : impl SampleRange < usize > ) -> Vec < u8 > {
17+ let mut rng = rand:: thread_rng ( ) ;
18+ let len = rng. gen_range ( len) ;
19+ let mut value = vec ! [ 0 ; len] ;
20+ rng. fill_bytes ( & mut value) ;
21+ value
22+ }
23+
1524#[ cfg( test) ]
1625mod persisted_mls_groups {
1726 use core_crypto_keystore:: {
1827 entities:: { ParentGroupId , PersistedMlsGroup } ,
1928 traits:: FetchFromDatabase as _,
2029 } ;
21- use rand:: { Rng , RngCore , distributions:: uniform:: SampleRange } ;
2230 use rstest_reuse:: apply;
2331
24- use crate :: common:: * ;
25-
26- fn random_bytes ( len : impl SampleRange < usize > ) -> Vec < u8 > {
27- let mut rng = rand:: thread_rng ( ) ;
28- let len = rng. gen_range ( len) ;
29- let mut value = vec ! [ 0 ; len] ;
30- rng. fill_bytes ( & mut value) ;
31- value
32- }
32+ use crate :: { common:: * , random_bytes} ;
3333
3434 fn random_entity ( ) -> PersistedMlsGroup {
3535 PersistedMlsGroup {
@@ -163,3 +163,166 @@ mod persisted_mls_groups {
163163 assert ! ( found. is_empty( ) ) ;
164164 }
165165}
166+
167+ #[ cfg( test) ]
168+ mod stored_credential {
169+ use core_crypto_keystore:: {
170+ entities:: { CredentialFindFilters , StoredCredential } ,
171+ traits:: { FetchFromDatabase as _, PrimaryKey as _} ,
172+ } ;
173+ use rand:: Rng ;
174+ use rstest_reuse:: apply;
175+
176+ use crate :: { common:: * , random_bytes} ;
177+
178+ fn random_entity ( ) -> StoredCredential {
179+ let mut rng = rand:: thread_rng ( ) ;
180+
181+ let session_id = random_bytes ( 32 ..=32 ) ;
182+ let credential = random_bytes ( 128 ..=256 ) ;
183+ let created_at = 0 ; // updated on save
184+ let ciphersuite = rng. gen_range ( 1_u16 ..=7 ) ;
185+ let public_key = random_bytes ( 512 ..=1024 ) ;
186+ let private_key = random_bytes ( 128 ..=256 ) ;
187+
188+ StoredCredential {
189+ session_id,
190+ credential,
191+ created_at,
192+ ciphersuite,
193+ public_key,
194+ private_key,
195+ }
196+ }
197+
198+ fn get_search_key ( entity : & StoredCredential ) -> CredentialFindFilters < ' _ > {
199+ loop {
200+ let ciphersuite = rand:: random :: < bool > ( ) . then_some ( entity. ciphersuite ) ;
201+ let earliest_validity = rand:: random :: < bool > ( ) . then_some ( entity. created_at ) ;
202+
203+ // don't generate hash or public key; those are unique to a credential
204+ // but ciphersuite and earliest validity are fair game
205+ if ciphersuite. is_some ( ) || earliest_validity. is_some ( ) {
206+ return CredentialFindFilters {
207+ hash : None ,
208+ public_key : None ,
209+ ciphersuite,
210+ earliest_validity,
211+ } ;
212+ }
213+ }
214+ }
215+
216+ #[ apply( all_storage_types) ]
217+ async fn search_can_find_an_entity ( context : KeystoreTestContext ) {
218+ let store = context. store ( ) ;
219+ let _ = env_logger:: try_init ( ) ;
220+
221+ let mut entity = random_entity ( ) ;
222+
223+ entity. created_at = store. save ( entity. clone ( ) ) . await . unwrap ( ) ;
224+ let search_key = get_search_key ( & entity) ;
225+
226+ store. commit_transaction ( ) . await . unwrap ( ) ;
227+ store. new_transaction ( ) . await . unwrap ( ) ;
228+
229+ let found = store
230+ . search :: < StoredCredential , CredentialFindFilters > ( & search_key)
231+ . await
232+ . unwrap ( ) ;
233+ assert_eq ! ( found, vec![ entity] ) ;
234+ }
235+
236+ #[ apply( all_storage_types) ]
237+ async fn search_can_find_two_entities ( context : KeystoreTestContext ) {
238+ let store = context. store ( ) ;
239+ let _ = env_logger:: try_init ( ) ;
240+
241+ let mut entities = vec ! [ random_entity( ) , random_entity( ) ] ;
242+ entities. sort_unstable_by ( |e1, e2| e1. public_key . cmp ( & e2. public_key ) ) ;
243+ entities[ 1 ] . ciphersuite = entities[ 0 ] . ciphersuite ;
244+
245+ for entity in & mut entities {
246+ entity. created_at = store. save ( entity. clone ( ) ) . await . unwrap ( ) ;
247+ }
248+ entities[ 1 ] . created_at = entities[ 0 ] . created_at ;
249+
250+ store. commit_transaction ( ) . await . unwrap ( ) ;
251+ store. new_transaction ( ) . await . unwrap ( ) ;
252+
253+ let search_key = get_search_key ( & entities[ 0 ] ) ;
254+ let mut found = store
255+ . search :: < StoredCredential , CredentialFindFilters > ( & search_key)
256+ . await
257+ . unwrap ( ) ;
258+ found. sort_unstable_by ( |e1, e2| e1. public_key . cmp ( & e2. public_key ) ) ;
259+
260+ assert_eq ! ( entities, found) ;
261+ }
262+
263+ // we don't have a good way to just delay for a second in wasm, so skip this test which relies on that behavior
264+ #[ cfg( not( target_family = "wasm" ) ) ]
265+ #[ apply( all_storage_types) ]
266+ async fn search_finds_only_entities_with_matching_search_key ( context : KeystoreTestContext ) {
267+ let store = context. store ( ) ;
268+ let _ = env_logger:: try_init ( ) ;
269+
270+ let mut relevant_entity = random_entity ( ) ;
271+ let mut irrelevant_entity = random_entity ( ) ;
272+ // ensure the irrelevant entity will definitely not accidentally match
273+ irrelevant_entity. ciphersuite = relevant_entity. ciphersuite + 1 ;
274+
275+ for entity in [ & mut relevant_entity, & mut irrelevant_entity] {
276+ entity. created_at = store. save ( entity. clone ( ) ) . await . unwrap ( ) ;
277+ // ensure the entities are created in different seconds so they don't accidentally match
278+ smol:: Timer :: after ( std:: time:: Duration :: from_secs ( 1 ) ) . await ;
279+ }
280+ store. commit_transaction ( ) . await . unwrap ( ) ;
281+ store. new_transaction ( ) . await . unwrap ( ) ;
282+
283+ let search_key = get_search_key ( & relevant_entity) ;
284+ let found = store
285+ . search :: < StoredCredential , CredentialFindFilters > ( & search_key)
286+ . await
287+ . unwrap ( ) ;
288+
289+ assert_eq ! ( found, vec![ relevant_entity] ) ;
290+ }
291+
292+ #[ apply( all_storage_types) ]
293+ async fn search_can_find_an_uncommitted_entity ( context : KeystoreTestContext ) {
294+ let store = context. store ( ) ;
295+ let _ = env_logger:: try_init ( ) ;
296+
297+ let mut entity = random_entity ( ) ;
298+ entity. created_at = store. save ( entity. clone ( ) ) . await . unwrap ( ) ;
299+ let search_key = get_search_key ( & entity) ;
300+
301+ let found = store
302+ . search :: < StoredCredential , CredentialFindFilters > ( & search_key)
303+ . await
304+ . unwrap ( ) ;
305+ assert_eq ! ( found, vec![ entity] ) ;
306+ }
307+
308+ #[ apply( all_storage_types) ]
309+ async fn search_does_not_find_uncommitted_deleted_entity ( context : KeystoreTestContext ) {
310+ let store = context. store ( ) ;
311+ let _ = env_logger:: try_init ( ) ;
312+
313+ let entity = random_entity ( ) ;
314+ let search_key = get_search_key ( & entity) ;
315+
316+ store. save ( entity. clone ( ) ) . await . unwrap ( ) ;
317+ store. commit_transaction ( ) . await . unwrap ( ) ;
318+ store. new_transaction ( ) . await . unwrap ( ) ;
319+
320+ store. remove :: < StoredCredential > ( & entity. primary_key ( ) ) . await . unwrap ( ) ;
321+
322+ let found = store
323+ . search :: < StoredCredential , CredentialFindFilters > ( & search_key)
324+ . await
325+ . unwrap ( ) ;
326+ assert ! ( found. is_empty( ) ) ;
327+ }
328+ }
0 commit comments