// SPDX-FileCopyrightText: © 2020 EteSync Authors
// SPDX-License-Identifier: LGPL-2.1-only
mod common;
use common::{sessionStorageKey, test_url, TestUser, USER, USER2};
use etebase::error::{Error, Result};
use etebase::utils::{from_base64, randombytes_deterministic};
use etebase::{
pretty_fingerprint, Account, Client, Collection, CollectionAccessLevel, FetchOptions, Item,
ItemMetadata, PrefetchOption, User,
};
use std::collections::HashSet;
use std::iter;
const CLIENT_NAME: &str = "etebase-tests";
macro_rules! assert_err {
($x:expr, $err:pat) => {
match ($x) {
Err($err) => (),
Err(err) => None.expect(&err.to_string()),
_ => None.expect("Got OK when expected failure"),
}
};
}
fn user_reset(user: &TestUser) -> Result<()> {
let client = Client::new(CLIENT_NAME, &test_url())?;
let acct_user = etebase::User::new(user.username, user.email);
// sign-up the account if necessary, ignoring errors as we're about to reset it anyway
let _ = Account::signup_key(
client.clone(),
&acct_user,
b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
);
etebase::test_helpers::test_reset(
&client,
&acct_user,
&from_base64(user.salt)?,
&from_base64(user.loginPubkey)?,
&from_base64(user.pubkey)?,
&from_base64(user.encryptedContent)?,
)?;
Ok(())
}
fn init_test(user: &TestUser) -> Result<Account> {
etebase::init()?;
user_reset(user)?;
let client = Client::new(CLIENT_NAME, &test_url())?;
let session_key = from_base64(sessionStorageKey)?;
let mut ret = Account::restore(client, user.storedSession, Some(&session_key))?;
ret.force_server_url(&test_url())?;
ret.fetch_token()?;
Ok(ret)
}
fn verify_collection(col: &Collection, meta: &ItemMetadata, content: &[u8]) -> Result<()> {
col.verify()?;
assert_eq!(&col.meta()?, meta);
assert_eq!(col.content()?, content);
Ok(())
}
fn verify_item(item: &Item, meta: &ItemMetadata, content: &[u8]) -> Result<()> {
item.verify()?;
assert_eq!(&item.meta()?, meta);
assert_eq!(item.content()?, content);
Ok(())
}
#[test]
fn is_etebase_server() -> Result<()> {
let client = Client::new(CLIENT_NAME, &test_url())?;
assert!(client.is_server_valid()?);
let test_url = format!("{}/api/", test_url());
let client = Client::new(CLIENT_NAME, &test_url)?;
assert!(!client.is_server_valid()?);
let client = Client::new(CLIENT_NAME, "http://doesnotexist")?;
assert!(client.is_server_valid().is_err());
// Verify we also fail correctly for login
let client = Client::new(CLIENT_NAME, &test_url)?;
assert_err!(
Account::login(client, USER2.username, USER2.password),
Error::NotFound(_)
);
Ok(())
}
#[test]
fn server_url_parsing() {
let client = Client::new(CLIENT_NAME, "localhost:1234");
assert!(client.is_err());
Client::new(CLIENT_NAME, "HTTPS://localhost:1234").unwrap();
}
#[test]
fn get_dashboard_url() -> Result<()> {
let etebase = init_test(&USER)?;
match etebase.fetch_dashboard_url() {
Ok(url) => assert!(!url.is_empty()),
err => assert_err!(err, Error::Http(_)),
};
etebase.logout()
}
#[test]
fn loading_cache_without_collection_type() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let meta = ItemMetadata::new()
.set_item_type(Some("type"))
.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"))
.clone();
let content = b"SomeContent";
let col = col_mgr.cache_load(&etebase::utils::from_base64("kgHcBTvMlMyVzNkgTl9vMjNITDFsVlQ1VG5Dd1QzVkx0VDN5TUtBbDZ0UDYBzMDMlMy2QktLMk50R2NCY1pDRDBTcllKeHBzQczEUzXMyVxJSQptzPDMyMzNPMzhAVbMrsyjzPfMjU0pzODMq2lmzMAUEBLM_My-zOhGMsyREmHM6Mz3zO7MwVF8F2tnzOXM3TnM9MypzOQ9CczHfMzGCywPzOPMlsyTzOE-zIRAKczQJzVxRMz9NMz3MczbzMHM5zY_MDrMwsyRzJLM2StoaDhPTTFiQmR5bWZUTlZlanhwcWtPOExWLUpIUDFsUGRST2lCWXhnaEFnzMUEKMy4KGNOJ8z3zN1pzNFGzNLM_8yiCczpzLIxzJQSzNjM58yACS0sP8yCzKDM-GLM0MyCzJMPL8z-Hsz-QMzRT1BCRcyjWszRzLPMkxjMzFYmzMJ8IE1_zMMnCczyzIlUzIbMhxDM3XHMskHM9x9uaWhDzM3Myk1pzI7M78yWzOMrOFHMw04WzK7M-iXMm3tdzKAazMVpzOXMzczqR38KWMyPGEvMzkoyWmodzOvMwczSzLXMplkGzPIDzKvMxGEfQ8zjMcyIdAB4JczLcczfd8z5Vsz4ccyRdMzXzKU1zKcRzP_MycynzLhQzOUvJ8z3FMyHzOpMzLDMm8y6HAw1zKQgJl8CzNrM6XQFRsySFH7Mrn81zPPM4W0HzIPMh1PMpMyUzPNdzNsSzNE6ZQPM9cyGC04eDw7MrR9CaAnM73ZNzJYWOUzMr8zkzK_MxczAzKDMysz5zOt8LlN9zK3MynNizKDM0czXzNhBzNEdNhU8zNTMzTFIb8zmVszHzIfMiALMlkzM68zbzINAzNbMl8z8zMh_T8zHc8zJci7MtkDMl8ywcWIoe1zMuczCRFbMpcy3zJhvzK5PCz4EzMvMlszcAMyWzKIEAMzozPrMlh4JK8z3zMDM8XHMnWLMiMybAijMzczzzOtVaSPM-GbMvXrMksyyzK8_TQ7M9xzMw1PM-lvM68yrXHXM1y5AC0LM6iROcMzmzPzMlBHMtwrM50bMtlrMzX11zIvM4DM4zNIpDC1TEsybQkcuecynQMzCzK_M0sz-zOvMrhdNzJpuzKbM6j7Mjn7M6MzSYszrMzjM5szszIEJYWtfdVs7JVIvS0AQQcyBennMjMzgY8z6zORezOnM5CAZY8z3WWAKPcykT8yVzJDM4BjMz8zNzONtJglpzKRNQQHMxRPM7MymzPnMpy5RzNNIIsyfzKXM9hvMlUbMuczfzK8xzLTM08zlMcyczLLMlGzMrsyOAz7MvMzZzJjMwMyHHsygJMzczMnM1cybzLDMyszqDXrMmGZ3zJTM4nhqZhRhC8yTzMTMtHjM41UTIxvMtMyFzOZ-zIZyHszlzJM9WszhEUbM9VXMmxoNzKfMzUoKKUg9PjYOFMzgzO8jPQzM02TM8BcXzP7Mz8zUfkTMnVRJzIFCzK1ZH8yyzIrMvcy-zP5PzKJjFMygzKvM415LYszXQB_MnCfMzkJmbl_MnmDM42HM88zezOQibhHMuczczMfM2ixwzMvMpczDYRtWzLLM9cydIkjMrxDMj8yYzKkKzLtRzMM1zPbMyS5JYGnM08ylDmdUzL0ANMzgYSfM4jTMm8zvPE0dzL3Mw8zmzOxyzP3M93PM78z1NnoZVxfM7loNfMzxzPHM4j7MpB4NPD0rIczKzN4zKXnMww3M3czlADnMqczxeszuzMDMx8y_B8zyzPYTEjpZPD3M5cz6OALMxH3MgMznzPIXWkMZRzHMuHXMxMzrHEDM9szLaWYSWx4LdMz0zIjM5szMzN3M9syAzKUmWMzbf08QaEc9bMypzOA5bMzjzMQQzJNtVcyCzKJqNmLM0sy1NDMDRcyxzLrMyAzM8hPMv8y5zKoizOzMwEByGszRzIs4HcztNszTzP7M-syyzL_Ms8zFzK5EOk0mYwBGC8yTzO_MynPMrsyxzNMeHszvzI4uzKZ9FTvMzQM2zO0yzNNkzIrM58zfzP7My3rMnMz0zLt9zMgIW8z3zL0ucMy0zK5OZsypT8zOzJnMmMzgGA_M6xLMn8zXzOZ8UnogzL7MtcygzKfMtszUzLXMjUcwbAUQWyzMv8yfLczGcUrM68zdSFlVzJhFzP_M0m3MohzMsljMz8yQGMz-HnBgzJbM3syUzPo7zM3MjknMriHM7QF7YicvBkltX8zeLMyBG3vMq8zNzPPMzcymzIvM4czmEMzTEcyrzK7MtMzszJXMj8z3W8ywzI_MtjHM68yTzKNBEGjMt8y-dTxcbMzATnjMyMzqXHRHHMznzKrMqMy4zKgozLxZDGlZzN7Ml8yAzP99zMdzDwdYTsy2zKYczMcgzKlmasyQzPlDzLRJzMMVzOvM2k3MswbMpjnM53XM-sykzN8QY2YQzMPMjELMwAHMxEjMo8zfFw7MzQEBbcybzJ7M2czrzMTM9x3Mmsy1zILM42xqzIo-MQ_M5czXzLrMxcyozNhHzNsNzLFHzI1vzL3MoyTM3AlKFxzM0WZ0GjnMs8yXURjMm2xhQ8yONGfM28zVzKPMtMzlzIHM6VjM2BLMwA")?)?;
assert_eq!(col.collection_type()?, "type");
verify_collection(&col, &meta, content)?;
etebase.logout()
}
#[test]
fn simple_collection_handling() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let mut meta = ItemMetadata::new();
meta.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"));
let content = b"SomeContent";
let mut col = col_mgr.create("some.coltype", &meta, content)?;
assert_eq!(col.collection_type()?, "some.coltype");
verify_collection(&col, &meta, content)?;
let meta2 = meta.set_name(Some("Collection meta2")).clone();
col.set_meta(&meta2)?;
verify_collection(&col, &meta2, content)?;
assert!(!col.is_deleted());
col.delete()?;
assert!(col.is_deleted());
verify_collection(&col, &meta2, content)?;
etebase.logout()
}
#[test]
fn simple_item_handling() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new().set_name(Some("Collection")).clone();
let col_content = b"SomeContent";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
let it_mgr = col_mgr.item_manager(&col)?;
let meta = ItemMetadata::new().set_name(Some("Item 1")).clone();
let content = b"ItemContent";
let mut item = it_mgr.create(&meta, content)?;
verify_item(&item, &meta, content)?;
let meta2 = ItemMetadata::new().set_name(Some("Item 2")).clone();
item.set_meta(&meta2)?;
verify_item(&item, &meta2, content)?;
assert!(!item.is_deleted());
item.delete()?;
assert!(item.is_deleted());
verify_item(&item, &meta2, content)?;
etebase.logout()
}
#[test]
fn simple_collection_sync() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let mut meta = ItemMetadata::new();
meta.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"));
let content = b"SomeContent";
let mut col = col_mgr.create("some.coltype", &meta, content)?;
verify_collection(&col, &meta, content)?;
let collections = col_mgr.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 0);
col_mgr.upload(&col, None)?;
let collections = col_mgr.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 1);
verify_collection(collections.data().first().unwrap(), &meta, content)?;
let mut col_old = col_mgr.fetch(col.uid(), None)?;
{
let fetch_options = FetchOptions::new().stoken(col_old.stoken());
let collections = col_mgr.list("some.coltype", Some(&fetch_options))?;
assert_eq!(collections.data().len(), 0);
}
let meta2 = meta.clone().set_name(Some("Collection meta2")).clone();
col.set_meta(&meta2)?;
col_mgr.upload(&col, None)?;
let collections = col_mgr.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 1);
{
let fetch_options = FetchOptions::new().stoken(col_old.stoken());
let collections = col_mgr.list("some.coltype", Some(&fetch_options))?;
assert_eq!(collections.data().len(), 1);
}
// Fail uploading because of an old stoken/etag
{
let content2 = b"Content2";
col_old.set_content(content2)?;
assert_err!(col_mgr.transaction(&col_old, None), Error::Conflict(_));
let fetch_options = FetchOptions::new().stoken(col_old.stoken());
assert_err!(
col_mgr.upload(&col, Some(&fetch_options)),
Error::Conflict(_)
);
}
let content2 = b"Content2";
col.set_content(content2)?;
let collections = col_mgr.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 1);
verify_collection(&col, &meta2, content2)?;
etebase.logout()
}
#[test]
fn collection_types() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new()
.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"))
.clone();
let col_content = b"SomeContent";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
{
let collections = col_mgr.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 0);
}
col_mgr.upload(&col, None)?;
{
let collections = col_mgr.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 1);
let collections = col_mgr.list("bad.coltype", None)?;
assert_eq!(collections.data().len(), 0);
}
{
let collections = col_mgr.list_multi(
vec!["bad.coltype", "some.coltype", "anotherbad"].into_iter(),
None,
)?;
assert_eq!(collections.data().len(), 1);
let collections =
col_mgr.list_multi(vec!["bad.coltype", "anotherbad"].into_iter(), None)?;
assert_eq!(collections.data().len(), 0);
}
etebase.logout()
}
#[test]
fn simple_item_sync() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new()
.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"))
.clone();
let col_content = b"SomeContent";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
let meta = ItemMetadata::new().set_name(Some("Item 1")).clone();
let content = b"Content 1";
let mut item = it_mgr.create(&meta, content)?;
it_mgr.batch(iter::once(&item), None)?;
{
let items = it_mgr.list(None)?;
assert_eq!(items.data().len(), 1);
verify_item(items.data().first().unwrap(), &meta, content)?;
}
let mut item_old = it_mgr.fetch(item.uid(), None)?;
let meta2 = ItemMetadata::new().set_name(Some("Item 2")).clone();
item.set_meta(&meta2)?;
let col_old = col_mgr.fetch(col.uid(), None)?;
it_mgr.batch(iter::once(&item), None)?;
// Adding the same item twice should work
it_mgr.batch(iter::once(&item), None)?;
{
let items = it_mgr.list(None)?;
assert_eq!(items.data().len(), 1);
verify_item(items.data().first().unwrap(), &meta2, content)?;
}
{
item_old.set_content(b"Bla bla")?;
assert_err!(
it_mgr.transaction(iter::once(&item_old), None),
Error::Conflict(_)
);
}
let content2 = b"Content 2";
item.set_content(content2)?;
{
let fetch_options = FetchOptions::new().stoken(col_old.stoken());
assert_err!(
it_mgr.batch(iter::once(&item), Some(&fetch_options)),
Error::Conflict(_)
);
}
it_mgr.transaction(iter::once(&item), None)?;
{
let items = it_mgr.list(None)?;
assert_eq!(items.data().len(), 1);
verify_item(items.data().first().unwrap(), &meta2, content2)?;
}
etebase.logout()
}
#[test]
fn collection_as_item() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new()
.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"))
.clone();
let col_content = b"SomeContent";
let mut col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
// Verify with_collection works
{
let items = it_mgr.list(None)?;
assert_eq!(items.data().len(), 0);
let fetch_options = FetchOptions::new().with_collection(true);
let items = it_mgr.list(Some(&fetch_options))?;
assert_eq!(items.data().len(), 1);
let meta = col.item()?.meta()?;
let first_item = items.data().first().unwrap();
verify_item(first_item, &meta, col_content)?;
// Also verify the collection metadata is good
assert_eq!(&first_item.meta_generic::<ItemMetadata>()?, &col_meta);
}
let meta = ItemMetadata::new();
let content = b"Item data";
let item = it_mgr.create(&meta, content)?;
it_mgr.batch(iter::once(&item), None)?;
{
let items = it_mgr.list(None)?;
assert_eq!(items.data().len(), 1);
let fetch_options = FetchOptions::new().with_collection(true);
let items = it_mgr.list(Some(&fetch_options))?;
assert_eq!(items.data().len(), 2);
}
let col_item_old = it_mgr.fetch(col.uid(), None)?;
// Manipulate the collection with batch/transaction
let col_content2 = b"Other content";
col.set_content(col_content2)?;
it_mgr.batch([col.item()?].iter(), None)?;
{
let collections = col_mgr.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 1);
verify_collection(collections.data().first().unwrap(), &col_meta, col_content2)?;
}
let mut col = col_mgr.fetch(col.uid(), None)?;
let col_content2 = b"Other content 3";
col.set_content(col_content2)?;
it_mgr.transaction([col.item()?].iter(), None)?;
{
let collections = col_mgr.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 1);
verify_collection(collections.data().first().unwrap(), &col_meta, col_content2)?;
}
{
let updates = it_mgr.fetch_updates(vec![&col_item_old, &item].into_iter(), None)?;
assert_eq!(updates.data().len(), 1);
let meta = col.item()?.meta()?;
let first_item = updates.data().first().unwrap();
verify_item(first_item, &meta, col_content2)?;
// Also verify the collection metadata is good
assert_eq!(&first_item.meta_generic::<ItemMetadata>()?, &col_meta);
}
etebase.logout()
}
#[test]
fn collection_and_item_deletion() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new().set_name(Some("Collection")).clone();
let col_content = b"";
let mut col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
let meta = ItemMetadata::new().set_name(Some("Item 1")).clone();
let content = b"Content 1";
let mut item = it_mgr.create(&meta, content)?;
it_mgr.batch(iter::once(&item), None)?;
let items = it_mgr.list(None)?;
assert_eq!(items.data().len(), 1);
item.delete()?;
it_mgr.batch(iter::once(&item), None)?;
{
let fetch_options = FetchOptions::new().stoken(items.stoken());
let items = it_mgr.list(Some(&fetch_options))?;
assert_eq!(items.data().len(), 1);
let first_item = items.data().first().unwrap();
verify_item(first_item, &meta, content)?;
assert!(first_item.is_deleted());
}
col.delete()?;
col_mgr.upload(&col, None)?;
{
let fetch_options = FetchOptions::new().stoken(col.stoken());
let collections = col_mgr.list("some.coltype", Some(&fetch_options))?;
assert_eq!(collections.data().len(), 1);
let first_col = collections.data().first().unwrap();
verify_collection(first_col, &col_meta, col_content)?;
assert!(first_col.is_deleted());
}
etebase.logout()
}
#[test]
fn empty_content() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new().set_name(Some("Collection")).clone();
let col_content = b"";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
{
let col2 = col_mgr.fetch(col.uid(), None)?;
verify_collection(&col2, &col_meta, col_content)?;
}
let it_mgr = col_mgr.item_manager(&col)?;
let meta = ItemMetadata::new().set_name(Some("Item 1")).clone();
let content = b"";
let item = it_mgr.create(&meta, content)?;
it_mgr.transaction(iter::once(&item), None)?;
{
let items = it_mgr.list(None)?;
let first_item = items.data().first().unwrap();
verify_item(first_item, &meta, content)?;
}
etebase.logout()
}
#[test]
fn list_response_correctness() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new().set_name(Some("Collection")).clone();
let col_content = b"";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
{
let col2 = col_mgr.fetch(col.uid(), None)?;
verify_collection(&col2, &col_meta, col_content)?;
}
let it_mgr = col_mgr.item_manager(&col)?;
let items: Vec<Item> = (0..5)
.map(|i| {
let meta = ItemMetadata::new()
.set_name(Some(&format!("Item {}", i)))
.clone();
let content = b"";
it_mgr.create(&meta, content).unwrap()
})
.collect();
it_mgr.batch(items.iter(), None)?;
{
let items = it_mgr.list(None)?;
assert_eq!(items.data().len(), 5);
assert!(items.done());
let fetch_options = FetchOptions::new().limit(5);
let items = it_mgr.list(Some(&fetch_options))?;
assert!(items.done());
}
let mut stoken = None;
for i in 0..3 {
let fetch_options = FetchOptions::new().limit(2).stoken(stoken.as_deref());
let items = it_mgr.list(Some(&fetch_options))?;
assert_eq!(items.done(), i == 2);
stoken = items.stoken().map(str::to_string);
}
// Also check collections
for i in 0..4 {
let meta = ItemMetadata::new()
.set_name(Some(&format!("Item {}", i)))
.clone();
let content = b"";
let col = col_mgr.create("some.coltype", &meta, content).unwrap();
col_mgr.upload(&col, None)?;
}
{
let collections = col_mgr.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 5);
assert!(collections.done());
let fetch_options = FetchOptions::new().limit(5);
let collections = col_mgr.list("some.coltype", Some(&fetch_options))?;
assert!(collections.done());
assert!(collections.done());
}
let mut stoken = None;
for i in 0..3 {
let fetch_options = FetchOptions::new().limit(2).stoken(stoken.as_deref());
let collections = col_mgr.list("some.coltype", Some(&fetch_options))?;
assert_eq!(collections.done(), i == 2);
stoken = collections.stoken().map(str::to_string);
}
etebase.logout()
}
#[test]
fn item_transactions() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new().set_name(Some("Collection")).clone();
let col_content = b"";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
let meta = ItemMetadata::new().set_name(Some("Item 1")).clone();
let content = b"";
let mut item = it_mgr.create(&meta, content)?;
let deps = vec![&item];
it_mgr.transaction(deps.clone().into_iter(), None)?;
let item_old = it_mgr.fetch(item.uid(), None)?;
let mut item_old2 = it_mgr.fetch(item.uid(), None)?;
let items: Vec<Item> = (0..5)
.map(|i| {
let meta = ItemMetadata::new()
.set_name(Some(&format!("Item {}", i)))
.clone();
let content = b"";
it_mgr.create(&meta, content).unwrap()
})
.collect();
it_mgr.transaction_deps(items.iter(), deps.clone().into_iter(), None)?;
{
let items = it_mgr.list(None)?;
assert_eq!(items.data().len(), 6);
}
let meta2 = ItemMetadata::new().set_name(Some("some")).clone();
item.set_meta(&meta2)?;
let deps = vec![&item];
it_mgr.transaction_deps(iter::once(&item), deps.clone().into_iter(), None)?;
{
let items = it_mgr.list(None)?;
assert_eq!(items.data().len(), 6);
}
{
let meta3 = ItemMetadata::new().set_name(Some("some2")).clone();
item.set_meta(&meta3)?;
let deps2 = items.iter().chain(iter::once(&item_old));
// Old in the deps
assert_err!(
it_mgr.transaction_deps(iter::once(&item), deps2.into_iter(), None),
Error::Conflict(_)
);
it_mgr.transaction(iter::once(&item), None)?;
item_old2.set_meta(&meta3)?;
// Old stoken in the item itself
assert_err!(
it_mgr.transaction(iter::once(&item_old2), None),
Error::Conflict(_)
);
}
{
let meta3 = ItemMetadata::new().set_name(Some("some3")).clone();
let mut item2 = it_mgr.fetch(items[0].uid(), None)?;
item2.set_meta(&meta3)?;
item_old2.set_meta(&meta3)?;
// Part of the transaction is bad, and part is good
assert_err!(
it_mgr.transaction(vec![&item2, &item_old2].into_iter(), None),
Error::Conflict(_)
);
// Verify it hasn't changed after the transaction above failed
let item2_fetch = it_mgr.fetch(item2.uid(), None)?;
assert_ne!(item2_fetch.meta()?, item2.meta()?);
}
{
// Global stoken test
let meta3 = ItemMetadata::new().set_name(Some("some4")).clone();
item.set_meta(&meta3)?;
let new_col = col_mgr.fetch(col.uid(), None)?;
let stoken = new_col.stoken();
let bad_etag = col.etag();
let fetch_options = FetchOptions::new().stoken(Some(bad_etag));
assert_err!(
it_mgr.transaction(iter::once(&item), Some(&fetch_options)),
Error::Conflict(_)
);
let fetch_options = FetchOptions::new().stoken(stoken);
it_mgr.transaction(iter::once(&item), Some(&fetch_options))?;
}
etebase.logout()
}
#[test]
fn item_batch_stoken() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new().set_name(Some("Collection")).clone();
let col_content = b"";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
let meta = ItemMetadata::new().set_name(Some("Item Orig")).clone();
let content = b"";
let mut item = it_mgr.create(&meta, content)?;
it_mgr.batch(iter::once(&item), None)?;
let mut item2 = it_mgr.fetch(item.uid(), None)?;
let items: Vec<Item> = (0..5)
.map(|i| {
let meta = ItemMetadata::new()
.set_name(Some(&format!("Item {}", i)))
.clone();
let content = b"";
it_mgr.create(&meta, content).unwrap()
})
.collect();
it_mgr.batch(items.iter(), None)?;
{
let meta3 = ItemMetadata::new().set_name(Some("some2")).clone();
item2.set_meta(&meta3)?;
it_mgr.batch(iter::once(&item2), None)?;
let meta3 = ItemMetadata::new().set_name(Some("some3")).clone();
item.set_meta(&meta3)?;
// Old stoken in the item itself should work for batch and fail for transaction or batch with deps
assert_err!(
it_mgr.transaction(iter::once(&item), None),
Error::Conflict(_)
);
assert_err!(
it_mgr.batch_deps(iter::once(&item), iter::once(&item), None),
Error::Conflict(_)
);
it_mgr.batch(iter::once(&item), None)?;
}
{
// Global stoken test
let meta3 = ItemMetadata::new().set_name(Some("some4")).clone();
item.set_meta(&meta3)?;
let new_col = col_mgr.fetch(col.uid(), None)?;
let stoken = new_col.stoken();
let bad_etag = col.etag();
let fetch_options = FetchOptions::new().stoken(Some(bad_etag));
assert_err!(
it_mgr.batch(iter::once(&item), Some(&fetch_options)),
Error::Conflict(_)
);
let fetch_options = FetchOptions::new().stoken(stoken);
it_mgr.batch(iter::once(&item), Some(&fetch_options))?;
}
etebase.logout()
}
#[test]
fn item_fetch_updates() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new().set_name(Some("Collection")).clone();
let col_content = b"";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
let meta = ItemMetadata::new().set_name(Some("Item Orig")).clone();
let content = b"";
let item = it_mgr.create(&meta, content)?;
it_mgr.batch(iter::once(&item), None)?;
let items: Vec<Item> = (0..5)
.map(|i| {
let meta = ItemMetadata::new()
.set_name(Some(&format!("Item {}", i)))
.clone();
let content = b"";
it_mgr.create(&meta, content).unwrap()
})
.collect();
it_mgr.batch(items.iter(), None)?;
{
let items = it_mgr.list(None)?;
assert_eq!(items.data().len(), 6);
}
// Fetch multi
{
let items2 = it_mgr.fetch_multi(items.iter().map(|x| x.uid()), None)?;
assert_eq!(items2.data().len(), 5);
let items2 = it_mgr.fetch_multi(
vec!["L4QQdlkCDJ9ySmrGD5fM0DsFo08MnWel", items[0].uid()].into_iter(),
None,
)?;
// Only 1 because only one of the items exists
assert_eq!(items2.data().len(), 1);
}
let new_col = col_mgr.fetch(col.uid(), None)?;
let stoken = new_col.stoken();
{
let updates = it_mgr.fetch_updates(items.iter(), None)?;
assert_eq!(updates.data().len(), 0);
let fetch_options = FetchOptions::new().stoken(stoken);
let updates = it_mgr.fetch_updates(items.iter(), Some(&fetch_options))?;
assert_eq!(updates.data().len(), 0);
}
{
let mut item2 = it_mgr.fetch(items[0].uid(), None)?;
let meta3 = ItemMetadata::new().set_name(Some("some2")).clone();
item2.set_meta(&meta3)?;
it_mgr.batch(iter::once(&item2), None)?;
}
{
let updates = it_mgr.fetch_updates(items.iter(), None)?;
assert_eq!(updates.data().len(), 1);
let fetch_options = FetchOptions::new().stoken(stoken);
let updates = it_mgr.fetch_updates(items.iter(), Some(&fetch_options))?;
assert_eq!(updates.data().len(), 1);
}
{
let item2 = it_mgr.fetch(items[0].uid(), None)?;
let updates = it_mgr.fetch_updates(iter::once(&item2), None)?;
assert_eq!(updates.data().len(), 0);
let fetch_options = FetchOptions::new().stoken(stoken);
let updates = it_mgr.fetch_updates(iter::once(&item2), Some(&fetch_options))?;
assert_eq!(updates.data().len(), 1);
}
let new_col = col_mgr.fetch(col.uid(), None)?;
let stoken = new_col.stoken();
{
let fetch_options = FetchOptions::new().stoken(stoken);
let updates = it_mgr.fetch_updates(items.iter(), Some(&fetch_options))?;
assert_eq!(updates.data().len(), 0);
}
etebase.logout()
}
#[test]
fn item_revisions() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new().set_name(Some("Collection")).clone();
let col_content = b"";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
let meta = ItemMetadata::new().set_name(Some("Item Orig")).clone();
let content = b"";
let mut item = it_mgr.create(&meta, content)?;
for i in 0..5 {
let meta = ItemMetadata::new()
.set_name(Some(&format!("Item {}", i)))
.clone();
item.set_meta(&meta)?;
it_mgr.batch(iter::once(&item), None)?;
}
{
let meta = ItemMetadata::new().set_name(Some("Latest")).clone();
item.set_meta(&meta)?;
it_mgr.batch(iter::once(&item), None)?;
}
{
let etag = item.etag();
let fetch_options = FetchOptions::new().iterator(Some(etag));
let revisions = it_mgr.item_revisions(&item, Some(&fetch_options))?;
assert_eq!(revisions.data().len(), 5);
assert!(revisions.done());
let etag = item.etag();
let fetch_options = FetchOptions::new().iterator(Some(etag)).limit(5);
let revisions = it_mgr.item_revisions(&item, Some(&fetch_options))?;
assert_eq!(revisions.data().len(), 5);
assert!(revisions.done());
for i in 0..5 {
let meta = ItemMetadata::new()
.set_name(Some(&format!("Item {}", i)))
.clone();
let rev = &revisions.data()[4 - i];
assert_eq!(&rev.meta()?, &meta);
}
// Iterate through revisions
{
let mut iterator = None;
for i in 0..2 {
let fetch_options = FetchOptions::new().limit(2).stoken(iterator.as_deref());
let revisions = it_mgr.item_revisions(&item, Some(&fetch_options))?;
assert_eq!(revisions.done(), i == 2);
iterator = revisions.iterator().map(str::to_string);
}
}
}
etebase.logout()
}
#[test]
fn collection_invitations() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new().set_name(Some("Collection")).clone();
let col_content = b"";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
let items: Vec<Item> = (0..5)
.map(|i| {
let meta = ItemMetadata::new()
.set_name(Some(&format!("Item {}", i)))
.clone();
let content = b"";
it_mgr.create(&meta, content).unwrap()
})
.collect();
it_mgr.batch(items.iter(), None)?;
let invite_mgr = etebase.invitation_manager()?;
let etebase2 = init_test(&USER2)?;
let col_mgr2 = etebase2.collection_manager()?;
let invite_mgr2 = etebase2.invitation_manager()?;
let user2_profile = invite_mgr.fetch_user_profile(USER2.username)?;
// Should be verified by user1 off-band
let user2_pubkey = invite_mgr2.pubkey();
assert_eq!(&user2_profile.pubkey(), &user2_pubkey);
// Off-band verification:
assert_eq!(
pretty_fingerprint(user2_profile.pubkey()),
pretty_fingerprint(user2_pubkey)
);
invite_mgr.invite(
&col,
USER2.username,
user2_profile.pubkey(),
CollectionAccessLevel::ReadWrite,
)?;
let invitations = invite_mgr.list_outgoing(None)?;
assert_eq!(invitations.data().len(), 1);
let invitations = invite_mgr2.list_incoming(None)?;
assert_eq!(invitations.data().len(), 1);
{
let invitation = invitations.data().first().unwrap();
assert_eq!(invitation.sender_username().unwrap(), USER.username);
}
invite_mgr2.reject(invitations.data().first().unwrap())?;
{
let collections = col_mgr2.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 0);
let invitations = invite_mgr2.list_incoming(None)?;
assert_eq!(invitations.data().len(), 0);
}
// Invite and then disinvite
invite_mgr.invite(
&col,
USER2.username,
user2_profile.pubkey(),
CollectionAccessLevel::ReadWrite,
)?;
let invitations = invite_mgr2.list_incoming(None)?;
assert_eq!(invitations.data().len(), 1);
invite_mgr.disinvite(invitations.data().first().unwrap())?;
{
let collections = col_mgr2.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 0);
let invitations = invite_mgr2.list_incoming(None)?;
assert_eq!(invitations.data().len(), 0);
}
// Invite again, this time accept
invite_mgr.invite(
&col,
USER2.username,
user2_profile.pubkey(),
CollectionAccessLevel::ReadWrite,
)?;
let invitations = invite_mgr2.list_incoming(None)?;
assert_eq!(invitations.data().len(), 1);
let stoken = col_mgr.fetch(col.uid(), None)?.stoken().map(str::to_string);
// Should be verified by user2 off-band
let user1_pubkey = invite_mgr.pubkey();
let invitation = invitations.data().first().unwrap();
assert_eq!(invitation.sender_pubkey(), user1_pubkey);
invite_mgr2.accept(invitation)?;
{
let collections = col_mgr2.list("some.coltype", None)?;
assert_eq!(collections.data().len(), 1);
// Verify we can decrypt it and it's what we expect
let col2 = collections.data().first().unwrap();
verify_collection(col2, &col_meta, col_content)?;
let invitations = invite_mgr2.list_incoming(None)?;
assert_eq!(invitations.data().len(), 0);
}
let col2 = col_mgr2.fetch(col.uid(), None)?;
let member_mgr2 = col_mgr2.member_manager(&col2)?;
member_mgr2.leave()?;
{
let fetch_options = FetchOptions::new().stoken(stoken.as_deref());
let collections = col_mgr2.list("some.coltype", Some(&fetch_options))?;
assert_eq!(collections.data().len(), 0);
assert_eq!(collections.removed_memberships().unwrap().len(), 1);
}
// Add again
invite_mgr.invite(
&col,
USER2.username,
user2_profile.pubkey(),
CollectionAccessLevel::ReadWrite,
)?;
let invitations = invite_mgr2.list_incoming(None)?;
assert_eq!(invitations.data().len(), 1);
let invitation = invitations.data().first().unwrap();
invite_mgr2.accept(invitation)?;
{
let new_col = col_mgr.fetch(col.uid(), None)?;
assert_ne!(stoken.as_deref(), new_col.stoken());
let fetch_options = FetchOptions::new().stoken(stoken.as_deref());
let collections = col_mgr2.list("some.coltype", Some(&fetch_options))?;
assert_eq!(collections.data().len(), 1);
let first_col = collections.data().first().unwrap();
assert_eq!(first_col.uid(), col.uid());
assert!(collections.removed_memberships().is_none());
}
// Remove
{
let new_col = col_mgr.fetch(col.uid(), None)?;
assert_ne!(stoken.as_deref(), new_col.stoken());
let member_mgr = col_mgr.member_manager(&col)?;
member_mgr.remove(USER2.username)?;
let fetch_options = FetchOptions::new().stoken(stoken.as_deref());
let collections = col_mgr2.list("some.coltype", Some(&fetch_options))?;
assert_eq!(collections.data().len(), 0);
assert_eq!(collections.removed_memberships().unwrap().len(), 1);
let stoken = new_col.stoken();
let fetch_options = FetchOptions::new().stoken(stoken);
let collections = col_mgr2.list("some.coltype", Some(&fetch_options))?;
assert_eq!(collections.data().len(), 0);
assert_eq!(collections.removed_memberships().unwrap().len(), 1);
}
etebase2.logout()?;
etebase.logout()
}
#[test]
fn iterating_invitations() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let etebase2 = init_test(&USER2)?;
let invite_mgr2 = etebase2.invitation_manager()?;
let invite_mgr = etebase.invitation_manager()?;
let user2_profile = invite_mgr.fetch_user_profile(USER2.username)?;
for i in 0..3 {
let meta = ItemMetadata::new()
.set_name(Some(&format!("Item {}", i)))
.clone();
let content = b"";
let col = col_mgr.create("some.coltype", &meta, content).unwrap();
col_mgr.upload(&col, None)?;
invite_mgr.invite(
&col,
USER2.username,
user2_profile.pubkey(),
CollectionAccessLevel::ReadWrite,
)?;
}
// Check incoming
let invitations = invite_mgr2.list_incoming(None)?;
assert_eq!(invitations.data().len(), 3);
{
let mut iterator = None;
for i in 0..2 {
let fetch_options = FetchOptions::new().limit(2).iterator(iterator.as_deref());
let invitations = invite_mgr2.list_incoming(Some(&fetch_options))?;
assert_eq!(invitations.done(), i == 1);
iterator = invitations.iterator().map(str::to_string);
}
}
// Check outgoing
let invitations = invite_mgr.list_outgoing(None)?;
assert_eq!(invitations.data().len(), 3);
{
let mut iterator = None;
for i in 0..2 {
let fetch_options = FetchOptions::new().limit(2).iterator(iterator.as_deref());
let invitations = invite_mgr.list_outgoing(Some(&fetch_options))?;
assert_eq!(invitations.done(), i == 1);
iterator = invitations.iterator().map(str::to_string);
}
}
etebase2.logout()?;
etebase.logout()
}
#[test]
fn collection_access_level() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new().set_name(Some("Collection")).clone();
let col_content = b"";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
let items: Vec<Item> = (0..5)
.map(|i| {
let meta = ItemMetadata::new()
.set_name(Some(&format!("Item {}", i)))
.clone();
let content = b"";
it_mgr.create(&meta, content).unwrap()
})
.collect();
it_mgr.batch(items.iter(), None)?;
let etebase2 = init_test(&USER2)?;
let col_mgr2 = etebase2.collection_manager()?;
let invite_mgr2 = etebase2.invitation_manager()?;
let invite_mgr = etebase.invitation_manager()?;
let member_mgr = col_mgr.member_manager(&col)?;
let user2_profile = invite_mgr.fetch_user_profile(USER2.username)?;
invite_mgr.invite(
&col,
USER2.username,
user2_profile.pubkey(),
CollectionAccessLevel::ReadWrite,
)?;
let invitations = invite_mgr2.list_incoming(None)?;
invite_mgr2.accept(invitations.data().first().unwrap())?;
let col2 = col_mgr2.fetch(col.uid(), None)?;
assert_eq!(col2.access_level(), CollectionAccessLevel::ReadWrite);
let it_mgr2 = col_mgr2.item_manager(&col2)?;
// Item creation: success
{
let members = member_mgr.list(None)?;
assert_eq!(members.data().len(), 2);
for member in members.data() {
if member.username() == USER2.username {
assert_eq!(member.access_level(), CollectionAccessLevel::ReadWrite);
}
}
let meta = ItemMetadata::new().set_name(Some("Some item")).clone();
let content = b"";
let item = it_mgr2.create(&meta, content)?;
it_mgr2.batch(iter::once(&item), None)?;
}
member_mgr.modify_access_level(USER2.username, CollectionAccessLevel::ReadOnly)?;
let col2 = col_mgr2.fetch(col.uid(), None)?;
assert_eq!(col2.access_level(), CollectionAccessLevel::ReadOnly);
// Item creation: fail
{
let members = member_mgr.list(None)?;
assert_eq!(members.data().len(), 2);
for member in members.data() {
if member.username() == USER2.username {
assert_eq!(member.access_level(), CollectionAccessLevel::ReadOnly);
}
}
let meta = ItemMetadata::new().set_name(Some("Some item")).clone();
let content = b"";
let item = it_mgr2.create(&meta, content)?;
assert_err!(
it_mgr2.batch(iter::once(&item), None),
Error::PermissionDenied(_)
);
}
member_mgr.modify_access_level(USER2.username, CollectionAccessLevel::Admin)?;
// Item creation: success
{
let members = member_mgr.list(None)?;
assert_eq!(members.data().len(), 2);
for member in members.data() {
if member.username() == USER2.username {
assert_eq!(member.access_level(), CollectionAccessLevel::Admin);
}
}
let meta = ItemMetadata::new().set_name(Some("Some item")).clone();
let content = b"";
let item = it_mgr2.create(&meta, content)?;
it_mgr2.batch(iter::once(&item), None)?;
}
// Iterate members
{
let fetch_options = FetchOptions::new().limit(1);
let members = member_mgr.list(Some(&fetch_options))?;
assert_eq!(members.data().len(), 1);
let fetch_options = FetchOptions::new().limit(1).iterator(members.iterator());
let members2 = member_mgr.list(Some(&fetch_options))?;
assert_eq!(members2.data().len(), 1);
assert!(members2.done());
// Verify we got two different usersnames
assert_ne!(
members.data().first().unwrap().username(),
members2.data().first().unwrap().username()
);
let members = member_mgr.list(None)?;
assert!(members.done());
}
etebase2.logout()?;
etebase.logout()
}
#[test]
fn chunking_large_data() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new()
.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"))
.clone();
let col_content = b"SomeContent";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
let meta = ItemMetadata::new();
let content = randombytes_deterministic(120 * 1024, &[0; 32]); // 120kb of pseuedorandom data
let mut item = it_mgr.create(&meta, &content)?;
verify_item(&item, &meta, &content)?;
let mut uid_set = HashSet::new();
// Get the first chunks and init uid_set
{
let chunks = etebase::test_helpers::chunk_uids(&item);
assert_eq!(chunks.len(), 8);
for chunk in chunks {
uid_set.insert(chunk);
}
}
// Bite a chunk off the new buffer
let bite_start = 10000_usize;
let bite_size = 210_usize;
let mut new_buf = [&content[..bite_start], &content[bite_start + bite_size..]].concat();
new_buf[39000] = 0;
new_buf[39001] = 1;
new_buf[39002] = 2;
new_buf[39003] = 3;
new_buf[39004] = 4;
item.set_content(&new_buf)?;
verify_item(&item, &meta, &new_buf)?;
// Verify how much has changed
{
let chunks = etebase::test_helpers::chunk_uids(&item);
assert_eq!(chunks.len(), 8);
let mut reused = 0;
for chunk in chunks {
if uid_set.contains(&chunk) {
reused += 1;
}
}
assert_eq!(reused, 5);
}
etebase.logout()
}
#[test]
fn login_and_password_change_with_key() -> Result<()> {
// Reset user
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new()
.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"))
.clone();
let col = col_mgr.create("some.coltype", &col_meta, b"")?;
col_mgr.upload(&col, None)?;
etebase.logout()?;
let client = Client::new(CLIENT_NAME, &test_url())?;
let main_key = from_base64(USER.key)?;
let etebase = Account::login_key(client, USER.username, &main_key)?;
let col_mgr = etebase.collection_manager()?;
let col2 = col_mgr.fetch(col.uid(), None)?;
verify_collection(&col2, &col_meta, b"")?;
etebase.logout()
}
#[test]
#[ignore]
fn signup_with_key() -> Result<()> {
// FIXME: Doesn't work at the moment, because we can't signup with the same user. We need reset
// to support wiping.
// Reset user manually because we want to signup
etebase::init()?;
user_reset(&USER)?;
let main_key = from_base64(USER.key)?;
let client = Client::new(CLIENT_NAME, &test_url())?;
let user = User::new(USER.username, USER.email);
let etebase = Account::signup_key(client, &user, &main_key)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new()
.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"))
.clone();
let col = col_mgr.create("some.coltype", &col_meta, b"")?;
col_mgr.upload(&col, None)?;
etebase.logout()?;
let client = Client::new(CLIENT_NAME, &test_url())?;
let etebase = Account::login_key(client, USER.username, &main_key)?;
let col_mgr = etebase.collection_manager()?;
let col2 = col_mgr.fetch(col.uid(), None)?;
verify_collection(&col2, &col_meta, b"")?;
etebase.logout()
}
#[test]
#[ignore]
fn login_and_password_change() -> Result<()> {
// Reset both users
let etebase = init_test(&USER)?;
etebase.logout()?;
let etebase2 = init_test(&USER2)?;
etebase2.logout()?;
let another_password = "AnotherPassword";
let client = Client::new(CLIENT_NAME, &test_url())?;
let mut etebase2 = Account::login(client.clone(), USER2.username, USER2.password)?;
let col_mgr2 = etebase2.collection_manager()?;
let col_meta = ItemMetadata::new()
.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"))
.clone();
let col_content = b"SomeContent";
let col = col_mgr2.create("some.coltype", &col_meta, col_content)?;
col_mgr2.upload(&col, None)?;
etebase2.change_password(another_password)?;
{
// Verify we can still access the data
let collections = col_mgr2.list("some.coltype", None)?;
verify_collection(collections.data().first().unwrap(), &col_meta, col_content)?;
}
etebase2.logout()?;
assert_err!(
Account::login(client.clone(), USER2.username, "BadPassword"),
Error::Unauthorized(_)
);
let mut etebase2 = Account::login(client, USER2.username, another_password)?;
let col_mgr2 = etebase2.collection_manager()?;
{
// Verify we can still access the data
let collections = col_mgr2.list("some.coltype", None)?;
verify_collection(collections.data().first().unwrap(), &col_meta, col_content)?;
}
etebase2.change_password(USER2.password)?;
etebase2.logout()
}
#[test]
fn session_save_and_restore() -> Result<()> {
let client = Client::new(CLIENT_NAME, &test_url())?;
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new()
.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"))
.clone();
let col_content = b"SomeContent";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
// Verify we can store and restore without an encryption key
{
let saved = etebase.save(None)?;
let etebase2 = Account::restore(client.clone(), &saved, None)?;
let col_mgr2 = etebase2.collection_manager()?;
let collections = col_mgr2.list("some.coltype", None)?;
verify_collection(collections.data().first().unwrap(), &col_meta, col_content)?;
}
// Verify we can store and restore with an encryption key
{
let key = etebase::utils::randombytes(32);
let saved = etebase.save(Some(&key))?;
assert_err!(
Account::restore(client.clone(), &saved, None),
Error::Encryption(_)
);
let etebase2 = Account::restore(client, &saved, Some(&key))?;
let col_mgr2 = etebase2.collection_manager()?;
let collections = col_mgr2.list("some.coltype", None)?;
verify_collection(collections.data().first().unwrap(), &col_meta, col_content)?;
}
etebase.logout()
}
#[test]
fn cache_collections_and_items() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new()
.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"))
.clone();
let col_content = b"SomeContent";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
let meta = ItemMetadata::new().set_name(Some("Item")).clone();
let content = b"SomeItemContent";
let item = it_mgr.create(&meta, content)?;
it_mgr.batch(iter::once(&item), None)?;
// With content
{
let saved_col = col_mgr.cache_save_with_content(&col)?;
let loaded_col = col_mgr.cache_load(&saved_col)?;
assert_eq!(col.uid(), loaded_col.uid());
assert_eq!(col.etag(), loaded_col.etag());
verify_collection(&loaded_col, &col_meta, col_content)?;
let saved_item = it_mgr.cache_save_with_content(&item)?;
let loaded_item = it_mgr.cache_load(&saved_item)?;
assert_eq!(item.uid(), loaded_item.uid());
assert_eq!(item.etag(), loaded_item.etag());
assert_eq!(item.meta()?, loaded_item.meta()?);
verify_item(&loaded_item, &meta, content)?;
}
// Without content
{
let saved_col = col_mgr.cache_save(&col)?;
let loaded_col = col_mgr.cache_load(&saved_col)?;
assert_eq!(col.uid(), loaded_col.uid());
assert_eq!(col.etag(), loaded_col.etag());
assert_eq!(col.meta()?, loaded_col.meta()?);
let saved_item = it_mgr.cache_save(&item)?;
let loaded_item = it_mgr.cache_load(&saved_item)?;
assert_eq!(item.uid(), loaded_item.uid());
assert_eq!(item.etag(), loaded_item.etag());
assert_eq!(item.meta()?, loaded_item.meta()?);
}
etebase.logout()
}
#[test]
fn chunk_preupload_and_download() -> Result<()> {
let etebase = init_test(&USER)?;
let col_mgr = etebase.collection_manager()?;
let col_meta = ItemMetadata::new()
.set_name(Some("Collection"))
.set_description(Some("Mine"))
.set_color(Some("#aabbcc"))
.clone();
let col_content = b"SomeContent";
let col = col_mgr.create("some.coltype", &col_meta, col_content)?;
col_mgr.upload(&col, None)?;
let it_mgr = col_mgr.item_manager(&col)?;
let meta = ItemMetadata::new().set_name(Some("Item")).clone();
let content = b"SomeItemContent";
let item = it_mgr.create(&meta, content)?;
assert!(!item.is_missing_content());
it_mgr.upload_content(&item)?;
// Verify we don't fail even when already uploaded
it_mgr.upload_content(&item)?;
it_mgr.batch(iter::once(&item), None)?;
{
let mut item2 = it_mgr.fetch(
item.uid(),
Some(&FetchOptions::new().prefetch(&PrefetchOption::Medium)),
)?;
assert_eq!(item.meta()?, item2.meta()?);
// We can't get the content of partial item
assert_err!(item2.content(), Error::MissingContent(_));
assert!(item2.is_missing_content());
// Fetch the content and then try to get it
it_mgr.download_content(&mut item2)?;
assert!(!item2.is_missing_content());
verify_item(&item2, &item.meta()?, &item.content()?)?;
}
etebase.logout()
}