From ed66200689d80041bb7057e717c36a97d79a05b9 Mon Sep 17 00:00:00 2001 From: Antoine Gersant Date: Fri, 17 Jan 2020 01:17:23 -0800 Subject: [PATCH] Decoupled most tests from rocket --- Cargo.lock | 1 + Cargo.toml | 1 + src/service/dto.rs | 16 ++ src/service/mod.rs | 2 +- src/service/rocket/api.rs | 32 +-- src/service/rocket/api_tests.rs | 479 -------------------------------- src/service/rocket/mod.rs | 2 - src/service/rocket/test.rs | 163 ++++++----- src/service/test.rs | 416 +++++++++++++++++++++++++++ src/service/tests.rs | 91 ------ 10 files changed, 533 insertions(+), 670 deletions(-) delete mode 100644 src/service/rocket/api_tests.rs create mode 100644 src/service/test.rs delete mode 100644 src/service/tests.rs diff --git a/Cargo.lock b/Cargo.lock index ac06477..ecd67e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1547,6 +1547,7 @@ dependencies = [ "tokio 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "unix-daemonize 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index df7d6a3..55bca09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ simplelog = "0.7" thiserror = "1.0" time = "0.1" toml = "0.5" +url = "2.1" [dependencies.rocket_contrib] version = "0.4.2" diff --git a/src/service/dto.rs b/src/service/dto.rs index fb31e5c..124e6c7 100644 --- a/src/service/dto.rs +++ b/src/service/dto.rs @@ -10,3 +10,19 @@ pub struct Version { pub struct InitialSetup { pub has_any_users: bool, } + +#[derive(Serialize, Deserialize)] +pub struct AuthCredentials { + pub username: String, + pub password: String, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct ListPlaylistsEntry { + pub name: String, +} + +#[derive(Serialize, Deserialize)] +pub struct SavePlaylistInput { + pub tracks: Vec, +} diff --git a/src/service/mod.rs b/src/service/mod.rs index 176de4f..e975016 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -3,7 +3,7 @@ mod dto; mod error; #[cfg(test)] -mod tests; +mod test; #[cfg(feature = "service-rocket")] mod rocket; diff --git a/src/service/rocket/api.rs b/src/service/rocket/api.rs index 6e2281c..19d227d 100644 --- a/src/service/rocket/api.rs +++ b/src/service/rocket/api.rs @@ -4,7 +4,6 @@ use rocket::request::{self, FromParam, FromRequest, Request}; use rocket::response::content::Html; use rocket::{delete, get, post, put, routes, Outcome, State}; use rocket_contrib::json::Json; -use serde::{Deserialize, Serialize}; use std::fs::File; use std::ops::Deref; use std::path::PathBuf; @@ -239,21 +238,10 @@ fn trigger_index( Ok(()) } -#[derive(Serialize, Deserialize)] -pub struct AuthCredentials { - pub username: String, - pub password: String, -} - -#[derive(Serialize)] -struct AuthOutput { - admin: bool, -} - #[post("/auth", data = "")] fn auth( db: State<'_, DB>, - credentials: Json, + credentials: Json, mut cookies: Cookies<'_>, ) -> std::result::Result<(), APIError> { if !user::auth(&db, &credentials.username, &credentials.password)? { @@ -335,33 +323,23 @@ fn serve(db: State<'_, DB>, _auth: Auth, path: VFSPathBuf) -> Result, auth: Auth) -> Result>> { +fn list_playlists(db: State<'_, DB>, auth: Auth) -> Result>> { let playlist_names = playlist::list_playlists(&auth.username, db.deref().deref())?; - let playlists: Vec = playlist_names + let playlists: Vec = playlist_names .into_iter() - .map(|p| ListPlaylistsEntry { name: p }) + .map(|p| dto::ListPlaylistsEntry { name: p }) .collect(); Ok(Json(playlists)) } -#[derive(Serialize, Deserialize)] -pub struct SavePlaylistInput { - pub tracks: Vec, -} - #[put("/playlist/", data = "")] fn save_playlist( db: State<'_, DB>, auth: Auth, name: String, - playlist: Json, + playlist: Json, ) -> Result<()> { playlist::save_playlist(&name, &auth.username, &playlist.tracks, db.deref().deref())?; Ok(()) diff --git a/src/service/rocket/api_tests.rs b/src/service/rocket/api_tests.rs deleted file mode 100644 index 5fdf362..0000000 --- a/src/service/rocket/api_tests.rs +++ /dev/null @@ -1,479 +0,0 @@ -use rocket::http::hyper::header::*; -use rocket::http::uri::Uri; -use rocket::http::Status; -use rocket::local::Client; -use std::{thread, time}; - -use super::api; -use crate::config; -use crate::ddns; -use crate::index; -use crate::vfs; - -use super::test::get_test_environment; - -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"; - -fn complete_initial_setup(client: &Client) { - 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(), - }]), - }; - let body = serde_json::to_string(&configuration).unwrap(); - let response = client.put("/api/settings").body(&body).dispatch(); - assert_eq!(response.status(), Status::Ok); -} - -fn do_auth(client: &Client) { - let credentials = api::AuthCredentials { - username: TEST_USERNAME.into(), - password: TEST_PASSWORD.into(), - }; - let body = serde_json::to_string(&credentials).unwrap(); - let response = client.post("/api/auth").body(body).dispatch(); - assert_eq!(response.status(), Status::Ok); -} - -#[test] -fn settings() { - let env = get_test_environment("api_settings.sqlite"); - let client = &env.client; - complete_initial_setup(client); - - { - let response = client.get("/api/settings").dispatch(); - assert_eq!(response.status(), Status::Unauthorized); - } - - do_auth(client); - - { - let mut response = client.get("/api/settings").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: config::Config = serde_json::from_str(&response_body).unwrap(); - assert_eq!( - response_json, - config::Config { - album_art_pattern: Some("Folder.(jpg|png)".to_string()), - reindex_every_n_seconds: Some(1800), - mount_dirs: Some(vec![vfs::MountPoint { - name: TEST_MOUNT_NAME.into(), - source: TEST_MOUNT_SOURCE.into() - }]), - prefix_url: None, - users: Some(vec![config::ConfigUser { - name: TEST_USERNAME.into(), - password: "".into(), - admin: true - }]), - ydns: Some(ddns::DDNSConfig { - host: "".into(), - username: "".into(), - password: "".into() - }), - } - ); - } - - let mut configuration = config::Config { - album_art_pattern: Some("my_pattern".to_owned()), - reindex_every_n_seconds: Some(3600), - mount_dirs: Some(vec![ - vfs::MountPoint { - name: TEST_MOUNT_NAME.into(), - source: TEST_MOUNT_SOURCE.into(), - }, - vfs::MountPoint { - name: "more_music".into(), - source: "test/collection".into(), - }, - ]), - prefix_url: Some("my_prefix".to_owned()), - users: Some(vec![ - config::ConfigUser { - name: "test_user".into(), - password: "some_password".into(), - admin: true, - }, - config::ConfigUser { - name: "other_user".into(), - password: "some_other_password".into(), - admin: false, - }, - ]), - ydns: Some(ddns::DDNSConfig { - host: "my_host".into(), - username: "my_username".into(), - password: "my_password".into(), - }), - }; - - let body = serde_json::to_string(&configuration).unwrap(); - - configuration.users = Some(vec![ - config::ConfigUser { - name: "test_user".into(), - password: "".into(), - admin: true, - }, - config::ConfigUser { - name: "other_user".into(), - password: "".into(), - admin: false, - }, - ]); - - client.put("/api/settings").body(body).dispatch(); - - { - let mut response = client.get("/api/settings").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: config::Config = serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json, configuration); - } -} - -#[test] -fn preferences() { - // TODO -} - -#[test] -fn trigger_index() { - let env = get_test_environment("api_trigger_index.sqlite"); - let client = &env.client; - complete_initial_setup(client); - do_auth(client); - - { - let mut response = client.get("/api/random").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 0); - } - - { - let response = client.post("/api/trigger_index").dispatch(); - assert_eq!(response.status(), Status::Ok); - } - - let timeout = time::Duration::from_secs(5); - thread::sleep(timeout); - - { - let mut response = client.get("/api/random").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 2); - } -} - -#[test] -fn auth() { - let env = get_test_environment("api_auth.sqlite"); - let client = &env.client; - complete_initial_setup(client); - - { - let credentials = api::AuthCredentials { - username: "garbage".into(), - password: "garbage".into(), - }; - let response = client - .post("/api/auth") - .body(serde_json::to_string(&credentials).unwrap()) - .dispatch(); - assert_eq!(response.status(), Status::Unauthorized); - } - { - let credentials = api::AuthCredentials { - username: TEST_USERNAME.into(), - password: "garbage".into(), - }; - let response = client - .post("/api/auth") - .body(serde_json::to_string(&credentials).unwrap()) - .dispatch(); - assert_eq!(response.status(), Status::Unauthorized); - } - { - let credentials = api::AuthCredentials { - username: TEST_USERNAME.into(), - password: TEST_PASSWORD.into(), - }; - let response = client - .post("/api/auth") - .body(serde_json::to_string(&credentials).unwrap()) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert!(response - .cookies() - .iter() - .any(|cookie| cookie.name() == "username")); - assert!(response - .cookies() - .iter() - .any(|cookie| cookie.name() == "admin")); - assert!(response - .cookies() - .iter() - .any(|cookie| cookie.name() == "session")); - } -} - -#[test] -fn browse() { - let env = get_test_environment("api_browse.sqlite"); - let client = &env.client; - complete_initial_setup(client); - do_auth(client); - env.update_index(); - - { - let mut response = client.get("/api/browse").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = - serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 1); - } - - let mut next; - { - let mut response = client.get("/api/browse/collection").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = - serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 2); - - match response_json[0] { - index::CollectionFile::Directory(ref d) => { - next = d.path.clone(); - } - _ => panic!(), - } - } - - // /api/browse/collection/Khemmis - { - let url = format!("/api/browse/{}", Uri::percent_encode(&next)); - let mut response = client.get(url).dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = - serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 1); - match response_json[0] { - index::CollectionFile::Directory(ref d) => { - next = d.path.clone(); - } - _ => panic!(), - } - } - - // /api/browse/collection/Khemmis/Hunted - { - let url = format!("/api/browse/{}", Uri::percent_encode(&next)); - let mut response = client.get(url).dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = - serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 5); - } -} - -#[test] -fn flatten() { - let env = get_test_environment("api_flatten.sqlite"); - let client = &env.client; - complete_initial_setup(client); - do_auth(client); - env.update_index(); - - { - let mut response = client.get("/api/flatten").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 12); - } - - { - let mut response = client.get("/api/flatten/collection").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 12); - } -} - -#[test] -fn random() { - let env = get_test_environment("api_random.sqlite"); - let client = &env.client; - complete_initial_setup(client); - do_auth(client); - env.update_index(); - - let mut response = client.get("/api/random").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 2); -} - -#[test] -fn recent() { - let env = get_test_environment("api_recent.sqlite"); - let client = &env.client; - complete_initial_setup(client); - do_auth(client); - env.update_index(); - - let mut response = client.get("/api/recent").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 2); -} - -#[test] -fn search() { - let env = get_test_environment("api_search.sqlite"); - let client = &env.client; - complete_initial_setup(client); - do_auth(client); - env.update_index(); - - let mut response = client.get("/api/search/door").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 1); - match response_json[0] { - index::CollectionFile::Song(ref s) => assert_eq!(s.title, Some("Beyond The Door".into())), - _ => panic!(), - } -} - -#[test] -fn serve() { - let env = get_test_environment("api_serve.sqlite"); - let client = &env.client; - complete_initial_setup(client); - do_auth(client); - env.update_index(); - - { - let mut response = client - .get("/api/serve/collection%2FKhemmis%2FHunted%2F02%20-%20Candlelight.mp3") - .dispatch(); - assert_eq!(response.status(), Status::Ok); - let body = response.body().unwrap(); - let body = body.into_bytes().unwrap(); - assert_eq!(body.len(), 24_142); - } - - { - let mut response = client - .get("/api/serve/collection%2FKhemmis%2FHunted%2F02%20-%20Candlelight.mp3") - .header(Range::bytes(100, 299)) - .dispatch(); - assert_eq!(response.status(), Status::PartialContent); - let body = response.body().unwrap(); - let body = body.into_bytes().unwrap(); - assert_eq!(body.len(), 200); - assert_eq!(response.headers().get_one("Content-Length").unwrap(), "200"); - } -} - -#[test] -fn playlists() { - let env = get_test_environment("api_playlists.sqlite"); - let client = &env.client; - complete_initial_setup(client); - do_auth(client); - env.update_index(); - - { - let mut response = client.get("/api/playlists").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = - serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 0); - } - - { - let songs: Vec; - { - let mut response = client.get("/api/flatten").dispatch(); - let response_body = response.body_string().unwrap(); - songs = serde_json::from_str(&response_body).unwrap(); - } - let my_playlist = api::SavePlaylistInput { - tracks: songs[2..6].into_iter().map(|s| s.path.clone()).collect(), - }; - let response = client - .put("/api/playlist/my_playlist") - .body(serde_json::to_string(&my_playlist).unwrap()) - .dispatch(); - assert_eq!(response.status(), Status::Ok); - } - - { - let mut response = client.get("/api/playlists").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = - serde_json::from_str(&response_body).unwrap(); - assert_eq!( - response_json, - vec![api::ListPlaylistsEntry { - name: "my_playlist".into() - }] - ); - } - - { - let mut response = client.get("/api/playlist/my_playlist").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 4); - } - - { - let response = client.delete("/api/playlist/my_playlist").dispatch(); - assert_eq!(response.status(), Status::Ok); - } - - { - let mut response = client.get("/api/playlists").dispatch(); - assert_eq!(response.status(), Status::Ok); - let response_body = response.body_string().unwrap(); - let response_json: Vec = - serde_json::from_str(&response_body).unwrap(); - assert_eq!(response_json.len(), 0); - } -} diff --git a/src/service/rocket/mod.rs b/src/service/rocket/mod.rs index 00ea1d8..10fbc8d 100644 --- a/src/service/rocket/mod.rs +++ b/src/service/rocket/mod.rs @@ -3,7 +3,5 @@ mod serve; pub mod server; -#[cfg(test)] -mod api_tests; #[cfg(test)] pub mod test; diff --git a/src/service/rocket/test.rs b/src/service/rocket/test.rs index f1de84a..1e673df 100644 --- a/src/service/rocket/test.rs +++ b/src/service/rocket/test.rs @@ -11,84 +11,107 @@ use std::sync::Arc; use super::server; use crate::db::DB; use crate::index; +use crate::service::test::{HttpStatus, TestService}; -pub struct TestEnvironment { - pub client: Client, +pub struct RocketTestService { + client: Client, command_sender: Arc, - db: DB, } -impl TestEnvironment { - pub fn update_index(&self) { - index::update(&self.db).unwrap(); +pub type ServiceType = RocketTestService; + +impl HttpStatus for Status { + fn is_ok(&self) -> bool { + *self == Status::Ok + } + + fn is_unauthorized(&self) -> bool { + *self == Status::Unauthorized } } -impl Drop for TestEnvironment { +impl TestService for RocketTestService { + type Status = Status; + + fn new(db_name: &str) -> Self { + 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(); + + let web_dir_path = PathBuf::from("web"); + let mut swagger_dir_path = PathBuf::from("docs"); + swagger_dir_path.push("swagger"); + let command_sender = index::init(db.clone()); + + let auth_secret: [u8; 32] = [0; 32]; + + let server = server::get_server( + 5050, + &auth_secret, + "/api", + "/", + &web_dir_path, + "/swagger", + &swagger_dir_path, + db.clone(), + command_sender.clone(), + ) + .unwrap(); + let client = Client::new(server).unwrap(); + RocketTestService { + client, + command_sender, + } + } + + fn get(&mut self, url: &str) -> Status { + let client = &self.client; + let response = client.get(url).dispatch(); + response.status() + } + + fn post(&mut self, url: &str) -> Status { + let client = &self.client; + let response = client.post(url).dispatch(); + response.status() + } + + fn delete(&mut self, url: &str) -> Status { + let client = &self.client; + let response = client.delete(url).dispatch(); + response.status() + } + + fn get_json(&mut self, url: &str) -> T { + let client = &self.client; + let mut response = client.get(url).dispatch(); + assert_eq!(response.status(), Status::Ok); + let response_body = response.body_string().unwrap(); + dbg!(&response_body); + serde_json::from_str(&response_body).unwrap() + } + + fn put_json(&mut self, url: &str, payload: &T) { + let client = &self.client; + let body = serde_json::to_string(payload).unwrap(); + let response = client.put(url).body(&body).dispatch(); + assert_eq!(response.status(), Status::Ok); + } + + fn post_json(&mut self, url: &str, payload: &T) -> Status { + let client = &self.client; + let body = serde_json::to_string(payload).unwrap(); + let response = client.post(url).body(&body).dispatch(); + response.status() + } +} + +impl Drop for RocketTestService { fn drop(&mut self) { self.command_sender.deref().exit().unwrap(); } } - -pub fn get_test_environment(db_name: &str) -> TestEnvironment { - let mut db_path = PathBuf::new(); - db_path.push("test"); - db_path.push(db_name); - if db_path.exists() { - fs::remove_file(&db_path).unwrap(); - } - let db = DB::new(&db_path).unwrap(); - - let web_dir_path = PathBuf::from("web"); - let mut swagger_dir_path = PathBuf::from("docs"); - swagger_dir_path.push("swagger"); - let command_sender = index::init(db.clone()); - - let auth_secret: [u8; 32] = [0; 32]; - - let server = server::get_server( - 5050, - &auth_secret, - "/api", - "/", - &web_dir_path, - "/swagger", - &swagger_dir_path, - db.clone(), - command_sender.clone(), - ) - .unwrap(); - let client = Client::new(server).unwrap(); - TestEnvironment { - client, - command_sender, - 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/test.rs b/src/service/test.rs new file mode 100644 index 0000000..a592036 --- /dev/null +++ b/src/service/test.rs @@ -0,0 +1,416 @@ +use function_name::named; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::path::PathBuf; +use std::time::Duration; +use url::form_urlencoded::byte_serialize; + +use crate::service::dto; +use crate::{config, ddns, index, vfs}; + +#[cfg(feature = "service-rocket")] +pub use crate::service::rocket::test::ServiceType; + +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"; + +pub trait HttpStatus { + fn is_ok(&self) -> bool; + fn is_unauthorized(&self) -> bool; +} + +pub trait TestService { + type Status: HttpStatus; + + fn new(db_name: &str) -> Self; + fn get(&mut self, url: &str) -> Self::Status; + fn post(&mut self, url: &str) -> Self::Status; + fn delete(&mut self, url: &str) -> Self::Status; + fn get_json(&mut self, url: &str) -> T; + fn put_json(&mut self, url: &str, payload: &T); + fn post_json(&mut self, url: &str, payload: &T) -> Self::Status; + + fn complete_initial_setup(&mut self) { + 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(), + }]), + }; + self.put_json("/api/settings", &configuration); + } + + fn login(&mut self) { + let credentials = dto::AuthCredentials { + username: TEST_USERNAME.into(), + password: TEST_PASSWORD.into(), + }; + self.post_json("/api/auth", &credentials); + } + + fn index(&mut self) { + assert!(self.post("/api/trigger_index").is_ok()); + for _ in 1..20 { + let entries: Vec = self.get_json("/api/browse"); + if entries.len() > 0 { + return; + } + std::thread::sleep(Duration::from_secs(1)); + } + panic!("index timeout"); + } +} + +#[named] +#[test] +fn test_service_index() { + let mut service = ServiceType::new(function_name!()); + service.get("/"); +} + +#[named] +#[test] +fn test_service_swagger_index() { + let mut service = ServiceType::new(function_name!()); + assert!(service.get("/swagger").is_ok()); +} + +#[named] +#[test] +fn test_service_swagger_index_with_trailing_slash() { + let mut service = ServiceType::new(function_name!()); + assert!(service.get("/swagger/").is_ok()); +} + +#[named] +#[test] +fn test_service_version() { + let mut service = ServiceType::new(function_name!()); + let version: dto::Version = service.get_json("/api/version"); + assert_eq!(version, dto::Version { major: 4, minor: 0 }); +} + +#[named] +#[test] +fn test_service_initial_setup() { + let mut service = ServiceType::new(function_name!()); + { + let initial_setup: dto::InitialSetup = service.get_json("/api/initial_setup"); + assert_eq!( + initial_setup, + dto::InitialSetup { + has_any_users: false + } + ); + } + service.complete_initial_setup(); + { + let initial_setup: dto::InitialSetup = service.get_json("/api/initial_setup"); + assert_eq!( + initial_setup, + dto::InitialSetup { + has_any_users: true + } + ); + } +} + +#[named] +#[test] +fn test_service_settings() { + let mut service = ServiceType::new(function_name!()); + service.complete_initial_setup(); + + assert!(service.get("/api/settings").is_unauthorized()); + service.login(); + + { + let configuration: config::Config = service.get_json("/api/settings"); + assert_eq!( + configuration, + config::Config { + album_art_pattern: Some("Folder.(jpg|png)".to_string()), + reindex_every_n_seconds: Some(1800), + mount_dirs: Some(vec![vfs::MountPoint { + name: TEST_MOUNT_NAME.into(), + source: TEST_MOUNT_SOURCE.into() + }]), + prefix_url: None, + users: Some(vec![config::ConfigUser { + name: TEST_USERNAME.into(), + password: "".into(), + admin: true + }]), + ydns: Some(ddns::DDNSConfig { + host: "".into(), + username: "".into(), + password: "".into() + }), + } + ); + } + + let mut configuration = config::Config { + album_art_pattern: Some("my_pattern".to_owned()), + reindex_every_n_seconds: Some(3600), + mount_dirs: Some(vec![ + vfs::MountPoint { + name: TEST_MOUNT_NAME.into(), + source: TEST_MOUNT_SOURCE.into(), + }, + vfs::MountPoint { + name: "more_music".into(), + source: "test/collection".into(), + }, + ]), + prefix_url: Some("my_prefix".to_owned()), + users: Some(vec![ + config::ConfigUser { + name: "test_user".into(), + password: "some_password".into(), + admin: true, + }, + config::ConfigUser { + name: "other_user".into(), + password: "some_other_password".into(), + admin: false, + }, + ]), + ydns: Some(ddns::DDNSConfig { + host: "my_host".into(), + username: "my_username".into(), + password: "my_password".into(), + }), + }; + + service.put_json("/api/settings", &configuration); + + configuration.users = Some(vec![ + config::ConfigUser { + name: "test_user".into(), + password: "".into(), + admin: true, + }, + config::ConfigUser { + name: "other_user".into(), + password: "".into(), + admin: false, + }, + ]); + + let received: config::Config = service.get_json("/api/settings"); + assert_eq!(received, configuration); +} + +#[named] +#[test] +fn test_service_preferences() { + // TODO +} + +#[named] +#[test] +fn test_service_trigger_index() { + let mut service = ServiceType::new(function_name!()); + service.complete_initial_setup(); + service.login(); + + let entries: Vec = service.get_json("/api/random"); + assert_eq!(entries.len(), 0); + + service.index(); + + let entries: Vec = service.get_json("/api/random"); + assert_eq!(entries.len(), 2); +} + +#[named] +#[test] +fn test_service_auth() { + let mut service = ServiceType::new(function_name!()); + service.complete_initial_setup(); + + { + let credentials = dto::AuthCredentials { + username: "garbage".into(), + password: "garbage".into(), + }; + assert!(service + .post_json("/api/auth", &credentials) + .is_unauthorized()); + } + { + let credentials = dto::AuthCredentials { + username: TEST_USERNAME.into(), + password: "garbage".into(), + }; + assert!(service + .post_json("/api/auth", &credentials) + .is_unauthorized()); + } + { + let credentials = dto::AuthCredentials { + username: TEST_USERNAME.into(), + password: TEST_PASSWORD.into(), + }; + assert!(service.post_json("/api/auth", &credentials).is_ok()); + // TODO validate cookies + } +} + +#[named] +#[test] +fn test_service_browse() { + let mut service = ServiceType::new(function_name!()); + service.complete_initial_setup(); + service.login(); + service.index(); + + let entries: Vec = service.get_json("/api/browse"); + assert_eq!(entries.len(), 1); + + let mut path = PathBuf::new(); + path.push("collection"); + path.push("Khemmis"); + path.push("Hunted"); + let encoded_path: String = byte_serialize(path.to_string_lossy().as_ref().as_bytes()).collect(); + let uri = format!("/api/browse/{}", encoded_path); + + let entries: Vec = service.get_json(&uri); + assert_eq!(entries.len(), 5); +} + +#[named] +#[test] +fn test_service_flatten() { + let mut service = ServiceType::new(function_name!()); + service.complete_initial_setup(); + service.login(); + service.index(); + + let entries: Vec = service.get_json("/api/flatten"); + assert_eq!(entries.len(), 12); + + let entries: Vec = service.get_json("/api/flatten/collection"); + assert_eq!(entries.len(), 12); +} + +#[named] +#[test] +fn test_service_random() { + let mut service = ServiceType::new(function_name!()); + service.complete_initial_setup(); + service.login(); + service.index(); + + let entries: Vec = service.get_json("/api/random"); + assert_eq!(entries.len(), 2); +} + +#[named] +#[test] +fn test_service_recent() { + let mut service = ServiceType::new(function_name!()); + service.complete_initial_setup(); + service.login(); + service.index(); + + let entries: Vec = service.get_json("/api/recent"); + assert_eq!(entries.len(), 2); +} + +#[named] +#[test] +fn test_service_search() { + let mut service = ServiceType::new(function_name!()); + service.complete_initial_setup(); + service.login(); + service.index(); + + let results: Vec = service.get_json("/api/search/door"); + assert_eq!(results.len(), 1); + match results[0] { + index::CollectionFile::Song(ref s) => assert_eq!(s.title, Some("Beyond The Door".into())), + _ => panic!(), + } +} + +/* TODO +#[named] +#[test] +fn test_service_serve() { + let mut service = ServiceType::new(function_name!()); + service.complete_initial_setup(); + service.login(); + service.index(); + + { + let mut response = client + .get("/api/serve/collection%2FKhemmis%2FHunted%2F02%20-%20Candlelight.mp3") + .dispatch(); + assert_eq!(response.status(), Status::Ok); + let body = response.body().unwrap(); + let body = body.into_bytes().unwrap(); + assert_eq!(body.len(), 24_142); + } + + { + let mut response = client + .get("/api/serve/collection%2FKhemmis%2FHunted%2F02%20-%20Candlelight.mp3") + .header(Range::bytes(100, 299)) + .dispatch(); + assert_eq!(response.status(), Status::PartialContent); + let body = response.body().unwrap(); + let body = body.into_bytes().unwrap(); + assert_eq!(body.len(), 200); + assert_eq!(response.headers().get_one("Content-Length").unwrap(), "200"); + } +} +*/ + +#[named] +#[test] +fn test_service_playlists() { + let mut service = ServiceType::new(function_name!()); + service.complete_initial_setup(); + service.login(); + service.index(); + + let playlists: Vec = service.get_json("/api/playlists"); + assert_eq!(playlists.len(), 0); + + let mut my_songs: Vec = service.get_json("/api/flatten"); + my_songs.pop(); + my_songs.pop(); + let my_playlist = dto::SavePlaylistInput { + tracks: my_songs.iter().map(|s| s.path.clone()).collect(), + }; + service.put_json("/api/playlist/my_playlist", &my_playlist); + + let playlists: Vec = service.get_json("/api/playlists"); + assert_eq!( + playlists, + vec![dto::ListPlaylistsEntry { + name: "my_playlist".into() + }] + ); + + let songs: Vec = service.get_json("/api/playlist/my_playlist"); + assert_eq!(songs, my_songs); + + service.delete("/api/playlist/my_playlist"); + + let playlists: Vec = service.get_json("/api/playlists"); + assert_eq!(playlists.len(), 0); +} diff --git a/src/service/tests.rs b/src/service/tests.rs deleted file mode 100644 index 05889b2..0000000 --- a/src/service/tests.rs +++ /dev/null @@ -1,91 +0,0 @@ -use function_name::named; - -use crate::config; -use crate::service::dto; -use crate::vfs; - -#[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] -#[tokio::test] -async fn test_index() { - let mut service = test::make_service(function_name!()).await; - test::get(&mut service, "/").await; -} - -#[named] -#[tokio::test] -async fn test_swagger_index() { - let mut service = test::make_service(function_name!()).await; - test::get(&mut service, "/swagger").await; -} - -#[named] -#[tokio::test] -async fn test_swagger_index_with_trailing_slash() { - let mut service = test::make_service(function_name!()).await; - test::get(&mut service, "/swagger/").await; -} - -async fn complete_initial_setup(service: &mut test::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(), - }]), - }; - test::put_json(service, "/api/settings", &configuration).await; -} - -#[named] -#[tokio::test] -async fn test_version() { - let mut service = test::make_service(function_name!()).await; - let version: dto::Version = test::get_json(&mut service, "/api/version").await; - assert_eq!(version, dto::Version { major: 4, minor: 0 }); -} - -#[named] -#[tokio::test] -async fn test_initial_setup() { - let mut service = test::make_service(function_name!()).await; - - { - let initial_setup: dto::InitialSetup = - test::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 = - test::get_json(&mut service, "/api/initial_setup").await; - assert_eq!( - initial_setup, - dto::InitialSetup { - has_any_users: true - } - ); - } -}