From 4a1085b7e9a6231f3e21334af1ea43b71ee8d918 Mon Sep 17 00:00:00 2001 From: Vika Date: Wed, 5 May 2021 16:31:52 +0300 Subject: Moved the Redis spawner to the Redis module where it belongs, refactored tests to use the Redis database instead of a fake one --- src/database/mod.rs | 52 +++++-------------------- src/database/redis/mod.rs | 39 +++++++++++++++++++ src/lib.rs | 97 +++++++++++++++++++++++++++++------------------ 3 files changed, 110 insertions(+), 78 deletions(-) diff --git a/src/database/mod.rs b/src/database/mod.rs index cdd5c43..1a6aa84 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,17 +1,17 @@ #![warn(missing_docs)] use async_trait::async_trait; use serde::{Serialize,Deserialize}; +use crate::indieauth::User; +mod redis; +pub use crate::database::redis::RedisStorage; +#[cfg(test)] +pub use redis::tests::get_redis_instance; #[cfg(test)] mod memory; #[cfg(test)] pub(crate) use crate::database::memory::MemoryStorage; -use crate::indieauth::User; - -mod redis; -pub use crate::database::redis::RedisStorage; - #[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct MicropubChannel { pub uid: String, @@ -169,9 +169,8 @@ pub trait Storage: Clone + Send + Sync { #[cfg(test)] mod tests { use super::{Storage, MicropubChannel}; - use std::{process}; - use std::time::Duration; use serde_json::json; + use super::redis::tests::get_redis_instance; async fn test_backend_basic_operations(backend: Backend) { let post: serde_json::Value = json!({ @@ -236,49 +235,18 @@ mod tests { test_backend_get_channel_list(backend).await } - async fn get_redis_backend() -> (tempdir::TempDir, process::Child, super::RedisStorage) { - let tempdir = tempdir::TempDir::new("redis").expect("failed to create tempdir"); - let socket = tempdir.path().join("redis.sock"); - let redis_child = process::Command::new("redis-server") - .current_dir(&tempdir) - .arg("--port").arg("0") - .arg("--unixsocket").arg(&socket) - .stdout(process::Stdio::null()) - .stderr(process::Stdio::null()) - .spawn().expect("Failed to spawn Redis"); - println!("redis+unix:///{}", socket.to_str().unwrap()); - let uri = format!("redis+unix:///{}", socket.to_str().unwrap()); - // There should be a slight delay, we need to wait for Redis to spin up - let client = redis::Client::open(uri.clone()).unwrap(); - let millisecond = Duration::from_millis(1); - let mut retries: usize = 0; - const MAX_RETRIES: usize = 60 * 1000/*ms*/; - while let Err(err) = client.get_connection() { - if err.is_connection_refusal() { - async_std::task::sleep(millisecond).await; - retries += 1; - if retries > MAX_RETRIES { - panic!("Timeout waiting for Redis, last error: {}", err); - } - } else { - panic!("Could not connect: {}", err); - } - } - let backend = super::RedisStorage::new(uri).await.unwrap(); - - return (tempdir, redis_child, backend) - } - #[async_std::test] async fn test_redis_storage_basic_operations() { - let (tempdir, mut redis, backend) = get_redis_backend().await; + let (tempdir, mut redis, uri) = get_redis_instance().await; + let backend = super::RedisStorage::new(uri).await.unwrap(); test_backend_basic_operations(backend).await; redis.kill().expect("Redis wasn't running"); drop(tempdir); } #[async_std::test] async fn test_redis_storage_channel_support() { - let (tempdir, mut redis, backend) = get_redis_backend().await; + let (tempdir, mut redis, uri) = get_redis_instance().await; + let backend = super::RedisStorage::new(uri).await.unwrap(); test_backend_get_channel_list(backend).await; redis.kill().expect("Redis wasn't running"); drop(tempdir); diff --git a/src/database/redis/mod.rs b/src/database/redis/mod.rs index eded583..ccbf831 100644 --- a/src/database/redis/mod.rs +++ b/src/database/redis/mod.rs @@ -245,4 +245,43 @@ impl RedisStorage { Err(e) => Err(e.into()) } } +} + +#[cfg(test)] +pub mod tests { + use std::{process}; + use std::time::Duration; + use mobc_redis::redis; + + pub async fn get_redis_instance() -> (tempdir::TempDir, process::Child, String) { + let tempdir = tempdir::TempDir::new("redis").expect("failed to create tempdir"); + let socket = tempdir.path().join("redis.sock"); + let redis_child = process::Command::new("redis-server") + .current_dir(&tempdir) + .arg("--port").arg("0") + .arg("--unixsocket").arg(&socket) + .stdout(process::Stdio::null()) + .stderr(process::Stdio::null()) + .spawn().expect("Failed to spawn Redis"); + println!("redis+unix:///{}", socket.to_str().unwrap()); + let uri = format!("redis+unix:///{}", socket.to_str().unwrap()); + // There should be a slight delay, we need to wait for Redis to spin up + let client = redis::Client::open(uri.clone()).unwrap(); + let millisecond = Duration::from_millis(1); + let mut retries: usize = 0; + const MAX_RETRIES: usize = 60 * 1000/*ms*/; + while let Err(err) = client.get_connection() { + if err.is_connection_refusal() { + async_std::task::sleep(millisecond).await; + retries += 1; + if retries > MAX_RETRIES { + panic!("Timeout waiting for Redis, last error: {}", err); + } + } else { + panic!("Could not connect: {}", err); + } + } + + return (tempdir, redis_child, uri) + } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 99d74b9..6a0a1cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,6 +92,17 @@ pub async fn get_app_with_memory_for_testing(token_endpoint: surf::Url) -> (data return (database, equip_app(app)) } +#[cfg(test)] +pub async fn get_app_with_test_redis(token_endpoint: surf::Url) -> (tempdir::TempDir, std::process::Child, database::RedisStorage, App) { + let (tempdir, child, uri) = crate::database::get_redis_instance().await; + let backend = database::RedisStorage::new(uri).await.unwrap(); + let app = tide::with_state(ApplicationState { + token_endpoint, media_endpoint: None, + storage: backend.clone(), + http_client: surf::Client::new(), + }); + return (tempdir, child, backend, equip_app(app)) +} #[cfg(test)] #[allow(unused_variables,unused_imports)] @@ -103,11 +114,13 @@ mod tests { use mockito::mock; // Helpers - async fn create_app() -> (database::MemoryStorage, App) { - get_app_with_memory_for_testing(surf::Url::parse(&*mockito::server_url()).unwrap()).await + async fn create_app() -> (database::RedisStorage, App, tempdir::TempDir, std::process::Child) { + //get_app_with_memory_for_testing(surf::Url::parse(&*mockito::server_url()).unwrap()).await + let (t, c, b, a) = get_app_with_test_redis(surf::Url::parse(&*mockito::server_url()).unwrap()).await; + (b, a, t, c) } - async fn post_json(app: &App, json: serde_json::Value) -> surf::Response { + async fn post_json(app: &App, json: serde_json::Value) -> surf::Response { let request = app.post("/micropub") .header("Authorization", "Bearer test") .header("Content-Type", "application/json") @@ -123,34 +136,36 @@ mod tests { .with_body(r#"{"me": "https://fireburn.ru", "client_id": "https://quill.p3k.io/", "scope": "create update media"}"#) .create(); - let (db, app) = create_app().await; + let (db, app, tempdir, mut child) = create_app().await; - let response = post_json(&app, json!({ - "type": ["h-entry"], - "properties": { - "content": ["Fake news about Aaron Parecki!"], - "uid": ["https://aaronparecki.com/posts/fake-news"] - } - })).await; - assert_eq!(response.status(), 403); - - let response = post_json(&app, json!({ - "type": ["h-entry"], - "properties": { - "content": ["More fake news about Aaron Parecki!"], - "url": ["https://aaronparecki.com/posts/more-fake-news"] - } - })).await; - assert_eq!(response.status(), 403); - - let response = post_json(&app, json!({ - "type": ["h-entry"], - "properties": { - "content": ["Sneaky advertisement designed to creep into someone else's feed! Buy whatever I'm promoting!"], - "channel": ["https://aaronparecki.com/feeds/main"] - } - })).await; - assert_eq!(response.status(), 403); + let response = post_json(&app, json!({ + "type": ["h-entry"], + "properties": { + "content": ["Fake news about Aaron Parecki!"], + "uid": ["https://aaronparecki.com/posts/fake-news"] + } + })).await; + assert_eq!(response.status(), 403); + + let response = post_json(&app, json!({ + "type": ["h-entry"], + "properties": { + "content": ["More fake news about Aaron Parecki!"], + "url": ["https://aaronparecki.com/posts/more-fake-news"] + } + })).await; + assert_eq!(response.status(), 403); + + let response = post_json(&app, json!({ + "type": ["h-entry"], + "properties": { + "content": ["Sneaky advertisement designed to creep into someone else's feed! Buy whatever I'm promoting!"], + "channel": ["https://aaronparecki.com/feeds/main"] + } + })).await; + assert_eq!(response.status(), 403); + + child.kill().expect("Couldn't kill Redis"); } #[async_std::test] @@ -161,12 +176,14 @@ mod tests { .with_body(r#"{"me": "https://fireburn.ru", "client_id": "https://quill.p3k.io/", "scope": "create update media"}"#) .create(); - let (db, app) = create_app().await; + let (db, app, tempdir, mut child) = create_app().await; let response: serde_json::Value = app.get("/micropub?q=config") .header("Authorization", "test") .recv_json().await.unwrap(); assert!(!response["q"].as_array().unwrap().is_empty()); + + child.kill().expect("Couldn't kill Redis"); } #[async_std::test] @@ -177,21 +194,25 @@ mod tests { .with_body(r#"{"error":"unauthorized","error_description":"A valid access token is required."}"#) .create(); - let (db, app) = create_app().await; + let (db, app, tempdir, mut child) = create_app().await; let response: surf::Response = app.get("/micropub?q=config") .header("Authorization", "test") .send().await.unwrap(); assert_eq!(response.status(), 401); + + child.kill().expect("Couldn't kill Redis"); } #[async_std::test] async fn test_no_auth_header() { - let (db, app) = create_app().await; + let (db, app, tempdir, mut child) = create_app().await; let request: surf::RequestBuilder = app.get("/micropub?q=config"); let response: surf::Response = request.send().await.unwrap(); assert_eq!(response.status(), 401); + + child.kill().expect("Couldn't kill Redis"); } #[async_std::test] @@ -202,7 +223,7 @@ mod tests { .with_body(r#"{"me": "https://fireburn.ru", "client_id": "https://quill.p3k.io/", "scope": "create update media"}"#) .create(); - let (storage, app) = create_app().await; + let (storage, app, tempdir, mut child) = create_app().await; let request: surf::RequestBuilder = app.post("/micropub") .header("Authorization", "Bearer test") @@ -215,6 +236,8 @@ mod tests { // Assume the post is in the database at this point. let post = storage.get_post(&uid).await.unwrap().unwrap(); assert_eq!(post["properties"]["content"][0]["html"].as_str().unwrap().trim(), "

something interesting

"); + + child.kill().expect("Couldn't kill Redis"); } #[async_std::test] @@ -225,7 +248,7 @@ mod tests { .with_body(r#"{"me": "https://fireburn.ru", "client_id": "https://quill.p3k.io/", "scope": "create update media"}"#) .create(); - let (storage, app) = create_app().await; + let (storage, app, tempdir, mut child) = create_app().await; let mut response = post_json(&app, json!({ "type": ["h-entry"], @@ -254,11 +277,13 @@ mod tests { assert!(response.status() == 201 || response.status() == 202); let uid = response.header("Location").unwrap().last().to_string(); // Assume the post is in the database at this point. - println!("Keys in database: {:?}", storage.mapping.read().await.keys()); + //println!("Keys in database: {:?}", storage.mapping.read().await.keys()); let new_feed = storage.get_post("https://fireburn.ru/feeds/main").await.unwrap().unwrap(); println!("{}", new_feed["children"]); assert_eq!(new_feed["children"].as_array().unwrap().len(), 2); assert_eq!(new_feed["children"][0].as_str().unwrap(), uid); assert_eq!(new_feed["children"][1].as_str().unwrap(), first_uid); + + child.kill().expect("Couldn't kill Redis"); } } -- cgit 1.4.1