diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.kt index 025655daf..2fa65b2d9 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.kt +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.kt @@ -7,44 +7,59 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import fr.free.nrw.commons.nearby.NearbyController import fr.free.nrw.commons.nearby.Place -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map +/** + * DAO for managing bookmark locations in the database. + */ @Dao abstract class BookmarkLocationsDao { + /** + * Adds or updates a bookmark location in the database. + */ @Insert(onConflict = OnConflictStrategy.REPLACE) abstract suspend fun addBookmarkLocation(bookmarkLocation: BookmarksLocations) + /** + * Fetches all bookmark locations from the database. + */ @Query("SELECT * FROM bookmarks_locations") abstract suspend fun getAllBookmarksLocations(): List + /** + * Checks if a bookmark location exists by name. + */ @Query("SELECT EXISTS (SELECT 1 FROM bookmarks_locations WHERE location_name = :name)") abstract suspend fun findBookmarkLocation(name: String): Boolean + /** + * Deletes a bookmark location from the database. + */ @Delete abstract suspend fun deleteBookmarkLocation(bookmarkLocation: BookmarksLocations) + /** + * Adds or removes a bookmark location and updates markers. + * @return `true` if added, `false` if removed. + */ suspend fun updateBookmarkLocation(bookmarkLocation: Place): Boolean { - val bookmarkLocationExists = findBookmarkLocation(bookmarkLocation.name) + val exists = findBookmarkLocation(bookmarkLocation.name) - if(bookmarkLocationExists) { - deleteBookmarkLocation( - bookmarkLocation.toBookmarksLocations() - ) + if (exists) { + deleteBookmarkLocation(bookmarkLocation.toBookmarksLocations()) NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false) } else { - addBookmarkLocation( - bookmarkLocation.toBookmarksLocations() - ) + addBookmarkLocation(bookmarkLocation.toBookmarksLocations()) NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true) } - return !bookmarkLocationExists + return !exists } + /** + * Fetches all bookmark locations as `Place` objects. + */ suspend fun getAllBookmarksLocationsPlace(): List { return getAllBookmarksLocations().map { it.toPlace() } } -} \ No newline at end of file +} diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookMarkLocationDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookMarkLocationDaoTest.kt index e677bde5d..f1c3f9798 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookMarkLocationDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookMarkLocationDaoTest.kt @@ -7,6 +7,8 @@ import android.database.MatrixCursor import android.database.sqlite.SQLiteDatabase import android.net.Uri import android.os.RemoteException +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.anyOrNull import com.nhaarman.mockitokotlin2.argumentCaptor @@ -18,35 +20,20 @@ import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.TestCommonsApplication -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_CATEGORY -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_COMMONS_LINK -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_DESCRIPTION -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_EXISTS -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_IMAGE_URL -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_LABEL_ICON -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_LABEL_TEXT -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_LANGUAGE -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_LAT -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_LONG -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_NAME -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_PIC -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_WIKIDATA_LINK -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_WIKIPEDIA_LINK -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.CREATE_TABLE_STATEMENT -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.DROP_TABLE_STATEMENT -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.onCreate -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.onDelete -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.onUpdate +import fr.free.nrw.commons.db.AppDatabase import fr.free.nrw.commons.location.LatLng import fr.free.nrw.commons.nearby.Label import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.nearby.Sitelinks +import kotlinx.coroutines.runBlocking +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock import org.mockito.Mockito.verifyNoInteractions import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @@ -54,28 +41,12 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [21], application = TestCommonsApplication::class) class BookMarkLocationDaoTest { - private val columns = - arrayOf( - COLUMN_NAME, - COLUMN_LANGUAGE, - COLUMN_DESCRIPTION, - COLUMN_CATEGORY, - COLUMN_LABEL_TEXT, - COLUMN_LABEL_ICON, - COLUMN_IMAGE_URL, - COLUMN_WIKIPEDIA_LINK, - COLUMN_WIKIDATA_LINK, - COLUMN_COMMONS_LINK, - COLUMN_LAT, - COLUMN_LONG, - COLUMN_PIC, - COLUMN_EXISTS, - ) - private val client: ContentProviderClient = mock() - private val database: SQLiteDatabase = mock() - private val captor = argumentCaptor() - private lateinit var testObject: BookmarkLocationsDao + @Mock + var bookmarkLocationsDao: BookmarkLocationsDao? = null + + private lateinit var database: AppDatabase + private lateinit var examplePlaceBookmark: Place private lateinit var exampleLabel: Label private lateinit var exampleUri: Uri @@ -88,10 +59,18 @@ class BookMarkLocationDaoTest { exampleUri = Uri.parse("wikimedia/uri") exampleLocation = LatLng(40.0, 51.4, 1f) - builder = Sitelinks.Builder() - builder.setWikipediaLink("wikipediaLink") - builder.setWikidataLink("wikidataLink") - builder.setCommonsLink("commonsLink") + database = Room.inMemoryDatabaseBuilder( + ApplicationProvider.getApplicationContext(), + AppDatabase::class.java + ).allowMainThreadQueries().build() + + bookmarkLocationsDao = database.bookmarkLocationsDao() + + builder = Sitelinks.Builder().apply { + setWikipediaLink("wikipediaLink") + setWikidataLink("wikidataLink") + setCommonsLink("commonsLink") + } examplePlaceBookmark = Place( @@ -105,236 +84,25 @@ class BookMarkLocationDaoTest { "picName", false, ) - testObject = BookmarkLocationsDao { client } + } + + @After + fun tearDown() { + database.close() } @Test - fun createTable() { - onCreate(database) - verify(database).execSQL(CREATE_TABLE_STATEMENT) + fun insertAndRetrieveBookmark() = runBlocking { + // Insert a bookmark + bookmarkLocationsDao?.addBookmarkLocation(examplePlaceBookmark.toBookmarksLocations()) + + // Retrieve all bookmarks + val bookmarks = bookmarkLocationsDao?.getAllBookmarksLocations() + + // Assert the bookmark exists + assertEquals(1, bookmarks?.size) + val retrievedBookmark = bookmarks?.first() + assertEquals(examplePlaceBookmark.name, retrievedBookmark?.locationName) + assertEquals(examplePlaceBookmark.language, retrievedBookmark?.locationLanguage) } - - @Test - fun deleteTable() { - onDelete(database) - inOrder(database) { - verify(database).execSQL(DROP_TABLE_STATEMENT) - verify(database).execSQL(CREATE_TABLE_STATEMENT) - } - } - - @Test - fun createFromCursor() { - createCursor(1).let { cursor -> - cursor.moveToFirst() - testObject.fromCursor(cursor).let { - assertEquals("en", it.language) - assertEquals("placeName", it.name) - assertEquals(Label.FOREST, it.label) - assertEquals("placeDescription", it.longDescription) - assertEquals(40.0, it.location.latitude, 0.001) - assertEquals(51.4, it.location.longitude, 0.001) - assertEquals("placeCategory", it.category) - assertEquals(builder.build().wikipediaLink, it.siteLinks.wikipediaLink) - assertEquals(builder.build().wikidataLink, it.siteLinks.wikidataLink) - assertEquals(builder.build().commonsLink, it.siteLinks.commonsLink) - assertEquals("picName", it.pic) - assertEquals(false, it.exists) - } - } - } - - @Test - fun getAllLocationBookmarks() { - whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(14)) - - var result = testObject.getAllBookmarksLocations() - - assertEquals(14, result.size) - } - - @Test(expected = RuntimeException::class) - fun getAllLocationBookmarksTranslatesExceptions() { - whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(RemoteException("")) - testObject.getAllBookmarksLocations() - } - - @Test - fun getAllLocationBookmarksReturnsEmptyList_emptyCursor() { - whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(0)) - assertTrue(testObject.getAllBookmarksLocations().isEmpty()) - } - - @Test - fun getAllLocationBookmarksReturnsEmptyList_nullCursor() { - whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(null) - assertTrue(testObject.getAllBookmarksLocations().isEmpty()) - } - - @Test - fun cursorsAreClosedAfterGetAllLocationBookmarksQuery() { - val mockCursor: Cursor = mock() - whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor) - whenever(mockCursor.moveToFirst()).thenReturn(false) - - testObject.getAllBookmarksLocations() - - verify(mockCursor).close() - } - - @Test - fun updateNewLocationBookmark() { - whenever(client.insert(any(), any())).thenReturn(Uri.EMPTY) - whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(null) - - assertTrue(testObject.updateBookmarkLocation(examplePlaceBookmark)) - verify(client).insert(eq(BookmarkLocationsContentProvider.BASE_URI), captor.capture()) - captor.firstValue.let { cv -> - assertEquals(13, cv.size()) - assertEquals(examplePlaceBookmark.name, cv.getAsString(COLUMN_NAME)) - assertEquals(examplePlaceBookmark.language, cv.getAsString(COLUMN_LANGUAGE)) - assertEquals(examplePlaceBookmark.longDescription, cv.getAsString(COLUMN_DESCRIPTION)) - assertEquals(examplePlaceBookmark.label.text, cv.getAsString(COLUMN_LABEL_TEXT)) - assertEquals(examplePlaceBookmark.category, cv.getAsString(COLUMN_CATEGORY)) - assertEquals(examplePlaceBookmark.location.latitude, cv.getAsDouble(COLUMN_LAT), 0.001) - assertEquals(examplePlaceBookmark.location.longitude, cv.getAsDouble(COLUMN_LONG), 0.001) - assertEquals(examplePlaceBookmark.siteLinks.wikipediaLink.toString(), cv.getAsString(COLUMN_WIKIPEDIA_LINK)) - assertEquals(examplePlaceBookmark.siteLinks.wikidataLink.toString(), cv.getAsString(COLUMN_WIKIDATA_LINK)) - assertEquals(examplePlaceBookmark.siteLinks.commonsLink.toString(), cv.getAsString(COLUMN_COMMONS_LINK)) - assertEquals(examplePlaceBookmark.pic, cv.getAsString(COLUMN_PIC)) - assertEquals(examplePlaceBookmark.exists.toString(), cv.getAsString(COLUMN_EXISTS)) - } - } - - @Test - fun updateExistingLocationBookmark() { - whenever(client.delete(isA(), isNull(), isNull())).thenReturn(1) - whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(1)) - - assertFalse(testObject.updateBookmarkLocation(examplePlaceBookmark)) - verify(client).delete(eq(BookmarkLocationsContentProvider.uriForName(examplePlaceBookmark.name)), isNull(), isNull()) - } - - @Test - fun findExistingLocationBookmark() { - whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(1)) - assertTrue(testObject.findBookmarkLocation(examplePlaceBookmark)) - } - - @Test(expected = RuntimeException::class) - fun findLocationBookmarkTranslatesExceptions() { - whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(RemoteException("")) - testObject.findBookmarkLocation(examplePlaceBookmark) - } - - @Test - fun findNotExistingLocationBookmarkReturnsNull_emptyCursor() { - whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(0)) - assertFalse(testObject.findBookmarkLocation(examplePlaceBookmark)) - } - - @Test - fun findNotExistingLocationBookmarkReturnsNull_nullCursor() { - whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(null) - assertFalse(testObject.findBookmarkLocation(examplePlaceBookmark)) - } - - @Test - fun cursorsAreClosedAfterFindLocationBookmarkQuery() { - val mockCursor: Cursor = mock() - whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor) - whenever(mockCursor.moveToFirst()).thenReturn(false) - - testObject.findBookmarkLocation(examplePlaceBookmark) - - verify(mockCursor).close() - } - - @Test - fun migrateTableVersionFrom_v1_to_v2() { - onUpdate(database, 1, 2) - // Table didn't exist before v5 - verifyNoInteractions(database) - } - - @Test - fun migrateTableVersionFrom_v2_to_v3() { - onUpdate(database, 2, 3) - // Table didn't exist before v5 - verifyNoInteractions(database) - } - - @Test - fun migrateTableVersionFrom_v3_to_v4() { - onUpdate(database, 3, 4) - // Table didnt exist before v5 - verifyNoInteractions(database) - } - - @Test - fun migrateTableVersionFrom_v4_to_v5() { - onUpdate(database, 4, 5) - // Table didnt change in version 5 - verifyNoInteractions(database) - } - - @Test - fun migrateTableVersionFrom_v5_to_v6() { - onUpdate(database, 5, 6) - // Table didnt change in version 6 - verifyNoInteractions(database) - } - - @Test - fun migrateTableVersionFrom_v6_to_v7() { - onUpdate(database, 6, 7) - // Table didnt change in version 7 - verifyNoInteractions(database) - } - - @Test - fun migrateTableVersionFrom_v7_to_v8() { - onUpdate(database, 7, 8) - verify(database).execSQL(CREATE_TABLE_STATEMENT) - } - - @Test - fun migrateTableVersionFrom_v12_to_v13() { - onUpdate(database, 12, 13) - verify(database).execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;") - } - - @Test - fun migrateTableVersionFrom_v13_to_v14() { - onUpdate(database, 13, 14) - verify(database).execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_language STRING;") - } - - @Test - fun migrateTableVersionFrom_v14_to_v15() { - onUpdate(database, 14, 15) - verify(database).execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_exists STRING;") - } - - private fun createCursor(rows: Int): Cursor = - MatrixCursor(columns, rows).apply { - repeat(rows) { - newRow().apply { - add("placeName") - add("en") - add("placeDescription") - add("placeCategory") - add(Label.FOREST.text) - add(Label.FOREST.icon) - add("placeImage") - add("wikipediaLink") - add("wikidataLink") - add("commonsLink") - add(40.0) - add(51.4) - add("picName") - add(false) - } - } - } } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationControllerTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationControllerTest.kt index 262489ba5..a7afc6b14 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationControllerTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationControllerTest.kt @@ -2,6 +2,7 @@ package fr.free.nrw.commons.bookmarks.locations import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.nearby.Place +import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Before import org.junit.Test @@ -19,9 +20,11 @@ class BookmarkLocationControllerTest { @Before fun setup() { - MockitoAnnotations.initMocks(this) - whenever(bookmarkDao!!.getAllBookmarksLocations()) - .thenReturn(mockBookmarkList) + MockitoAnnotations.openMocks(this) + runBlocking { + whenever(bookmarkDao!!.getAllBookmarksLocationsPlace()) + .thenReturn(mockBookmarkList) + } } /** @@ -66,7 +69,7 @@ class BookmarkLocationControllerTest { * Test case where all bookmark locations are fetched and media is found against it */ @Test - fun loadBookmarkedLocations() { + fun loadBookmarkedLocations() = runBlocking { val bookmarkedLocations = bookmarkLocationsController.loadFavoritesLocations() Assert.assertEquals(2, bookmarkedLocations.size.toLong()) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationFragmentUnitTests.kt index 24693dc85..d3d3f98be 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationFragmentUnitTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationFragmentUnitTests.kt @@ -22,6 +22,7 @@ import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.nearby.fragments.CommonPlaceClickActions import fr.free.nrw.commons.nearby.fragments.PlaceAdapter import fr.free.nrw.commons.profile.ProfileActivity +import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Before import org.junit.Test @@ -129,7 +130,9 @@ class BookmarkLocationFragmentUnitTests { */ @Test fun testInitNonEmpty() { - whenever(controller.loadFavoritesLocations()).thenReturn(mockBookmarkList) + runBlocking { + whenever(controller.loadFavoritesLocations()).thenReturn(mockBookmarkList) + } val method: Method = BookmarkLocationsFragment::class.java.getDeclaredMethod("initList") method.isAccessible = true diff --git a/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyParentFragmentPresenterTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyParentFragmentPresenterTest.kt index 12b2319d0..96098e2a0 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyParentFragmentPresenterTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyParentFragmentPresenterTest.kt @@ -8,6 +8,7 @@ import fr.free.nrw.commons.location.LatLng import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter +import kotlinx.coroutines.runBlocking import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -463,7 +464,9 @@ class NearbyParentFragmentPresenterTest { nearbyPlacesInfo.searchLatLng = latestLocation nearbyPlacesInfo.placeList = emptyList() - whenever(bookmarkLocationsDao.getAllBookmarksLocations()).thenReturn(Collections.emptyList()) + runBlocking { + whenever(bookmarkLocationsDao.getAllBookmarksLocations()).thenReturn(Collections.emptyList()) + } nearbyPresenter.updateMapMarkers(nearbyPlacesInfo.placeList, latestLocation, null) Mockito.verify(nearbyParentFragmentView).setProgressBarVisibility(false) }