Skip to content

Commit

Permalink
Merge pull request #1 from kingledion/initial-publication
Browse files Browse the repository at this point in the history
Initial publication
  • Loading branch information
kingledion authored Nov 6, 2022
2 parents d2eb3fc + 6210200 commit 00ef65f
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 9 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ rand = "0.8"
tokio-util = "0.7"
log = "0.4"
tokio = { version = "1", features = ["full"] }
serde_json = "1.0"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# coouch_rs_test
Tools for testing a CouchDB repository implemented with couch_rs
# couch_rs_test
Tools for testing a CouchDB repository implemented in Rust with couch_rs
77 changes: 70 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
//! A set of helper functions for executing tests with couch_rs library
//!
//! Allows easy execution of tests by providing automated creation and destruction of test databases in a
//! CouchDB instance. For a given database name, the created databases append a random string to guarantee
//! uniqueness of multiple tests in parallel in the same CouchDB instance.
//!
//! In a 'data layer' application model, there is a discrete structure that is used to represent the data
//! layer of an application. When using this package, that data layer is a CouchDB instance. Advanced
//! functionality can be built into the data layer by implementing methods on the data layer struct. A
//! example use is defining a commonly used but complex query as a method.
//!
//! If the data layer application model is used, a helper trait [CouchDBWrapper] can be applied to the data
//! layer struct; this struct would wraps a [couch_rs::Client](https://docs.rs/couch_rs/latest/couch_rs/struct.Client.html)
//! and the name of a database to use. For testing,
//! implementing the [CouchDBWrapper] trait allows it to be wrapped in the [TestRepo] class
//! to provide automatic creationg and destruction of tables during integration tests.
//!
//! The struct against which [CouchDBWrapper] is implemented should only refer to a single CouchDB database.
//! Even though the primary interface is the [couch_rs::Client](https://docs.rs/couch_rs/latest/couch_rs/struct.Client.html),
//! and not the couch_rs database, the automatic
//! creation and destruction implies that only one underlying database is managed by one struct.

#![warn(missing_docs)]

use couch_rs::{document::TypedCouchDocument, error::CouchError, Client};
use rand::{distributions::Alphanumeric, Rng};
use tokio_util::sync::CancellationToken;

pub trait CouchWrapper {
fn new(uri: &str, username: &str, password: &str, db_name: &str) -> Self;
/// A wrapper for a struct that manages on CouchDB database
///
/// This trait should be implemented on a struct that manages a CouchDB database, thus enabling that
/// struct to be used in [TestRepo].
pub trait CouchDBWrapper {
/// Create a new instance of the underlying struct, by passing the sepcifications for the couch_rs
/// client and the name of the database.
fn wrap(uri: &str, username: &str, password: &str, db_name: &str) -> Self;
/// Return the underlying client
fn client(&self) -> &Client;
/// Return the underlying database name
fn dbname(&self) -> &str;
}


/// Configuration for [TestRepo]. This configuration is also passed to the underlying struct on which
/// [CouchDBWrapper] is implemented, in order to create a new [couch_rs::Client](https://docs.rs/couch_rs/latest/couch_rs/struct.Client.html)
/// and identify the associated database.
#[derive(Clone)]
pub struct TestRepoConfig {
uri: String,
Expand All @@ -18,6 +52,8 @@ pub struct TestRepoConfig {
}

impl TestRepoConfig {
/// Create a new configuration; identifying the uri, username and password for the CouchDB instance as
/// well as the database name.
pub fn new(uri: &str, uname: &str, pwd: &str, dbname: &str) -> TestRepoConfig {
TestRepoConfig {
uri: uri.to_string(),
Expand All @@ -35,14 +71,37 @@ impl TestRepoConfig {
}
}

pub struct TestRepo<T: CouchWrapper> {
/// A wrapper for a struct that encapsulates functionality of an application's data layer.
///
/// The type parameter is the type of the data layer struct to be wrapped. The data layer struct must
/// implement the [CouchDBWrapper] trait.
///
/// Creation of a new instance of this struct will create a unique, associated CouchDB database. Internally,
/// this struct implements a drop token and watcher to determine when this struct is de-allocated, thus
/// triggering destruction of the associated CouchDB database.
pub struct TestRepo<T: CouchDBWrapper> {
/// A struct that encapsulates the functionality of an application's data layer. Test using
/// this wrapper should call for this struct in order to perform test actions against this
/// TestRepo's ephemeral CouchDB database.
pub repo: T,

drop_token: CancellationToken,
dropped_token: CancellationToken,
}

impl<T: CouchWrapper> TestRepo<T> {
impl<T: CouchDBWrapper> TestRepo<T> {
/// Creates a new instance of TestRepo wrapping a new instance of the type paramter (an implementation
/// of [CouchDBWrapper]). This function will create a new [couch_rs::Client](https://docs.rs/couch_rs/latest/couch_rs/struct.Client.html)
/// from the parameters passed
/// as part of the [TestRepoConfig] argument. It will then create a new database in CouchDB using the
/// client connection and a database name consisting of the name defined in config plus a random suffix.
/// This randomization of database names helps prevent collisions during parallel test runs against
/// the same CouchDB instance.
///
/// This function also creates a drop token and watcher to determine when this instance is de-allocated
/// The watcher spawn an asynchronous thread that will observe the drop token every 100 milliseconds.
/// when this instance is deallocated, the drop token is destroyed and the watcher will trigger the
/// destruction of the database instance created by this method.
pub async fn new(arg_cfg: TestRepoConfig) -> TestRepo<T> {
// create random identifier for database and append to db name
let test_identifier = rand::thread_rng()
Expand All @@ -55,7 +114,7 @@ impl<T: CouchWrapper> TestRepo<T> {
let db_unique_name = format!("{}-{}", arg_cfg.db_name, test_identifier);
let cfg = arg_cfg.with_name(db_unique_name);

let wrapped = T::new(&cfg.uri, &cfg.username, &cfg.password, &cfg.db_name);
let wrapped = T::wrap(&cfg.uri, &cfg.username, &cfg.password, &cfg.db_name);

let drop_token = CancellationToken::new();
let dropped_token = TestRepo::<T>::start_drop_watcher(&drop_token, cfg.clone()).await;
Expand Down Expand Up @@ -94,6 +153,9 @@ impl<T: CouchWrapper> TestRepo<T> {
}
}

/// Pushes data to the unique database associated with this instance. Data is pushed via the
/// [couch_rs::database::bulk_docs](https://docs.rs/couch_rs/latest/couch_rs/database/struct.Database.html#method.bulk_docs)
/// method.
pub async fn with_data<S: TypedCouchDocument>(
&self,
data: &mut [S],
Expand Down Expand Up @@ -138,9 +200,10 @@ impl<T: CouchWrapper> TestRepo<T> {
Err(e) => log::error!("Error while cleaning up {}: {}", cfg.db_name, e),
};
}

}

impl<T: CouchWrapper> Drop for TestRepo<T> {
impl<T: CouchDBWrapper> Drop for TestRepo<T> {
fn drop(&mut self) {
self.drop_token.cancel();

Expand Down

0 comments on commit 00ef65f

Please sign in to comment.