diff --git a/src/external/database/serde/common.rs b/src/external/database/serde/common.rs index e46b3de..07f085b 100644 --- a/src/external/database/serde/common.rs +++ b/src/external/database/serde/common.rs @@ -13,7 +13,7 @@ pub enum AlbumLibIdDef { None, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct SerdeAlbumLibId(#[serde(with = "AlbumLibIdDef")] AlbumLibId); impl From for AlbumLibId { @@ -36,7 +36,7 @@ pub struct AlbumDateDef { day: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct SerdeAlbumDate(#[serde(with = "AlbumDateDef")] pub AlbumDate); impl From for AlbumDate { @@ -69,7 +69,7 @@ pub enum AlbumPrimaryTypeDef { Other, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct SerdeAlbumPrimaryType(#[serde(with = "AlbumPrimaryTypeDef")] AlbumPrimaryType); impl From for AlbumPrimaryType { @@ -101,7 +101,7 @@ pub enum AlbumSecondaryTypeDef { FieldRecording, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct SerdeAlbumSecondaryType(#[serde(with = "AlbumSecondaryTypeDef")] AlbumSecondaryType); impl From for AlbumSecondaryType { diff --git a/src/external/database/serde/serialize.rs b/src/external/database/serde/serialize.rs index aa9b336..197fc71 100644 --- a/src/external/database/serde/serialize.rs +++ b/src/external/database/serde/serialize.rs @@ -22,7 +22,7 @@ impl<'a> From<&'a Collection> for SerializeDatabase<'a> { } } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct SerializeArtist<'a> { pub name: &'a str, pub sort: Option<&'a str>, @@ -31,7 +31,7 @@ pub struct SerializeArtist<'a> { pub albums: Vec>, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct SerializeAlbum<'a> { pub title: &'a str, pub lib_id: SerdeAlbumLibId, @@ -42,12 +42,12 @@ pub struct SerializeAlbum<'a> { pub secondary_types: Vec, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct SerializeMbRefOption<'a>( #[serde(with = "MbRefOptionDef")] MbRefOption>, ); -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct SerializeMbid<'a>(&'a Mbid); impl<'a, T: IMusicBrainzRef> From<&'a MbRefOption> for SerializeMbRefOption<'a> { diff --git a/src/external/database/sql/mod.rs b/src/external/database/sql/mod.rs index f215a5a..d442002 100644 --- a/src/external/database/sql/mod.rs +++ b/src/external/database/sql/mod.rs @@ -144,9 +144,9 @@ impl ISqlDatabaseBackend<'conn>> SqlDatabase { } fn drop_tables(tx: &Tx) -> Result<(), Error> { - tx.drop_database_metadata_table()?; - tx.drop_artists_table()?; tx.drop_albums_table()?; + tx.drop_artists_table()?; + tx.drop_database_metadata_table()?; Ok(()) } } @@ -201,103 +201,186 @@ impl ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase // #[cfg(test)] // pub mod testmod; -// #[cfg(test)] -// mod tests { -// use std::collections::HashMap; +#[cfg(test)] +mod tests { + // use std::collections::HashMap; -// use mockall::predicate; + use std::collections::VecDeque; -// use crate::core::{ -// collection::{artist::Artist, Collection}, -// testmod::FULL_COLLECTION, -// }; + use mockall::{predicate, Sequence}; -// use super::*; -// use testmod::DATABASE_JSON; + use crate::core::{ + // collection::{artist::Artist, Collection}, + testmod::FULL_COLLECTION, + }; -// fn expected() -> Collection { -// let mut expected = FULL_COLLECTION.to_owned(); -// for artist in expected.iter_mut() { -// for album in artist.albums.iter_mut() { -// album.tracks.clear(); -// } -// } -// expected -// } + use super::*; -// #[test] -// fn save() { -// let write_data = FULL_COLLECTION.to_owned(); -// let input = DATABASE_JSON.to_owned(); + // fn expected() -> Collection { + // let mut expected = FULL_COLLECTION.to_owned(); + // for artist in expected.iter_mut() { + // for album in artist.albums.iter_mut() { + // album.tracks.clear(); + // } + // } + // expected + // } -// let mut backend = MockISqlDatabaseBackend::new(); -// backend -// .expect_write() -// .with(predicate::eq(input)) -// .times(1) -// .return_once(|_| Ok(())); + struct SqlDatabaseTestBackend { + pub txs: VecDeque, + } -// SqlDatabase::new(backend).save(&write_data).unwrap(); -// } + impl SqlDatabaseTestBackend { + fn new(txs: VecDeque) -> Self { + SqlDatabaseTestBackend { txs } + } + } -// #[test] -// fn load() { -// let expected = expected(); -// let result = Ok(DATABASE_JSON.to_owned()); -// eprintln!("{DATABASE_JSON}"); + impl ISqlDatabaseBackend<'_> for SqlDatabaseTestBackend { + type Tx = MockISqlTransactionBackend; -// let mut backend = MockISqlDatabaseBackend::new(); -// backend.expect_read().times(1).return_once(|| result); + fn transaction(&mut self) -> Result { + Ok(self.txs.pop_front().unwrap()) + } + } -// let read_data: Vec = SqlDatabase::new(backend).load().unwrap(); + macro_rules! then { + ($tx:ident, $seq:ident, $expect:ident) => { + $tx.$expect().times(1).in_sequence(&mut $seq) + }; + } -// assert_eq!(read_data, expected); -// } + macro_rules! then0 { + ($tx:ident, $seq:ident, $expect:ident) => { + then!($tx, $seq, $expect).return_once(|| Ok(())) + }; + } -// #[test] -// fn reverse() { -// let input = DATABASE_JSON.to_owned(); -// let result = Ok(input.clone()); + macro_rules! then1 { + ($tx:ident, $seq:ident, $expect:ident) => { + then!($tx, $seq, $expect).return_once(|_| Ok(())) + }; + } -// let mut backend = MockISqlDatabaseBackend::new(); -// backend -// .expect_write() -// .with(predicate::eq(input)) -// .times(1) -// .return_once(|_| Ok(())); -// backend.expect_read().times(1).return_once(|| result); -// let mut database = SqlDatabase::new(backend); + macro_rules! then2 { + ($tx:ident, $seq:ident, $expect:ident) => { + then!($tx, $seq, $expect).return_once(|_, _| Ok(())) + }; + } -// let write_data = FULL_COLLECTION.to_owned(); -// database.save(&write_data).unwrap(); -// let read_data: Vec = database.load().unwrap(); + macro_rules! expect_create { + ($tx:ident, $seq:ident) => { + let mut seq = Sequence::new(); + then0!($tx, seq, expect_create_database_metadata_table); + then0!($tx, seq, expect_create_artists_table); + then0!($tx, seq, expect_create_albums_table); + }; + } -// // Album information is not saved to disk. -// let expected = expected(); -// assert_eq!(read_data, expected); -// } + macro_rules! expect_drop { + ($tx:ident, $seq:ident) => { + let mut seq = Sequence::new(); + then0!($tx, seq, expect_drop_albums_table); + then0!($tx, seq, expect_drop_artists_table); + then0!($tx, seq, expect_drop_database_metadata_table); + }; + } -// #[test] -// fn load_errors() { -// let json = String::from(""); -// let serde_err = serde_json::from_str::(&json); -// assert!(serde_err.is_err()); + fn database(txs: VecDeque) -> SqlDatabase { + let mut backend = SqlDatabaseTestBackend::new(txs); + let mut tx = MockISqlTransactionBackend::new(); -// let serde_err: LoadError = serde_err.unwrap_err().into(); -// assert!(!serde_err.to_string().is_empty()); -// assert!(!format!("{:?}", serde_err).is_empty()); -// } + let mut seq = Sequence::new(); + expect_create!(tx, seq); + then0!(tx, seq, expect_commit); -// #[test] -// fn save_errors() { -// // serde_json will raise an error as it has certain requirements on keys. -// let mut object = HashMap::, String>::new(); -// object.insert(Ok(()), String::from("string")); -// let serde_err = serde_json::to_string(&object); -// assert!(serde_err.is_err()); + backend.txs.push_front(tx); + SqlDatabase::new(backend).unwrap() + } -// let serde_err: SaveError = serde_err.unwrap_err().into(); -// assert!(!serde_err.to_string().is_empty()); -// assert!(!format!("{:?}", serde_err).is_empty()); -// } -// } + #[test] + fn save() { + let write_data = FULL_COLLECTION.to_owned(); + + let mut tx = MockISqlTransactionBackend::new(); + + let mut seq = Sequence::new(); + expect_drop!(tx, seq); + expect_create!(tx, seq); + then1!(tx, seq, expect_insert_database_version).with(predicate::eq(V20250103)); + for artist in write_data.iter() { + let ac = artist.clone(); + then1!(tx, seq, expect_insert_artist) + .withf(move |a| a == &Into::::into(&ac)); + for album in artist.albums.iter() { + let (nc, ac) = (artist.meta.id.name.clone(), album.clone()); + then2!(tx, seq, expect_insert_album) + .withf(move |n, a| n == nc && a == &Into::::into(&ac)); + } + } + then0!(tx, seq, expect_commit); + + assert!(database(VecDeque::from([tx])).save(&write_data).is_ok()); + } + + // #[test] + // fn load() { + // let expected = expected(); + // let result = Ok(DATABASE_JSON.to_owned()); + // eprintln!("{DATABASE_JSON}"); + + // let mut backend = MockISqlDatabaseBackend::new(); + // backend.expect_read().times(1).return_once(|| result); + + // let read_data: Vec = SqlDatabase::new(backend).load().unwrap(); + + // assert_eq!(read_data, expected); + // } + + // #[test] + // fn reverse() { + // let input = DATABASE_JSON.to_owned(); + // let result = Ok(input.clone()); + + // let mut backend = MockISqlDatabaseBackend::new(); + // backend + // .expect_write() + // .with(predicate::eq(input)) + // .times(1) + // .return_once(|_| Ok(())); + // backend.expect_read().times(1).return_once(|| result); + // let mut database = SqlDatabase::new(backend); + + // let write_data = FULL_COLLECTION.to_owned(); + // database.save(&write_data).unwrap(); + // let read_data: Vec = database.load().unwrap(); + + // // Album information is not saved to disk. + // let expected = expected(); + // assert_eq!(read_data, expected); + // } + + // #[test] + // fn load_errors() { + // let json = String::from(""); + // let serde_err = serde_json::from_str::(&json); + // assert!(serde_err.is_err()); + + // let serde_err: LoadError = serde_err.unwrap_err().into(); + // assert!(!serde_err.to_string().is_empty()); + // assert!(!format!("{:?}", serde_err).is_empty()); + // } + + // #[test] + // fn save_errors() { + // // serde_json will raise an error as it has certain requirements on keys. + // let mut object = HashMap::, String>::new(); + // object.insert(Ok(()), String::from("string")); + // let serde_err = serde_json::to_string(&object); + // assert!(serde_err.is_err()); + + // let serde_err: SaveError = serde_err.unwrap_err().into(); + // assert!(!serde_err.to_string().is_empty()); + // assert!(!format!("{:?}", serde_err).is_empty()); + // } +}