t-rust-less-lib 0.1.11

Password manager library
Documentation
use super::{open_block_store, BlockStore, StoreError};
use crate::block_store::model::Operation;
use crate::block_store::{Change, ChangeLog};
use crate::memguard::weak::ZeroingWords;
use rand::rngs::ThreadRng;
use rand::{distributions, thread_rng, Rng};
use spectral::prelude::*;
use std::sync::Arc;
use tempdir::TempDir;

fn common_store_tests(store: Arc<dyn BlockStore>) {
  let mut rng = thread_rng();
  common_test_ring(store.as_ref(), &mut rng);
  common_test_index(store.as_ref(), &mut rng);
  common_test_blocks_commits(store.as_ref(), &mut rng);
}

fn common_test_ring(store: &dyn BlockStore, rng: &mut ThreadRng) {
  let ring1a = rng
    .sample_iter(&distributions::Standard)
    .take(200 * 8)
    .collect::<Vec<u8>>();
  let ring1b = rng.sample_iter(&distributions::Standard).take(200).collect::<Vec<u8>>();
  let ring2a = rng
    .sample_iter(&distributions::Standard)
    .take(300 * 8)
    .collect::<Vec<u8>>();
  let ring2b = rng
    .sample_iter(&distributions::Standard)
    .take(300 * 8)
    .collect::<Vec<u8>>();

  assert_that(&store.list_ring_ids()).is_ok_containing(vec![]);
  assert_that(&store.store_ring("ring1", &ring1a)).is_ok();
  assert_that(&store.get_ring("ring1")).is_ok_containing(ZeroingWords::from(ring1a.as_ref()));
  assert_that(&store.store_ring("ring1", &ring1b)).is_ok();
  assert_that(&store.get_ring("ring1")).is_ok_containing(ZeroingWords::from(ring1b.as_ref()));

  let mut ring_ids1 = store.list_ring_ids().unwrap();
  ring_ids1.sort();
  assert_that(&ring_ids1).is_equal_to(vec!["ring1".to_string()]);

  assert_that(&store.store_ring("ring2", &ring2a)).is_ok();
  assert_that(&store.get_ring("ring2")).is_ok_containing(ZeroingWords::from(ring2a.as_ref()));
  assert_that(&store.store_ring("ring2", &ring2b)).is_ok();
  assert_that(&store.get_ring("ring2")).is_ok_containing(ZeroingWords::from(ring2b.as_ref()));

  let mut ring_ids2 = store.list_ring_ids().unwrap();
  ring_ids2.sort();
  assert_that(&ring_ids2).is_equal_to(vec!["ring1".to_string(), "ring2".to_string()]);
}

fn common_test_index(store: &dyn BlockStore, rng: &mut ThreadRng) {
  let node1 = rng
    .sample_iter(&distributions::Alphanumeric)
    .take(40)
    .collect::<String>();
  let node1_index1 = rng
    .sample_iter(&distributions::Standard)
    .take(200 * 8)
    .collect::<Vec<u8>>();
  let node1_index2 = rng
    .sample_iter(&distributions::Standard)
    .take(200 * 8)
    .collect::<Vec<u8>>();
  let node2 = rng
    .sample_iter(&distributions::Alphanumeric)
    .take(40)
    .collect::<String>();
  let node2_index1 = rng
    .sample_iter(&distributions::Standard)
    .take(200 * 8)
    .collect::<Vec<u8>>();
  let node2_index2 = rng
    .sample_iter(&distributions::Standard)
    .take(200 * 8)
    .collect::<Vec<u8>>();

  assert_that(&store.get_index(&node1)).is_ok_containing(None);
  assert_that(&store.store_index(&node1, &node1_index1)).is_ok();
  assert_that(&store.get_index(&node2)).is_ok_containing(None);
  assert_that(&store.store_index(&node2, &node2_index1)).is_ok();
  assert_that(&store.get_index(&node1)).is_ok_containing(Some(ZeroingWords::from(node1_index1.as_ref())));
  assert_that(&store.get_index(&node2)).is_ok_containing(Some(ZeroingWords::from(node2_index1.as_ref())));
  assert_that(&store.store_index(&node1, &node1_index2)).is_ok();
  assert_that(&store.store_index(&node2, &node2_index2)).is_ok();
  assert_that(&store.get_index(&node1)).is_ok_containing(Some(ZeroingWords::from(node1_index2.as_ref())));
  assert_that(&store.get_index(&node2)).is_ok_containing(Some(ZeroingWords::from(node2_index2.as_ref())));
}

fn common_test_blocks_commits(store: &dyn BlockStore, rng: &mut ThreadRng) {
  assert_that(&store.get_block("00000000000")).is_err_containing(StoreError::InvalidBlock("00000000000".to_string()));

  let block1 = rng
    .sample_iter(&distributions::Standard)
    .take(200 * 8)
    .collect::<Vec<u8>>();
  let block2 = rng
    .sample_iter(&distributions::Standard)
    .take(200 * 8)
    .collect::<Vec<u8>>();
  let block3 = rng
    .sample_iter(&distributions::Standard)
    .take(200 * 8)
    .collect::<Vec<u8>>();

  let block1_id = store.add_block(&block1).unwrap();
  let block2_id = store.add_block(&block2).unwrap();
  let block3_id = store.add_block(&block3).unwrap();

  assert_that(&block1_id).is_not_equal_to(&block2_id);
  assert_that(&block1_id).is_not_equal_to(&block3_id);
  assert_that(&block2_id).is_not_equal_to(&block3_id);

  assert_that(&store.get_block(&block1_id)).is_ok_containing(ZeroingWords::from(block1.as_ref()));
  assert_that(&store.get_block(&block2_id)).is_ok_containing(ZeroingWords::from(block2.as_ref()));
  assert_that(&store.get_block(&block3_id)).is_ok_containing(ZeroingWords::from(block3.as_ref()));

  assert_that(&store.commit(&[
    Change {
      op: Operation::Add,
      block: block1_id.clone(),
    },
    Change {
      op: Operation::Add,
      block: block2_id.clone(),
    },
  ]))
  .is_ok();

  assert_that(&store.change_logs()).is_ok_containing(vec![ChangeLog {
    node: store.node_id().to_string(),
    changes: vec![
      Change {
        op: Operation::Add,
        block: block1_id.clone(),
      },
      Change {
        op: Operation::Add,
        block: block2_id.clone(),
      },
    ],
  }]);

  assert_that(&store.commit(&[Change {
    op: Operation::Add,
    block: block2_id.clone(),
  }]))
  .is_err()
  .matches(|error| match error {
    StoreError::Conflict(_) => true,
    _ => false,
  });

  assert_that(&store.commit(&[Change {
    op: Operation::Add,
    block: block3_id.clone(),
  }]))
  .is_ok();

  assert_that(&store.change_logs()).is_ok_containing(vec![ChangeLog {
    node: store.node_id().to_string(),
    changes: vec![
      Change {
        op: Operation::Add,
        block: block1_id,
      },
      Change {
        op: Operation::Add,
        block: block2_id,
      },
      Change {
        op: Operation::Add,
        block: block3_id,
      },
    ],
  }]);
}

#[test]
fn test_local_dir_store() {
  let tempdir = TempDir::new("t-rust-less-test").unwrap();
  let url = format!("file://{}", tempdir.path().to_string_lossy());

  let store = open_block_store(&url, "node1").unwrap();

  common_store_tests(store);
}

#[test]
fn test_memory_store() {
  let store = open_block_store("memory://", "node1").unwrap();

  common_store_tests(store);
}