Increase code coverage to a sensible amount (#33)
Closes #26 Reviewed-on: https://git.wojciechkozlowski.eu/wojtek/musichoard/pulls/33
This commit is contained in:
parent
3e6c95d188
commit
3e9c84656e
@ -137,7 +137,7 @@ mod tests {
|
|||||||
let mut library = MockLibrary::new();
|
let mut library = MockLibrary::new();
|
||||||
let database = MockDatabase::new();
|
let database = MockDatabase::new();
|
||||||
|
|
||||||
let library_result = Err(library::Error::InvalidData(String::from("invalid data")));
|
let library_result = Err(library::Error::Invalid(String::from("invalid data")));
|
||||||
|
|
||||||
library
|
library
|
||||||
.expect_list()
|
.expect_list()
|
||||||
@ -147,9 +147,8 @@ mod tests {
|
|||||||
let mut collection_manager = MhCollectionManager::new(library, database);
|
let mut collection_manager = MhCollectionManager::new(library, database);
|
||||||
|
|
||||||
let actual_err = collection_manager.rescan_library().unwrap_err();
|
let actual_err = collection_manager.rescan_library().unwrap_err();
|
||||||
let expected_err = Error::LibraryError(
|
let expected_err =
|
||||||
library::Error::InvalidData(String::from("invalid data")).to_string(),
|
Error::LibraryError(library::Error::Invalid(String::from("invalid data")).to_string());
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(actual_err, expected_err);
|
assert_eq!(actual_err, expected_err);
|
||||||
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
||||||
|
@ -81,11 +81,13 @@ impl JsonDatabaseBackend for JsonDatabaseFileBackend {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use mockall::predicate;
|
use mockall::predicate;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::{tests::COLLECTION, Artist, TrackFormat};
|
use crate::{tests::COLLECTION, Artist, ArtistId, TrackFormat};
|
||||||
|
|
||||||
fn artist_to_json(artist: &Artist) -> String {
|
fn artist_to_json(artist: &Artist) -> String {
|
||||||
let album_artist = &artist.id.name;
|
let album_artist = &artist.id.name;
|
||||||
@ -203,4 +205,21 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(write_data, read_data);
|
assert_eq!(write_data, read_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn errors() {
|
||||||
|
let mut object = HashMap::<ArtistId, String>::new();
|
||||||
|
object.insert(
|
||||||
|
ArtistId {
|
||||||
|
name: String::from("artist"),
|
||||||
|
},
|
||||||
|
String::from("string"),
|
||||||
|
);
|
||||||
|
let serde_err = serde_json::to_string(&object);
|
||||||
|
assert!(serde_err.is_err());
|
||||||
|
|
||||||
|
let serde_err: Error = serde_err.unwrap_err().into();
|
||||||
|
assert!(!serde_err.to_string().is_empty());
|
||||||
|
assert!(!format!("{:?}", serde_err).is_empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,3 +44,17 @@ pub trait Database {
|
|||||||
/// Write collection to the database.
|
/// Write collection to the database.
|
||||||
fn write<S: Serialize + 'static>(&mut self, collection: &S) -> Result<(), Error>;
|
fn write<S: Serialize + 'static>(&mut self, collection: &S) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use super::Error;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn errors() {
|
||||||
|
let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "error").into();
|
||||||
|
assert!(!io_err.to_string().is_empty());
|
||||||
|
assert!(!format!("{:?}", io_err).is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -164,7 +164,7 @@ impl<BLE: BeetsLibraryExecutor> LibraryPrivate for BeetsLibrary<BLE> {
|
|||||||
|
|
||||||
let split: Vec<&str> = line.split(LIST_FORMAT_SEPARATOR).collect();
|
let split: Vec<&str> = line.split(LIST_FORMAT_SEPARATOR).collect();
|
||||||
if split.len() != 7 {
|
if split.len() != 7 {
|
||||||
return Err(Error::InvalidData(line.to_string()));
|
return Err(Error::Invalid(line.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let album_artist = split[0].to_string();
|
let album_artist = split[0].to_string();
|
||||||
@ -189,7 +189,7 @@ impl<BLE: BeetsLibraryExecutor> LibraryPrivate for BeetsLibrary<BLE> {
|
|||||||
format: match track_format.as_ref() {
|
format: match track_format.as_ref() {
|
||||||
TRACK_FORMAT_FLAC => TrackFormat::Flac,
|
TRACK_FORMAT_FLAC => TrackFormat::Flac,
|
||||||
TRACK_FORMAT_MP3 => TrackFormat::Mp3,
|
TRACK_FORMAT_MP3 => TrackFormat::Mp3,
|
||||||
_ => return Err(Error::InvalidData(track_format)),
|
_ => return Err(Error::Invalid(line.to_string())),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -272,7 +272,7 @@ impl BeetsLibraryExecutor for BeetsLibraryCommandExecutor {
|
|||||||
}
|
}
|
||||||
let output = cmd.args(arguments).output()?;
|
let output = cmd.args(arguments).output()?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(Error::CmdExecError(
|
return Err(Error::CmdExec(
|
||||||
String::from_utf8_lossy(&output.stderr).to_string(),
|
String::from_utf8_lossy(&output.stderr).to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -346,6 +346,25 @@ mod tests {
|
|||||||
String::from("^some.all"),
|
String::from("^some.all"),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let query = Query::new()
|
||||||
|
.album_artist(QueryOption::Exclude(String::from("some.albumartist")))
|
||||||
|
.album_year(QueryOption::Include(3030))
|
||||||
|
.track_title(QueryOption::Include(String::from("some.track")))
|
||||||
|
.track_artist(QueryOption::Exclude(vec![
|
||||||
|
String::from("some.artist.1"),
|
||||||
|
String::from("some.artist.2"),
|
||||||
|
]));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
query.to_args(),
|
||||||
|
vec![
|
||||||
|
String::from("^albumartist:some.albumartist"),
|
||||||
|
String::from("year:3030"),
|
||||||
|
String::from("title:some.track"),
|
||||||
|
String::from("^artist:some.artist.1; some.artist.2"),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -472,4 +491,61 @@ mod tests {
|
|||||||
let expected: Vec<Artist> = vec![];
|
let expected: Vec<Artist> = vec![];
|
||||||
assert_eq!(output, expected);
|
assert_eq!(output, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_data_split() {
|
||||||
|
let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()];
|
||||||
|
let expected = COLLECTION.to_owned();
|
||||||
|
let mut output = artists_to_beets_string(&expected);
|
||||||
|
let invalid_string = output[2]
|
||||||
|
.split(LIST_FORMAT_SEPARATOR)
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.collect::<Vec<String>>()[..4]
|
||||||
|
.join(LIST_FORMAT_SEPARATOR);
|
||||||
|
output[2] = invalid_string.clone();
|
||||||
|
let result = Ok(output);
|
||||||
|
|
||||||
|
let mut executor = MockBeetsLibraryExecutor::new();
|
||||||
|
executor
|
||||||
|
.expect_exec()
|
||||||
|
.with(predicate::eq(arguments))
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_| result);
|
||||||
|
|
||||||
|
let mut beets = BeetsLibrary::new(executor);
|
||||||
|
let err = beets.list(&Query::default()).unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(err, Error::Invalid(invalid_string));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_data_format() {
|
||||||
|
let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()];
|
||||||
|
let expected = COLLECTION.to_owned();
|
||||||
|
let mut output = artists_to_beets_string(&expected);
|
||||||
|
let mut invalid_string = output[2]
|
||||||
|
.split(LIST_FORMAT_SEPARATOR)
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
invalid_string.last_mut().unwrap().clear();
|
||||||
|
invalid_string
|
||||||
|
.last_mut()
|
||||||
|
.unwrap()
|
||||||
|
.push_str("invalid format");
|
||||||
|
let invalid_string = invalid_string.join(LIST_FORMAT_SEPARATOR);
|
||||||
|
output[2] = invalid_string.clone();
|
||||||
|
let result = Ok(output);
|
||||||
|
|
||||||
|
let mut executor = MockBeetsLibraryExecutor::new();
|
||||||
|
executor
|
||||||
|
.expect_exec()
|
||||||
|
.with(predicate::eq(arguments))
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_| result);
|
||||||
|
|
||||||
|
let mut beets = BeetsLibrary::new(executor);
|
||||||
|
let err = beets.list(&Query::default()).unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(err, Error::Invalid(invalid_string));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,18 +20,6 @@ pub enum QueryOption<T> {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> QueryOption<T> {
|
|
||||||
/// Return `true` if [`QueryOption`] is not [`QueryOption::None`].
|
|
||||||
pub fn is_some(&self) -> bool {
|
|
||||||
!matches!(self, QueryOption::None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return `true` if [`QueryOption`] is [`QueryOption::None`].
|
|
||||||
pub fn is_none(&self) -> bool {
|
|
||||||
matches!(self, QueryOption::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for QueryOption<T> {
|
impl<T> Default for QueryOption<T> {
|
||||||
/// Create a [`QueryOption::None`] for type `T`.
|
/// Create a [`QueryOption::None`] for type `T`.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@ -101,47 +89,47 @@ impl Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Error type for library calls.
|
/// Error type for library calls.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// The underlying library failed to execute a command.
|
/// The underlying library failed to execute a command.
|
||||||
CmdExecError(String),
|
CmdExec(String),
|
||||||
/// The underlying library returned invalid data.
|
/// The underlying library returned invalid data.
|
||||||
InvalidData(String),
|
Invalid(String),
|
||||||
/// The underlying library experienced an I/O error.
|
/// The underlying library experienced an I/O error.
|
||||||
IoError(String),
|
Io(String),
|
||||||
/// The underlying library failed to parse an integer.
|
/// The underlying library failed to parse an integer.
|
||||||
ParseIntError(String),
|
ParseInt(String),
|
||||||
/// The underlying library failed to parse a UTF-8 string.
|
/// The underlying library failed to parse a UTF-8 string.
|
||||||
Utf8Error(String),
|
Utf8(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
Self::CmdExecError(ref s) => write!(f, "the library failed to execute a command: {s}"),
|
Self::CmdExec(ref s) => write!(f, "the library failed to execute a command: {s}"),
|
||||||
Self::InvalidData(ref s) => write!(f, "the library received invalid data: {s}"),
|
Self::Invalid(ref s) => write!(f, "the library received invalid data: {s}"),
|
||||||
Self::IoError(ref s) => write!(f, "the library experienced an I/O error: {s}"),
|
Self::Io(ref s) => write!(f, "the library experienced an I/O error: {s}"),
|
||||||
Self::ParseIntError(ref s) => write!(f, "the library received an invalid integer: {s}"),
|
Self::ParseInt(ref s) => write!(f, "the library received an invalid integer: {s}"),
|
||||||
Self::Utf8Error(ref s) => write!(f, "the library received invalid UTF-8: {s}"),
|
Self::Utf8(ref s) => write!(f, "the library received invalid UTF-8: {s}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for Error {
|
impl From<std::io::Error> for Error {
|
||||||
fn from(err: std::io::Error) -> Error {
|
fn from(err: std::io::Error) -> Error {
|
||||||
Error::IoError(err.to_string())
|
Error::Io(err.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParseIntError> for Error {
|
impl From<ParseIntError> for Error {
|
||||||
fn from(err: ParseIntError) -> Error {
|
fn from(err: ParseIntError) -> Error {
|
||||||
Error::ParseIntError(err.to_string())
|
Error::ParseInt(err.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Utf8Error> for Error {
|
impl From<Utf8Error> for Error {
|
||||||
fn from(err: Utf8Error) -> Error {
|
fn from(err: Utf8Error) -> Error {
|
||||||
Error::Utf8Error(err.to_string())
|
Error::Utf8(err.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,3 +139,31 @@ pub trait Library {
|
|||||||
/// List lirbary items that match the a specific query.
|
/// List lirbary items that match the a specific query.
|
||||||
fn list(&mut self, query: &Query) -> Result<Vec<Artist>, Error>;
|
fn list(&mut self, query: &Query) -> Result<Vec<Artist>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use super::Error;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn errors() {
|
||||||
|
let cmd_err = Error::CmdExec(String::from("CmdExec"));
|
||||||
|
let inv_err = Error::Invalid(String::from("Invalid"));
|
||||||
|
let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "Interrupted").into();
|
||||||
|
let int_err: Error = "five".parse::<u32>().unwrap_err().into();
|
||||||
|
let utf_err: Error = std::str::from_utf8(b"\xe2\x28\xa1").unwrap_err().into();
|
||||||
|
|
||||||
|
assert!(!cmd_err.to_string().is_empty());
|
||||||
|
assert!(!inv_err.to_string().is_empty());
|
||||||
|
assert!(!io_err.to_string().is_empty());
|
||||||
|
assert!(!int_err.to_string().is_empty());
|
||||||
|
assert!(!utf_err.to_string().is_empty());
|
||||||
|
|
||||||
|
assert!(!format!("{:?}", cmd_err).is_empty());
|
||||||
|
assert!(!format!("{:?}", inv_err).is_empty());
|
||||||
|
assert!(!format!("{:?}", io_err).is_empty());
|
||||||
|
assert!(!format!("{:?}", int_err).is_empty());
|
||||||
|
assert!(!format!("{:?}", utf_err).is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
|
path::PathBuf,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,6 +43,17 @@ fn test_no_config_list() {
|
|||||||
assert_eq!(output, expected);
|
assert_eq!(output, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_config() {
|
||||||
|
let mut beets = BeetsLibrary::new(BeetsLibraryCommandExecutor::default().config(Some(
|
||||||
|
&PathBuf::from("./tests/files/library/config-does-not-exist.yml"),
|
||||||
|
)));
|
||||||
|
|
||||||
|
let result = beets.list(&Query::default());
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(!result.unwrap_err().to_string().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_full_list() {
|
fn test_full_list() {
|
||||||
let beets_arc = BEETS_TEST_CONFIG.clone();
|
let beets_arc = BEETS_TEST_CONFIG.clone();
|
||||||
|
Loading…
Reference in New Issue
Block a user