diff --git a/Cargo.lock b/Cargo.lock index 990e659..cb80aa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2071,6 +2071,7 @@ dependencies = [ "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "ape 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "app_dirs 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-trait 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "diesel 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 8f7eb28..c0a2f44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,17 +8,18 @@ edition = "2018" default = ["service-actix"] ui = [] profile-index = ["flame", "flamer"] -service-actix = ["actix-files", "actix-http", "actix-rt", "actix-web"] +service-actix = ["actix-files", "actix-http", "actix-web"] service-rocket = ["rocket", "rocket_contrib"] [dependencies] actix-files = { version = "0.2", optional = true } actix-http = { version = "1.0", optional = true } actix-web = { version = "2.0", optional = true } -actix-rt = { version = "1.0", optional = true } +actix-rt = { version = "1.0"} anyhow = "1.0" ape = "0.2.0" app_dirs = "1.1.1" +async-trait = "0.1.22" base64 = "0.11.0" diesel = { version = "1.4", features = ["sqlite", "r2d2"] } diesel_migrations = { version = "1.4", features = ["sqlite"] } diff --git a/src/main.rs b/src/main.rs index ced7af9..cb02cf8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ #![recursion_limit = "256"] -#![feature(proc_macro_hygiene, decl_macro)] +#![feature(proc_macro_hygiene, decl_macro, type_alias_impl_trait)] #[macro_use] extern crate diesel; diff --git a/src/service/actix/api.rs b/src/service/actix/api.rs index ef7e2e3..f7d5b32 100644 --- a/src/service/actix/api.rs +++ b/src/service/actix/api.rs @@ -2,7 +2,7 @@ use actix_http::ResponseBuilder; use actix_web::{error, get, http::header, http::StatusCode, put, web, HttpResponse}; use anyhow::*; -use crate::config::{self, Config, Preferences}; +use crate::config::{self, Config}; use crate::db::DB; use crate::service::constants::*; use crate::service::dto; @@ -46,7 +46,7 @@ pub async fn get_initial_setup(db: web::Data) -> Result, - // _admin_rights: AdminRights, // TODO + // _admin_rights: AdminRights, // TODO.important config: web::Json, ) -> Result { web::block(move || config::amend(&db, &config)) diff --git a/src/service/actix/mod.rs b/src/service/actix/mod.rs index e420973..d2e41f7 100644 --- a/src/service/actix/mod.rs +++ b/src/service/actix/mod.rs @@ -8,7 +8,7 @@ pub mod server; mod api; #[cfg(test)] -mod tests; +pub mod test; fn configure_app( cfg: &mut web::ServiceConfig, diff --git a/src/service/actix/test.rs b/src/service/actix/test.rs new file mode 100644 index 0000000..a140bec --- /dev/null +++ b/src/service/actix/test.rs @@ -0,0 +1,84 @@ +use actix_http::{Error, Request}; +use actix_web::dev::{Body, ResponseBody, Service, ServiceResponse}; +use actix_web::test::TestRequest; +use actix_web::{test, App}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::fs; +use std::path::PathBuf; + +use crate::db::DB; + +fn configure_test_app(cfg: &mut actix_web::web::ServiceConfig, db_name: &str) { + let web_url = "/"; + let web_dir_path = PathBuf::from("web"); + + let swagger_url = "swagger"; + let mut swagger_dir_path = PathBuf::from("docs"); + swagger_dir_path.push("swagger"); + + let mut db_path = PathBuf::new(); + db_path.push("test"); + db_path.push(format!("{}.sqlite", db_name)); + if db_path.exists() { + fs::remove_file(&db_path).unwrap(); + } + let db = DB::new(&db_path).unwrap(); + + super::configure_app( + cfg, + web_url, + web_dir_path.as_path(), + swagger_url, + swagger_dir_path.as_path(), + &db, + ); +} + +pub type ServiceType = impl Service; + +pub async fn make_service(test_name: &str) -> ServiceType { + let app = App::new().configure(|cfg| configure_test_app(cfg, test_name)); + let service = test::init_service(app).await; + service +} + +pub async fn get(service: &mut ServiceType, url: &str) { + let req = TestRequest::get().uri(url).to_request(); + let resp = service.call(req).await.unwrap(); + assert!(resp.status().is_success()); +} + +pub async fn get_json(service: &mut ServiceType, url: &str) -> T { + let req = TestRequest::get().uri(url).to_request(); + let resp = service.call(req).await.unwrap(); + assert!(resp.status().is_success()); + let body = resp.response().body().as_u8(); + let response_json: T = serde_json::from_slice(body).unwrap(); + response_json +} + +pub async fn put_json(service: &mut ServiceType, url: &str, payload: &T) { + let req = TestRequest::put().uri(url).set_json(payload).to_request(); + let resp = service.call(req).await.unwrap(); + assert!(resp.status().is_success()); +} + +trait BodyToBytes { + fn as_u8(&self) -> &[u8]; +} + +impl BodyToBytes for ResponseBody { + fn as_u8(&self) -> &[u8] { + match self { + ResponseBody::Body(ref b) => match b { + Body::Bytes(ref by) => by.as_ref(), + _ => panic!(), + }, + ResponseBody::Other(ref b) => match b { + Body::Bytes(ref by) => by.as_ref(), + _ => panic!(), + }, + } + } +} diff --git a/src/service/actix/tests/api.rs b/src/service/actix/tests/api.rs deleted file mode 100644 index af27cd2..0000000 --- a/src/service/actix/tests/api.rs +++ /dev/null @@ -1,117 +0,0 @@ -use actix_http::Request; -use actix_web::dev::*; -use actix_web::test::TestRequest; -use actix_web::{test, App}; -use function_name::named; - -use super::configure_test_app; -use crate::config; -use crate::service::dto; -use crate::vfs; - -const TEST_USERNAME: &str = "test_user"; -const TEST_PASSWORD: &str = "test_password"; -const TEST_MOUNT_NAME: &str = "collection"; -const TEST_MOUNT_SOURCE: &str = "test/collection"; - -trait BodyTest { - fn as_u8(&self) -> &[u8]; -} - -impl BodyTest for ResponseBody { - fn as_u8(&self) -> &[u8] { - match self { - ResponseBody::Body(ref b) => match b { - Body::Bytes(ref by) => by.as_ref(), - _ => panic!(), - }, - ResponseBody::Other(ref b) => match b { - Body::Bytes(ref by) => by.as_ref(), - _ => panic!(), - }, - } - } -} - -fn initial_setup() -> Request { - let configuration = config::Config { - album_art_pattern: None, - prefix_url: None, - reindex_every_n_seconds: None, - ydns: None, - users: Some(vec![config::ConfigUser { - name: TEST_USERNAME.into(), - password: TEST_PASSWORD.into(), - admin: true, - }]), - mount_dirs: Some(vec![vfs::MountPoint { - name: TEST_MOUNT_NAME.into(), - source: TEST_MOUNT_SOURCE.into(), - }]), - }; - - TestRequest::put() - .uri("/api/settings") - .set_json(&configuration) - .to_request() -} - -#[named] -#[actix_rt::test] -async fn test_version() { - let app = App::new().configure(|cfg| configure_test_app(cfg, function_name!())); - let mut service = test::init_service(app).await; - let req = TestRequest::get().uri("/api/version").to_request(); - let resp = service.call(req).await.unwrap(); - assert!(resp.status().is_success()); - - let body = resp.response().body().as_u8(); - let response_json: dto::Version = serde_json::from_slice(body).unwrap(); - assert_eq!(response_json, dto::Version { major: 4, minor: 0 }); -} - -#[named] -#[actix_rt::test] -async fn test_initial_setup() { - let app = App::new().configure(|cfg| configure_test_app(cfg, function_name!())); - let mut service = test::init_service(app).await; - - { - let req = TestRequest::get().uri("/api/initial_setup").to_request(); - let resp = service.call(req).await.unwrap(); - assert!(resp.status().is_success()); - - let body = resp.response().body().as_u8(); - let response_json: dto::InitialSetup = serde_json::from_slice(body).unwrap(); - - assert_eq!( - response_json, - dto::InitialSetup { - has_any_users: false - } - ); - } - - assert!(service - .call(initial_setup()) - .await - .unwrap() - .status() - .is_success()); - - { - let req = TestRequest::get().uri("/api/initial_setup").to_request(); - let resp = service.call(req).await.unwrap(); - assert!(resp.status().is_success()); - - let body = resp.response().body().as_u8(); - let response_json: dto::InitialSetup = serde_json::from_slice(body).unwrap(); - - assert_eq!( - response_json, - dto::InitialSetup { - has_any_users: true - } - ); - } -} diff --git a/src/service/actix/tests/mod.rs b/src/service/actix/tests/mod.rs deleted file mode 100644 index 897a9ea..0000000 --- a/src/service/actix/tests/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::fs; -use std::path::PathBuf; - -use crate::db::DB; - -mod api; -mod swagger; -mod web; - -fn configure_test_app(cfg: &mut actix_web::web::ServiceConfig, db_name: &str) { - let web_url = "/"; - let web_dir_path = PathBuf::from("web"); - - let swagger_url = "swagger"; - let mut swagger_dir_path = PathBuf::from("docs"); - swagger_dir_path.push("swagger"); - - let mut db_path = PathBuf::new(); - db_path.push("test"); - db_path.push(format!("{}.sqlite", db_name)); - if db_path.exists() { - fs::remove_file(&db_path).unwrap(); - } - let db = DB::new(&db_path).unwrap(); - - super::configure_app( - cfg, - web_url, - web_dir_path.as_path(), - swagger_url, - swagger_dir_path.as_path(), - &db, - ); -} diff --git a/src/service/actix/tests/swagger.rs b/src/service/actix/tests/swagger.rs deleted file mode 100644 index e421ff1..0000000 --- a/src/service/actix/tests/swagger.rs +++ /dev/null @@ -1,26 +0,0 @@ -use actix_web::dev::Service; -use actix_web::test::TestRequest; -use actix_web::{test, App}; -use function_name::named; - -use super::configure_test_app; - -#[named] -#[actix_rt::test] -async fn test_swagger_index() { - let app = App::new().configure(|cfg| configure_test_app(cfg, function_name!())); - let mut service = test::init_service(app).await; - let req = TestRequest::get().uri("/swagger").to_request(); - let resp = service.call(req).await.unwrap(); - assert!(resp.status().is_success()); -} - -#[named] -#[actix_rt::test] -async fn test_swagger_index_with_trailing_slash() { - let app = App::new().configure(|cfg| configure_test_app(cfg, function_name!())); - let mut service = test::init_service(app).await; - let req = TestRequest::get().uri("/swagger/").to_request(); - let resp = service.call(req).await.unwrap(); - assert!(resp.status().is_success()); -} diff --git a/src/service/actix/tests/web.rs b/src/service/actix/tests/web.rs deleted file mode 100644 index 1f890fe..0000000 --- a/src/service/actix/tests/web.rs +++ /dev/null @@ -1,16 +0,0 @@ -use actix_web::dev::Service; -use actix_web::test::TestRequest; -use actix_web::{test, App}; -use function_name::named; - -use super::configure_test_app; - -#[named] -#[actix_rt::test] -async fn test_index() { - let app = App::new().configure(|cfg| configure_test_app(cfg, function_name!())); - let mut service = test::init_service(app).await; - let req = TestRequest::get().uri("/").to_request(); - let resp = service.call(req).await.unwrap(); - assert!(resp.status().is_success()); -} diff --git a/src/service/mod.rs b/src/service/mod.rs index 6be9bb4..0896149 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -2,6 +2,9 @@ mod constants; mod dto; mod error; +#[cfg(test)] +mod tests; + #[cfg(feature = "service-actix")] mod actix; #[cfg(feature = "service-actix")] diff --git a/src/service/rocket/api_tests.rs b/src/service/rocket/api_tests.rs index 52914c5..5fdf362 100644 --- a/src/service/rocket/api_tests.rs +++ b/src/service/rocket/api_tests.rs @@ -8,7 +8,6 @@ use super::api; use crate::config; use crate::ddns; use crate::index; -use crate::service::dto; use crate::vfs; use super::test::get_test_environment; @@ -49,53 +48,6 @@ fn do_auth(client: &Client) { assert_eq!(response.status(), Status::Ok); } -#[test] -fn version() { - let env = get_test_environment("api_version.sqlite"); - let client = &env.client; - let mut response = client.get("/api/version").dispatch(); - - assert_eq!(response.status(), Status::Ok); - - let response_body = response.body_string().unwrap(); - let response_json: dto::Version = serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json, dto::Version { major: 4, minor: 0 }); -} - -#[test] -fn initial_setup() { - let env = get_test_environment("api_initial_setup.sqlite"); - let client = &env.client; - - { - let mut response = client.get("/api/initial_setup").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: dto::InitialSetup = serde_json::from_str(&response_body).unwrap(); - assert_eq!( - response_json, - dto::InitialSetup { - has_any_users: false - } - ); - } - - complete_initial_setup(client); - - { - let mut response = client.get("/api/initial_setup").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: dto::InitialSetup = serde_json::from_str(&response_body).unwrap(); - assert_eq!( - response_json, - dto::InitialSetup { - has_any_users: true - } - ); - } -} - #[test] fn settings() { let env = get_test_environment("api_settings.sqlite"); diff --git a/src/service/rocket/mod.rs b/src/service/rocket/mod.rs index d335ee8..00ea1d8 100644 --- a/src/service/rocket/mod.rs +++ b/src/service/rocket/mod.rs @@ -6,8 +6,4 @@ pub mod server; #[cfg(test)] mod api_tests; #[cfg(test)] -mod swagger; -#[cfg(test)] -mod test; -#[cfg(test)] -mod web; +pub mod test; diff --git a/src/service/rocket/swagger.rs b/src/service/rocket/swagger.rs deleted file mode 100644 index 6eeafe5..0000000 --- a/src/service/rocket/swagger.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::test::get_test_environment; - -#[test] -fn test_index() { - use rocket::http::Status; - let env = get_test_environment("swagger_index.sqlite"); - let client = &env.client; - let response = client.get("/swagger").dispatch(); - assert_eq!(response.status(), Status::Ok); -} - -#[test] -fn test_index_with_trailing_slash() { - use rocket::http::Status; - let env = get_test_environment("swagger_index_with_trailing_slash.sqlite"); - let client = &env.client; - let response = client.get("/swagger/").dispatch(); - assert_eq!(response.status(), Status::Ok); -} diff --git a/src/service/rocket/test.rs b/src/service/rocket/test.rs index a0072f8..f72022b 100644 --- a/src/service/rocket/test.rs +++ b/src/service/rocket/test.rs @@ -1,5 +1,8 @@ use rocket; +use rocket::http::Status; use rocket::local::Client; +use serde::de::DeserializeOwned; +use serde::Serialize; use std::fs; use std::ops::Deref; use std::path::PathBuf; @@ -60,3 +63,30 @@ pub fn get_test_environment(db_name: &str) -> TestEnvironment { db, } } + +pub type ServiceType = TestEnvironment; + +pub async fn make_service(test_name: &str) -> TestEnvironment { + get_test_environment(&format!("{}.sqlite", test_name)) +} + +pub async fn get(service: &mut TestEnvironment, url: &str) { + let client = &service.client; + let response = client.get(url).dispatch(); + assert_eq!(response.status(), Status::Ok); +} + +pub async fn get_json(service: &mut TestEnvironment, url: &str) -> T { + let client = &service.client; + let mut response = client.get(url).dispatch(); + assert_eq!(response.status(), Status::Ok); + let response_body = response.body_string().unwrap(); + serde_json::from_str(&response_body).unwrap() +} + +pub async fn put_json(service: &mut TestEnvironment, url: &str, payload: &T) { + let client = &service.client; + let body = serde_json::to_string(payload).unwrap(); + let response = client.put(url).body(&body).dispatch(); + assert_eq!(response.status(), Status::Ok); +} diff --git a/src/service/rocket/web.rs b/src/service/rocket/web.rs deleted file mode 100644 index a9a46cf..0000000 --- a/src/service/rocket/web.rs +++ /dev/null @@ -1,10 +0,0 @@ -use super::test::get_test_environment; -use rocket::http::Status; - -#[test] -fn test_index() { - let env = get_test_environment("web_index.sqlite"); - let client = &env.client; - let response = client.get("/").dispatch(); - assert_eq!(response.status(), Status::Ok); -} diff --git a/src/service/tests.rs b/src/service/tests.rs new file mode 100644 index 0000000..8f4ccb0 --- /dev/null +++ b/src/service/tests.rs @@ -0,0 +1,95 @@ +use function_name::named; + +mod api; +mod static_files; + +use crate::config; +use crate::service::dto; +use crate::vfs; + +#[cfg(feature = "service-actix")] +pub use crate::service::actix::test::*; + +#[cfg(feature = "service-rocket")] +pub use crate::service::rocket::test::*; + +const TEST_USERNAME: &str = "test_user"; +const TEST_PASSWORD: &str = "test_password"; +const TEST_MOUNT_NAME: &str = "collection"; +const TEST_MOUNT_SOURCE: &str = "test/collection"; + +#[named] +#[actix_rt::test] +async fn test_index() { + let mut service = make_service(function_name!()).await; + get(&mut service, "/").await; +} + +#[named] +#[actix_rt::test] +async fn test_swagger_index() { + let mut service = make_service(function_name!()).await; + get(&mut service, "/swagger").await; +} + +#[named] +#[actix_rt::test] +async fn test_swagger_index_with_trailing_slash() { + let mut service = make_service(function_name!()).await; + get(&mut service, "/swagger/").await; +} + +async fn complete_initial_setup(service: &mut ServiceType) { + let configuration = config::Config { + album_art_pattern: None, + prefix_url: None, + reindex_every_n_seconds: None, + ydns: None, + users: Some(vec![config::ConfigUser { + name: TEST_USERNAME.into(), + password: TEST_PASSWORD.into(), + admin: true, + }]), + mount_dirs: Some(vec![vfs::MountPoint { + name: TEST_MOUNT_NAME.into(), + source: TEST_MOUNT_SOURCE.into(), + }]), + }; + put_json(service, "/api/settings", &configuration).await; +} + +#[named] +#[actix_rt::test] +async fn test_version() { + let mut service = make_service(function_name!()).await; + let version: dto::Version = get_json(&mut service, "/api/version").await; + assert_eq!(version, dto::Version { major: 4, minor: 0 }); +} + +#[named] +#[actix_rt::test] +async fn test_initial_setup() { + let mut service = make_service(function_name!()).await; + + { + let initial_setup: dto::InitialSetup = get_json(&mut service, "/api/initial_setup").await; + assert_eq!( + initial_setup, + dto::InitialSetup { + has_any_users: false + } + ); + } + + complete_initial_setup(&mut service).await; + + { + let initial_setup: dto::InitialSetup = get_json(&mut service, "/api/initial_setup").await; + assert_eq!( + initial_setup, + dto::InitialSetup { + has_any_users: true + } + ); + } +}