Skip to content

Commit f1af9c6

Browse files
committed
test(keystore): add tests for searchable entitiy for credential
Also fix some tiny logic errors in the query builder.
1 parent 1e95563 commit f1af9c6

File tree

2 files changed

+175
-12
lines changed

2 files changed

+175
-12
lines changed

keystore/src/entities/platform/generic/mls/credential.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,14 +211,14 @@ impl<'a> SearchableEntity<CredentialFindFilters<'a>> for StoredCredential {
211211
public_key,
212212
private_key
213213
FROM mls_credentials
214-
WHERE true "
214+
WHERE (true OR ?1 OR ?2) "
215215
.to_owned();
216216

217217
if ciphersuite.is_some() {
218218
query.push_str("AND ciphersuite = ?1 ");
219219
}
220220
if earliest_validity.is_some() {
221-
query.push_str("AND earliest_validity = ?2 ");
221+
query.push_str("AND unixepoch(created_at) = ?2 ");
222222
}
223223

224224
let conn = conn.conn().await;

keystore/tests/searchable_entities.rs

Lines changed: 173 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
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};
78
pub use rstest::*;
89
pub use rstest_reuse::{self, *};
910
use wasm_bindgen_test::*;
@@ -12,24 +13,23 @@ mod common;
1213

1314
wasm_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)]
1625
mod 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

Comments
 (0)