diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index e4de7b1a2..39aeab02d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -21,6 +21,7 @@ package org.oxycblt.auxio.music import android.content.Context import android.os.Parcelable +import androidx.annotation.VisibleForTesting import java.security.MessageDigest import java.text.CollationKey import java.text.Collator @@ -763,16 +764,15 @@ class Album constructor(raw: Raw, override val songs: List) : MusicParent( override fun hashCode() = hashCode - override fun equals(other: Any?): Boolean { - if (other !is Raw) return false - if (musicBrainzId != null && - other.musicBrainzId != null && - musicBrainzId == other.musicBrainzId) { - return true - } - - return name.equals(other.name, true) && rawArtists == other.rawArtists - } + override fun equals(other: Any?) = + other is Raw && + when { + musicBrainzId != null && other.musicBrainzId != null -> + musicBrainzId == other.musicBrainzId + musicBrainzId == null && other.musicBrainzId == null -> + name.equals(other.name, true) && rawArtists == other.rawArtists + else -> false + } } } @@ -916,21 +916,19 @@ class Artist constructor(private val raw: Raw, songAlbums: List) : MusicP override fun hashCode() = hashCode - override fun equals(other: Any?): Boolean { - if (other !is Raw) return false - - if (musicBrainzId != null && - other.musicBrainzId != null && - musicBrainzId == other.musicBrainzId) { - return true - } - - return when { - name != null && other.name != null -> name.equals(other.name, true) - name == null && other.name == null -> true - else -> false - } - } + override fun equals(other: Any?) = + other is Raw && + when { + musicBrainzId != null && other.musicBrainzId != null -> + musicBrainzId == other.musicBrainzId + musicBrainzId == null && other.musicBrainzId == null -> + when { + name != null && other.name != null -> name.equals(other.name, true) + name == null && other.name == null -> true + else -> false + } + else -> false + } } } @@ -1025,7 +1023,7 @@ class Genre constructor(private val raw: Raw, override val songs: List) : * @return A [UUID] converted from the [String] value, or null if the value was not valid. * @see UUID.fromString */ -fun String.toUuidOrNull(): UUID? = +private fun String.toUuidOrNull(): UUID? = try { UUID.fromString(this) } catch (e: IllegalArgumentException) { @@ -1036,7 +1034,8 @@ fun String.toUuidOrNull(): UUID? = * Update a [MessageDigest] with a lowercase [String]. * @param string The [String] to hash. If null, it will not be hashed. */ -private fun MessageDigest.update(string: String?) { +@VisibleForTesting +fun MessageDigest.update(string: String?) { if (string != null) { update(string.lowercase().toByteArray()) } else { @@ -1048,7 +1047,8 @@ private fun MessageDigest.update(string: String?) { * Update a [MessageDigest] with the string representation of a [Date]. * @param date The [Date] to hash. If null, nothing will be done. */ -private fun MessageDigest.update(date: Date?) { +@VisibleForTesting +fun MessageDigest.update(date: Date?) { if (date != null) { update(date.toString().toByteArray()) } else { @@ -1060,7 +1060,8 @@ private fun MessageDigest.update(date: Date?) { * Update a [MessageDigest] with the lowercase versions of all of the input [String]s. * @param strings The [String]s to hash. If a [String] is null, it will not be hashed. */ -private fun MessageDigest.update(strings: List) { +@VisibleForTesting +fun MessageDigest.update(strings: List) { strings.forEach(::update) } @@ -1068,7 +1069,8 @@ private fun MessageDigest.update(strings: List) { * Update a [MessageDigest] with the little-endian bytes of a [Int]. * @param n The [Int] to write. If null, nothing will be done. */ -private fun MessageDigest.update(n: Int?) { +@VisibleForTesting +fun MessageDigest.update(n: Int?) { if (n != null) { update(byteArrayOf(n.toByte(), n.shr(8).toByte(), n.shr(16).toByte(), n.shr(24).toByte())) } else { diff --git a/app/src/test/java/org/oxycblt/auxio/music/MusicTest.kt b/app/src/test/java/org/oxycblt/auxio/music/MusicTest.kt new file mode 100644 index 000000000..d1262e11f --- /dev/null +++ b/app/src/test/java/org/oxycblt/auxio/music/MusicTest.kt @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2023 Auxio Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.music + +import java.util.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.oxycblt.auxio.music.tags.Date + +class MusicTest { + @Test + fun musicUid_auxio() { + val uid = + Music.UID.auxio(MusicMode.SONGS) { + update("Wheel") + update(listOf("Parannoul", "Asian Glow")) + update("Paraglow") + update(null as String?) + update(Date.from(2022)) + update(4 as Int?) + update(null as Int?) + } + + assertEquals("org.oxycblt.auxio:a10b-3d29c202-cd52-fbe0-4714-47cd07f07a59", uid.toString()) + } + + @Test + fun musicUid_musicBrainz() { + val uid = + Music.UID.musicBrainz( + MusicMode.ALBUMS, UUID.fromString("9b3b0695-0cdc-4560-8486-8deadee136cb")) + assertEquals("org.musicbrainz:a10a-9b3b0695-0cdc-4560-8486-8deadee136cb", uid.toString()) + } + + @Test + fun albumRaw_equals_inconsistentCase() { + val a = + Album.Raw( + mediaStoreId = -1, + musicBrainzId = null, + name = "Paraglow", + sortName = null, + releaseType = null, + rawArtists = + listOf(Artist.Raw(name = "Parannoul"), Artist.Raw(name = "Asian Glow"))) + val b = + Album.Raw( + mediaStoreId = -1, + musicBrainzId = null, + name = "paraglow", + sortName = null, + releaseType = null, + rawArtists = + listOf(Artist.Raw(name = "Parannoul"), Artist.Raw(name = "Asian glow"))) + assertTrue(a == b) + assertTrue(a.hashCode() == b.hashCode()) + } + + @Test + fun albumRaw_equals_withMbids() { + val a = + Album.Raw( + mediaStoreId = -1, + musicBrainzId = UUID.fromString("c7b245c9-8099-32ea-af95-893acedde2cf"), + name = "Weezer", + sortName = "Blue Album", + releaseType = null, + rawArtists = listOf(Artist.Raw(name = "Weezer"))) + val b = + Album.Raw( + mediaStoreId = -1, + musicBrainzId = UUID.fromString("923d5ba6-7eee-3bce-bcb2-c913b2bd69d4"), + name = "Weezer", + sortName = "Green Album", + releaseType = null, + rawArtists = listOf(Artist.Raw(name = "Weezer"))) + assertTrue(a != b) + assertTrue(a.hashCode() != b.hashCode()) + } + + @Test + fun albumRaw_equals_inconsistentMbids() { + val a = + Album.Raw( + mediaStoreId = -1, + musicBrainzId = UUID.fromString("c7b245c9-8099-32ea-af95-893acedde2cf"), + name = "Weezer", + sortName = "Blue Album", + releaseType = null, + rawArtists = listOf(Artist.Raw(name = "Weezer"))) + val b = + Album.Raw( + mediaStoreId = -1, + musicBrainzId = null, + name = "Weezer", + sortName = "Green Album", + releaseType = null, + rawArtists = listOf(Artist.Raw(name = "Weezer"))) + assertTrue(a != b) + assertTrue(a.hashCode() != b.hashCode()) + } + + @Test + fun albumRaw_equals_withArtists() { + val a = + Album.Raw( + mediaStoreId = -1, + musicBrainzId = null, + name = "Album", + sortName = null, + releaseType = null, + rawArtists = listOf(Artist.Raw(name = "Artist A"))) + val b = + Album.Raw( + mediaStoreId = -1, + musicBrainzId = null, + name = "Album", + sortName = null, + releaseType = null, + rawArtists = listOf(Artist.Raw(name = "Artist B"))) + assertTrue(a != b) + assertTrue(a.hashCode() != b.hashCode()) + } + + @Test + fun artistRaw_equals_inconsistentCase() { + val a = Artist.Raw(musicBrainzId = null, name = "Parannoul") + val b = Artist.Raw(musicBrainzId = null, name = "parannoul") + assertTrue(a == b) + assertTrue(a.hashCode() == b.hashCode()) + } + + @Test + fun artistRaw_equals_withMbids() { + val a = + Artist.Raw( + musicBrainzId = UUID.fromString("677325ef-d850-44bb-8258-0d69bbc0b3f7"), + name = "Artist") + val b = + Artist.Raw( + musicBrainzId = UUID.fromString("6b625592-d88d-48c8-ac1a-c5b476d78bcc"), + name = "Artist") + assertTrue(a != b) + assertTrue(a.hashCode() != b.hashCode()) + } + + @Test + fun artistRaw_equals_inconsistentMbids() { + val a = + Artist.Raw( + musicBrainzId = UUID.fromString("677325ef-d850-44bb-8258-0d69bbc0b3f7"), + name = "Artist") + val b = Artist.Raw(musicBrainzId = null, name = "Artist") + assertTrue(a != b) + assertTrue(a.hashCode() != b.hashCode()) + } + + @Test + fun artistRaw_equals_missingNames() { + val a = Artist.Raw(name = null) + val b = Artist.Raw(name = null) + assertTrue(a == b) + assertTrue(a.hashCode() == b.hashCode()) + } + + @Test + fun artistRaw_equals_inconsistentNames() { + val a = Artist.Raw(name = null) + val b = Artist.Raw(name = "Parannoul") + assertTrue(a != b) + assertTrue(a.hashCode() != b.hashCode()) + } + + @Test + fun genreRaw_equals_inconsistentCase() { + val a = Genre.Raw("Future Garage") + val b = Genre.Raw("future garage") + assertTrue(a == b) + assertTrue(a.hashCode() == b.hashCode()) + } + + @Test + fun genreRaw_equals_missingNames() { + val a = Genre.Raw(name = null) + val b = Genre.Raw(name = null) + assertTrue(a == b) + assertTrue(a.hashCode() == b.hashCode()) + } + + @Test + fun genreRaw_equals_inconsistentNames() { + val a = Genre.Raw(name = null) + val b = Genre.Raw(name = "Future Garage") + assertTrue(a != b) + assertTrue(a.hashCode() != b.hashCode()) + } +}