use rstest::fixture;
use crate::document::{DocumentId, DocumentViewId};
use crate::entry::encode::{encode_entry, sign_entry};
use crate::entry::traits::AsEncodedEntry;
use crate::entry::EncodedEntry;
use crate::identity::{Author, KeyPair};
use crate::operation::encode::encode_operation;
use crate::operation::traits::Actionable;
use crate::operation::{Operation, OperationAction, OperationBuilder, OperationValue};
use crate::schema::Schema;
use crate::storage_provider::traits::StorageProvider;
use crate::storage_provider::utils::Result;
use crate::test_utils::constants;
use crate::test_utils::db::{EntryArgsResponse, MemoryStore};
use crate::test_utils::fixtures::schema;
use super::domain::{next_args, publish};
#[derive(Debug)]
pub struct PopulateDatabaseConfig {
pub no_of_entries: usize,
pub no_of_logs: usize,
pub no_of_authors: usize,
pub with_delete: bool,
pub schema: Schema,
pub create_operation_fields: Vec<(&'static str, OperationValue)>,
pub update_operation_fields: Vec<(&'static str, OperationValue)>,
}
impl Default for PopulateDatabaseConfig {
fn default() -> Self {
Self {
no_of_entries: 0,
no_of_logs: 0,
no_of_authors: 0,
with_delete: false,
schema: constants::schema(),
create_operation_fields: constants::test_fields(),
update_operation_fields: constants::test_fields(),
}
}
}
#[fixture]
pub fn test_db_config(
#[default(0)] no_of_entries: usize,
#[default(0)] no_of_logs: usize,
#[default(0)] no_of_authors: usize,
#[default(false)] with_delete: bool,
#[from(schema)] schema: Schema,
#[default(constants::test_fields())] create_operation_fields: Vec<(
&'static str,
OperationValue,
)>,
#[default(constants::test_fields())] update_operation_fields: Vec<(
&'static str,
OperationValue,
)>,
) -> PopulateDatabaseConfig {
PopulateDatabaseConfig {
no_of_entries,
no_of_logs,
no_of_authors,
with_delete,
schema,
create_operation_fields,
update_operation_fields,
}
}
#[derive(Default, Debug)]
pub struct TestDatabase {
pub store: MemoryStore,
pub test_data: TestData,
}
impl TestDatabase {
pub fn new(store: &MemoryStore, test_data: TestData) -> Self {
Self {
store: store.clone(),
test_data,
}
}
}
#[derive(Default, Debug)]
pub struct TestData {
pub key_pairs: Vec<KeyPair>,
pub documents: Vec<DocumentId>,
}
#[fixture]
pub async fn test_db(
#[default(0)] no_of_entries: usize,
#[default(0)] no_of_logs: usize,
#[default(0)] no_of_authors: usize,
#[default(false)] with_delete: bool,
#[from(schema)] schema: Schema,
#[default(constants::test_fields())] create_operation_fields: Vec<(
&'static str,
OperationValue,
)>,
#[default(constants::test_fields())] update_operation_fields: Vec<(
&'static str,
OperationValue,
)>,
) -> TestDatabase {
let config = PopulateDatabaseConfig {
no_of_entries,
no_of_logs,
no_of_authors,
with_delete,
schema,
create_operation_fields,
update_operation_fields,
};
let store = MemoryStore::default();
let (key_pairs, documents) = populate_store(&store, &config).await;
TestDatabase::new(
&store,
TestData {
key_pairs,
documents,
},
)
}
pub fn test_key_pairs(no_of_authors: usize) -> Vec<KeyPair> {
let mut key_pairs = Vec::new();
match no_of_authors {
0 => (),
1 => key_pairs.push(KeyPair::from_private_key_str(constants::PRIVATE_KEY).unwrap()),
_ => {
key_pairs.push(KeyPair::from_private_key_str(constants::PRIVATE_KEY).unwrap());
for _index in 1..no_of_authors {
key_pairs.push(KeyPair::new())
}
}
};
key_pairs
}
pub async fn populate_store<S: StorageProvider>(
store: &S,
config: &PopulateDatabaseConfig,
) -> (Vec<KeyPair>, Vec<DocumentId>) {
let key_pairs = test_key_pairs(config.no_of_authors);
let mut documents: Vec<DocumentId> = Vec::new();
for key_pair in &key_pairs {
for _log_id in 0..config.no_of_logs {
let mut previous_operation: Option<DocumentViewId> = None;
for index in 0..config.no_of_entries {
let operation = match index {
0 => OperationBuilder::new(config.schema.id())
.fields(&config.create_operation_fields)
.build()
.expect("Error building operation"),
seq if seq == (config.no_of_entries - 1) && config.with_delete => {
OperationBuilder::new(config.schema.id())
.action(OperationAction::Delete)
.previous_operations(
&previous_operation.expect("Previous operations should be set"),
)
.build()
.expect("Error building operation")
}
_ => OperationBuilder::new(config.schema.id())
.action(OperationAction::Update)
.fields(&config.update_operation_fields)
.previous_operations(
&previous_operation.expect("Previous operations should be set"),
)
.build()
.expect("Error building operation"),
};
let (entry_encoded, publish_entry_response) =
send_to_store(store, &operation, &config.schema, key_pair)
.await
.expect("Send to store");
previous_operation = publish_entry_response.backlink.map(DocumentViewId::from);
if index == 0 {
documents.push(entry_encoded.hash().into());
}
}
}
}
(key_pairs, documents)
}
pub async fn send_to_store<S: StorageProvider>(
store: &S,
operation: &Operation,
schema: &Schema,
key_pair: &KeyPair,
) -> Result<(EncodedEntry, EntryArgsResponse)> {
let author = Author::from(key_pair.public_key());
let next_args = next_args(store, &author, operation.previous_operations()).await?;
let encoded_operation = encode_operation(operation)?;
let entry = sign_entry(
&next_args.log_id,
&next_args.seq_num,
next_args.skiplink.as_ref(),
next_args.backlink.as_ref(),
&encoded_operation,
key_pair,
)?;
let encoded_entry = encode_entry(&entry)?;
let publish_entry_response = publish(
store,
schema,
&encoded_entry,
&operation.into(),
&encoded_operation,
)
.await?;
Ok((encoded_entry, publish_entry_response))
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use crate::entry::traits::{AsEncodedEntry, AsEntry};
use crate::entry::{LogId, SeqNum};
use crate::test_utils::constants::SKIPLINK_SEQ_NUMS;
use super::{test_db, TestDatabase};
#[rstest]
#[tokio::test]
async fn correct_next_args(
#[from(test_db)]
#[with(17, 1, 1)]
#[future]
db: TestDatabase,
) {
let db = db.await;
let entries = db.store.entries.lock().unwrap().clone();
for seq_num in 1..17 {
let entry = entries
.values()
.find(|entry| entry.seq_num().as_u64() as usize == seq_num)
.unwrap();
let expected_seq_num = SeqNum::new(seq_num as u64).unwrap();
assert_eq!(expected_seq_num, *entry.seq_num());
let expected_log_id = LogId::default();
assert_eq!(expected_log_id, *entry.log_id());
let mut expected_backlink_hash = None;
if seq_num != 1 {
expected_backlink_hash = Some(
entries
.values()
.find(|entry| entry.seq_num().as_u64() as usize == seq_num - 1)
.unwrap()
.hash(),
);
}
assert_eq!(expected_backlink_hash.as_ref(), entry.backlink());
let mut expected_skiplink_hash = None;
if SKIPLINK_SEQ_NUMS.contains(&(seq_num as u64)) {
let skiplink_seq_num = entry.seq_num().skiplink_seq_num().unwrap().as_u64();
let skiplink_entry = entries
.values()
.find(|entry| entry.seq_num().as_u64() == skiplink_seq_num)
.unwrap();
expected_skiplink_hash = Some(skiplink_entry.hash());
};
assert_eq!(expected_skiplink_hash.as_ref(), entry.skiplink());
}
}
#[rstest]
#[tokio::test]
async fn correct_test_values(
#[from(test_db)]
#[with(10, 4, 2)]
#[future]
db: TestDatabase,
) {
let db = db.await;
assert_eq!(db.test_data.key_pairs.len(), 2);
assert_eq!(db.test_data.documents.len(), 8);
assert_eq!(db.store.entries.lock().unwrap().len(), 80);
assert_eq!(db.store.operations.lock().unwrap().len(), 80);
assert_eq!(db.store.documents.lock().unwrap().len(), 0);
assert_eq!(db.store.document_views.lock().unwrap().len(), 0);
}
}