diff --git a/app/src/androidTest/java/fr/free/nrw/commons/ReviewDaoTest.java b/app/src/androidTest/java/fr/free/nrw/commons/ReviewDaoTest.java deleted file mode 100644 index f485d5152..000000000 --- a/app/src/androidTest/java/fr/free/nrw/commons/ReviewDaoTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package fr.free.nrw.commons; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; - -import android.content.Context; -import androidx.room.Room; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import fr.free.nrw.commons.db.AppDatabase; -import fr.free.nrw.commons.review.ReviewDao; -import fr.free.nrw.commons.review.ReviewEntity; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class ReviewDaoTest { - - private ReviewDao reviewDao; - private AppDatabase database; - - /** - * Set up the application database - */ - @Before - public void createDb() { - Context context = ApplicationProvider.getApplicationContext(); - database = Room.inMemoryDatabaseBuilder( - context, AppDatabase.class) - .allowMainThreadQueries() - .build(); - reviewDao = database.ReviewDao(); - } - - /** - * Close the database - */ - @After - public void closeDb() { - database.close(); - } - - /** - * Test insertion - * Also checks isReviewedAlready(): - * Case 1: When image has been reviewed/skipped by the user - */ - @Test - public void insert() { - // Insert data - String imageId = "1234"; - ReviewEntity reviewEntity = new ReviewEntity(imageId); - reviewDao.insert(reviewEntity); - - // Check insertion - // Covers the case where the image exists in the database - // And isReviewedAlready() returns true - Boolean isInserted = reviewDao.isReviewedAlready(imageId); - assertThat(isInserted, equalTo(true)); - } - - /** - * Test review status of the image - * Case 2: When image has not been reviewed/skipped - */ - @Test - public void isReviewedAlready(){ - String imageId = "5856"; - Boolean isInserted = reviewDao.isReviewedAlready(imageId); - assertThat(isInserted, equalTo(false)); - } -} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/UserClient.java b/app/src/main/java/fr/free/nrw/commons/mwapi/UserClient.java deleted file mode 100644 index 8b5cfa72a..000000000 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/UserClient.java +++ /dev/null @@ -1,46 +0,0 @@ -package fr.free.nrw.commons.mwapi; - -import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse; -import fr.free.nrw.commons.wikidata.mwapi.MwQueryResult; -import fr.free.nrw.commons.wikidata.mwapi.UserInfo; -import fr.free.nrw.commons.utils.DateUtil; - -import java.util.Collections; -import java.util.Date; - -import javax.inject.Inject; - -import io.reactivex.Observable; -import io.reactivex.Single; - -public class UserClient { - private final UserInterface userInterface; - - @Inject - public UserClient(UserInterface userInterface) { - this.userInterface = userInterface; - } - - /** - * Checks to see if a user is currently blocked from Commons - * - * @return whether or not the user is blocked from Commons - */ - public Single isUserBlockedFromCommons() { - return userInterface.getUserBlockInfo() - .map(MwQueryResponse::query) - .map(MwQueryResult::userInfo) - .map(UserInfo::blockexpiry) - .map(blockExpiry -> { - if (blockExpiry.isEmpty()) - return false; - else if ("infinite".equals(blockExpiry)) - return true; - else { - Date endDate = DateUtil.iso8601DateParse(blockExpiry); - Date current = new Date(); - return endDate.after(current); - } - }).single(false); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/UserClient.kt b/app/src/main/java/fr/free/nrw/commons/mwapi/UserClient.kt new file mode 100644 index 000000000..b6e3b873b --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/UserClient.kt @@ -0,0 +1,36 @@ +package fr.free.nrw.commons.mwapi + +import fr.free.nrw.commons.utils.DateUtil +import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse +import fr.free.nrw.commons.wikidata.mwapi.MwQueryResult +import fr.free.nrw.commons.wikidata.mwapi.UserInfo +import io.reactivex.Single +import java.text.ParseException +import java.util.Date +import javax.inject.Inject + +class UserClient @Inject constructor(private val userInterface: UserInterface) { + /** + * Checks to see if a user is currently blocked from Commons + * + * @return whether or not the user is blocked from Commons + */ + fun isUserBlockedFromCommons(): Single = + userInterface.getUserBlockInfo() + .map(::processBlockExpiry) + .single(false) + + @Throws(ParseException::class) + private fun processBlockExpiry(response: MwQueryResponse): Boolean { + val blockExpiry = response.query()?.userInfo()?.blockexpiry() + return when { + blockExpiry.isNullOrEmpty() -> false + "infinite" == blockExpiry -> true + else -> { + val endDate = DateUtil.iso8601DateParse(blockExpiry) + val current = Date() + endDate.after(current) + } + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.java deleted file mode 100644 index 4119a1751..000000000 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.java +++ /dev/null @@ -1,166 +0,0 @@ -package fr.free.nrw.commons.review; - - -import androidx.annotation.VisibleForTesting; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.media.MediaClient; -import io.reactivex.Completable; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import java.util.Collections; -import javax.inject.Inject; -import javax.inject.Singleton; -import org.apache.commons.lang3.StringUtils; -import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage; -import timber.log.Timber; - -@Singleton -public class ReviewHelper { - - private static final String[] imageExtensions = new String[]{".jpg", ".jpeg", ".png"}; - - private final MediaClient mediaClient; - private final ReviewInterface reviewInterface; - - @Inject - ReviewDao dao; - - @Inject - public ReviewHelper(MediaClient mediaClient, ReviewInterface reviewInterface) { - this.mediaClient = mediaClient; - this.reviewInterface = reviewInterface; - } - - /** - * Fetches recent changes from MediaWiki API - * Calls the API to get the latest 50 changes - * When more results are available, the query gets continued beyond this range - * - * @return - */ - private Observable getRecentChanges() { - return reviewInterface.getRecentChanges() - .map(mwQueryResponse -> mwQueryResponse.query().pages()) - .map(recentChanges -> { - Collections.shuffle(recentChanges); - return recentChanges; - }) - .flatMapIterable(changes -> changes) - .filter(recentChange -> isChangeReviewable(recentChange)); - } - - /** - * Gets a random file change for review. - * - Picks a random file from those changes - * - Checks if the file is nominated for deletion - * - Retries upto 5 times for getting a file which is not nominated for deletion - * - * @return Random file change - */ - public Single getRandomMedia() { - return getRecentChanges() - .flatMapSingle(change -> getRandomMediaFromRecentChange(change)) - .filter(media -> !StringUtils.isBlank(media.getFilename()) - && !getReviewStatus(media.getPageId()) // Check if the image has already been shown to the user - ) - .firstOrError(); - } - - /** - * Returns a proper Media object if the file is not already nominated for deletion - * Else it returns an empty Media object - * - * @param recentChange - * @return - */ - private Single getRandomMediaFromRecentChange(MwQueryPage recentChange) { - return Single.just(recentChange) - .flatMap(change -> mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + change.title())) - .flatMap(isDeleted -> { - if (isDeleted) { - return Single.error(new Exception(recentChange.title() + " is deleted")); - } - return mediaClient.getMedia(recentChange.title()); - }); - - } - - /** - * Checks if the image exists in the reviewed images entity - * - * @param image - * @return - */ - @VisibleForTesting - Boolean getReviewStatus(String image){ - if(dao == null){ - return false; - } - return Observable.fromCallable(()-> dao.isReviewedAlready(image)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()).blockingSingle(); - } - - /** - * Gets the first revision of the file from filename - * - * @param filename - * @return - */ - public Observable getFirstRevisionOfFile(String filename) { - return reviewInterface.getFirstRevisionOfFile(filename) - .map(response -> response.query().firstPage().revisions().get(0)); - } - - /** - * Checks Whether Given File is used in any Wiki page or not - * by calling api for given file - * - * @param filename - * @return - */ - Observable checkFileUsage(final String filename) { - return reviewInterface.getGlobalUsageInfo(filename) - .map(mwQueryResponse -> mwQueryResponse.query().firstPage() - .checkWhetherFileIsUsedInWikis()); - } - - /** - * Checks if the change is reviewable or not. - * - checks the type and revisionId of the change - * - checks supported image extensions - * - * @param recentChange - * @return - */ - private boolean isChangeReviewable(MwQueryPage recentChange) { - for (String extension : imageExtensions) { - if (recentChange.title().endsWith(extension)) { - return true; - } - } - - return false; - } - - /** - * Adds reviewed/skipped images to the database - * - * @param imageId - */ - public void addViewedImagesToDB(String imageId) { - Completable.fromAction(() -> dao.insert(new ReviewEntity(imageId))) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(() -> { - // Inserted successfully - Timber.i("Image inserted successfully."); - }, - throwable -> { - Timber.e("Image not inserted into the reviewed images database"); - } - ); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt new file mode 100644 index 000000000..16b8d26de --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt @@ -0,0 +1,138 @@ +package fr.free.nrw.commons.review + +import androidx.annotation.VisibleForTesting +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.media.MediaClient +import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage +import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage.Revision +import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse +import io.reactivex.Completable +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import org.apache.commons.lang3.StringUtils +import timber.log.Timber +import java.util.Collections +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ReviewHelper @Inject constructor( + private val mediaClient: MediaClient, + private val reviewInterface: ReviewInterface +) { + @JvmField @Inject var dao: ReviewDao? = null + + /** + * Fetches recent changes from MediaWiki API + * Calls the API to get the latest 50 changes + * When more results are available, the query gets continued beyond this range + * + * @return + */ + private fun getRecentChanges() = reviewInterface.getRecentChanges() + .map { it.query()?.pages() } + .map(MutableList::shuffled) + .flatMapIterable { changes: List? -> changes } + .filter { isChangeReviewable(it) } + + /** + * Gets a random file change for review. Checks if the image has already been shown to the user + * - Picks a random file from those changes + * - Checks if the file is nominated for deletion + * - Retries upto 5 times for getting a file which is not nominated for deletion + * + * @return Random file change + */ + fun getRandomMedia(): Single = getRecentChanges() + .flatMapSingle(::getRandomMediaFromRecentChange) + .filter { !it.filename.isNullOrBlank() && !getReviewStatus(it.pageId) } + .firstOrError() + + /** + * Returns a proper Media object if the file is not already nominated for deletion + * Else it returns an empty Media object + * + * @param recentChange + * @return + */ + private fun getRandomMediaFromRecentChange(recentChange: MwQueryPage) = + Single.just(recentChange) + .flatMap { mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/${it.title()}") } + .flatMap { + if (it) { + Single.error(Exception("${recentChange.title()} is deleted")) + } else { + mediaClient.getMedia(recentChange.title()) + } + } + + /** + * Checks if the image exists in the reviewed images entity + * + * @param image + * @return + */ + fun getReviewStatus(image: String?): Boolean = + dao?.isReviewedAlready(image) ?: false + + /** + * Gets the first revision of the file from filename + * + * @param filename + * @return + */ + fun getFirstRevisionOfFile(filename: String?): Observable = + reviewInterface.getFirstRevisionOfFile(filename) + .map { it.query()?.firstPage()?.revisions()?.get(0) } + + /** + * Checks Whether Given File is used in any Wiki page or not + * by calling api for given file + * + * @param filename + * @return + */ + fun checkFileUsage(filename: String?): Observable = + reviewInterface.getGlobalUsageInfo(filename) + .map { it.query()?.firstPage()?.checkWhetherFileIsUsedInWikis() } + + /** + * Checks if the change is reviewable or not. + * - checks the type and revisionId of the change + * - checks supported image extensions + * + * @param recentChange + * @return + */ + private fun isChangeReviewable(recentChange: MwQueryPage): Boolean { + for (extension in imageExtensions) { + if (recentChange.title().endsWith(extension)) { + return true + } + } + return false + } + + /** + * Adds reviewed/skipped images to the database + * + * @param imageId + */ + fun addViewedImagesToDB(imageId: String?) { + Completable.fromAction { dao!!.insert(ReviewEntity(imageId)) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + // Inserted successfully + Timber.i("Image inserted successfully.") + } + ) { throwable: Throwable? -> Timber.e("Image not inserted into the reviewed images database") } + } + + companion object { + private val imageExtensions = arrayOf(".jpg", ".jpeg", ".png") + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.java deleted file mode 100644 index 201d97fe2..000000000 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.java +++ /dev/null @@ -1,76 +0,0 @@ -package fr.free.nrw.commons.wikidata; - -import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF; -import static fr.free.nrw.commons.media.MediaClientKt.PAGE_ID_PREFIX; - -import fr.free.nrw.commons.upload.UploadResult; -import fr.free.nrw.commons.upload.WikiBaseInterface; -import io.reactivex.Observable; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; -import fr.free.nrw.commons.auth.csrf.CsrfTokenClient; -import fr.free.nrw.commons.wikidata.mwapi.MwPostResponse; -import timber.log.Timber; - -/** - * Wikibase Client for calling WikiBase APIs - */ -@Singleton -public class WikiBaseClient { - - private final WikiBaseInterface wikiBaseInterface; - private final CsrfTokenClient csrfTokenClient; - - @Inject - public WikiBaseClient(WikiBaseInterface wikiBaseInterface, - @Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) { - this.wikiBaseInterface = wikiBaseInterface; - this.csrfTokenClient = csrfTokenClient; - } - - public Observable postEditEntity(String fileEntityId, String data) { - return csrfToken() - .switchMap(editToken -> wikiBaseInterface.postEditEntity(fileEntityId, editToken, data) - .map(response -> (response.getSuccessVal() == 1))); - } - - /** - * Makes the server call for posting new depicts - * - * @param filename name of the file - * @param data data of the depicts to be uploaded - * @return Observable - */ - public Observable postEditEntityByFilename(final String filename, final String data) { - return csrfToken() - .switchMap(editToken -> wikiBaseInterface.postEditEntityByFilename(filename, - editToken, data) - .map(response -> (response.getSuccessVal() == 1))); - } - - public Observable getFileEntityId(UploadResult uploadResult) { - return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName()) - .map(response -> (long) (response.query().pages().get(0).pageId())); - } - - public Observable addLabelstoWikidata(long fileEntityId, - String languageCode, String captionValue) { - return csrfToken() - .switchMap(editToken -> wikiBaseInterface - .addLabelstoWikidata(PAGE_ID_PREFIX + fileEntityId, editToken, languageCode, - captionValue)); - - } - - private Observable csrfToken() { - return Observable.fromCallable(() -> { - try { - return csrfTokenClient.getTokenBlocking(); - } catch (Throwable throwable) { - Timber.e(throwable); - return ""; - } - }); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.kt new file mode 100644 index 000000000..22bb6d8b2 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikiBaseClient.kt @@ -0,0 +1,69 @@ +package fr.free.nrw.commons.wikidata + +import fr.free.nrw.commons.auth.csrf.CsrfTokenClient +import fr.free.nrw.commons.di.NetworkingModule +import fr.free.nrw.commons.media.PAGE_ID_PREFIX +import fr.free.nrw.commons.upload.UploadResult +import fr.free.nrw.commons.upload.WikiBaseInterface +import fr.free.nrw.commons.wikidata.mwapi.MwPostResponse +import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse +import io.reactivex.Observable +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Singleton + +/** + * Wikibase Client for calling WikiBase APIs + */ +@Singleton +class WikiBaseClient @Inject constructor( + private val wikiBaseInterface: WikiBaseInterface, + @param:Named(NetworkingModule.NAMED_COMMONS_CSRF) private val csrfTokenClient: CsrfTokenClient +) { + fun postEditEntity(fileEntityId: String?, data: String?): Observable { + return csrfToken().switchMap { editToken -> + wikiBaseInterface.postEditEntity(fileEntityId!!, editToken, data!!) + .map { response: MwPostResponse -> response.successVal == 1 } + } + } + + /** + * Makes the server call for posting new depicts + * + * @param filename name of the file + * @param data data of the depicts to be uploaded + * @return Observable + */ + fun postEditEntityByFilename(filename: String?, data: String?): Observable { + return csrfToken().switchMap { editToken -> + wikiBaseInterface.postEditEntityByFilename(filename!!, editToken, data!!) + .map { response: MwPostResponse -> response.successVal == 1 } + } + } + + fun getFileEntityId(uploadResult: UploadResult): Observable { + return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName()) + .map { response: MwQueryResponse -> response.query()!!.pages()!![0].pageId().toLong() } + } + + fun addLabelstoWikidata(fileEntityId: Long, languageCode: String?, captionValue: String?): Observable { + return csrfToken().switchMap { editToken -> + wikiBaseInterface.addLabelstoWikidata( + PAGE_ID_PREFIX + fileEntityId, + editToken, + languageCode, + captionValue + ) + } + } + + private fun csrfToken(): Observable = Observable.fromCallable { + try { + csrfTokenClient.getTokenBlocking() + } catch (throwable: Throwable) { + Timber.e(throwable) + "" + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java deleted file mode 100644 index 79d1e459c..000000000 --- a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.java +++ /dev/null @@ -1,47 +0,0 @@ -package fr.free.nrw.commons.wikidata; - -import com.google.gson.Gson; -import org.jetbrains.annotations.NotNull; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import fr.free.nrw.commons.wikidata.model.AddEditTagResponse; -import io.reactivex.Observable; -import io.reactivex.ObservableSource; -import fr.free.nrw.commons.wikidata.model.Statement_partial; - -@Singleton -public class WikidataClient { - - - private final WikidataInterface wikidataInterface; - private final Gson gson; - - @Inject - public WikidataClient(WikidataInterface wikidataInterface, final Gson gson) { - this.wikidataInterface = wikidataInterface; - this.gson = gson; - } - - /** - * Create wikidata claim to add P18 value - * - * @return revisionID of the edit - */ - Observable setClaim(Statement_partial claim, String tags) { - return getCsrfToken() - .flatMap( - csrfToken -> wikidataInterface.postSetClaim(gson.toJson(claim), tags, csrfToken)) - .map(mwPostResponse -> mwPostResponse.getPageinfo().getLastrevid()); - } - - /** - * Get csrf token for wikidata edit - */ - @NotNull - private Observable getCsrfToken() { - return wikidataInterface.getCsrfToken() - .map(mwQueryResponse -> mwQueryResponse.query().csrfToken()); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.kt b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.kt new file mode 100644 index 000000000..d9d6dc33d --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/wikidata/WikidataClient.kt @@ -0,0 +1,32 @@ +package fr.free.nrw.commons.wikidata + +import com.google.gson.Gson +import fr.free.nrw.commons.wikidata.model.Statement_partial +import fr.free.nrw.commons.wikidata.model.WbCreateClaimResponse +import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse +import io.reactivex.Observable +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WikidataClient @Inject constructor( + private val wikidataInterface: WikidataInterface, + private val gson: Gson +) { + /** + * Create wikidata claim to add P18 value + * + * @return revisionID of the edit + */ + fun setClaim(claim: Statement_partial?, tags: String?): Observable { + return csrfToken().flatMap { csrfToken: String? -> + wikidataInterface.postSetClaim(gson.toJson(claim), tags!!, csrfToken!!) + }.map { mwPostResponse: WbCreateClaimResponse -> mwPostResponse.pageinfo.lastrevid } + } + + /** + * Get csrf token for wikidata edit + */ + private fun csrfToken(): Observable = + wikidataInterface.getCsrfToken().map { it.query()?.csrfToken() } +} diff --git a/app/src/test/kotlin/fr/free/nrw/commons/mwapi/UserClientTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/mwapi/UserClientTest.kt index c1075562d..b8728f485 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/mwapi/UserClientTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/mwapi/UserClientTest.kt @@ -36,7 +36,7 @@ class UserClientTest{ Mockito.`when`(userInterface!!.getUserBlockInfo()) .thenReturn(Observable.just(mockResponse)) - val isBanned = userClient!!.isUserBlockedFromCommons.blockingGet() + val isBanned = userClient!!.isUserBlockedFromCommons().blockingGet() assertTrue(isBanned) } @@ -54,7 +54,7 @@ class UserClientTest{ Mockito.`when`(userInterface!!.getUserBlockInfo()) .thenReturn(Observable.just(mockResponse)) - val isBanned = userClient!!.isUserBlockedFromCommons.blockingGet() + val isBanned = userClient!!.isUserBlockedFromCommons().blockingGet() assertTrue(isBanned) } @@ -69,7 +69,7 @@ class UserClientTest{ Mockito.`when`(userInterface!!.getUserBlockInfo()) .thenReturn(Observable.just(mockResponse)) - val isBanned = userClient!!.isUserBlockedFromCommons.blockingGet() + val isBanned = userClient!!.isUserBlockedFromCommons().blockingGet() assertFalse(isBanned) } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewActivityTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewActivityTest.kt index 5fffa1a2f..042559f8a 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewActivityTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewActivityTest.kt @@ -111,10 +111,10 @@ class ReviewActivityTest { val media = mock(Media::class.java) doReturn(mapOf("test" to false)).`when`(media).categoriesHiddenStatus - doReturn(Single.just(media)).`when`(reviewHelper)?.randomMedia - Assert.assertNotNull(reviewHelper?.randomMedia) + doReturn(Single.just(media)).`when`(reviewHelper)?.getRandomMedia() + Assert.assertNotNull(reviewHelper?.getRandomMedia()) reviewHelper - ?.randomMedia + ?.getRandomMedia() ?.test() ?.assertValue(media); activity.swipeToNext() diff --git a/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewDaoTest.kt new file mode 100644 index 000000000..55623b3cb --- /dev/null +++ b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewDaoTest.kt @@ -0,0 +1,73 @@ +package fr.free.nrw.commons.review + +import androidx.room.Room.inMemoryDatabaseBuilder +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import fr.free.nrw.commons.TestCommonsApplication +import fr.free.nrw.commons.db.AppDatabase +import org.hamcrest.CoreMatchers +import org.hamcrest.MatcherAssert +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode + +@RunWith(AndroidJUnit4::class) +@Config(sdk = [21], application = TestCommonsApplication::class) +@LooperMode(LooperMode.Mode.PAUSED) +class ReviewDaoTest { + private lateinit var reviewDao: ReviewDao + private lateinit var database: AppDatabase + + /** + * Set up the application database + */ + @Before + fun createDb() { + database = inMemoryDatabaseBuilder( + context = ApplicationProvider.getApplicationContext(), + klass = AppDatabase::class.java + ).allowMainThreadQueries().build() + reviewDao = database.ReviewDao() + } + + /** + * Close the database + */ + @After + fun closeDb() { + database.close() + } + + /** + * Test insertion + * Also checks isReviewedAlready(): + * Case 1: When image has been reviewed/skipped by the user + */ + @Test + fun insert() { + // Insert data + val imageId = "1234" + val reviewEntity = ReviewEntity(imageId) + reviewDao.insert(reviewEntity) + + // Check insertion + // Covers the case where the image exists in the database + // And isReviewedAlready() returns true + val isInserted = reviewDao.isReviewedAlready(imageId) + MatcherAssert.assertThat(isInserted, CoreMatchers.equalTo(true)) + } + + @Test + fun isReviewedAlready() { + /** + * Test review status of the image + * Case 2: When image has not been reviewed/skipped + */ + val imageId = "5856" + val isInserted = reviewDao.isReviewedAlready(imageId) + MatcherAssert.assertThat(isInserted, CoreMatchers.equalTo(false)) + } +} \ No newline at end of file diff --git a/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt index 6cea81f10..74be27d5b 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt @@ -1,40 +1,32 @@ package fr.free.nrw.commons.review -import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.Media import fr.free.nrw.commons.media.MediaClient -import io.reactivex.Completable +import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage +import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse +import fr.free.nrw.commons.wikidata.mwapi.MwQueryResult import io.reactivex.Observable import io.reactivex.Single +import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test -import org.mockito.ArgumentMatchers -import org.mockito.InjectMocks -import org.mockito.Mock import org.mockito.Mockito.* -import org.mockito.MockitoAnnotations -import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage -import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse -import fr.free.nrw.commons.wikidata.mwapi.MwQueryResult -import java.util.concurrent.Callable /** * Test class for ReviewHelper */ class ReviewHelperTest { - @Mock - internal var reviewInterface: ReviewInterface? = null - @Mock - internal var mediaClient: MediaClient? = null + private val reviewInterface = mock() + private val mediaClient = mock() + private val reviewHelper = ReviewHelper(mediaClient, reviewInterface) - @InjectMocks - var reviewHelper: ReviewHelper? = null - val dao = mock(ReviewDao::class.java) + private val mwQueryResult = mock() + private val mockResponse = mock() /** * Init mocks @@ -42,28 +34,8 @@ class ReviewHelperTest { @Before @Throws(Exception::class) fun setUp() { - MockitoAnnotations.openMocks(this) - - val mwQueryPage = mock(MwQueryPage::class.java) - val mockRevision = mock(MwQueryPage.Revision::class.java) - `when`(mockRevision.user).thenReturn("TestUser") - `when`(mwQueryPage.revisions()).thenReturn(listOf(mockRevision)) - - val mwQueryResult = mock(MwQueryResult::class.java) - `when`(mwQueryResult.firstPage()).thenReturn(mwQueryPage) - `when`(mwQueryResult.pages()).thenReturn(listOf(mwQueryPage)) - val mockResponse = mock(MwQueryResponse::class.java) - `when`(mockResponse.query()).thenReturn(mwQueryResult) - `when`(reviewInterface?.getRecentChanges()) - .thenReturn(Observable.just(mockResponse)) - - `when`(reviewInterface?.getFirstRevisionOfFile(ArgumentMatchers.anyString())) - .thenReturn(Observable.just(mockResponse)) - - val media = mock(Media::class.java) - whenever(media.filename).thenReturn("Test file.jpg") - `when`(mediaClient?.getMedia(ArgumentMatchers.anyString())) - .thenReturn(Single.just(media)) + whenever(mockResponse.query()).thenReturn(mwQueryResult) + whenever(reviewInterface.getRecentChanges()).thenReturn(Observable.just(mockResponse)) } /** @@ -71,14 +43,19 @@ class ReviewHelperTest { */ @Test fun getRandomMedia() { - `when`(mediaClient?.checkPageExistsUsingTitle(ArgumentMatchers.anyString())) - .thenReturn(Single.just(false)) + whenever(mediaClient.checkPageExistsUsingTitle(any())).thenReturn(Single.just(false)) - `when`(mediaClient?.checkPageExistsUsingTitle(ArgumentMatchers.anyString())) - .thenReturn(Single.just(false)) + val page1 = setupMedia("one.jpg") + val page2 = setupMedia("two.jpeg") + val page3 = setupMedia("three.png") + val ignored = setupMedia("ignored.txt") + whenever(mwQueryResult.pages()).thenReturn(listOf(page1, page2, page3, ignored)) - reviewHelper?.randomMedia - verify(reviewInterface, times(1))!!.getRecentChanges() + val random = reviewHelper.getRandomMedia().test() + + random.assertNoErrors() + assertEquals(1, random.valueCount()) + assertTrue(setOf("one.jpg", "two.jpeg", "three.png").contains(random.values().first().filename)) } /** @@ -86,9 +63,12 @@ class ReviewHelperTest { */ @Test(expected = RuntimeException::class) fun getRandomMediaWithWithAllMediaNominatedForDeletion() { - `when`(mediaClient?.checkPageExistsUsingTitle(ArgumentMatchers.anyString())) - .thenReturn(Single.just(true)) - val media = reviewHelper?.randomMedia?.blockingGet() + whenever(mediaClient.checkPageExistsUsingTitle(any())).thenReturn(Single.just(true)) + + val page1 = setupMedia("one.jpg") + whenever(mwQueryResult.pages()).thenReturn(listOf(page1)) + + val media = reviewHelper.getRandomMedia().blockingGet() assertNull(media) verify(reviewInterface, times(1))!!.getRecentChanges() } @@ -98,15 +78,14 @@ class ReviewHelperTest { */ @Test fun getRandomMediaWithWithOneMediaNominatedForDeletion() { - `when`(mediaClient?.checkPageExistsUsingTitle("Commons:Deletion_requests/File:Test1.jpeg")) - .thenReturn(Single.just(true)) - `when`(mediaClient?.checkPageExistsUsingTitle("Commons:Deletion_requests/File:Test2.png")) - .thenReturn(Single.just(false)) - `when`(mediaClient?.checkPageExistsUsingTitle("Commons:Deletion_requests/File:Test3.jpg")) - .thenReturn(Single.just(true)) + whenever(mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/one.jpg")).thenReturn(Single.just(true)) - reviewHelper?.randomMedia - verify(reviewInterface, times(1))!!.getRecentChanges() + val page1 = setupMedia("one.jpg") + whenever(mwQueryResult.pages()).thenReturn(listOf(page1)) + + val random = reviewHelper.getRandomMedia().test() + + assertEquals("one.jpg is deleted", random.errors().first().message) } /** @@ -114,57 +93,55 @@ class ReviewHelperTest { */ @Test fun getFirstRevisionOfFile() { - val firstRevisionOfFile = reviewHelper?.getFirstRevisionOfFile("Test.jpg")?.blockingFirst() + val rev1 = mock() + whenever(rev1.user).thenReturn("TestUser") + whenever(rev1.revisionId).thenReturn(1L) + val rev2 = mock() + whenever(rev2.user).thenReturn("TestUser") + whenever(rev2.revisionId).thenReturn(2L) - assertTrue(firstRevisionOfFile is MwQueryPage.Revision) + val page = setupMedia("Test.jpg", rev1, rev2) + whenever(mwQueryResult.firstPage()).thenReturn(page) + whenever(reviewInterface.getFirstRevisionOfFile(any())).thenReturn(Observable.just(mockResponse)) + + val firstRevisionOfFile = reviewHelper.getFirstRevisionOfFile("Test.jpg").blockingFirst() + + assertEquals(1, firstRevisionOfFile.revisionId) } - /** - * Test the review status of the image - * Case 1: Image identifier exists in the database - */ @Test - fun getReviewStatusWhenImageHasBeenReviewedAlready() { - val testImageId1 = "123456" - `when`(dao.isReviewedAlready(testImageId1)).thenReturn(true) + fun checkFileUsage() { + whenever(reviewInterface.getGlobalUsageInfo(any())).thenReturn(Observable.just(mockResponse)) + val page = setupMedia("Test.jpg") + whenever(mwQueryResult.firstPage()).thenReturn(page) + whenever(page.checkWhetherFileIsUsedInWikis()).thenReturn(true) - val observer = io.reactivex.observers.TestObserver() - Observable.fromCallable(Callable { - dao.isReviewedAlready(testImageId1) - }).subscribeWith(observer) - observer.assertValue(true) - observer.dispose() + val result = reviewHelper.checkFileUsage("Test.jpg").test() + + assertTrue(result.values().first()) } - /** - * Test the review status of the image - * Case 2: Image identifier does not exist in the database - */ @Test - fun getReviewStatusWhenImageHasBeenNotReviewedAlready() { - val testImageId2 = "789101" - `when`(dao.isReviewedAlready(testImageId2)).thenReturn(false) + fun testReviewStatus() { + val reviewDao = mock() + whenever(reviewDao.isReviewedAlready("Test.jpg")).thenReturn(true) - val observer = io.reactivex.observers.TestObserver() - Observable.fromCallable(Callable { - dao.isReviewedAlready(testImageId2) - }).subscribeWith(observer) - observer.assertValue(false) - observer.dispose() + reviewHelper.dao = reviewDao + val result = reviewHelper.getReviewStatus("Test.jpg") + + assertTrue(result) } - /** - * Test the successful insertion of the image identifier into the database - */ - @Test - fun addViewedImagesToDB() { - val testImageId = "123456" + private fun setupMedia(file: String, vararg revision: MwQueryPage.Revision): MwQueryPage = mock().apply { + whenever(title()).thenReturn(file) + if (revision.isNotEmpty()) { + whenever(revisions()).thenReturn(*revision.toMutableList()) + } - val observer = io.reactivex.observers.TestObserver() - Completable.fromAction { - dao.insert(ReviewEntity(testImageId)) - }.subscribeWith(observer) - observer.assertComplete() - observer.dispose() + val media = mock().apply { + whenever(filename).thenReturn(file) + whenever(pageId).thenReturn(file.split(".").first()) + } + whenever(mediaClient.getMedia(file)).thenReturn(Single.just(media)) } } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikiBaseClientUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikiBaseClientUnitTest.kt index a42082a74..956b5d4a0 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikiBaseClientUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikiBaseClientUnitTest.kt @@ -1,31 +1,84 @@ package fr.free.nrw.commons.wikidata +import com.nhaarman.mockitokotlin2.whenever +import fr.free.nrw.commons.auth.csrf.CsrfTokenClient +import fr.free.nrw.commons.upload.UploadResult +import fr.free.nrw.commons.upload.WikiBaseInterface +import fr.free.nrw.commons.wikidata.mwapi.MwPostResponse +import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage +import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse +import fr.free.nrw.commons.wikidata.mwapi.MwQueryResult +import io.reactivex.Observable +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertSame +import junit.framework.TestCase.assertTrue import org.junit.Before import org.junit.Test -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.MockitoAnnotations -import fr.free.nrw.commons.auth.csrf.CsrfTokenClient +import org.mockito.Mockito.mock class WikiBaseClientUnitTest { - @Mock - internal var csrfTokenClient: CsrfTokenClient? = null - - @InjectMocks - var wikiBaseClient: WikiBaseClient? = null + private val csrfTokenClient: CsrfTokenClient = mock() + private val wikiBaseInterface: WikiBaseInterface = mock() + private val wikiBaseClient = WikiBaseClient(wikiBaseInterface, csrfTokenClient) @Before @Throws(Exception::class) fun setUp() { - MockitoAnnotations.openMocks(this) - Mockito.`when`(csrfTokenClient!!.getTokenBlocking()) - .thenReturn("test") + whenever(csrfTokenClient.getTokenBlocking()).thenReturn("test") + } + + @Test + fun testPostEditEntity() { + val response = mock() + whenever(response.successVal).thenReturn(1) + whenever(wikiBaseInterface.postEditEntity("the-id", "test", "the-data")) + .thenReturn(Observable.just(response)) + + val result = wikiBaseClient.postEditEntity("the-id", "the-data").blockingFirst() + + assertTrue(result) } @Test fun testPostEditEntityByFilename() { - wikiBaseClient?.postEditEntityByFilename("File:Example.jpg", "data") + val response = mock() + whenever(response.successVal).thenReturn(1) + whenever(wikiBaseInterface.postEditEntityByFilename("File:Example.jpg", "test", "the-data")) + .thenReturn(Observable.just(response)) + + val result = wikiBaseClient.postEditEntityByFilename("File:Example.jpg", "the-data").blockingFirst() + + assertTrue(result) + } + + @Test + fun getFileEntityId() { + val upload = UploadResult("result", "key", 0, "Example.jpg") + + val response = mock() + val query = mock() + val page = mock() + whenever(response.query()).thenReturn(query) + whenever(query.pages()).thenReturn(mutableListOf(page)) + whenever(page.pageId()).thenReturn(123) + whenever(wikiBaseInterface.getFileEntityId("File:Example.jpg")) + .thenReturn(Observable.just(response)) + + val result = wikiBaseClient.getFileEntityId(upload).blockingFirst() + + assertEquals(123L, result) + } + + @Test + fun addLabelstoWikidata() { + val mwPostResponse = mock() + whenever(wikiBaseInterface.addLabelstoWikidata( + "M123", "test", "en", "caption" + )).thenReturn(Observable.just(mwPostResponse)) + + val result = wikiBaseClient.addLabelstoWikidata(123L, "en", "caption").blockingFirst() + + assertSame(mwPostResponse, result) } } \ No newline at end of file diff --git a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt index 041a0fa97..b4d4a4af5 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/wikidata/WikidataEditServiceTest.kt @@ -2,6 +2,7 @@ package fr.free.nrw.commons.wikidata import android.content.Context import com.google.gson.Gson +import com.nhaarman.mockitokotlin2.anyOrNull import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.kvstore.JsonKvStore @@ -61,7 +62,7 @@ class WikidataEditServiceTest { fun createImageClaim() { whenever(directKvStore.getBoolean("Picture_Has_Correct_Location", true)) .thenReturn(true) - whenever(wikibaseClient.getFileEntityId(any())).thenReturn(Observable.just(1L)) + whenever(wikibaseClient.getFileEntityId(anyOrNull())).thenReturn(Observable.just(1L)) whenever(wikidataClient.setClaim(any(), anyString())) .thenReturn(Observable.just(1L)) val wikidataPlace: WikidataPlace = mock()