mod common;
use std::sync::Arc;
use tempfile::tempdir;
use vstorage::ItemKind;
use vstorage::base::{CreateItemOptions, Storage};
use vstorage::sync::OneWaySync;
use vstorage::sync::declare::OnEmpty;
use vstorage::sync::plan::Plan;
use vstorage::sync::{
declare::{StoragePair, SyncedCollection},
execute::Executor,
status::StatusDatabase,
};
use vstorage::vdir::VdirStorage;
use common::minimal_icalendar_with_uid;
#[tokio::test]
async fn test_onewaysync_deletes_item_only_on_b() -> anyhow::Result<()> {
let dir_a = tempdir()?;
let dir_b = tempdir()?;
let storage_a = Arc::new(
VdirStorage::builder(dir_a.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let storage_b = Arc::new(
VdirStorage::builder(dir_b.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let collection_name = "test-calendar";
let collection_a = storage_a.create_collection(collection_name).await?;
let collection_b = storage_b.create_collection(collection_name).await?;
let item_data = minimal_icalendar_with_uid("test-item-uid", "Event on B only")?.into();
storage_b
.create_item(
collection_b.href(),
&item_data,
CreateItemOptions::default(),
)
.await?;
let items_b_before = storage_b.get_all_items(collection_b.href()).await?;
assert_eq!(items_b_before.len(), 1, "B should have 1 item before sync");
let items_a_before = storage_a.get_all_items(collection_a.href()).await?;
assert_eq!(items_a_before.len(), 0, "A should be empty before sync");
let status = Arc::new(StatusDatabase::open_or_create(":memory:")?);
let pair = StoragePair::new(storage_a.clone(), storage_b.clone())
.with_mode(Arc::new(OneWaySync))
.with_mapping(SyncedCollection::direct(collection_name.parse().unwrap()));
let plan = Plan::new(pair, Some(status.clone())).await?;
let on_error = |err| panic!("Sync error: {:?}", err);
Executor::new(on_error)
.execute_stream(storage_a.clone(), storage_b.clone(), plan, &status)
.await?
.unwrap();
let items_b_after = storage_b.get_all_items(collection_b.href()).await?;
assert_eq!(items_b_after.len(), 0, "B should be empty after sync");
let items_a_after = storage_a.get_all_items(collection_a.href()).await?;
assert_eq!(items_a_after.len(), 0, "A should still be empty after sync");
Ok(())
}
#[tokio::test]
async fn test_onewaysync_uploads_item_from_a_to_b() -> anyhow::Result<()> {
let dir_a = tempdir()?;
let dir_b = tempdir()?;
let storage_a = Arc::new(
VdirStorage::builder(dir_a.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let storage_b = Arc::new(
VdirStorage::builder(dir_b.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let collection_name = "test-calendar";
let collection_a = storage_a.create_collection(collection_name).await?;
let collection_b = storage_b.create_collection(collection_name).await?;
let item_uid = "test-item-uid";
let item_data = minimal_icalendar_with_uid(item_uid, "Event on A only")?.into();
storage_a
.create_item(
collection_a.href(),
&item_data,
CreateItemOptions::default(),
)
.await?;
let items_a_before = storage_a.get_all_items(collection_a.href()).await?;
assert_eq!(items_a_before.len(), 1, "A should have 1 item before sync");
let items_b_before = storage_b.get_all_items(collection_b.href()).await?;
assert_eq!(items_b_before.len(), 0, "B should be empty before sync");
let status = Arc::new(StatusDatabase::open_or_create(":memory:")?);
let pair = StoragePair::new(storage_a.clone(), storage_b.clone())
.with_mode(Arc::new(OneWaySync))
.with_mapping(SyncedCollection::direct(collection_name.parse().unwrap()));
let plan = Plan::new(pair, Some(status.clone())).await?;
let on_error = |err| panic!("Sync error: {:?}", err);
Executor::new(on_error)
.execute_stream(storage_a.clone(), storage_b.clone(), plan, &status)
.await?
.unwrap();
let items_b_after = storage_b.get_all_items(collection_b.href()).await?;
assert_eq!(items_b_after.len(), 1, "B should have 1 item after sync");
let item_b = &items_b_after[0];
assert_eq!(
item_b.item.ident(),
item_uid,
"Item should have matching UID"
);
assert!(
item_b.item.as_str().contains("Event on A only"),
"Item should have correct content"
);
let items_a_after = storage_a.get_all_items(collection_a.href()).await?;
assert_eq!(items_a_after.len(), 1, "A should have 1 item after sync");
Ok(())
}
#[tokio::test]
async fn test_onewaysync_updates_b_when_a_changes() -> anyhow::Result<()> {
let dir_a = tempdir()?;
let dir_b = tempdir()?;
let storage_a = Arc::new(
VdirStorage::builder(dir_a.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let storage_b = Arc::new(
VdirStorage::builder(dir_b.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let collection_name = "test-calendar";
let collection_a = storage_a.create_collection(collection_name).await?;
let collection_b = storage_b.create_collection(collection_name).await?;
let item_uid = "test-item-uid";
let item_a = minimal_icalendar_with_uid(item_uid, "Original Event")?.into();
let item_b = minimal_icalendar_with_uid(item_uid, "Original Event")?.into();
storage_a
.create_item(collection_a.href(), &item_a, CreateItemOptions::default())
.await?;
storage_b
.create_item(collection_b.href(), &item_b, CreateItemOptions::default())
.await?;
let status = Arc::new(StatusDatabase::open_or_create(":memory:")?);
let pair = StoragePair::new(storage_a.clone(), storage_b.clone())
.with_mode(Arc::new(OneWaySync))
.with_mapping(SyncedCollection::direct(collection_name.parse().unwrap()));
let plan = Plan::new(pair.clone(), Some(status.clone())).await?;
let on_error = |err| panic!("Sync error: {:?}", err);
Executor::new(on_error)
.execute_stream(storage_a.clone(), storage_b.clone(), plan, &status)
.await?
.unwrap();
let items_a = storage_a.get_all_items(collection_a.href()).await?;
let item_a_version = &items_a[0];
let updated_item_a = minimal_icalendar_with_uid(item_uid, "Modified Event on A")?.into();
storage_a
.update_item(&item_a_version.href, &item_a_version.etag, &updated_item_a)
.await?;
let plan = Plan::new(pair, Some(status.clone())).await?;
Executor::new(on_error)
.execute_stream(storage_a.clone(), storage_b.clone(), plan, &status)
.await?
.unwrap();
let items_b_after = storage_b.get_all_items(collection_b.href()).await?;
assert_eq!(items_b_after.len(), 1, "B should have 1 item");
let item_b_after = &items_b_after[0];
assert!(
item_b_after.item.as_str().contains("Modified Event on A"),
"B should have updated content from A"
);
Ok(())
}
#[tokio::test]
async fn test_onewaysync_reverts_changes_on_b() -> anyhow::Result<()> {
let dir_a = tempdir()?;
let dir_b = tempdir()?;
let storage_a = Arc::new(
VdirStorage::builder(dir_a.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let storage_b = Arc::new(
VdirStorage::builder(dir_b.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let collection_name = "test-calendar";
let collection_a = storage_a.create_collection(collection_name).await?;
let collection_b = storage_b.create_collection(collection_name).await?;
let item_uid = "test-item-uid";
let item_a = minimal_icalendar_with_uid(item_uid, "Original Event")?.into();
let item_b = minimal_icalendar_with_uid(item_uid, "Original Event")?.into();
storage_a
.create_item(collection_a.href(), &item_a, CreateItemOptions::default())
.await?;
storage_b
.create_item(collection_b.href(), &item_b, CreateItemOptions::default())
.await?;
let status = Arc::new(StatusDatabase::open_or_create(":memory:")?);
let pair = StoragePair::new(storage_a.clone(), storage_b.clone())
.with_mode(Arc::new(OneWaySync))
.with_mapping(SyncedCollection::direct(collection_name.parse().unwrap()));
let plan = Plan::new(pair.clone(), Some(status.clone())).await?;
let on_error = |err| panic!("Sync error: {:?}", err);
Executor::new(on_error)
.execute_stream(storage_a.clone(), storage_b.clone(), plan, &status)
.await?
.unwrap();
let items_b = storage_b.get_all_items(collection_b.href()).await?;
let item_b_version = &items_b[0];
let updated_item_b = minimal_icalendar_with_uid(item_uid, "Modified Event on B")?.into();
storage_b
.update_item(&item_b_version.href, &item_b_version.etag, &updated_item_b)
.await?;
let plan = Plan::new(pair, Some(status.clone())).await?;
Executor::new(on_error)
.execute_stream(storage_a.clone(), storage_b.clone(), plan, &status)
.await?
.unwrap();
let items_a_after = storage_a.get_all_items(collection_a.href()).await?;
assert_eq!(items_a_after.len(), 1, "A should have 1 item");
let item_a_after = &items_a_after[0];
assert!(
item_a_after.item.as_str().contains("Original Event"),
"A should have original content (unchanged)"
);
let items_b_after = storage_b.get_all_items(collection_b.href()).await?;
assert_eq!(items_b_after.len(), 1, "B should have 1 item");
let item_b_after = &items_b_after[0];
assert!(
item_b_after.item.as_str().contains("Original Event"),
"B should have been overwritten with A's original content"
);
Ok(())
}
#[tokio::test]
async fn test_onewaysync_deletes_from_b_when_deleted_from_a() -> anyhow::Result<()> {
let dir_a = tempdir()?;
let dir_b = tempdir()?;
let storage_a = Arc::new(
VdirStorage::builder(dir_a.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let storage_b = Arc::new(
VdirStorage::builder(dir_b.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let collection_name = "test-calendar";
let collection_a = storage_a.create_collection(collection_name).await?;
let collection_b = storage_b.create_collection(collection_name).await?;
let item_uid = "test-item-uid";
let item_a = minimal_icalendar_with_uid(item_uid, "Test Event")?.into();
let item_b = minimal_icalendar_with_uid(item_uid, "Test Event")?.into();
let item_a_version = storage_a
.create_item(collection_a.href(), &item_a, CreateItemOptions::default())
.await?;
storage_b
.create_item(collection_b.href(), &item_b, CreateItemOptions::default())
.await?;
let status = Arc::new(StatusDatabase::open_or_create(":memory:")?);
let pair = StoragePair::new(storage_a.clone(), storage_b.clone())
.with_mode(Arc::new(OneWaySync))
.on_empty(OnEmpty::Sync) .with_mapping(SyncedCollection::direct(collection_name.parse().unwrap()));
let plan = Plan::new(pair.clone(), Some(status.clone())).await?;
let on_error = |err| panic!("Sync error: {:?}", err);
Executor::new(on_error)
.execute_stream(storage_a.clone(), storage_b.clone(), plan, &status)
.await?
.unwrap();
storage_a
.delete_item(&item_a_version.href, &item_a_version.etag)
.await?;
let items_a_after_delete = storage_a.get_all_items(collection_a.href()).await?;
assert_eq!(items_a_after_delete.len(), 0, "A should now be empty");
let plan = Plan::new(pair, Some(status.clone())).await?;
Executor::new(on_error)
.execute_stream(storage_a.clone(), storage_b.clone(), plan, &status)
.await?
.unwrap();
let items_b_after = storage_b.get_all_items(collection_b.href()).await?;
assert_eq!(items_b_after.len(), 0, "B should be empty after sync");
Ok(())
}
#[tokio::test]
async fn test_onewaysync_recreates_item_on_b_after_deletion() -> anyhow::Result<()> {
let dir_a = tempdir()?;
let dir_b = tempdir()?;
let storage_a = Arc::new(
VdirStorage::builder(dir_a.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let storage_b = Arc::new(
VdirStorage::builder(dir_b.path().to_path_buf().try_into().unwrap())
.unwrap()
.build(ItemKind::Calendar),
);
let collection_name = "test-calendar";
let collection_a = storage_a.create_collection(collection_name).await?;
let collection_b = storage_b.create_collection(collection_name).await?;
let item_uid = "test-item-uid";
let item_a = minimal_icalendar_with_uid(item_uid, "Original Event")?.into();
let item_b = minimal_icalendar_with_uid(item_uid, "Original Event")?.into();
storage_a
.create_item(collection_a.href(), &item_a, CreateItemOptions::default())
.await?;
let item_b_version = storage_b
.create_item(collection_b.href(), &item_b, CreateItemOptions::default())
.await?;
let status = Arc::new(StatusDatabase::open_or_create(":memory:")?);
let pair = StoragePair::new(storage_a.clone(), storage_b.clone())
.with_mode(Arc::new(OneWaySync))
.with_mapping(SyncedCollection::direct(collection_name.parse().unwrap()));
let plan = Plan::new(pair, Some(status.clone())).await?;
let on_error = |err| panic!("Sync error: {:?}", err);
Executor::new(on_error)
.execute_stream(storage_a.clone(), storage_b.clone(), plan, &status)
.await?
.unwrap();
storage_b
.delete_item(&item_b_version.href, &item_b_version.etag)
.await?;
let items_b_after_delete = storage_b.get_all_items(collection_b.href()).await?;
assert_eq!(
items_b_after_delete.len(),
0,
"B should be empty after deletion"
);
let items_a_after_delete = storage_a.get_all_items(collection_a.href()).await?;
assert_eq!(items_a_after_delete.len(), 1, "A should still have item");
let pair = StoragePair::new(storage_a.clone(), storage_b.clone())
.with_mode(Arc::new(OneWaySync))
.on_empty(OnEmpty::Sync)
.with_mapping(SyncedCollection::direct(collection_name.parse().unwrap()));
let plan = Plan::new(pair, Some(status.clone())).await?;
Executor::new(on_error)
.execute_stream(storage_a.clone(), storage_b.clone(), plan, &status)
.await?
.unwrap();
let items_a_after = storage_a.get_all_items(collection_a.href()).await?;
assert_eq!(items_a_after.len(), 1, "A should still have 1 item");
let items_b_after = storage_b.get_all_items(collection_b.href()).await?;
assert_eq!(items_b_after.len(), 1, "B should have 1 item recreated");
let item_b_after = &items_b_after[0];
assert_eq!(item_b_after.item.ident(), item_uid);
assert!(item_b_after.item.as_str().contains("Original Event"));
Ok(())
}