From ebcf9527dcaad95981e9b975fadf85ef6311abd9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 7 Sep 2021 02:44:38 +0000 Subject: [PATCH 1/4] refactor sync-server into a lib crate with a binary --- sync-server/src/api/add_version.rs | 31 ++++++------ sync-server/src/api/get_child_version.rs | 24 ++++----- sync-server/src/api/mod.rs | 2 +- .../taskchampion-sync-server.rs} | 49 +++++-------------- sync-server/src/lib.rs | 37 ++++++++++++++ sync-server/src/storage/inmemory.rs | 5 +- sync-server/src/storage/mod.rs | 25 +++++----- sync-server/src/storage/sqlite.rs | 2 +- 8 files changed, 93 insertions(+), 82 deletions(-) rename sync-server/src/{main.rs => bin/taskchampion-sync-server.rs} (52%) create mode 100644 sync-server/src/lib.rs diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 60db1f385..9804258a6 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -77,9 +77,8 @@ pub(crate) async fn service( #[cfg(test)] mod test { - use crate::api::ServerState; - use crate::app_scope; use crate::storage::{InMemoryStorage, Storage}; + use crate::Server; use actix_web::{http::StatusCode, test, App}; use uuid::Uuid; @@ -88,16 +87,16 @@ mod test { let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // set up the storage contents.. { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, Uuid::nil()).unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -125,16 +124,16 @@ mod test { let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // set up the storage contents.. { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, version_id).unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -159,9 +158,9 @@ mod test { async fn test_bad_content_type() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let storage: Box = Box::new(InMemoryStorage::new()); + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() @@ -178,9 +177,9 @@ mod test { async fn test_empty_body() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let storage: Box = Box::new(InMemoryStorage::new()); + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index a38113803..f5d67184e 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -45,8 +45,8 @@ pub(crate) async fn service( #[cfg(test)] mod test { use crate::api::ServerState; - use crate::app_scope; use crate::storage::{InMemoryStorage, Storage}; + use crate::Server; use actix_web::{http::StatusCode, test, App}; use uuid::Uuid; @@ -55,18 +55,18 @@ mod test { let client_key = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // set up the storage contents.. { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, Uuid::new_v4()).unwrap(); txn.add_version(client_key, version_id, parent_version_id, b"abcd".to_vec()) .unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() @@ -97,9 +97,9 @@ mod test { async fn test_client_not_found() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let storage: Box = Box::new(InMemoryStorage::new()); + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() @@ -116,15 +116,15 @@ mod test { async fn test_version_not_found() { let client_key = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(InMemoryStorage::new()); + let storage: Box = Box::new(InMemoryStorage::new()); // create the client, but not the version { - let mut txn = server_box.txn().unwrap(); + let mut txn = storage.txn().unwrap(); txn.new_client(client_key, Uuid::new_v4()).unwrap(); } - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(storage); + let mut app = test::init_service(App::new().service(server.service())).await; let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index 26aa8eba0..917dda83e 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -20,7 +20,7 @@ pub(crate) const CLIENT_KEY_HEADER: &str = "X-Client-Key"; pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id"; /// The type containing a reference to the Storage object in the Actix state. -pub(crate) type ServerState = Arc>; +pub(crate) type ServerState = Arc; pub(crate) fn api_scope() -> Scope { web::scope("") diff --git a/sync-server/src/main.rs b/sync-server/src/bin/taskchampion-sync-server.rs similarity index 52% rename from sync-server/src/main.rs rename to sync-server/src/bin/taskchampion-sync-server.rs index 394031738..88f0bb180 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/bin/taskchampion-sync-server.rs @@ -1,29 +1,9 @@ #![deny(clippy::all)] -use crate::storage::{SqliteStorage, Storage}; -use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder, Scope}; -use api::{api_scope, ServerState}; +use actix_web::{middleware::Logger, App, HttpServer}; use clap::Arg; - -mod api; -mod server; -mod storage; - -// TODO: use hawk to sign requests - -#[get("/")] -async fn index() -> impl Responder { - format!("TaskChampion sync server v{}", env!("CARGO_PKG_VERSION")) -} - -/// Return a scope defining the URL rules for this server, with access to -/// the given ServerState. -pub(crate) fn app_scope(server_state: ServerState) -> Scope { - web::scope("") - .data(server_state) - .service(index) - .service(api_scope()) -} +use taskchampion_sync_server::storage::SqliteStorage; +use taskchampion_sync_server::Server; #[actix_web::main] async fn main() -> anyhow::Result<()> { @@ -56,33 +36,26 @@ async fn main() -> anyhow::Result<()> { let data_dir = matches.value_of("data-dir").unwrap(); let port = matches.value_of("port").unwrap(); - let server_box: Box = Box::new(SqliteStorage::new(data_dir)?); - let server_state = ServerState::new(server_box); + let server = Server::new(Box::new(SqliteStorage::new(data_dir)?)); log::warn!("Serving on port {}", port); - HttpServer::new(move || { - App::new() - .wrap(Logger::default()) - .service(app_scope(server_state.clone())) - }) - .bind(format!("0.0.0.0:{}", port))? - .run() - .await?; + HttpServer::new(move || App::new().wrap(Logger::default()).service(server.service())) + .bind(format!("0.0.0.0:{}", port))? + .run() + .await?; Ok(()) } #[cfg(test)] mod test { use super::*; - use crate::api::ServerState; - use crate::storage::{InMemoryStorage, Storage}; use actix_web::{test, App}; + use taskchampion_sync_server::storage::{InMemoryStorage, Storage}; #[actix_rt::test] async fn test_index_get() { - let server_box: Box = Box::new(InMemoryStorage::new()); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; + let server = Server::new(Box::new(InMemoryStorage::new())); + let mut app = test::init_service(App::new().service(server.service())).await; let req = test::TestRequest::get().uri("/").to_request(); let resp = test::call_service(&mut app, req).await; diff --git a/sync-server/src/lib.rs b/sync-server/src/lib.rs new file mode 100644 index 000000000..c219f2e1b --- /dev/null +++ b/sync-server/src/lib.rs @@ -0,0 +1,37 @@ +#![deny(clippy::all)] + +mod api; +mod server; +pub mod storage; + +use crate::storage::Storage; +use actix_web::{get, web, Responder, Scope}; +use api::{api_scope, ServerState}; + +#[get("/")] +async fn index() -> impl Responder { + format!("TaskChampion sync server v{}", env!("CARGO_PKG_VERSION")) +} + +/// A Server represents a sync server. +#[derive(Clone)] +pub struct Server { + storage: ServerState, +} + +impl Server { + /// Create a new sync server with the given storage implementation. + pub fn new(storage: Box) -> Self { + Self { + storage: storage.into(), + } + } + + /// Get an Actix-web service for this server. + pub fn service(&self) -> Scope { + web::scope("") + .data(self.storage.clone()) + .service(index) + .service(api_scope()) + } +} diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs index e37abb07a..143b2e760 100644 --- a/sync-server/src/storage/inmemory.rs +++ b/sync-server/src/storage/inmemory.rs @@ -10,10 +10,11 @@ struct Inner { versions: HashMap<(Uuid, Uuid), Version>, } -pub(crate) struct InMemoryStorage(Mutex); +pub struct InMemoryStorage(Mutex); impl InMemoryStorage { - pub(crate) fn new() -> Self { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { Self(Mutex::new(Inner { clients: HashMap::new(), versions: HashMap::new(), diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs index f9a2fc699..30b961fa1 100644 --- a/sync-server/src/storage/mod.rs +++ b/sync-server/src/storage/mod.rs @@ -1,27 +1,28 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; -#[cfg(test)] +#[cfg(debug_assertions)] mod inmemory; -#[cfg(test)] -pub(crate) use inmemory::InMemoryStorage; + +#[cfg(debug_assertions)] +pub use inmemory::InMemoryStorage; mod sqlite; -pub(crate) use self::sqlite::SqliteStorage; +pub use self::sqlite::SqliteStorage; #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub(crate) struct Client { - pub(crate) latest_version_id: Uuid, +pub struct Client { + pub latest_version_id: Uuid, } #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub(crate) struct Version { - pub(crate) version_id: Uuid, - pub(crate) parent_version_id: Uuid, - pub(crate) history_segment: Vec, +pub struct Version { + pub version_id: Uuid, + pub parent_version_id: Uuid, + pub history_segment: Vec, } -pub(crate) trait StorageTxn { +pub trait StorageTxn { /// Get information about the given client fn get_client(&mut self, client_key: Uuid) -> anyhow::Result>; @@ -58,7 +59,7 @@ pub(crate) trait StorageTxn { /// A trait for objects able to act as storage. Most of the interesting behavior is in the /// [`crate::storage::StorageTxn`] trait. -pub(crate) trait Storage: Send + Sync { +pub trait Storage: Send + Sync { /// Begin a transaction fn txn<'a>(&'a self) -> anyhow::Result>; } diff --git a/sync-server/src/storage/sqlite.rs b/sync-server/src/storage/sqlite.rs index 34cdd05b7..ba56cb0a6 100644 --- a/sync-server/src/storage/sqlite.rs +++ b/sync-server/src/storage/sqlite.rs @@ -49,7 +49,7 @@ impl ToSql for Client { } /// An on-disk storage backend which uses SQLite -pub(crate) struct SqliteStorage { +pub struct SqliteStorage { db_file: std::path::PathBuf, } From fb39c90592db1254d89a02f8d208f6d062e902d7 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 7 Sep 2021 03:08:35 +0000 Subject: [PATCH 2/4] Add an integration test combining replica and server This confirms that task changes are replicated via the server. --- Cargo.lock | 136 +++++++---------------- Cargo.toml | 3 +- README.md | 3 +- cli/Cargo.toml | 1 + replica-server-tests/Cargo.toml | 18 +++ replica-server-tests/src/lib.rs | 1 + replica-server-tests/tests/cross-sync.rs | 84 ++++++++++++++ sync-server/Cargo.toml | 3 +- 8 files changed, 151 insertions(+), 98 deletions(-) create mode 100644 replica-server-tests/Cargo.toml create mode 100644 replica-server-tests/src/lib.rs create mode 100644 replica-server-tests/tests/cross-sync.rs diff --git a/Cargo.lock b/Cargo.lock index 28bed3b3e..0dec00598 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "actix-codec" version = "0.3.0" @@ -12,7 +14,7 @@ dependencies = [ "futures-sink", "log", "pin-project 0.4.28", - "tokio 0.2.25", + "tokio", "tokio-util", ] @@ -23,7 +25,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" dependencies = [ "actix-codec", - "actix-rt 1.1.1", + "actix-rt", "actix-service", "actix-utils", "derive_more", @@ -43,7 +45,7 @@ checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" dependencies = [ "actix-codec", "actix-connect", - "actix-rt 1.1.1", + "actix-rt", "actix-service", "actix-threadpool", "actix-utils", @@ -92,16 +94,6 @@ dependencies = [ "syn", ] -[[package]] -name = "actix-macros" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f86cd6857c135e6e9fe57b1619a88d1f94a7df34c00e11fe13e64fd3438837" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "actix-router" version = "0.2.7" @@ -121,24 +113,13 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" dependencies = [ - "actix-macros 0.1.3", + "actix-macros", "actix-threadpool", "copyless", "futures-channel", "futures-util", "smallvec", - "tokio 0.2.25", -] - -[[package]] -name = "actix-rt" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d7cd957c9ed92288a7c3c96af81fa5291f65247a76a34dac7b6af74e52ba0" -dependencies = [ - "actix-macros 0.2.1", - "futures-core", - "tokio 1.6.2", + "tokio", ] [[package]] @@ -148,13 +129,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e" dependencies = [ "actix-codec", - "actix-rt 1.1.1", + "actix-rt", "actix-service", "actix-utils", "futures-channel", "futures-util", "log", - "mio 0.6.23", + "mio", "mio-uds", "num_cpus", "slab", @@ -177,8 +158,8 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c" dependencies = [ - "actix-macros 0.1.3", - "actix-rt 1.1.1", + "actix-macros", + "actix-rt", "actix-server", "actix-service", "log", @@ -219,7 +200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" dependencies = [ "actix-codec", - "actix-rt 1.1.1", + "actix-rt", "actix-service", "bitflags", "bytes 0.5.6", @@ -240,9 +221,9 @@ checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86" dependencies = [ "actix-codec", "actix-http", - "actix-macros 0.1.3", + "actix-macros", "actix-router", - "actix-rt 1.1.1", + "actix-rt", "actix-server", "actix-service", "actix-testing", @@ -400,7 +381,7 @@ checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691" dependencies = [ "actix-codec", "actix-http", - "actix-rt 1.1.1", + "actix-rt", "actix-service", "base64 0.13.0", "bytes 0.5.6", @@ -1252,7 +1233,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 0.2.25", + "tokio", "tokio-util", "tracing", "tracing-futures", @@ -1430,7 +1411,7 @@ dependencies = [ "itoa", "pin-project 1.0.7", "socket2", - "tokio 0.2.25", + "tokio", "tower-service", "tracing", "want", @@ -1741,7 +1722,7 @@ dependencies = [ "serde_json", "shlex", "tempfile", - "tokio 0.2.25", + "tokio", "toml", "warp", ] @@ -1791,25 +1772,12 @@ dependencies = [ "kernel32-sys", "libc", "log", - "miow 0.2.2", + "miow", "net2", "slab", "winapi 0.2.8", ] -[[package]] -name = "mio" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" -dependencies = [ - "libc", - "log", - "miow 0.3.7", - "ntapi", - "winapi 0.3.9", -] - [[package]] name = "mio-extras" version = "2.0.6" @@ -1818,7 +1786,7 @@ checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", "log", - "mio 0.6.23", + "mio", "slab", ] @@ -1830,7 +1798,7 @@ checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" dependencies = [ "iovec", "libc", - "mio 0.6.23", + "mio", ] [[package]] @@ -1845,15 +1813,6 @@ dependencies = [ "ws2_32-sys", ] -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "net2" version = "0.2.37" @@ -1913,21 +1872,12 @@ dependencies = [ "fsevent-sys", "inotify", "libc", - "mio 0.6.23", + "mio", "mio-extras", "walkdir", "winapi 0.3.9", ] -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "num-integer" version = "0.1.44" @@ -2470,6 +2420,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "replica-server-tests" +version = "0.3.0" +dependencies = [ + "actix-rt", + "actix-web", + "anyhow", + "taskchampion", + "taskchampion-sync-server", + "tempfile", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -3002,7 +2964,7 @@ dependencies = [ name = "taskchampion-sync-server" version = "0.3.0" dependencies = [ - "actix-rt 2.2.0", + "actix-rt", "actix-web", "anyhow", "clap", @@ -3209,7 +3171,7 @@ dependencies = [ "lazy_static", "libc", "memchr", - "mio 0.6.23", + "mio", "mio-uds", "pin-project-lite 0.1.12", "signal-hook-registry", @@ -3218,22 +3180,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "tokio" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aea337f72e96efe29acc234d803a5981cd9a2b6ed21655cd7fc21cfe021e8ec7" -dependencies = [ - "autocfg", - "libc", - "mio 0.7.13", - "once_cell", - "parking_lot", - "pin-project-lite 0.2.6", - "signal-hook-registry", - "winapi 0.3.9", -] - [[package]] name = "tokio-macros" version = "0.2.6" @@ -3254,7 +3200,7 @@ dependencies = [ "futures-util", "log", "pin-project 0.4.28", - "tokio 0.2.25", + "tokio", "tungstenite", ] @@ -3269,7 +3215,7 @@ dependencies = [ "futures-sink", "log", "pin-project-lite 0.1.12", - "tokio 0.2.25", + "tokio", ] [[package]] @@ -3351,7 +3297,7 @@ dependencies = [ "rand 0.7.3", "smallvec", "thiserror", - "tokio 0.2.25", + "tokio", "url", ] @@ -3370,7 +3316,7 @@ dependencies = [ "resolv-conf", "smallvec", "thiserror", - "tokio 0.2.25", + "tokio", "trust-dns-proto", ] @@ -3579,7 +3525,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded 0.6.1", - "tokio 0.2.25", + "tokio", "tokio-tungstenite", "tower-service", "tracing", diff --git a/Cargo.toml b/Cargo.toml index cd6520de1..eef6d1ce7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,5 +3,6 @@ members = [ "taskchampion", "cli", - "sync-server" + "sync-server", + "replica-server-tests" ] diff --git a/README.md b/README.md index 1b4e1d664..e937b17d2 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,12 @@ Assuming that continues, it is unlikely that TaskChampion will ever be recommend ## Structure -There are three crates here: +There are four crates here: * [taskchampion](./taskchampion) - the core of the tool * [taskchampion-cli](./cli) - the command-line binary * [taskchampion-sync-server](./sync-server) - the server against which `task sync` operates + * [replica-server-tests](./replica-server-tests) - the server against which `task sync` operates ## Documentation Generation diff --git a/cli/Cargo.toml b/cli/Cargo.toml index feada5597..351e1ecfc 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -3,6 +3,7 @@ authors = ["Dustin J. Mitchell "] edition = "2018" name = "taskchampion-cli" version = "0.3.0" +publish = false build = "build.rs" diff --git a/replica-server-tests/Cargo.toml b/replica-server-tests/Cargo.toml new file mode 100644 index 000000000..c702e4164 --- /dev/null +++ b/replica-server-tests/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "replica-server-tests" +version = "0.3.0" +authors = ["Dustin J. Mitchell "] +edition = "2018" +publish = false + +[dependencies.taskchampion-sync-server] +path = "../sync-server" + +[dependencies.taskchampion] +path = "../taskchampion" + +[dev-dependencies] +anyhow = "1.0" +actix-web = "^3.3.2" +actix-rt = "^1.1.1" +tempfile = "3" diff --git a/replica-server-tests/src/lib.rs b/replica-server-tests/src/lib.rs new file mode 100644 index 000000000..e783558fc --- /dev/null +++ b/replica-server-tests/src/lib.rs @@ -0,0 +1 @@ +// test-only crate diff --git a/replica-server-tests/tests/cross-sync.rs b/replica-server-tests/tests/cross-sync.rs new file mode 100644 index 000000000..a828d0f0d --- /dev/null +++ b/replica-server-tests/tests/cross-sync.rs @@ -0,0 +1,84 @@ +use actix_web::{App, HttpServer}; +use taskchampion::{Replica, ServerConfig, Status, StorageConfig, Uuid}; +use taskchampion_sync_server::{storage::InMemoryStorage, Server}; + +#[actix_rt::test] +async fn cross_sync() -> anyhow::Result<()> { + let server = Server::new(Box::new(InMemoryStorage::new())); + let httpserver = + HttpServer::new(move || App::new().service(server.service())).bind("0.0.0.0:0")?; + + // bind was to :0, so the kernel will have selected an unused port + let port = httpserver.addrs()[0].port(); + + httpserver.run(); + + // set up two replicas, and demonstrate replication between them + let mut rep1 = Replica::new(StorageConfig::InMemory.into_storage()?); + let mut rep2 = Replica::new(StorageConfig::InMemory.into_storage()?); + + let client_key = Uuid::new_v4(); + let encryption_secret = b"abc123".to_vec(); + let make_server = || { + ServerConfig::Remote { + origin: format!("http://127.0.0.1:{}", port), + client_key, + encryption_secret: encryption_secret.clone(), + } + .into_server() + }; + + let mut serv1 = make_server()?; + let mut serv2 = make_server()?; + + // add some tasks on rep1 + let t1 = rep1.new_task(Status::Pending, "test 1".into())?; + let t2 = rep1.new_task(Status::Pending, "test 2".into())?; + + // modify t1 + let mut t1 = t1.into_mut(&mut rep1); + t1.start()?; + let t1 = t1.into_immut(); + + rep1.sync(&mut serv1)?; + rep2.sync(&mut serv2)?; + + // those tasks should exist on rep2 now + let t12 = rep2 + .get_task(t1.get_uuid())? + .expect("expected task 1 on rep2"); + let t22 = rep2 + .get_task(t2.get_uuid())? + .expect("expected task 2 on rep2"); + + assert_eq!(t12.get_description(), "test 1"); + assert_eq!(t12.is_active(), true); + assert_eq!(t22.get_description(), "test 2"); + assert_eq!(t22.is_active(), false); + + // make non-conflicting changes on the two replicas + let mut t2 = t2.into_mut(&mut rep1); + t2.set_status(Status::Completed)?; + let t2 = t2.into_immut(); + + let mut t12 = t12.into_mut(&mut rep2); + t12.set_status(Status::Completed)?; + + // sync those changes back and forth + rep1.sync(&mut serv1)?; // rep1 -> server + rep2.sync(&mut serv2)?; // server -> rep2, rep2 -> server + rep1.sync(&mut serv1)?; // server -> rep1 + + let t1 = rep1 + .get_task(t1.get_uuid())? + .expect("expected task 1 on rep1"); + assert_eq!(t1.get_status(), Status::Completed); + + let t22 = rep2 + .get_task(t2.get_uuid())? + .expect("expected task 2 on rep2"); + assert_eq!(t22.get_status(), Status::Completed); + + // note that we just drop the server here.. + Ok(()) +} diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 6c66f6726..ef1442fad 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -3,6 +3,7 @@ name = "taskchampion-sync-server" version = "0.3.0" authors = ["Dustin J. Mitchell "] edition = "2018" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -20,5 +21,5 @@ env_logger = "^0.8.3" rusqlite = { version = "0.25", features = ["bundled"] } [dev-dependencies] -actix-rt = "^2.2.0" +actix-rt = "^1.1.1" tempfile = "3" From 757f923c66e1adc2400328acb6cdc9ff80a8409d Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 13 Sep 2021 17:42:16 -0400 Subject: [PATCH 3/4] reword README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e937b17d2..a4582257e 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ There are four crates here: * [taskchampion](./taskchampion) - the core of the tool * [taskchampion-cli](./cli) - the command-line binary * [taskchampion-sync-server](./sync-server) - the server against which `task sync` operates - * [replica-server-tests](./replica-server-tests) - the server against which `task sync` operates + * [replica-server-tests](./replica-server-tests) - integration tests covering both _taskchampion-cli_ and _taskchampion-sync-server_ ## Documentation Generation From 7d3aae4555220f48a9098ddf809ba16b7c12920e Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 14 Sep 2021 09:26:44 -0400 Subject: [PATCH 4/4] allow publishing taskchampion-cli, to allow 'cargo install' --- cli/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 351e1ecfc..feada5597 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -3,7 +3,6 @@ authors = ["Dustin J. Mitchell "] edition = "2018" name = "taskchampion-cli" version = "0.3.0" -publish = false build = "build.rs"