use chrono::Utc; use proptest::prelude::*; use rask::{Operation, Server, DB}; use uuid::Uuid; #[derive(Debug)] enum Action { Op(Operation), Sync, } fn action_sequence_strategy() -> impl Strategy> { // Create, Update, Delete, or Sync on client 1, 2, .., followed by a round of syncs "([CUDS][123])*S1S2S3S1S2".prop_map(|seq| { let uuid = Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap(); seq.as_bytes() .chunks(2) .map(|action_on| { let action = match action_on[0] { b'C' => Action::Op(Operation::Create { uuid }), b'U' => Action::Op(Operation::Update { uuid, property: "title".into(), value: Some("foo".into()), timestamp: Utc::now(), }), b'D' => Action::Op(Operation::Delete { uuid }), b'S' => Action::Sync, _ => unreachable!(), }; let acton = action_on[1] - b'1'; (action, acton) }) .collect::>() }) } proptest! { #[test] // check that various sequences of operations on mulitple db's do not get the db's into an // incompatible state. The main concern here is that there might be a sequence of create // and delete operations that results in a task existing in one DB but not existing in // another. So, the generated sequences focus on a single task UUID. fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { let mut server = Server::new(); let mut dbs = [DB::new(), DB::new(), DB::new()]; for (action, db) in action_sequence { println!("{:?} on db {}", action, db); let db = &mut dbs[db as usize]; match action { Action::Op(op) => { if let Err(e) = db.apply(op) { println!(" {:?} (ignored)", e); } }, Action::Sync => db.sync("me", &mut server), } } println!("{:?}", dbs[0].tasks()); println!("{:?}", dbs[1].tasks()); println!("{:?}", dbs[2].tasks()); assert_eq!(dbs[0].tasks(), dbs[1].tasks()); assert_eq!(dbs[1].tasks(), dbs[2].tasks()); } }