Skip to content

Commit

Permalink
Initial addition of code to repo; documentation and docs tests tbd
Browse files Browse the repository at this point in the history
  • Loading branch information
kingledion committed Sep 26, 2022
1 parent f06cb86 commit d2eb3fc
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk


# Added by cargo

/target
/Cargo.lock
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "couch_rs_test"
version = "0.1.0"
edition = "2021"

[dependencies]
http = "0.2"
couch_rs = "0.9"
rand = "0.8"
tokio-util = "0.7"
log = "0.4"
tokio = { version = "1", features = ["full"] }
151 changes: 151 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
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;
fn client(&self) -> &Client;
fn dbname(&self) -> &str;
}


#[derive(Clone)]
pub struct TestRepoConfig {
uri: String,
username: String,
password: String,
db_name: String,
}

impl TestRepoConfig {
pub fn new(uri: &str, uname: &str, pwd: &str, dbname: &str) -> TestRepoConfig {
TestRepoConfig {
uri: uri.to_string(),
username: uname.to_string(),
password: pwd.to_string(),
db_name: dbname.to_string(),
}
}

fn with_name(self, db_unique_name: String) -> TestRepoConfig {
TestRepoConfig {
db_name: db_unique_name,
..self
}
}
}

pub struct TestRepo<T: CouchWrapper> {
pub repo: T,

drop_token: CancellationToken,
dropped_token: CancellationToken,
}

impl<T: CouchWrapper> TestRepo<T> {
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()
.sample_iter(&Alphanumeric)
.take(12)
.map(char::from)
.collect::<String>()
.to_lowercase();

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 drop_token = CancellationToken::new();
let dropped_token = TestRepo::<T>::start_drop_watcher(&drop_token, cfg.clone()).await;

// connect to database and return wrapping repository
log::info!("Creating database {} for testing", cfg.db_name);

// create test database - panic on fail
let c = wrapped.client();
match c.make_db(&cfg.db_name).await {
Ok(_) => {}
Err(e) => {
match e.status() {
Some(code) => {
match code {
// database already exists; this should not happen,
// requires manual cleanup
http::status::StatusCode::PRECONDITION_FAILED => {
panic!(
"Database {} already exists and must be manually removed.",
cfg.db_name
)
}
_ => panic!("Error while creating new database: {}", e),
}
}
None => panic!("Error while creating new database: {}", e),
}
}
};

TestRepo {
repo: wrapped,
drop_token: drop_token,
dropped_token: dropped_token,
}
}

pub async fn with_data<S: TypedCouchDocument>(
&self,
data: &mut [S],
) -> Result<usize, CouchError> {
let db = self.repo.client().db(&self.repo.dbname()).await?;
let result = db.bulk_docs(data).await?;
return Ok(result.len());
}

async fn start_drop_watcher(
drop_token: &CancellationToken,
cfg: TestRepoConfig,
) -> CancellationToken {
let drop_child = drop_token.child_token();

let dropped_token = CancellationToken::new();
let dropped_child = dropped_token.child_token();

tokio::spawn(async move {
while !drop_child.is_cancelled() {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}

TestRepo::<T>::drop(cfg).await;

dropped_token.cancel();
});

dropped_child
}

async fn drop(cfg: TestRepoConfig) {
// delete test db - panic on fail
let c = couch_rs::Client::new(&cfg.uri, &cfg.username, &cfg.password).unwrap();

match c.destroy_db(&cfg.db_name).await {
Ok(b) => match b {
true => log::info!("Cleaned up database {}", cfg.db_name),
false => log::info!("Failed to clean up database {}", cfg.db_name),
},

Err(e) => log::error!("Error while cleaning up {}: {}", cfg.db_name, e),
};
}
}

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

while !self.dropped_token.is_cancelled() {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}

0 comments on commit d2eb3fc

Please sign in to comment.