mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Add CustomOkHttpNetworkFetcher Unit Tests (#4826)
This commit is contained in:
parent
863477aa02
commit
01904bccdf
2 changed files with 269 additions and 59 deletions
|
|
@ -42,15 +42,15 @@ public class CustomOkHttpNetworkFetcher
|
|||
private final Call.Factory mCallFactory;
|
||||
private final @Nullable
|
||||
CacheControl mCacheControl;
|
||||
private Executor mCancellationExecutor;
|
||||
private JsonKvStore defaultKvStore;
|
||||
private final Executor mCancellationExecutor;
|
||||
private final JsonKvStore defaultKvStore;
|
||||
|
||||
/**
|
||||
* @param okHttpClient client to use
|
||||
*/
|
||||
@Inject
|
||||
public CustomOkHttpNetworkFetcher(OkHttpClient okHttpClient,
|
||||
@Named("default_preferences") JsonKvStore defaultKvStore) {
|
||||
public CustomOkHttpNetworkFetcher(final OkHttpClient okHttpClient,
|
||||
@Named("default_preferences") final JsonKvStore defaultKvStore) {
|
||||
this(okHttpClient, okHttpClient.dispatcher().executorService(), defaultKvStore);
|
||||
}
|
||||
|
||||
|
|
@ -59,8 +59,9 @@ public class CustomOkHttpNetworkFetcher
|
|||
* @param cancellationExecutor executor on which fetching cancellation is performed if
|
||||
* cancellation is requested from the UI Thread
|
||||
*/
|
||||
public CustomOkHttpNetworkFetcher(Call.Factory callFactory, Executor cancellationExecutor,
|
||||
JsonKvStore defaultKvStore) {
|
||||
public CustomOkHttpNetworkFetcher(final Call.Factory callFactory,
|
||||
final Executor cancellationExecutor,
|
||||
final JsonKvStore defaultKvStore) {
|
||||
this(callFactory, cancellationExecutor, defaultKvStore, true);
|
||||
}
|
||||
|
||||
|
|
@ -71,8 +72,9 @@ public class CustomOkHttpNetworkFetcher
|
|||
* @param disableOkHttpCache true if network requests should not be cached by OkHttp
|
||||
*/
|
||||
public CustomOkHttpNetworkFetcher(
|
||||
Call.Factory callFactory, Executor cancellationExecutor, JsonKvStore defaultKvStore,
|
||||
boolean disableOkHttpCache) {
|
||||
final Call.Factory callFactory, final Executor cancellationExecutor,
|
||||
final JsonKvStore defaultKvStore,
|
||||
final boolean disableOkHttpCache) {
|
||||
this.defaultKvStore = defaultKvStore;
|
||||
mCallFactory = callFactory;
|
||||
mCancellationExecutor = cancellationExecutor;
|
||||
|
|
@ -81,7 +83,7 @@ public class CustomOkHttpNetworkFetcher
|
|||
|
||||
@Override
|
||||
public OkHttpNetworkFetchState createFetchState(
|
||||
Consumer<EncodedImage> consumer, ProducerContext context) {
|
||||
final Consumer<EncodedImage> consumer, final ProducerContext context) {
|
||||
return new OkHttpNetworkFetchState(consumer, context);
|
||||
}
|
||||
|
||||
|
|
@ -111,20 +113,21 @@ public class CustomOkHttpNetworkFetcher
|
|||
}
|
||||
|
||||
fetchWithRequest(fetchState, callback, requestBuilder.build());
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
// handle error while creating the request
|
||||
callback.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||
public void onFetchCompletion(final OkHttpNetworkFetchState fetchState, final int byteSize) {
|
||||
fetchState.fetchCompleteTime = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||
Map<String, String> extraMap = new HashMap<>(4);
|
||||
public Map<String, String> getExtraMap(final OkHttpNetworkFetchState fetchState,
|
||||
final int byteSize) {
|
||||
final Map<String, String> extraMap = new HashMap<>(4);
|
||||
extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime));
|
||||
extraMap
|
||||
.put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime));
|
||||
|
|
@ -146,69 +149,69 @@ public class CustomOkHttpNetworkFetcher
|
|||
new BaseProducerContextCallbacks() {
|
||||
@Override
|
||||
public void onCancellationRequested() {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
call.cancel();
|
||||
} else {
|
||||
mCancellationExecutor.execute(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
call.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
onFetchCancellationRequested(call);
|
||||
}
|
||||
});
|
||||
|
||||
call.enqueue(
|
||||
new okhttp3.Callback() {
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
fetchState.responseTime = SystemClock.elapsedRealtime();
|
||||
final ResponseBody body = response.body();
|
||||
try {
|
||||
if (!response.isSuccessful()) {
|
||||
handleException(
|
||||
call, new IOException("Unexpected HTTP code " + response),
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
|
||||
BytesRange responseRange =
|
||||
BytesRange.fromContentRangeHeader(response.header("Content-Range"));
|
||||
if (responseRange != null
|
||||
&& !(responseRange.from == 0
|
||||
&& responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
|
||||
// Only treat as a partial image if the range is not all of the content
|
||||
fetchState.setResponseBytesRange(responseRange);
|
||||
fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT);
|
||||
}
|
||||
|
||||
long contentLength = body.contentLength();
|
||||
if (contentLength < 0) {
|
||||
contentLength = 0;
|
||||
}
|
||||
callback.onResponse(body.byteStream(), (int) contentLength);
|
||||
} catch (Exception e) {
|
||||
handleException(call, e, callback);
|
||||
} finally {
|
||||
body.close();
|
||||
}
|
||||
public void onResponse(final Call call, final Response response) {
|
||||
onFetchResponse(fetchState, call, response, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
public void onFailure(final Call call, final IOException e) {
|
||||
handleException(call, e, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onFetchCancellationRequested(final Call call) {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
call.cancel();
|
||||
} else {
|
||||
mCancellationExecutor.execute(call::cancel);
|
||||
}
|
||||
}
|
||||
|
||||
private void onFetchResponse(final OkHttpNetworkFetchState fetchState, final Call call,
|
||||
final Response response,
|
||||
final NetworkFetcher.Callback callback) {
|
||||
fetchState.responseTime = SystemClock.elapsedRealtime();
|
||||
try (final ResponseBody body = response.body()) {
|
||||
if (!response.isSuccessful()) {
|
||||
handleException(
|
||||
call, new IOException("Unexpected HTTP code " + response),
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
|
||||
final BytesRange responseRange =
|
||||
BytesRange.fromContentRangeHeader(response.header("Content-Range"));
|
||||
if (responseRange != null
|
||||
&& !(responseRange.from == 0
|
||||
&& responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
|
||||
// Only treat as a partial image if the range is not all of the content
|
||||
fetchState.setResponseBytesRange(responseRange);
|
||||
fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT);
|
||||
}
|
||||
|
||||
long contentLength = body.contentLength();
|
||||
if (contentLength < 0) {
|
||||
contentLength = 0;
|
||||
}
|
||||
callback.onResponse(body.byteStream(), (int) contentLength);
|
||||
} catch (final Exception e) {
|
||||
handleException(call, e, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles exceptions.
|
||||
*
|
||||
* <p>OkHttp notifies callers of cancellations via an IOException. If IOException is caught
|
||||
* after
|
||||
* request cancellation, then the exception is interpreted as successful cancellation and
|
||||
* after request cancellation, then the exception is interpreted as successful cancellation and
|
||||
* onCancellation is called. Otherwise onFailure is called.
|
||||
*/
|
||||
private void handleException(final Call call, final Exception e, final Callback callback) {
|
||||
|
|
@ -226,7 +229,7 @@ public class CustomOkHttpNetworkFetcher
|
|||
public long fetchCompleteTime;
|
||||
|
||||
public OkHttpNetworkFetchState(
|
||||
Consumer<EncodedImage> consumer, ProducerContext producerContext) {
|
||||
final Consumer<EncodedImage> consumer, final ProducerContext producerContext) {
|
||||
super(consumer, producerContext);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,207 @@
|
|||
package fr.free.nrw.commons.media
|
||||
|
||||
import android.net.Uri
|
||||
import com.facebook.imagepipeline.common.BytesRange
|
||||
import com.facebook.imagepipeline.image.EncodedImage
|
||||
import com.facebook.imagepipeline.producers.Consumer
|
||||
import com.facebook.imagepipeline.producers.NetworkFetcher
|
||||
import com.facebook.imagepipeline.producers.ProducerContext
|
||||
import com.facebook.imagepipeline.request.ImageRequest
|
||||
import com.nhaarman.mockitokotlin2.any
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.CommonsApplication
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||
import okhttp3.*
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.powermock.reflect.Whitebox
|
||||
import java.lang.reflect.Method
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
class CustomOkHttpNetworkFetcherUnitTest {
|
||||
|
||||
private lateinit var fetcher: CustomOkHttpNetworkFetcher
|
||||
private lateinit var okHttpClient: OkHttpClient
|
||||
private lateinit var state: CustomOkHttpNetworkFetcher.OkHttpNetworkFetchState
|
||||
|
||||
@Mock
|
||||
private lateinit var callback: NetworkFetcher.Callback
|
||||
|
||||
@Mock
|
||||
private lateinit var defaultKvStore: JsonKvStore
|
||||
|
||||
@Mock
|
||||
private lateinit var consumer: Consumer<EncodedImage>
|
||||
|
||||
@Mock
|
||||
private lateinit var context: ProducerContext
|
||||
|
||||
@Mock
|
||||
private lateinit var imageRequest: ImageRequest
|
||||
|
||||
@Mock
|
||||
private lateinit var uri: Uri
|
||||
|
||||
@Mock
|
||||
private lateinit var mCacheControl: CacheControl
|
||||
|
||||
@Mock
|
||||
private lateinit var bytesRange: BytesRange
|
||||
|
||||
@Mock
|
||||
private lateinit var executor: Executor
|
||||
|
||||
@Mock
|
||||
private lateinit var call: Call
|
||||
|
||||
@Mock
|
||||
private lateinit var response: Response
|
||||
|
||||
@Mock
|
||||
private lateinit var body: ResponseBody
|
||||
|
||||
@Before
|
||||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
okHttpClient = OkHttpClient()
|
||||
fetcher = CustomOkHttpNetworkFetcher(okHttpClient, defaultKvStore)
|
||||
whenever(context.imageRequest).thenReturn(imageRequest)
|
||||
whenever(imageRequest.sourceUri).thenReturn(uri)
|
||||
whenever(imageRequest.bytesRange).thenReturn(bytesRange)
|
||||
whenever(bytesRange.toHttpRangeHeaderValue()).thenReturn("bytes 200-1000/67589")
|
||||
whenever(uri.toString()).thenReturn("https://example.com")
|
||||
state = fetcher.createFetchState(consumer, context)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun checkNotNull() {
|
||||
Assert.assertNotNull(fetcher)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testFetchCaseReturn() {
|
||||
whenever(
|
||||
defaultKvStore.getBoolean(
|
||||
CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED,
|
||||
false
|
||||
)
|
||||
).thenReturn(true)
|
||||
fetcher.fetch(state, callback)
|
||||
verify(callback).onFailure(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testFetch() {
|
||||
Whitebox.setInternalState(fetcher, "mCacheControl", mCacheControl)
|
||||
whenever(
|
||||
defaultKvStore.getBoolean(
|
||||
CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED,
|
||||
false
|
||||
)
|
||||
).thenReturn(false)
|
||||
fetcher.fetch(state, callback)
|
||||
fetcher.onFetchCompletion(state, 0)
|
||||
verify(bytesRange).toHttpRangeHeaderValue()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testFetchCaseException() {
|
||||
whenever(uri.toString()).thenReturn("")
|
||||
whenever(
|
||||
defaultKvStore.getBoolean(
|
||||
CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED,
|
||||
false
|
||||
)
|
||||
).thenReturn(false)
|
||||
fetcher.fetch(state, callback)
|
||||
verify(callback).onFailure(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testGetExtraMap() {
|
||||
val map = fetcher.getExtraMap(state, 40)
|
||||
Assertions.assertEquals(map!!["image_size"], 40.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testOnFetchCancellationRequested() {
|
||||
Whitebox.setInternalState(fetcher, "mCancellationExecutor", executor)
|
||||
val method: Method = CustomOkHttpNetworkFetcher::class.java.getDeclaredMethod(
|
||||
"onFetchCancellationRequested", Call::class.java,
|
||||
)
|
||||
method.isAccessible = true
|
||||
method.invoke(fetcher, call)
|
||||
verify(executor).execute(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testOnFetchResponseCaseReturn() {
|
||||
whenever(response.body()).thenReturn(body)
|
||||
whenever(response.isSuccessful).thenReturn(false)
|
||||
whenever(call.isCanceled).thenReturn(true)
|
||||
val method: Method = CustomOkHttpNetworkFetcher::class.java.getDeclaredMethod(
|
||||
"onFetchResponse",
|
||||
CustomOkHttpNetworkFetcher.OkHttpNetworkFetchState::class.java,
|
||||
Call::class.java,
|
||||
Response::class.java,
|
||||
NetworkFetcher.Callback::class.java,
|
||||
)
|
||||
method.isAccessible = true
|
||||
method.invoke(fetcher, state, call, response, callback)
|
||||
verify(callback).onCancellation()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testOnFetchResponse() {
|
||||
whenever(response.body()).thenReturn(body)
|
||||
whenever(response.isSuccessful).thenReturn(true)
|
||||
whenever(response.header("Content-Range")).thenReturn("bytes 200-1000/67589")
|
||||
whenever(call.isCanceled).thenReturn(true)
|
||||
whenever(body.contentLength()).thenReturn(-1)
|
||||
val method: Method = CustomOkHttpNetworkFetcher::class.java.getDeclaredMethod(
|
||||
"onFetchResponse",
|
||||
CustomOkHttpNetworkFetcher.OkHttpNetworkFetchState::class.java,
|
||||
Call::class.java,
|
||||
Response::class.java,
|
||||
NetworkFetcher.Callback::class.java,
|
||||
)
|
||||
method.isAccessible = true
|
||||
method.invoke(fetcher, state, call, response, callback)
|
||||
verify(callback).onResponse(null, 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testOnFetchResponseCaseException() {
|
||||
whenever(response.body()).thenReturn(body)
|
||||
whenever(response.isSuccessful).thenReturn(true)
|
||||
whenever(response.header("Content-Range")).thenReturn("test")
|
||||
whenever(call.isCanceled).thenReturn(false)
|
||||
whenever(body.contentLength()).thenReturn(-1)
|
||||
val method: Method = CustomOkHttpNetworkFetcher::class.java.getDeclaredMethod(
|
||||
"onFetchResponse",
|
||||
CustomOkHttpNetworkFetcher.OkHttpNetworkFetchState::class.java,
|
||||
Call::class.java,
|
||||
Response::class.java,
|
||||
NetworkFetcher.Callback::class.java,
|
||||
)
|
||||
method.isAccessible = true
|
||||
method.invoke(fetcher, state, call, response, callback)
|
||||
verify(callback).onFailure(any())
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue