Convert API clients to kotlin (#5567)

* Convert UserClient to kotlin

* Added tests for WikiBaseClient

* Removed superfluous dao tests in review helper and got the proper ReviewDaoTest running in the unit test suite

* Improved tests for ReviewHelper

* Convert the ReviewHelper to kotlin

* Convert the WikiBaseClient to kotlin

* Convert the WikidataClient to kotlin
This commit is contained in:
Paul Hawke 2024-02-19 18:23:11 -06:00 committed by GitHub
parent c43405267a
commit 728712c4e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 496 additions and 526 deletions

View file

@ -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));
}
}

View file

@ -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<Boolean> 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);
}
}

View file

@ -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<Boolean> =
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)
}
}
}
}

View file

@ -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<MwQueryPage> 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<Media> 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<Media> 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<MwQueryPage.Revision> 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<Boolean> 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");
}
);
}
}

View file

@ -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<MwQueryPage>::shuffled)
.flatMapIterable { changes: List<MwQueryPage>? -> 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<Media> = 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<Revision> =
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<Boolean> =
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")
}
}

View file

@ -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<Boolean> 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<Boolean>
*/
public Observable<Boolean> postEditEntityByFilename(final String filename, final String data) {
return csrfToken()
.switchMap(editToken -> wikiBaseInterface.postEditEntityByFilename(filename,
editToken, data)
.map(response -> (response.getSuccessVal() == 1)));
}
public Observable<Long> getFileEntityId(UploadResult uploadResult) {
return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName())
.map(response -> (long) (response.query().pages().get(0).pageId()));
}
public Observable<MwPostResponse> addLabelstoWikidata(long fileEntityId,
String languageCode, String captionValue) {
return csrfToken()
.switchMap(editToken -> wikiBaseInterface
.addLabelstoWikidata(PAGE_ID_PREFIX + fileEntityId, editToken, languageCode,
captionValue));
}
private Observable<String> csrfToken() {
return Observable.fromCallable(() -> {
try {
return csrfTokenClient.getTokenBlocking();
} catch (Throwable throwable) {
Timber.e(throwable);
return "";
}
});
}
}

View file

@ -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<Boolean> {
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<Boolean>
</Boolean> */
fun postEditEntityByFilename(filename: String?, data: String?): Observable<Boolean> {
return csrfToken().switchMap { editToken ->
wikiBaseInterface.postEditEntityByFilename(filename!!, editToken, data!!)
.map { response: MwPostResponse -> response.successVal == 1 }
}
}
fun getFileEntityId(uploadResult: UploadResult): Observable<Long> {
return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName())
.map { response: MwQueryResponse -> response.query()!!.pages()!![0].pageId().toLong() }
}
fun addLabelstoWikidata(fileEntityId: Long, languageCode: String?, captionValue: String?): Observable<MwPostResponse> {
return csrfToken().switchMap { editToken ->
wikiBaseInterface.addLabelstoWikidata(
PAGE_ID_PREFIX + fileEntityId,
editToken,
languageCode,
captionValue
)
}
}
private fun csrfToken(): Observable<String> = Observable.fromCallable {
try {
csrfTokenClient.getTokenBlocking()
} catch (throwable: Throwable) {
Timber.e(throwable)
""
}
}
}

View file

@ -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<Long> 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<String> getCsrfToken() {
return wikidataInterface.getCsrfToken()
.map(mwQueryResponse -> mwQueryResponse.query().csrfToken());
}
}

View file

@ -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<Long> {
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<String?> =
wikidataInterface.getCsrfToken().map { it.query()?.csrfToken() }
}

View file

@ -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)
}

View file

@ -111,10 +111,10 @@ class ReviewActivityTest {
val media = mock(Media::class.java)
doReturn(mapOf<String, Boolean>("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()

View file

@ -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))
}
}

View file

@ -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<ReviewInterface>()
private val mediaClient = mock<MediaClient>()
private val reviewHelper = ReviewHelper(mediaClient, reviewInterface)
@InjectMocks
var reviewHelper: ReviewHelper? = null
val dao = mock(ReviewDao::class.java)
private val mwQueryResult = mock<MwQueryResult>()
private val mockResponse = mock<MwQueryResponse>()
/**
* 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<MwQueryPage.Revision>()
whenever(rev1.user).thenReturn("TestUser")
whenever(rev1.revisionId).thenReturn(1L)
val rev2 = mock<MwQueryPage.Revision>()
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<Boolean>()
Observable.fromCallable(Callable<Boolean> {
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<ReviewDao>()
whenever(reviewDao.isReviewedAlready("Test.jpg")).thenReturn(true)
val observer = io.reactivex.observers.TestObserver<Boolean>()
Observable.fromCallable(Callable<Boolean> {
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<MwQueryPage>().apply {
whenever(title()).thenReturn(file)
if (revision.isNotEmpty()) {
whenever(revisions()).thenReturn(*revision.toMutableList())
}
val observer = io.reactivex.observers.TestObserver<Boolean>()
Completable.fromAction {
dao.insert(ReviewEntity(testImageId))
}.subscribeWith(observer)
observer.assertComplete()
observer.dispose()
val media = mock<Media>().apply {
whenever(filename).thenReturn(file)
whenever(pageId).thenReturn(file.split(".").first())
}
whenever(mediaClient.getMedia(file)).thenReturn(Single.just(media))
}
}

View file

@ -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<MwPostResponse>()
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<MwPostResponse>()
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<MwQueryResponse>()
val query = mock<MwQueryResult>()
val page = mock<MwQueryPage>()
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<MwPostResponse>()
whenever(wikiBaseInterface.addLabelstoWikidata(
"M123", "test", "en", "caption"
)).thenReturn(Observable.just(mwPostResponse))
val result = wikiBaseClient.addLabelstoWikidata(123L, "en", "caption").blockingFirst()
assertSame(mwPostResponse, result)
}
}

View file

@ -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()