From fb22b9686ff32477220d6e6b7d190d8233af6d21 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 26 Nov 2020 19:19:51 -0500 Subject: [PATCH] refactor sync server to use pluggable storage ..with a fixed implementation of the replica / server protocol logic. There isn't much logic yet, and there's a lot of boilerplate to take care of, so this looks a little lopsided, but I'm confident this is the right structure for this code's future. --- sync-server/src/api/add_version.rs | 110 +++++++++++++++-------- sync-server/src/api/get_child_version.rs | 97 ++++++++++++++------ sync-server/src/api/mod.rs | 13 ++- sync-server/src/main.rs | 27 ++++-- sync-server/src/server/inmemory.rs | 108 ---------------------- sync-server/src/server/mod.rs | 20 ----- sync-server/src/storage/inmemory.rs | 90 +++++++++++++++++++ sync-server/src/storage/mod.rs | 56 ++++++++++++ sync-server/src/test.rs | 57 ------------ 9 files changed, 319 insertions(+), 259 deletions(-) delete mode 100644 sync-server/src/server/inmemory.rs create mode 100644 sync-server/src/storage/inmemory.rs create mode 100644 sync-server/src/storage/mod.rs delete mode 100644 sync-server/src/test.rs diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 4dc8393cf..2413c5fce 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -1,11 +1,13 @@ use crate::api::{ - ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, -}; -use crate::server::{AddVersionResult, ClientId, VersionId}; -use actix_web::{ - error, http::StatusCode, post, web, HttpMessage, HttpRequest, HttpResponse, Result, + failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, + VERSION_ID_HEADER, }; +use crate::server::{AddVersionResult, ClientId, HistorySegment, VersionId, NO_VERSION_ID}; +use crate::storage::{Client, StorageTxn}; +use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; +use failure::Fallible; use futures::StreamExt; +use taskchampion::Uuid; /// Max history segment size: 100MB const MAX_SIZE: usize = 100 * 1024 * 1024; @@ -23,7 +25,7 @@ const MAX_SIZE: usize = 100 * 1024 * 1024; #[post("/client/{client_id}/add-version/{parent_version_id}")] pub(crate) async fn service( req: HttpRequest, - data: web::Data, + server_state: web::Data, web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, mut payload: web::Payload, ) -> Result { @@ -47,9 +49,18 @@ pub(crate) async fn service( return Err(error::ErrorBadRequest("Empty body")); } - let result = data - .add_version(client_id, parent_version_id, body.to_vec()) - .map_err(|e| error::InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?; + // note that we do not open the transaction until the body has been read + // completely, to avoid blocking other storage access while that data is + // in transit. + let mut txn = server_state.txn().map_err(failure_to_ise)?; + + let client = txn + .get_client(client_id) + .map_err(failure_to_ise)? + .ok_or_else(|| error::ErrorNotFound("no such client"))?; + + let result = add_version(txn, client_id, client, parent_version_id, body.to_vec()) + .map_err(failure_to_ise)?; Ok(match result { AddVersionResult::Ok(version_id) => HttpResponse::Ok() .header(VERSION_ID_HEADER, version_id.to_string()) @@ -60,14 +71,37 @@ pub(crate) async fn service( }) } +fn add_version<'a>( + mut txn: Box, + client_id: ClientId, + client: Client, + parent_version_id: VersionId, + history_segment: HistorySegment, +) -> Fallible { + // check if this version is acceptable, under the protection of the transaction + if client.latest_version_id != NO_VERSION_ID && parent_version_id != client.latest_version_id { + return Ok(AddVersionResult::ExpectedParentVersion( + client.latest_version_id, + )); + } + + // invent a version ID + let version_id = Uuid::new_v4(); + + // update the DB + txn.add_version(client_id, version_id, parent_version_id, history_segment)?; + txn.set_client_latest_version_id(client_id, version_id)?; + txn.commit()?; + + Ok(AddVersionResult::Ok(version_id)) +} + #[cfg(test)] mod test { - use super::*; use crate::api::ServerState; use crate::app_scope; - use crate::server::SyncServer; - use crate::test::TestServer; - use actix_web::{test, App}; + use crate::storage::{InMemoryStorage, Storage}; + use actix_web::{http::StatusCode, test, App}; use taskchampion::Uuid; #[actix_rt::test] @@ -75,13 +109,15 @@ mod test { let client_id = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - expected_client_id: client_id, - av_expected_parent_version_id: parent_version_id, - av_expected_history_segment: b"abcd".to_vec(), - av_result: Some(AddVersionResult::Ok(version_id)), - ..Default::default() - }); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, Uuid::nil()) + .unwrap(); + } + let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; @@ -96,10 +132,12 @@ mod test { .to_request(); let resp = test::call_service(&mut app, req).await; assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get("X-Version-Id").unwrap(), - &version_id.to_string() - ); + + // the returned version ID is random, but let's check that it's not + // the passed parent version ID, at least + let new_version_id = resp.headers().get("X-Version-Id").unwrap(); + assert!(new_version_id != &version_id.to_string()); + assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); } @@ -108,13 +146,15 @@ mod test { let client_id = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - expected_client_id: client_id, - av_expected_parent_version_id: parent_version_id, - av_expected_history_segment: b"abcd".to_vec(), - av_result: Some(AddVersionResult::ExpectedParentVersion(version_id)), - ..Default::default() - }); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, version_id) + .unwrap(); + } + let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; @@ -140,9 +180,7 @@ mod test { async fn test_bad_content_type() { let client_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - ..Default::default() - }); + 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; @@ -160,9 +198,7 @@ mod test { async fn test_empty_body() { let client_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - ..Default::default() - }); + 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; diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index b16f5219b..84af0b179 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -1,8 +1,11 @@ use crate::api::{ - ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, + failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, + VERSION_ID_HEADER, }; -use crate::server::{ClientId, VersionId}; -use actix_web::{error, get, http::StatusCode, web, HttpResponse, Result}; +use crate::server::{ClientId, GetVersionResult, VersionId}; +use crate::storage::StorageTxn; +use actix_web::{error, get, web, HttpResponse, Result}; +use failure::Fallible; /// Get a child version. /// @@ -14,12 +17,16 @@ use actix_web::{error, get, http::StatusCode, web, HttpResponse, Result}; /// Returns other 4xx or 5xx responses on other errors. #[get("/client/{client_id}/get-child-version/{parent_version_id}")] pub(crate) async fn service( - data: web::Data, + server_state: web::Data, web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, ) -> Result { - let result = data - .get_child_version(client_id, parent_version_id) - .map_err(|e| error::InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?; + let mut txn = server_state.txn().map_err(failure_to_ise)?; + + txn.get_client(client_id) + .map_err(failure_to_ise)? + .ok_or_else(|| error::ErrorNotFound("no such client"))?; + + let result = get_child_version(txn, client_id, parent_version_id).map_err(failure_to_ise)?; if let Some(result) = result { Ok(HttpResponse::Ok() .content_type(HISTORY_SEGMENT_CONTENT_TYPE) @@ -34,14 +41,26 @@ pub(crate) async fn service( } } +fn get_child_version<'a>( + mut txn: Box, + client_id: ClientId, + parent_version_id: VersionId, +) -> Fallible> { + Ok(txn + .get_version_by_parent(client_id, parent_version_id)? + .map(|version| GetVersionResult { + version_id: version.version_id, + parent_version_id: version.parent_version_id, + history_segment: version.history_segment, + })) +} + #[cfg(test)] mod test { - use super::*; use crate::api::ServerState; use crate::app_scope; - use crate::server::{GetVersionResult, SyncServer}; - use crate::test::TestServer; - use actix_web::{test, App}; + use crate::storage::{InMemoryStorage, Storage}; + use actix_web::{http::StatusCode, test, App}; use taskchampion::Uuid; #[actix_rt::test] @@ -49,16 +68,17 @@ mod test { let client_id = Uuid::new_v4(); let version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - expected_client_id: client_id, - gcv_expected_parent_version_id: parent_version_id, - gcv_result: Some(GetVersionResult { - version_id: version_id, - parent_version_id: parent_version_id, - history_segment: b"abcd".to_vec(), - }), - ..Default::default() - }); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // set up the storage contents.. + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, Uuid::new_v4()) + .unwrap(); + txn.add_version(client_id, 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; @@ -88,15 +108,36 @@ mod test { } #[actix_rt::test] - async fn test_not_found() { + async fn test_client_not_found() { let client_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4(); - let server_box: Box = Box::new(TestServer { - expected_client_id: client_id, - gcv_expected_parent_version_id: parent_version_id, - gcv_result: None, - ..Default::default() - }); + 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 uri = format!( + "/client/{}/get-child-version/{}", + client_id, parent_version_id + ); + let req = test::TestRequest::get().uri(&uri).to_request(); + let resp = test::call_service(&mut app, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + assert_eq!(resp.headers().get("X-Version-Id"), None); + assert_eq!(resp.headers().get("X-Parent-Version-Id"), None); + } + + #[actix_rt::test] + async fn test_version_not_found() { + let client_id = Uuid::new_v4(); + let parent_version_id = Uuid::new_v4(); + let server_box: Box = Box::new(InMemoryStorage::new()); + + // create the client, but not the version + { + let mut txn = server_box.txn().unwrap(); + txn.set_client_latest_version_id(client_id, 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; diff --git a/sync-server/src/api/mod.rs b/sync-server/src/api/mod.rs index c58d2420b..cddcab59c 100644 --- a/sync-server/src/api/mod.rs +++ b/sync-server/src/api/mod.rs @@ -1,5 +1,5 @@ -use crate::server::SyncServer; -use actix_web::{web, Scope}; +use crate::storage::Storage; +use actix_web::{error, http::StatusCode, web, Scope}; use std::sync::Arc; mod add_version; @@ -15,11 +15,16 @@ pub(crate) const VERSION_ID_HEADER: &str = "X-Version-Id"; /// The header names for parent version ID pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id"; -/// The type containing a reference to the SyncServer object in the Actix state. -pub(crate) type ServerState = Arc>; +/// The type containing a reference to the Storage object in the Actix state. +pub(crate) type ServerState = Arc>; pub(crate) fn api_scope() -> Scope { web::scope("") .service(get_child_version::service) .service(add_version::service) } + +/// Convert a failure::Error to an Actix ISE +fn failure_to_ise(err: failure::Error) -> impl actix_web::ResponseError { + error::InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR) +} diff --git a/sync-server/src/main.rs b/sync-server/src/main.rs index 3123c80db..7b91c5e3c 100644 --- a/sync-server/src/main.rs +++ b/sync-server/src/main.rs @@ -1,12 +1,10 @@ +use crate::storage::{InMemoryStorage, Storage}; use actix_web::{get, web, App, HttpServer, Responder, Scope}; use api::{api_scope, ServerState}; -use server::{InMemorySyncServer, SyncServer}; mod api; mod server; - -#[cfg(test)] -mod test; +mod storage; // TODO: use hawk to sign requests @@ -27,7 +25,7 @@ pub(crate) fn app_scope(server_state: ServerState) -> Scope { #[actix_web::main] async fn main() -> std::io::Result<()> { - let server_box: Box = Box::new(InMemorySyncServer::new()); + let server_box: Box = Box::new(InMemoryStorage::new()); let server_state = ServerState::new(server_box); HttpServer::new(move || App::new().service(app_scope(server_state.clone()))) @@ -35,3 +33,22 @@ async fn main() -> std::io::Result<()> { .run() .await } + +#[cfg(test)] +mod test { + use super::*; + use crate::api::ServerState; + use crate::storage::{InMemoryStorage, Storage}; + use actix_web::{test, App}; + + #[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 req = test::TestRequest::get().uri("/").to_request(); + let resp = test::call_service(&mut app, req).await; + assert!(resp.status().is_success()); + } +} diff --git a/sync-server/src/server/inmemory.rs b/sync-server/src/server/inmemory.rs deleted file mode 100644 index 37302282e..000000000 --- a/sync-server/src/server/inmemory.rs +++ /dev/null @@ -1,108 +0,0 @@ -use super::{ - AddVersionResult, ClientId, GetVersionResult, HistorySegment, SyncServer, VersionId, - NO_VERSION_ID, -}; -use failure::Fallible; -use std::collections::HashMap; -use std::sync::{Mutex, RwLock}; -use taskchampion::Uuid; - -/// An in-memory server backend that can be useful for testing. -pub(crate) struct InMemorySyncServer { - clients: RwLock>>, -} - -struct Version { - version_id: VersionId, - history_segment: HistorySegment, -} - -struct Client { - latest_version_id: VersionId, - // NOTE: indexed by parent_version_id! - versions: HashMap, -} - -impl InMemorySyncServer { - pub(crate) fn new() -> Self { - Self { - clients: RwLock::new(HashMap::new()), - } - } -} - -impl SyncServer for InMemorySyncServer { - fn get_child_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - ) -> Fallible> { - let clients = self.clients.read().expect("poisoned lock"); - if let Some(client) = clients.get(&client_id) { - let client = client.lock().expect("poisoned lock"); - if let Some(version) = client.versions.get(&parent_version_id) { - return Ok(Some(GetVersionResult { - version_id: version.version_id, - parent_version_id, - history_segment: version.history_segment.clone(), - })); - } - } - Ok(None) - } - - fn add_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - history_segment: HistorySegment, - ) -> Fallible { - let mut clients = self.clients.write().expect("poisoned lock"); - if let Some(client) = clients.get_mut(&client_id) { - let mut client = client.lock().expect("poisoned lock"); - if client.latest_version_id != NO_VERSION_ID { - if parent_version_id != client.latest_version_id { - return Ok(AddVersionResult::ExpectedParentVersion( - client.latest_version_id, - )); - } - } - - // invent a new ID for this version - let version_id = Uuid::new_v4(); - - client.versions.insert( - parent_version_id, - Version { - version_id, - history_segment, - }, - ); - client.latest_version_id = version_id; - - Ok(AddVersionResult::Ok(version_id)) - } else { - // new client, so insert a client with just this new version - - let latest_version_id = Uuid::new_v4(); - let mut versions = HashMap::new(); - versions.insert( - parent_version_id, - Version { - version_id: latest_version_id, - history_segment, - }, - ); - - clients.insert( - client_id, - Mutex::new(Client { - latest_version_id, - versions, - }), - ); - - Ok(AddVersionResult::Ok(latest_version_id)) - } - } -} diff --git a/sync-server/src/server/mod.rs b/sync-server/src/server/mod.rs index 7768c0c08..9e2412ba3 100644 --- a/sync-server/src/server/mod.rs +++ b/sync-server/src/server/mod.rs @@ -1,10 +1,5 @@ -use failure::Fallible; use taskchampion::Uuid; -mod inmemory; - -pub(crate) use inmemory::InMemorySyncServer; - /// The distinguished value for "no version" pub const NO_VERSION_ID: VersionId = Uuid::nil(); @@ -28,18 +23,3 @@ pub(crate) enum AddVersionResult { /// Rejected; expected a version with the given parent version ExpectedParentVersion(VersionId), } - -pub(crate) trait SyncServer: Sync + Send { - fn get_child_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - ) -> Fallible>; - - fn add_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - history_segment: HistorySegment, - ) -> Fallible; -} diff --git a/sync-server/src/storage/inmemory.rs b/sync-server/src/storage/inmemory.rs new file mode 100644 index 000000000..91c868d42 --- /dev/null +++ b/sync-server/src/storage/inmemory.rs @@ -0,0 +1,90 @@ +use super::{Client, Storage, StorageTxn, Uuid, Version}; +use failure::Fallible; +use std::collections::HashMap; +use std::sync::{Mutex, MutexGuard}; + +struct Inner { + /// Clients, indexed by client_id + clients: HashMap, + + /// Versions, indexed by (client_id, parent_version_id) + versions: HashMap<(Uuid, Uuid), Version>, +} + +pub(crate) struct InMemoryStorage(Mutex); + +impl InMemoryStorage { + pub(crate) fn new() -> Self { + Self(Mutex::new(Inner { + clients: HashMap::new(), + versions: HashMap::new(), + })) + } +} + +struct InnerTxn<'a>(MutexGuard<'a, Inner>); + +/// In-memory storage for testing and experimentation. +/// +/// NOTE: this does not implement transaction rollback. +impl Storage for InMemoryStorage { + fn txn<'a>(&'a self) -> Fallible> { + Ok(Box::new(InnerTxn(self.0.lock().expect("poisoned lock")))) + } +} + +impl<'a> StorageTxn for InnerTxn<'a> { + fn get_client(&mut self, client_id: Uuid) -> Fallible> { + Ok(self.0.clients.get(&client_id).cloned()) + } + + fn set_client_latest_version_id( + &mut self, + client_id: Uuid, + latest_version_id: Uuid, + ) -> Fallible<()> { + if let Some(client) = self.0.clients.get_mut(&client_id) { + client.latest_version_id = latest_version_id; + } else { + self.0 + .clients + .insert(client_id, Client { latest_version_id }); + } + Ok(()) + } + + fn get_version_by_parent( + &mut self, + client_id: Uuid, + parent_version_id: Uuid, + ) -> Fallible> { + Ok(self + .0 + .versions + .get(&(client_id, parent_version_id)) + .cloned()) + } + + fn add_version( + &mut self, + client_id: Uuid, + version_id: Uuid, + parent_version_id: Uuid, + history_segment: Vec, + ) -> Fallible<()> { + // TODO: verify it doesn't exist (`.entry`?) + let version = Version { + version_id, + parent_version_id, + history_segment, + }; + self.0 + .versions + .insert((client_id, version.parent_version_id), version); + Ok(()) + } + + fn commit(&mut self) -> Fallible<()> { + Ok(()) + } +} diff --git a/sync-server/src/storage/mod.rs b/sync-server/src/storage/mod.rs new file mode 100644 index 000000000..2b9bb4dc0 --- /dev/null +++ b/sync-server/src/storage/mod.rs @@ -0,0 +1,56 @@ +use failure::Fallible; +use taskchampion::Uuid; + +mod inmemory; +pub(crate) use inmemory::InMemoryStorage; + +#[derive(Clone)] +pub(crate) struct Client { + pub(crate) latest_version_id: Uuid, +} + +#[derive(Clone)] +pub(crate) struct Version { + pub(crate) version_id: Uuid, + pub(crate) parent_version_id: Uuid, + pub(crate) history_segment: Vec, +} + +pub(crate) trait StorageTxn { + /// Get information about the given client + fn get_client(&mut self, client_id: Uuid) -> Fallible>; + + /// Set the client's latest_version_id (creating the client if necessary) + fn set_client_latest_version_id( + &mut self, + client_id: Uuid, + latest_version_id: Uuid, + ) -> Fallible<()>; + + /// Get a version, indexed by parent version id + fn get_version_by_parent( + &mut self, + client_id: Uuid, + parent_version_id: Uuid, + ) -> Fallible>; + + /// Add a version (that must not already exist) + fn add_version( + &mut self, + client_id: Uuid, + version_id: Uuid, + parent_version_id: Uuid, + history_segment: Vec, + ) -> Fallible<()>; + + /// Commit any changes made in the transaction. It is an error to call this more than + /// once. It is safe to skip this call for read-only operations. + fn commit(&mut self) -> Fallible<()>; +} + +/// 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 { + /// Begin a transaction + fn txn<'a>(&'a self) -> Fallible>; +} diff --git a/sync-server/src/test.rs b/sync-server/src/test.rs deleted file mode 100644 index b9f68a66d..000000000 --- a/sync-server/src/test.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::api::ServerState; -use crate::app_scope; -use crate::server::{ - AddVersionResult, ClientId, GetVersionResult, HistorySegment, SyncServer, VersionId, -}; -use actix_web::{test, App}; -use failure::Fallible; - -#[derive(Default)] -pub(crate) struct TestServer { - /// test server will panic if this is not given - pub expected_client_id: ClientId, - - pub gcv_expected_parent_version_id: VersionId, - pub gcv_result: Option, - - pub av_expected_parent_version_id: VersionId, - pub av_expected_history_segment: HistorySegment, - pub av_result: Option, -} - -impl SyncServer for TestServer { - fn get_child_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - ) -> Fallible> { - assert_eq!(client_id, self.expected_client_id); - assert_eq!(parent_version_id, self.gcv_expected_parent_version_id); - Ok(self.gcv_result.clone()) - } - - fn add_version( - &self, - client_id: ClientId, - parent_version_id: VersionId, - history_segment: HistorySegment, - ) -> Fallible { - assert_eq!(client_id, self.expected_client_id); - assert_eq!(parent_version_id, self.av_expected_parent_version_id); - assert_eq!(history_segment, self.av_expected_history_segment); - Ok(self.av_result.clone().unwrap()) - } -} - -#[actix_rt::test] -async fn test_index_get() { - let server_box: Box = Box::new(TestServer { - ..Default::default() - }); - let server_state = ServerState::new(server_box); - let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - - let req = test::TestRequest::get().uri("/").to_request(); - let resp = test::call_service(&mut app, req).await; - assert!(resp.status().is_success()); -}