diff --git a/proc_macros/proc_macros.rs b/proc_macros/proc_macros.rs index 3c9685f..b913e5f 100644 --- a/proc_macros/proc_macros.rs +++ b/proc_macros/proc_macros.rs @@ -114,7 +114,7 @@ pub fn async_timeout_test(attr: TokenStream, item: TokenStream) -> TokenStream { let f = async { #(#stmts)* }; - tokio::time::timeout(dura, f).await.unwrap(); + tokio::time::timeout(dura, f).await.unwrap() } }; stream.into() diff --git a/src/db/mod.rs b/src/db/mod.rs index 3165e30..96d5d72 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -16,7 +16,7 @@ pub use traits::PixivDownloaderDb; pub use user::User; pub type PixivDownloaderDbError = anyhow::Error; -use crate::{get_helper, gettext}; +use crate::gettext; #[cfg(feature = "db_sqlite")] impl From for PixivDownloaderDbError { @@ -29,8 +29,9 @@ impl From for PixivDownloaderDbError { compile_error!("No database backend is enabled."); /// Open the database -pub fn open_database() -> Result, PixivDownloaderDbError> { - let cfg = get_helper().db(); +pub fn open_database( + cfg: PixivDownloaderDbConfig, +) -> Result, PixivDownloaderDbError> { if cfg.is_none() { return Err(PixivDownloaderDbError::msg(String::from(gettext( "No database configuration provided.", @@ -49,8 +50,9 @@ pub fn open_database() -> Result, Pixiv /// Open the database and initialize it pub async fn open_and_init_database( + cfg: PixivDownloaderDbConfig, ) -> Result, PixivDownloaderDbError> { - let db = open_database()?; + let db = open_database(cfg)?; db.init().await?; Ok(db) } diff --git a/src/error.rs b/src/error.rs index 04f5fd8..1492418 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,6 +23,8 @@ pub enum PixivDownloaderError { FromUtf8Error(std::string::FromUtf8Error), #[cfg(feature = "server")] ToStrError(http::header::ToStrError), + #[cfg(all(feature = "server", feature = "db_sqlite", test))] + JSONError(json::Error), } impl From<&str> for PixivDownloaderError { diff --git a/src/server/auth/user.rs b/src/server/auth/user.rs index 27bdd86..ab4e04c 100644 --- a/src/server/auth/user.rs +++ b/src/server/auth/user.rs @@ -45,6 +45,22 @@ impl AuthUserContext { .try_err((3, gettext("No password specified.")))?; let password = base64::decode(password) .try_err3(4, gettext("Failed to decode password with base64:"))?; + let rsa_key = self.ctx.rsa_key.lock().await; + match *rsa_key { + Some(ref key) => { + if key.is_too_old() { + return Err(( + 6, + gettext("RSA key is too old. A new key should be generated."), + ) + .into()); + } + let password = key + .decrypt(&password) + .try_err3(7, gettext("Failed to decrypt password with RSA:"))?; + } + None => return Err((5, gettext("No RSA key found.")).into()), + } Ok(json::object! {}) } }, diff --git a/src/server/context.rs b/src/server/context.rs index 176d72b..acc453f 100644 --- a/src/server/context.rs +++ b/src/server/context.rs @@ -1,6 +1,7 @@ use super::auth::RSAKey; use super::cors::CorsContext; use crate::db::{open_and_init_database, PixivDownloaderDb}; +use crate::get_helper; use crate::gettext; use futures_util::lock::Mutex; @@ -14,7 +15,7 @@ impl ServerContext { pub async fn default() -> Self { Self { cors: CorsContext::default(), - db: match open_and_init_database().await { + db: match open_and_init_database(get_helper().db()).await { Ok(db) => db, Err(e) => panic!("{} {}", gettext("Failed to open database:"), e), }, diff --git a/src/server/cors.rs b/src/server/cors.rs index 80939c4..8bc896b 100644 --- a/src/server/cors.rs +++ b/src/server/cors.rs @@ -361,6 +361,15 @@ pub struct CorsContext { } impl CorsContext { + #[cfg(test)] + pub fn new(allow_all: bool, entries: Vec, hosts: Vec) -> Self { + Self { + allow_all, + entries, + hosts, + } + } + pub fn matches(&self, host: T) -> CorsResult where CorsEntry: PartialEq, diff --git a/src/server/mod.rs b/src/server/mod.rs index 5c8b67e..f9a7633 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -15,5 +15,7 @@ pub mod route; pub mod service; /// Traits pub mod traits; +#[cfg(all(test, feature = "db_sqlite"))] +mod unittest; /// Version pub mod version; diff --git a/src/server/unittest/mod.rs b/src/server/unittest/mod.rs new file mode 100644 index 0000000..33ae657 --- /dev/null +++ b/src/server/unittest/mod.rs @@ -0,0 +1,67 @@ +mod version; + +use super::context::ServerContext; +use super::cors::CorsContext; +use super::route::ServerRoutes; +use crate::db::{open_and_init_database, PixivDownloaderDbConfig}; +use crate::error::PixivDownloaderError; +use futures_util::lock::Mutex; +use hyper::{Body, Request, Response}; +use json::JsonValue; +use std::sync::Arc; + +pub struct UnitTestContext { + ctx: Arc, + routes: ServerRoutes, +} + +impl UnitTestContext { + pub async fn new() -> Self { + Self { + ctx: Arc::new(ServerContext { + cors: CorsContext::new(true, vec![], vec![]), + db: open_and_init_database( + PixivDownloaderDbConfig::new(&json::object! { + "type": "sqlite", + "path": "test/server.db", + }) + .unwrap(), + ) + .await + .unwrap(), + rsa_key: Mutex::new(None), + }), + routes: ServerRoutes::new(), + } + } + + pub async fn request( + &self, + req: Request, + ) -> Result>, PixivDownloaderError> { + Ok(match self.routes.match_route(&req, &self.ctx) { + Some(r) => Some(r.response(req).await?), + None => None, + }) + } + + pub async fn request_json( + &self, + req: Request, + ) -> Result, PixivDownloaderError> { + Ok(match self.request(req).await? { + Some(r) => Some(json::parse(&String::from_utf8_lossy( + &hyper::body::to_bytes(r.into_body()).await?, + ))?), + None => None, + }) + } +} + +#[proc_macros::async_timeout_test(120s)] +#[tokio::test(flavor = "multi_thread")] +async fn test() -> Result<(), PixivDownloaderError> { + let ctx = UnitTestContext::new().await; + version::test(&ctx).await?; + Ok(()) +} diff --git a/src/server/unittest/version.rs b/src/server/unittest/version.rs new file mode 100644 index 0000000..b27837b --- /dev/null +++ b/src/server/unittest/version.rs @@ -0,0 +1,11 @@ +use super::super::version::VERSION; +use super::UnitTestContext; +use crate::error::PixivDownloaderError; +use hyper::{Body, Request}; + +pub async fn test(ctx: &UnitTestContext) -> Result<(), PixivDownloaderError> { + let re = Request::builder().uri("/version").body(Body::empty())?; + let res = ctx.request_json(re).await?.unwrap(); + assert_eq!(res, json::object! {"version": VERSION.to_vec()}); + Ok(()) +} diff --git a/src/server/version.rs b/src/server/version.rs index 49fb566..b591adf 100644 --- a/src/server/version.rs +++ b/src/server/version.rs @@ -1,5 +1,7 @@ use super::preclude::*; +pub const VERSION: [u8; 4] = [0, 0, 1, 0]; + pub struct VersionContext { ctx: Arc, } @@ -26,7 +28,7 @@ impl ResponseJsonFor for VersionContext { OPTIONS, POST, ); - Ok(builder.body(json::object! {"version": [0, 0, 1, 0]})?) + Ok(builder.body(json::object! {"version": VERSION.to_vec()})?) } }