Convert upload client to kotlin (#5568)

* Write unit tests for the UploadClient (with gentle refactoring to make it testable)

* Convert Upload client to kotlin
This commit is contained in:
Paul Hawke 2024-02-21 00:55:35 -06:00 committed by GitHub
parent 728712c4e1
commit f0a1d036a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 552 additions and 247 deletions

View file

@ -0,0 +1,241 @@
package fr.free.nrw.commons.upload
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.nhaarman.mockitokotlin2.KArgumentCaptor
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.CommonsApplication.DEFAULT_EDIT_SUMMARY
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
import fr.free.nrw.commons.contributions.ChunkInfo
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.upload.UploadClient.TimeProvider
import fr.free.nrw.commons.wikidata.mwapi.MwException
import fr.free.nrw.commons.wikidata.mwapi.MwServiceError
import io.reactivex.Observable
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertSame
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okio.Buffer
import org.junit.Before
import org.junit.Test
import java.io.File
import java.util.Date
class UploadClientTest {
private val contribution = mock<Contribution>()
private val uploadResult = mock<UploadResult>()
private val uploadInterface = mock<UploadInterface>()
private val csrfTokenClient = mock<CsrfTokenClient>()
private val pageContentsCreator = mock<PageContentsCreator>()
private val fileUtilsWrapper = mock<FileUtilsWrapper>()
private val gson = mock<Gson>()
private val timeProvider = mock<TimeProvider>()
private val uploadClient = UploadClient(uploadInterface, csrfTokenClient, pageContentsCreator, fileUtilsWrapper, gson, timeProvider)
private val expectedChunkSize = 512 * 1024
private val testToken = "test-token"
private val createdContent = "content"
private val filename = "test.jpg"
private val filekey = "the-key"
private val errorCode = "the-code"
private val uploadJson = Gson().fromJson("{\"foo\" = 1}", JsonObject::class.java)
private val uploadResponse = UploadResponse(uploadResult)
private val errorResponse = UploadResponse(null)
@Before
fun setUp() {
whenever(csrfTokenClient.getTokenBlocking()).thenReturn(testToken)
whenever(pageContentsCreator.createFrom(contribution)).thenReturn(createdContent)
}
@Test
fun testUploadFileFromStash_NoErrors() {
whenever(gson.fromJson(uploadJson, UploadResponse::class.java)).thenReturn(uploadResponse)
whenever(uploadInterface.uploadFileFromStash(testToken, createdContent, DEFAULT_EDIT_SUMMARY, filename, filekey)).thenReturn(Observable.just(uploadJson))
val result = uploadClient.uploadFileFromStash(contribution, filename, filekey).test()
result.assertNoErrors()
assertSame(uploadResult, result.values()[0])
}
@Test
fun testUploadFileFromStash_WithError() {
val error = mock<MwServiceError>()
whenever(error.code).thenReturn(errorCode)
val uploadException = MwException(error, null)
whenever(gson.fromJson(uploadJson, UploadResponse::class.java)).thenReturn(errorResponse)
whenever(gson.fromJson(uploadJson, MwException::class.java)).thenReturn(uploadException)
whenever(uploadInterface.uploadFileFromStash(testToken, createdContent, DEFAULT_EDIT_SUMMARY, filename, filekey)).thenReturn(Observable.just(uploadJson))
val result = uploadClient.uploadFileFromStash(contribution, filename, filekey).test()
result.assertNoValues()
assertEquals(errorCode, result.errors()[0].message)
}
@Test
fun testUploadFileFromStash_Failure() {
val exception = Exception("test")
whenever(uploadInterface.uploadFileFromStash(testToken, createdContent, DEFAULT_EDIT_SUMMARY, filename, filekey))
.thenReturn(Observable.error(exception))
val result = uploadClient.uploadFileFromStash(contribution, filename, filekey).test()
result.assertNoValues()
assertEquals(exception, result.errors()[0])
}
@Test
fun testUploadChunkToStash_Success() {
val fileContent = "content"
val requestBody: RequestBody = fileContent.toRequestBody("text/plain".toMediaType())
val countingRequestBody = CountingRequestBody(requestBody, mock(), 0, fileContent.length.toLong())
val filenameCaptor: KArgumentCaptor<RequestBody> = argumentCaptor<RequestBody>()
val totalFileSizeCaptor = argumentCaptor<RequestBody>()
val offsetCaptor = argumentCaptor<RequestBody>()
val fileKeyCaptor = argumentCaptor<RequestBody>()
val tokenCaptor = argumentCaptor<RequestBody>()
val fileCaptor = argumentCaptor<MultipartBody.Part>()
whenever(uploadInterface.uploadFileToStash(
filenameCaptor.capture(), totalFileSizeCaptor.capture(), offsetCaptor.capture(),
fileKeyCaptor.capture(), tokenCaptor.capture(), fileCaptor.capture()
)).thenReturn(Observable.just(uploadResponse))
val result = uploadClient.uploadChunkToStash(filename, 100, 10, filekey, countingRequestBody).test()
result.assertNoErrors()
assertSame(uploadResult, result.values()[0])
assertEquals(filename, filenameCaptor.asString())
assertEquals("100", totalFileSizeCaptor.asString())
assertEquals("10", offsetCaptor.asString())
assertEquals(filekey, fileKeyCaptor.asString())
assertEquals(testToken, tokenCaptor.asString())
assertEquals(fileContent, fileCaptor.firstValue.body.asString())
}
@Test
fun testUploadChunkToStash_Failure() {
val exception = Exception("expected")
whenever(uploadInterface.uploadFileToStash(any(), any(), any(), any(), any(), any()))
.thenReturn(Observable.error(exception))
val result = uploadClient.uploadChunkToStash(filename, 100, 10, filekey, mock()).test()
result.assertNoValues()
assertSame(exception, result.errors()[0])
}
@Test
fun uploadFileToStash_completedContribution() {
whenever(contribution.isCompleted()).thenReturn(true)
whenever(contribution.fileKey).thenReturn(filekey)
val result = uploadClient.uploadFileToStash(filename, contribution, mock()).test()
result.assertNoErrors()
val stashResult = result.values()[0]
assertEquals(filekey, stashResult.fileKey)
assertEquals(StashUploadState.SUCCESS, stashResult.state)
}
@Test
fun uploadFileToStash_contributionIsUnpaused() {
whenever(contribution.isCompleted()).thenReturn(false)
whenever(contribution.fileKey).thenReturn(filekey)
whenever(fileUtilsWrapper.getMimeType(anyOrNull<File>())).thenReturn("image/png")
whenever(fileUtilsWrapper.getFileChunks(anyOrNull<File>(), eq(expectedChunkSize))).thenReturn(emptyList())
val result = uploadClient.uploadFileToStash(filename, contribution, mock()).test()
result.assertNoErrors()
verify(contribution, times(1)).unpause()
}
@Test
fun uploadFileToStash_returnsFailureIfNothingToUpload() {
whenever(contribution.isCompleted()).thenReturn(false)
whenever(contribution.fileKey).thenReturn(filekey)
whenever(fileUtilsWrapper.getMimeType(anyOrNull<File>())).thenReturn("image/png")
whenever(fileUtilsWrapper.getFileChunks(anyOrNull<File>(), eq(expectedChunkSize))).thenReturn(emptyList())
val result = uploadClient.uploadFileToStash(filename, contribution, mock()).test()
result.assertNoErrors()
assertEquals(StashUploadState.FAILED, result.values()[0].state)
}
@Test
fun uploadFileToStash_returnsFailureIfAnyChunkFails() {
val mockFile = mock<File>()
whenever(mockFile.length()).thenReturn(1)
whenever(contribution.localUriPath).thenReturn(mockFile)
whenever(contribution.isCompleted()).thenReturn(false)
whenever(contribution.fileKey).thenReturn(filekey)
whenever(fileUtilsWrapper.getMimeType(anyOrNull<File>())).thenReturn("image/png")
whenever(fileUtilsWrapper.getFileChunks(anyOrNull<File>(), eq(expectedChunkSize))).thenReturn(listOf(mockFile))
whenever(uploadInterface.uploadFileToStash(any(), any(), any(), any(), any(), any())).thenReturn(Observable.just(uploadResponse))
val result = uploadClient.uploadFileToStash(filename, contribution, mock()).test()
result.assertNoErrors()
assertEquals(StashUploadState.FAILED, result.values()[0].state)
}
@Test
fun uploadFileToStash_successWithOneChunk() {
val mockFile = mock<File>()
val chunkInfo = mock<ChunkInfo>()
whenever(mockFile.length()).thenReturn(10)
whenever(chunkInfo.uploadResult).thenReturn(uploadResult)
whenever(uploadResult.offset).thenReturn(1)
whenever(uploadResult.filekey).thenReturn(filekey)
whenever(contribution.localUriPath).thenReturn(mockFile)
whenever(contribution.chunkInfo).thenReturn(chunkInfo)
whenever(contribution.isCompleted()).thenReturn(false)
whenever(contribution.dateModified).thenReturn(Date(100))
whenever(timeProvider.currentTimeMillis()).thenReturn(200)
whenever(contribution.fileKey).thenReturn(filekey)
whenever(fileUtilsWrapper.getMimeType(anyOrNull<File>())).thenReturn("image/png")
whenever(fileUtilsWrapper.getFileChunks(anyOrNull<File>(), eq(expectedChunkSize))).thenReturn(listOf(mockFile))
whenever(uploadInterface.uploadFileToStash(anyOrNull(), anyOrNull(), anyOrNull(),
anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Observable.just(uploadResponse))
val result = uploadClient.uploadFileToStash(filename, contribution, mock()).test()
result.assertNoErrors()
assertEquals(StashUploadState.SUCCESS, result.values()[0].state)
assertEquals(filekey, result.values()[0].fileKey)
}
private fun KArgumentCaptor<RequestBody>.asString(): String =
firstValue.asString()
private fun RequestBody.asString(): String {
val b = Buffer()
writeTo(b)
return b.readUtf8()
}
}

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.utils
import com.nhaarman.mockitokotlin2.mock
import fr.free.nrw.commons.upload.FileUtils
import fr.free.nrw.commons.upload.FileUtilsWrapper
import org.junit.Assert.assertEquals
@ -19,7 +20,7 @@ class FileUtilsTest {
@Test
fun testSHA1() {
val fileUtilsWrapper = FileUtilsWrapper()
val fileUtilsWrapper = FileUtilsWrapper(mock())
assertEquals(
"907d14fb3af2b0d4f18c2d46abe8aedce17367bd",