Make database and library optional #86

Merged
wojtek merged 4 commits from 25---make-database-and-library-optional into main 2024-01-07 11:07:36 +01:00
6 changed files with 129 additions and 32 deletions

View File

@ -1,12 +1,17 @@
import argparse import argparse
import json import json
import sys
def main(coverage_file, fail_under): def main(coverage_file, fail_under):
with open(coverage_file, encoding="utf-8") as f: with open(coverage_file, encoding="utf-8") as f:
coverage_json = json.load(f) coverage_json = json.load(f)
coverage = float(coverage_json["message"][:-1]) coverage = float(coverage_json["message"][:-1])
return coverage >= fail_under print(f"Code coverage: {coverage:.2f}%; Threshold: {fail_under:.2f}%")
success = coverage >= fail_under
if coverage < fail_under:
print("Insufficient code coverage", file=sys.stderr)
return success
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -20,6 +20,19 @@ pub trait IDatabase {
fn save<S: Serialize + 'static>(&mut self, collection: &S) -> Result<(), SaveError>; fn save<S: Serialize + 'static>(&mut self, collection: &S) -> Result<(), SaveError>;
} }
/// Non-implementation defined to make generics easier.
pub struct NoDatabase {}
impl IDatabase for NoDatabase {
fn load<D: DeserializeOwned + 'static>(&self, _collection: &mut D) -> Result<(), LoadError> {
panic!()
}
fn save<S: Serialize + 'static>(&mut self, _collection: &S) -> Result<(), SaveError> {
panic!()
}
}
/// Error type for database calls. /// Error type for database calls.
#[derive(Debug)] #[derive(Debug)]
pub enum LoadError { pub enum LoadError {
@ -76,7 +89,23 @@ impl From<std::io::Error> for SaveError {
mod tests { mod tests {
use std::io; use std::io;
use super::{LoadError, SaveError}; use super::{IDatabase, LoadError, NoDatabase, SaveError};
#[test]
#[should_panic]
fn no_database_load() {
let database = NoDatabase {};
let mut collection: Vec<String> = vec![];
_ = database.load(&mut collection);
}
#[test]
#[should_panic]
fn no_database_save() {
let mut database = NoDatabase {};
let collection: Vec<String> = vec![];
_ = database.save(&collection);
}
#[test] #[test]
fn errors() { fn errors() {

View File

@ -460,14 +460,14 @@ impl From<InvalidUrlError> for Error {
/// The Music Hoard. It is responsible for pulling information from both the library and the /// The Music Hoard. It is responsible for pulling information from both the library and the
/// database, ensuring its consistent and writing back any changes. /// database, ensuring its consistent and writing back any changes.
pub struct MusicHoard<LIB, DB> { pub struct MusicHoard<LIB, DB> {
library: LIB, library: Option<LIB>,
database: DB, database: Option<DB>,
collection: Collection, collection: Collection,
} }
impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> { impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> {
/// Create a new [`MusicHoard`] with the provided [`ILibrary`] and [`IDatabase`]. /// Create a new [`MusicHoard`] with the provided [`ILibrary`] and [`IDatabase`].
pub fn new(library: LIB, database: DB) -> Self { pub fn new(library: Option<LIB>, database: Option<DB>) -> Self {
MusicHoard { MusicHoard {
library, library,
database, database,
@ -476,31 +476,46 @@ impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> {
} }
pub fn rescan_library(&mut self) -> Result<(), Error> { pub fn rescan_library(&mut self) -> Result<(), Error> {
let items = self.library.list(&Query::new())?; match self.library {
let mut library = Self::items_to_artists(items); Some(ref mut library) => {
Self::sort(&mut library); let items = library.list(&Query::new())?;
let mut library_collection = Self::items_to_artists(items);
Self::sort(&mut library_collection);
let collection = mem::take(&mut self.collection); let collection = mem::take(&mut self.collection);
self.collection = Self::merge(library, collection); self.collection = Self::merge(library_collection, collection);
Ok(()) Ok(())
} }
None => Err(Error::LibraryError(String::from("library not provided"))),
}
}
pub fn load_from_database(&mut self) -> Result<(), Error> { pub fn load_from_database(&mut self) -> Result<(), Error> {
let mut database: Collection = vec![]; match self.database {
self.database.load(&mut database)?; Some(ref mut database) => {
Self::sort(&mut database); let mut database_collection: Collection = vec![];
database.load(&mut database_collection)?;
Self::sort(&mut database_collection);
let collection = mem::take(&mut self.collection); let collection = mem::take(&mut self.collection);
self.collection = Self::merge(collection, database); self.collection = Self::merge(collection, database_collection);
Ok(()) Ok(())
} }
None => Err(Error::DatabaseError(String::from("database not provided"))),
}
}
pub fn save_to_database(&mut self) -> Result<(), Error> { pub fn save_to_database(&mut self) -> Result<(), Error> {
self.database.save(&self.collection)?; match self.database {
Some(ref mut database) => {
database.save(&self.collection)?;
Ok(()) Ok(())
} }
None => Err(Error::DatabaseError(String::from("database not provided"))),
}
}
pub fn get_collection(&self) -> &Collection { pub fn get_collection(&self) -> &Collection {
&self.collection &self.collection
@ -601,7 +616,10 @@ mod tests {
use mockall::predicate; use mockall::predicate;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::{database::MockIDatabase, library::MockILibrary}; use crate::{
database::{MockIDatabase, NoDatabase},
library::{MockILibrary, NoLibrary},
};
use super::*; use super::*;
@ -834,7 +852,7 @@ mod tests {
.times(1) .times(1)
.return_once(|_| library_result); .return_once(|_| library_result);
let mut music_hoard = MusicHoard::new(library, database); let mut music_hoard = MusicHoard::new(Some(library), Some(database));
music_hoard.rescan_library().unwrap(); music_hoard.rescan_library().unwrap();
assert_eq!( assert_eq!(
@ -861,7 +879,7 @@ mod tests {
.times(1) .times(1)
.return_once(|_| library_result); .return_once(|_| library_result);
let mut music_hoard = MusicHoard::new(library, database); let mut music_hoard = MusicHoard::new(Some(library), Some(database));
music_hoard.rescan_library().unwrap(); music_hoard.rescan_library().unwrap();
assert_eq!( assert_eq!(
@ -888,7 +906,7 @@ mod tests {
.times(1) .times(1)
.return_once(|_| library_result); .return_once(|_| library_result);
let mut music_hoard = MusicHoard::new(library, database); let mut music_hoard = MusicHoard::new(Some(library), Some(database));
music_hoard.rescan_library().unwrap(); music_hoard.rescan_library().unwrap();
assert_eq!(music_hoard.get_collection(), &expected); assert_eq!(music_hoard.get_collection(), &expected);
@ -907,7 +925,7 @@ mod tests {
Ok(()) Ok(())
}); });
let mut music_hoard = MusicHoard::new(library, database); let mut music_hoard = MusicHoard::new(Some(library), Some(database));
music_hoard.load_from_database().unwrap(); music_hoard.load_from_database().unwrap();
assert_eq!(music_hoard.get_collection(), &*COLLECTION); assert_eq!(music_hoard.get_collection(), &*COLLECTION);
@ -936,7 +954,7 @@ mod tests {
.times(1) .times(1)
.return_once(|_: &Collection| database_result); .return_once(|_: &Collection| database_result);
let mut music_hoard = MusicHoard::new(library, database); let mut music_hoard = MusicHoard::new(Some(library), Some(database));
music_hoard.rescan_library().unwrap(); music_hoard.rescan_library().unwrap();
assert_eq!( assert_eq!(
@ -946,6 +964,18 @@ mod tests {
music_hoard.save_to_database().unwrap(); music_hoard.save_to_database().unwrap();
} }
#[test]
fn library_not_provided() {
let library: Option<NoLibrary> = None;
let database = Some(MockIDatabase::new());
let mut music_hoard = MusicHoard::new(library, database);
let actual_err = music_hoard.rescan_library().unwrap_err();
let expected_err = Error::LibraryError(String::from("library not provided"));
assert_eq!(actual_err, expected_err);
}
#[test] #[test]
fn library_error() { fn library_error() {
let mut library = MockILibrary::new(); let mut library = MockILibrary::new();
@ -958,7 +988,7 @@ mod tests {
.times(1) .times(1)
.return_once(|_| library_result); .return_once(|_| library_result);
let mut music_hoard = MusicHoard::new(library, database); let mut music_hoard = MusicHoard::new(Some(library), Some(database));
let actual_err = music_hoard.rescan_library().unwrap_err(); let actual_err = music_hoard.rescan_library().unwrap_err();
let expected_err = let expected_err =
@ -968,6 +998,23 @@ mod tests {
assert_eq!(actual_err.to_string(), expected_err.to_string()); assert_eq!(actual_err.to_string(), expected_err.to_string());
} }
#[test]
fn database_not_provided() {
let library = Some(MockILibrary::new());
let database: Option<NoDatabase> = None;
let mut music_hoard = MusicHoard::new(library, database);
let expected_err = Error::DatabaseError(String::from("database not provided"));
let actual_err = music_hoard.load_from_database().unwrap_err();
assert_eq!(actual_err, expected_err);
assert_eq!(actual_err.to_string(), expected_err.to_string());
let actual_err = music_hoard.save_to_database().unwrap_err();
assert_eq!(actual_err, expected_err);
assert_eq!(actual_err.to_string(), expected_err.to_string());
}
#[test] #[test]
fn database_load_error() { fn database_load_error() {
let library = MockILibrary::new(); let library = MockILibrary::new();
@ -980,7 +1027,7 @@ mod tests {
.times(1) .times(1)
.return_once(|_: &mut Collection| database_result); .return_once(|_: &mut Collection| database_result);
let mut music_hoard = MusicHoard::new(library, database); let mut music_hoard = MusicHoard::new(Some(library), Some(database));
let actual_err = music_hoard.load_from_database().unwrap_err(); let actual_err = music_hoard.load_from_database().unwrap_err();
let expected_err = Error::DatabaseError( let expected_err = Error::DatabaseError(
@ -1003,7 +1050,7 @@ mod tests {
.times(1) .times(1)
.return_once(|_: &Collection| database_result); .return_once(|_: &Collection| database_result);
let mut music_hoard = MusicHoard::new(library, database); let mut music_hoard = MusicHoard::new(Some(library), Some(database));
let actual_err = music_hoard.save_to_database().unwrap_err(); let actual_err = music_hoard.save_to_database().unwrap_err();
let expected_err = Error::DatabaseError( let expected_err = Error::DatabaseError(

View File

@ -17,6 +17,15 @@ pub trait ILibrary {
fn list(&mut self, query: &Query) -> Result<Vec<Item>, Error>; fn list(&mut self, query: &Query) -> Result<Vec<Item>, Error>;
} }
/// Non-implementation defined to make generics easier.
pub struct NoLibrary {}
impl ILibrary for NoLibrary {
fn list(&mut self, _query: &Query) -> Result<Vec<Item>, Error> {
panic!()
}
}
/// An item from the library. An item corresponds to an individual file (usually a single track). /// An item from the library. An item corresponds to an individual file (usually a single track).
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub struct Item { pub struct Item {
@ -127,7 +136,14 @@ impl From<Utf8Error> for Error {
mod tests { mod tests {
use std::io; use std::io;
use super::{Error, Field, Query}; use super::{Error, Field, ILibrary, NoLibrary, Query};
#[test]
#[should_panic]
fn no_library_list() {
let mut library = NoLibrary {};
_ = library.list(&Query::default());
}
#[test] #[test]
fn query() { fn query() {

View File

@ -42,7 +42,7 @@ struct Opt {
} }
fn with<LIB: ILibrary, DB: IDatabase>(lib: LIB, db: DB) { fn with<LIB: ILibrary, DB: IDatabase>(lib: LIB, db: DB) {
let music_hoard = MusicHoard::new(lib, db); let music_hoard = MusicHoard::new(Some(lib), Some(db));
// Initialize the terminal user interface. // Initialize the terminal user interface.
let backend = CrosstermBackend::new(io::stdout()); let backend = CrosstermBackend::new(io::stdout());

View File

@ -30,7 +30,7 @@ fn merge_library_then_database() {
let backend = JsonDatabaseFileBackend::new(&*database::json::DATABASE_TEST_FILE); let backend = JsonDatabaseFileBackend::new(&*database::json::DATABASE_TEST_FILE);
let database = JsonDatabase::new(backend); let database = JsonDatabase::new(backend);
let mut music_hoard = MusicHoard::new(library, database); let mut music_hoard = MusicHoard::new(Some(library), Some(database));
music_hoard.rescan_library().unwrap(); music_hoard.rescan_library().unwrap();
music_hoard.load_from_database().unwrap(); music_hoard.load_from_database().unwrap();
@ -55,7 +55,7 @@ fn merge_database_then_library() {
let backend = JsonDatabaseFileBackend::new(&*database::json::DATABASE_TEST_FILE); let backend = JsonDatabaseFileBackend::new(&*database::json::DATABASE_TEST_FILE);
let database = JsonDatabase::new(backend); let database = JsonDatabase::new(backend);
let mut music_hoard = MusicHoard::new(library, database); let mut music_hoard = MusicHoard::new(Some(library), Some(database));
music_hoard.load_from_database().unwrap(); music_hoard.load_from_database().unwrap();
music_hoard.rescan_library().unwrap(); music_hoard.rescan_library().unwrap();