// 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 { 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::()?, &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::()?, &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 = (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 = (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 = (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 = (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 = (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 = (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() }