diff --git a/README.md b/README.md index eb7259a..4587a39 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![Build Status](https://www.bitrise.io/app/98b385dd0144faa0/status.svg?token=9gD8GNFS5DF6iIQ7eecIXA&branch=master)](https://www.bitrise.io/app/98b385dd0144faa0) [![Release](https://jitpack.io/v/Wolox/wolmo-networking-android.svg)](https://jitpack.io/#Wolox/wolmo-networking-android) +[![Coverage Status](https://coveralls.io/repos/github/Wolox/wolmo-networking-android/badge.svg?branch=master)](https://coveralls.io/github/Wolox/wolmo-networking-android?branch=master)

diff --git a/build.gradle b/build.gradle index 6c13427..3cf7ad5 100755 --- a/build.gradle +++ b/build.gradle @@ -8,11 +8,13 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.android.tools.build:gradle:3.0.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files classpath 'com.google.gms:google-services:3.0.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + classpath 'com.dicedmelon.gradle:jacoco-android:0.1.2' + classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.2' } } diff --git a/networking/build.gradle b/networking/build.gradle index cfe4add..6037ba5 100755 --- a/networking/build.gradle +++ b/networking/build.gradle @@ -1,5 +1,7 @@ apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' +apply plugin: 'jacoco-android' +apply plugin: 'com.github.kt3k.coveralls' group = 'com.github.Wolox' @@ -10,38 +12,54 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 26 - consumerProguardFiles 'proguard-joda-time.pro', 'proguard-retrofit.pro', - 'proguard-okhttp.pro' + consumerProguardFiles 'proguard-retrofit.pro', 'proguard-okhttp.pro', 'proguard-gson.pro', + 'proguard-joda-time.pro' } lintOptions { abortOnError false } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } +jacocoAndroidUnitTestReport { + excludes = defaultExcludes + ['**/*_Factory.class'] +} + +coveralls { + jacocoReportPath = "${buildDir}/reports/jacoco/jacocoTestReleaseUnitTestReport/jacocoTestReleaseUnitTestReport.xml" +} + buildscript { - ext.wolmo_version = 'v1.3.1' + ext.wolmo_version = 'v2.0.0-rc1' ext.retrofit_version = '2.3.0' ext.okhttp3_version = '3.9.0' + ext.dagger_version = '2.13' } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(dir: 'libs', include: ['*.jar']) // Wolmo - compile "com.github.Wolox:wolmo-core-android:$wolmo_version" + implementation "com.github.Wolox:wolmo-core-android:$wolmo_version" - // Third party - compile "com.squareup.retrofit2:retrofit:$retrofit_version" - compile "com.squareup.retrofit2:converter-gson:$retrofit_version" + // Dagger + annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" - compile "com.squareup.okhttp3:okhttp-urlconnection:$okhttp3_version" - compile "com.squareup.okhttp3:okhttp:$okhttp3_version" - compile "com.squareup.okhttp3:logging-interceptor:$okhttp3_version" + // Third party + api "com.squareup.retrofit2:retrofit:$retrofit_version" + api "com.squareup.retrofit2:converter-gson:$retrofit_version" + api "com.squareup.okhttp3:okhttp-urlconnection:$okhttp3_version" + api "com.squareup.okhttp3:okhttp:$okhttp3_version" + api 'joda-time:joda-time:2.9.9' - compile 'joda-time:joda-time:2.9.9' -} \ No newline at end of file + // Test + testImplementation "junit:junit:4.12" + testImplementation "org.mockito:mockito-inline:2.11.0" // Mockito inline adds support for mocking final classes and methods + testImplementation "org.assertj:assertj-core:3.8.0" + testImplementation "com.squareup.okhttp3:mockwebserver:$okhttp3_version" +} diff --git a/networking/proguard-gson.pro b/networking/proguard-gson.pro new file mode 100644 index 0000000..284b5a1 --- /dev/null +++ b/networking/proguard-gson.pro @@ -0,0 +1,17 @@ +-keepattributes Signature + +# For using GSON @Expose annotation +-keepattributes *Annotation* + +# Gson specific classes +-keep class sun.misc.Unsafe { *; } +#-keep class com.google.gson.stream.** { *; } + +# Application classes that will be serialized/deserialized over Gson +-keep class com.google.gson.examples.android.model.** { *; } + +# Prevent proguard from stripping interface information from TypeAdapterFactory, +# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) +-keep class * implements com.google.gson.TypeAdapterFactory +-keep class * implements com.google.gson.JsonSerializer +-keep class * implements com.google.gson.JsonDeserializer \ No newline at end of file diff --git a/networking/proguard-joda-time.pro b/networking/proguard-joda-time.pro index 2a81fbf..fecb337 100644 --- a/networking/proguard-joda-time.pro +++ b/networking/proguard-joda-time.pro @@ -1,6 +1,6 @@ -## Joda Time 2.3 +## Joda Time 2.9 -dontwarn org.joda.convert.** -dontwarn org.joda.time.** -keep class org.joda.time.** { *; } --keep interface org.joda.time.** { *; } \ No newline at end of file +-keep interface org.joda.time.** { *; } diff --git a/networking/proguard-okhttp.pro b/networking/proguard-okhttp.pro index d7e9677..d2a7a4d 100644 --- a/networking/proguard-okhttp.pro +++ b/networking/proguard-okhttp.pro @@ -4,5 +4,4 @@ -keep interface com.squareup.okhttp.** { *; } -dontwarn com.squareup.okhttp.** -dontwarn okhttp3.** - -dontwarn okio.** \ No newline at end of file diff --git a/networking/proguard-retrofit.pro b/networking/proguard-retrofit.pro index a7b7a80..586c0f8 100644 --- a/networking/proguard-retrofit.pro +++ b/networking/proguard-retrofit.pro @@ -1,11 +1,11 @@ # Retrofit 2.X ## https://square.github.io/retrofit/ ## --dontwarn retrofit.** --keep class retrofit.** { *; } +-dontwarn retrofit2.** +-keep class retrofit2.** { *; } -keepattributes Signature -keepattributes Exceptions -keepclasseswithmembers class * { - @retrofit.http.* ; -} \ No newline at end of file + @retrofit2.http.* ; +} diff --git a/networking/proguard-rules.pro b/networking/proguard-rules.pro deleted file mode 100644 index 04bddee..0000000 --- a/networking/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /home/lucasdelatorre/Android/Sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/di/NetworkingComponent.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/NetworkingComponent.java new file mode 100644 index 0000000..5a5242b --- /dev/null +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/NetworkingComponent.java @@ -0,0 +1,62 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.di; + +import android.support.annotation.Nullable; + +import com.google.gson.FieldNamingPolicy; + +import ar.com.wolox.wolmo.networking.di.modules.GsonModule; +import ar.com.wolox.wolmo.networking.di.modules.NetworkingModule; +import ar.com.wolox.wolmo.networking.di.modules.OkHttpClientModule; +import ar.com.wolox.wolmo.networking.di.scopes.NetworkingScope; +import ar.com.wolox.wolmo.networking.retrofit.RetrofitServices; +import ar.com.wolox.wolmo.networking.utils.GsonTypeAdapter; + +import dagger.BindsInstance; +import dagger.Component; +import okhttp3.Interceptor; + +@NetworkingScope +@Component(modules = { GsonModule.class, OkHttpClientModule.class, NetworkingModule.class }) +public interface NetworkingComponent { + + RetrofitServices retrofitServices(); + + @Component.Builder + interface Builder { + + @BindsInstance + Builder baseUrl(String baseUrl); + + @BindsInstance + Builder okHttpInterceptors(@Nullable Interceptor... interceptors); + + @BindsInstance + Builder gsonNamingPolicy(FieldNamingPolicy namingPolicy); + + @BindsInstance + Builder gsonTypeAdapters(@Nullable GsonTypeAdapter... typeAdapters); + + NetworkingComponent build(); + } +} diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/CachingModule.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/CachingModule.java new file mode 100644 index 0000000..8fbcdb6 --- /dev/null +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/CachingModule.java @@ -0,0 +1,21 @@ +package ar.com.wolox.wolmo.networking.di.modules; + +import ar.com.wolox.wolmo.networking.di.scopes.NetworkingScope; +import ar.com.wolox.wolmo.networking.optimizations.BaseCallCollapser; +import ar.com.wolox.wolmo.networking.optimizations.ICallCollapser; + +import dagger.Module; +import dagger.Provides; + +/** + * Default module with caching dependencies + */ +@Module +public class CachingModule { + + @Provides + @NetworkingScope + static ICallCollapser provideBaseCallCollapser() { + return new BaseCallCollapser(); + } +} diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/GsonModule.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/GsonModule.java new file mode 100644 index 0000000..c2da27f --- /dev/null +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/GsonModule.java @@ -0,0 +1,79 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.di.modules; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import ar.com.wolox.wolmo.networking.retrofit.serializer.LocalDateSerializer; +import ar.com.wolox.wolmo.networking.utils.GsonTypeAdapter; + +import org.joda.time.LocalDate; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; +import retrofit2.converter.gson.GsonConverterFactory; + +@Module +public class GsonModule { + + @Provides + static GsonConverterFactory provideGsonConverterFactory(Gson gson) { + return GsonConverterFactory.create(gson); + } + + @Provides + static Gson provideGson(GsonBuilder gsonBuilder) { + return gsonBuilder.create(); + } + + @Provides + @Named("newInstance") + static GsonBuilder provideNewGsonBuilder() { + return new GsonBuilder(); + } + + @Provides + static GsonBuilder provideGsonBuilder(@Named("newInstance") GsonBuilder gsonBuilder, + @NonNull FieldNamingPolicy namingPolicy, + @Nullable GsonTypeAdapter... typeAdapters) { + + gsonBuilder.setFieldNamingPolicy(namingPolicy); + + if (typeAdapters != null && typeAdapters.length > 0) { + for (GsonTypeAdapter typeAdapter : typeAdapters) { + gsonBuilder + .registerTypeAdapter(typeAdapter.getType(), typeAdapter.getTypeAdapter()); + } + } else { + gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateSerializer()); + } + + return gsonBuilder; + } +} diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/NetworkingModule.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/NetworkingModule.java new file mode 100644 index 0000000..01ce2fe --- /dev/null +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/NetworkingModule.java @@ -0,0 +1,51 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.di.modules; + +import ar.com.wolox.wolmo.networking.di.scopes.NetworkingScope; + +import dagger.Module; +import dagger.Provides; +import okhttp3.OkHttpClient; +import retrofit2.Retrofit; +import retrofit2.Retrofit.Builder; +import retrofit2.converter.gson.GsonConverterFactory; + +@Module +public class NetworkingModule { + + @Provides + @NetworkingScope + static Retrofit.Builder provideRetrofitBuilder() { + return new Builder(); + } + + @Provides + @NetworkingScope + static Retrofit provideRetrofit(Retrofit.Builder builder, String baseUrl, + GsonConverterFactory gsonConverterFactory, + OkHttpClient client) { + + return builder.baseUrl(baseUrl).addConverterFactory(gsonConverterFactory).client(client) + .build(); + } +} diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/OkHttpClientModule.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/OkHttpClientModule.java new file mode 100644 index 0000000..ad81c62 --- /dev/null +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/modules/OkHttpClientModule.java @@ -0,0 +1,49 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.di.modules; + +import android.support.annotation.Nullable; + +import dagger.Module; +import dagger.Provides; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; + +@Module +public class OkHttpClientModule { + + @Provides + static OkHttpClient provideOkHttpClient(OkHttpClient.Builder okHttpBuilder, + @Nullable Interceptor... interceptors) { + if (interceptors != null) { + for (Interceptor interceptor : interceptors) { + okHttpBuilder.addInterceptor(interceptor); + } + } + return okHttpBuilder.build(); + } + + @Provides + static OkHttpClient.Builder provideOkHttpClientBuilder() { + return new OkHttpClient.Builder(); + } +} diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/di/scopes/NetworkingScope.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/scopes/NetworkingScope.java new file mode 100644 index 0000000..5c258e4 --- /dev/null +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/di/scopes/NetworkingScope.java @@ -0,0 +1,31 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.di.scopes; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +@Scope +@Retention(RetentionPolicy.RUNTIME) +public @interface NetworkingScope {} \ No newline at end of file diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/CacheMissException.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/CacheMissException.java index f09b53a..aed46da 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/CacheMissException.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/CacheMissException.java @@ -1,3 +1,24 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ package ar.com.wolox.wolmo.networking.exception; import ar.com.wolox.wolmo.networking.offline.Repository; @@ -7,10 +28,10 @@ */ public final class CacheMissException extends RuntimeException { - private static final String UNEXPECTED_CACHE_MISS_MESSAGE = "There was an unexpected cache miss"; + private static final String UNEXPECTED_CACHE_MISS_MESSAGE = + "There was an unexpected cache miss"; public CacheMissException() { super(UNEXPECTED_CACHE_MISS_MESSAGE); } - } diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/NetworkResourceException.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/NetworkResourceException.java index b58f7fc..e615724 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/NetworkResourceException.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/NetworkResourceException.java @@ -1,3 +1,24 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ package ar.com.wolox.wolmo.networking.exception; import android.annotation.SuppressLint; @@ -11,7 +32,8 @@ */ public final class NetworkResourceException extends Exception { - private static final String REPORT_MESSAGE_FORMAT = "Network resource requested at %s yielded a %d error code"; + private static final String REPORT_MESSAGE_FORMAT = + "Network resource requested at %s yielded a %d error code"; private final int mErrorCode; @@ -27,5 +49,4 @@ public NetworkResourceException(@NonNull String resourceUrl, int errorCode) { public int getErrorCode() { return mErrorCode; } - } diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/PollRunOutOfTriesException.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/PollRunOutOfTriesException.java index ae98bab..e592beb 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/PollRunOutOfTriesException.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/exception/PollRunOutOfTriesException.java @@ -1,3 +1,24 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ package ar.com.wolox.wolmo.networking.exception; import android.support.annotation.NonNull; @@ -11,7 +32,7 @@ public final class PollRunOutOfTriesException extends RuntimeException { public PollRunOutOfTriesException(@NonNull Call call) { - super("Polling to " + call.request().url().toString() + " failed after running out of tries"); + super("Polling to " + call.request().url().toString() + + " failed after running out of tries"); } - } diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/IRepositoryCallback.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/IRepositoryCallback.java index 5c45001..bfdac87 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/IRepositoryCallback.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/IRepositoryCallback.java @@ -1,4 +1,4 @@ -/** +/* * MIT License *

* Copyright (c) 2017 Wolox S.A @@ -19,7 +19,6 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ - package ar.com.wolox.wolmo.networking.offline; /** @@ -42,5 +41,4 @@ public interface IRepositoryCallback { * @param throwable distinguishing the error. */ void onError(Throwable throwable); - } diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/Repository.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/Repository.java index 621f2f3..4bf693d 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/Repository.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/Repository.java @@ -1,4 +1,4 @@ -/** +/* * MIT License *

* Copyright (c) 2017 Wolox S.A @@ -26,15 +26,15 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - import ar.com.wolox.wolmo.networking.exception.CacheMissException; import ar.com.wolox.wolmo.networking.exception.NetworkResourceException; -import ar.com.wolox.wolmo.networking.optimizations.BaseCallCollapser; import ar.com.wolox.wolmo.networking.optimizations.ICallCollapser; import ar.com.wolox.wolmo.networking.retrofit.callback.NetworkCallback; import ar.com.wolox.wolmo.networking.utils.Consumer; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + import okhttp3.ResponseBody; import retrofit2.Call; @@ -50,7 +50,7 @@ public final class Repository { * Flags for cache access control. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({CACHE_NONE, CACHE_FIRST, CACHE_ONLY}) + @IntDef({ CACHE_NONE, CACHE_FIRST, CACHE_ONLY }) public @interface AccessPolicy {} /** @@ -74,8 +74,6 @@ public final class Repository { */ public static @AccessPolicy int DEFAULT_ACCESS_POLICY = CACHE_FIRST; - private static ICallCollapser CALL_COLLAPSER_INSTANCE = new BaseCallCollapser(); - private final C mCache; private final @AccessPolicy int mDefaultAccessPolicy; private final ICallCollapser mCallCollapser; @@ -83,22 +81,25 @@ public final class Repository { /** * Creates a repository with a default {@link AccessPolicy}. *

+ * * @param cache to query for cached items * @param defaultAccessPolicy that determines default interaction with cache */ - public Repository(@NonNull C cache, @AccessPolicy int defaultAccessPolicy) { + public Repository(@NonNull C cache, @NonNull ICallCollapser callCollapser, + @AccessPolicy int defaultAccessPolicy) { mCache = cache; mDefaultAccessPolicy = defaultAccessPolicy; - mCallCollapser = CALL_COLLAPSER_INSTANCE; + mCallCollapser = callCollapser; } /** * Creates a repository with {@link #DEFAULT_ACCESS_POLICY} as its policy. *

+ * * @param cache to query for cached items */ - public Repository(@NonNull C cache) { - this(cache, DEFAULT_ACCESS_POLICY); + public Repository(@NonNull C cache, @NonNull ICallCollapser callCollapser) { + this(cache, callCollapser, DEFAULT_ACCESS_POLICY); } /** @@ -228,7 +229,7 @@ public void onResponseFailed(ResponseBody responseBody, int code) { } @Override - public void onCallFailure(Throwable throwable) { + public void onCallFailure(@NonNull Throwable throwable) { repositoryQuery.doOnError(throwable); } }); @@ -262,7 +263,6 @@ public interface QueryStrategy { * @param cache to interact with */ void consumeRemoteSource(@NonNull T data, @NonNull C cache); - } /** @@ -312,6 +312,5 @@ void doOnSuccess(T data) { void doOnError(Throwable throwable) { if (errorConsumer != null) errorConsumer.accept(throwable); } - } } diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/TimeResolveQueryStrategy.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/TimeResolveQueryStrategy.java index 6a1fe27..026a537 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/TimeResolveQueryStrategy.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/offline/TimeResolveQueryStrategy.java @@ -1,3 +1,24 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ package ar.com.wolox.wolmo.networking.offline; import android.support.annotation.IntRange; @@ -65,7 +86,6 @@ public final T readLocalSource(@NonNull C cache) { * @param cache to read from * * @return the {@link T} data read from cache - * * @see #readLocalSource(C) */ public abstract T cleanReadLocalSource(@NonNull C cache); @@ -98,5 +118,4 @@ private void updateRefreshMoment() { private boolean shouldInvalidateCache() { return (System.currentTimeMillis() - mLastRefreshMoment) >= mRefreshDeltaInMillis; } - } diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/optimizations/BaseCallCollapser.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/optimizations/BaseCallCollapser.java index fbb5333..12104af 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/optimizations/BaseCallCollapser.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/optimizations/BaseCallCollapser.java @@ -1,4 +1,4 @@ -/** +/* * MIT License *

* Copyright (c) 2017 Wolox S.A @@ -19,7 +19,6 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ - package ar.com.wolox.wolmo.networking.optimizations; import android.support.annotation.NonNull; @@ -38,7 +37,7 @@ */ public class BaseCallCollapser implements ICallCollapser { - private static final String HTTP_METHOD_GET = "GET"; + public static final String HTTP_METHOD_GET = "GET"; private final ConcurrentHashMap> mGetCallbackQueues; @@ -53,7 +52,7 @@ public BaseCallCollapser() { * Collapsing the call means adding it to the queue of the same * request, and executing it if it's the first to be added to said queue. * - * @param call to be enqueued + * @param call to be enqueued * @param callback to be called when executing it */ public final void enqueue(@NonNull Call call, @NonNull Callback callback) { @@ -78,6 +77,7 @@ private boolean isGetCall(Call call) { * yet, it creates it and adds it to {@link #mGetCallbackQueues}. * * @param call for the queue retrieval + * * @return a {@link Queue} associated to the given {@link Call} url. */ @NonNull @@ -142,5 +142,4 @@ private void applyFailureToQueue(@NonNull Call call, @NonNull Throwable t private void removeQueueFromRequest(Call call) { mGetCallbackQueues.remove(call.request().url().toString()); } - } diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/optimizations/ICallCollapser.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/optimizations/ICallCollapser.java index 5a882ad..50b3318 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/optimizations/ICallCollapser.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/optimizations/ICallCollapser.java @@ -1,4 +1,4 @@ -/** +/* * MIT License *

* Copyright (c) 2017 Wolox S.A @@ -19,7 +19,6 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ - package ar.com.wolox.wolmo.networking.optimizations; import android.support.annotation.NonNull; @@ -44,9 +43,8 @@ public interface ICallCollapser { /** * Handles the API call avoiding repetitive and useless requests as much as possible * - * @param call to be made to the API + * @param call to be made to the API * @param callback to be called after executing it */ void enqueue(@NonNull Call call, @NonNull Callback callback); - } diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/NetworkingApplication.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/NetworkingApplication.java deleted file mode 100644 index 6a6b194..0000000 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/NetworkingApplication.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * MIT License - *

- * Copyright (c) 2017 Wolox S.A - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software - * and associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -package ar.com.wolox.wolmo.networking.retrofit; - -import android.support.annotation.CallSuper; - -import ar.com.wolox.wolmo.core.WolmoApplication; - - -/** - * This class extends {@link ar.com.wolox.wolmo.core.WolmoApplication} to easily initialize and get and instance of - * {@link RetrofitServices} to perform API calls. - */ -public abstract class NetworkingApplication extends WolmoApplication { - - private static RetrofitServices sRetrofitServices; - - /** - * Overrides the {@link android.app.Application} onCreate() method to initialize retrofit - * services provided by the subclass. - */ - @CallSuper - @Override - public void onCreate() { - super.onCreate(); - sRetrofitServices = getRetrofitServices(); - sRetrofitServices.init(); - } - - /** - * Must provide an instance of {@link RetrofitServices} that will be initialized and - * used to perform API calls by the application. It's not necessary to call init() on the - * {@link RetrofitServices} instance, it will be called by this class. - * - * @return an instance of {@link RetrofitServices} available to perform API requests - */ - public abstract RetrofitServices getRetrofitServices(); - -} \ No newline at end of file diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/RetrofitServices.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/RetrofitServices.java index 1a3bd8b..847267a 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/RetrofitServices.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/RetrofitServices.java @@ -1,4 +1,4 @@ -/** +/* * MIT License *

* Copyright (c) 2017 Wolox S.A @@ -19,137 +19,33 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ - package ar.com.wolox.wolmo.networking.retrofit; import android.support.annotation.NonNull; -import com.google.gson.Gson; +import ar.com.wolox.wolmo.networking.di.scopes.NetworkingScope; -import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; -import ar.com.wolox.wolmo.networking.retrofit.serializer.BaseGsonBuilder; -import okhttp3.OkHttpClient; -import okhttp3.logging.HttpLoggingInterceptor; -import retrofit2.Converter; +import javax.inject.Inject; + import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; /** * This class handles {@link Retrofit} main class initialization and services instances to perform * API calls to several endpoints. */ -public abstract class RetrofitServices { +@NetworkingScope +public class RetrofitServices { private Retrofit mRetrofit; private Map mServices; - /** - * This method must be called to start using this class. It initializes required variables - * and Retrofit. - * Please note that calling this method on an already initialized class will reset it to a - * clean state, configuring Retrofit to work with the endpoint provided in getApiEndpoint() - */ - public void init() { + @Inject + public RetrofitServices(Retrofit retrofit) { + mRetrofit = retrofit; mServices = new HashMap<>(); - mRetrofit = buildRetrofitInstance(); - } - - private Retrofit buildRetrofitInstance() { - Retrofit.Builder retrofitBuilder = new Retrofit.Builder() - .baseUrl(getApiEndpoint()) - .client(getOkHttpClient()); - - for (Converter.Factory converterFactory : getConverterFactories()) { - retrofitBuilder.addConverterFactory(converterFactory); - } - - return retrofitBuilder.build(); - } - - /** - * Returns the API endpoint. - * - * @return URL endpoint - */ - @NonNull - public abstract String getApiEndpoint(); - - /** - * Allows configuration of the {@link Converter.Factory} to be used by {@link Retrofit}. - * The default implementation returns a list with a single {@link GsonConverterFactory} - * instance. The {@link} must not contain a {@code null} {@link Converter.Factory}. - *

- * An important implicit aspect of what is returned in this method is that {@link Retrofit} - * iterates through the list in order to match a {@link okhttp3.ResponseBody} - * with a {@link Converter}. - *

- * Implementations should pay attention to these because, for example, - * {@link GsonConverterFactory} always matches, thus preventing the fall-through. - * - * @return the {@link List} of {@link GsonConverterFactory} to use. - */ - protected List getConverterFactories() { - return Collections.singletonList(GsonConverterFactory.create(getGson())); - } - - /** - * Returns an instance of Gson to use for conversion. - * This method calls initGson(builder) to configure the Gson Builder. - * - * @return A configured Gson instance - */ - @NonNull - protected Gson getGson() { - com.google.gson.GsonBuilder builder = BaseGsonBuilder.getBaseGsonBuilder(); - initGson(builder); - return builder.create(); - } - - /** - * Override if needed to configure a gson builder. - * You should add serializers and/or deserializers inside this method. - * - * @param builder Builder to configure - */ - protected void initGson(@NonNull com.google.gson.GsonBuilder builder) { - } - - /** - * Returns an OkHttpClient. - * This method calls initClient(builder) to configure the builder for OkHttpClient. - * - * @return A configured instance of OkHttpClient. - */ - @NonNull - protected OkHttpClient getOkHttpClient() { - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - initClient(builder); - return builder.build(); - } - - /** - * Configures an OkHttpClient.Builder. - * You must add interceptors and configure the builder inside this method. - */ - protected void initClient(@NonNull OkHttpClient.Builder builder) { - HttpLoggingInterceptor loggerInterceptor = new HttpLoggingInterceptor(); - loggerInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); - - builder.addInterceptor(loggerInterceptor); - } - - /** - * Checks if the {@link Retrofit} client has been initialized at least once. - * - * @return Returns True if the Retrofit client has been initialized and is ready to - * be used, False otherwise. - */ - private boolean isInitialized() { - return mRetrofit != null; } /** @@ -164,13 +60,12 @@ private boolean isInitialized() { * } * * @param clazz RetrofitService Class - * @param Service class + * @param Service class + * * @return service */ + @SuppressWarnings("unchecked") public final T getService(@NonNull Class clazz) { - if (!isInitialized()) throw new RuntimeException("RetrofitServices is not initialized! " + - "Must call init() at least once before calling getService(clazz)"); - T service = (T) mServices.get(clazz); if (service != null) return service; service = mRetrofit.create(clazz); diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/callback/NetworkCallback.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/callback/NetworkCallback.java index 5fba1f9..5d2af8e 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/callback/NetworkCallback.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/callback/NetworkCallback.java @@ -1,4 +1,4 @@ -/** +/* * MIT License *

* Copyright (c) 2017 Wolox S.A @@ -19,9 +19,12 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ - package ar.com.wolox.wolmo.networking.retrofit.callback; +import android.support.annotation.Nullable; + +import javax.annotation.ParametersAreNonnullByDefault; + import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; @@ -33,6 +36,7 @@ * * @param the type of object expected to be returned from the API call */ +@ParametersAreNonnullByDefault public abstract class NetworkCallback implements Callback { /** @@ -62,10 +66,12 @@ public void onFailure(Call call, Throwable t) { /** * Checks whether the response is an auth error or not. *

- * You should override this method and check if the response is an auth error, then return true if it is. + * You should override this method and check if the response is an auth error, then return + * true if it is. * By default, this method returns false. * * @param response Retrofit response + * * @return true if the response is an auth error, false otherwise */ protected boolean isAuthError(Response response) { @@ -88,18 +94,18 @@ protected void handleAuthError(Response response) { * The server received the request, answered it and the response is not of an error type. * * @param response the API JSON response converted to a Java object. - * The API response code is included in the response object. + * The API response code is included in the response object. */ - public abstract void onResponseSuccessful(T response); + public abstract void onResponseSuccessful(@Nullable T response); /** * Successful HTTP response from the server, but has an error body. * The server received the request, answered it and reported an error. * * @param responseBody The error body - * @param code The error code + * @param code The error code */ - public abstract void onResponseFailed(ResponseBody responseBody, int code); + public abstract void onResponseFailed(@Nullable ResponseBody responseBody, int code); /** * The HTTP request to the server failed on the local device, no data was transmitted. @@ -109,5 +115,4 @@ protected void handleAuthError(Response response) { * @param t A Throwable with the cause of the call failure */ public abstract void onCallFailure(Throwable t); - } \ No newline at end of file diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/interceptor/ApiRestInterceptor.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/interceptor/ApiRestInterceptor.java index 0720b38..0e543c3 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/interceptor/ApiRestInterceptor.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/interceptor/ApiRestInterceptor.java @@ -1,4 +1,4 @@ -/** +/* * MIT License *

* Copyright (c) 2017 Wolox S.A @@ -19,7 +19,6 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ - package ar.com.wolox.wolmo.networking.retrofit.interceptor; import android.support.annotation.NonNull; @@ -36,8 +35,8 @@ */ public abstract class ApiRestInterceptor implements Interceptor { - private static final String CONTENT_TYPE_HEADER = "Content-Type"; - private static final String ACCEPT_HEADER = "Accept"; + protected static final String CONTENT_TYPE_HEADER = "Content-Type"; + protected static final String ACCEPT_HEADER = "Accept"; /** * Intercepts the API call and adds custom headers to the request. By default, it will @@ -46,14 +45,15 @@ public abstract class ApiRestInterceptor implements Interceptor { * of overwriting this one. * * @param chain an object provided by OkHTTP with data of the request being made + * * @return an instance of {@link Response} by OkHTTP */ @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); - Request.Builder requestBuilder = request.newBuilder() - .addHeader(CONTENT_TYPE_HEADER, "application/json") - .addHeader(ACCEPT_HEADER, "application/json"); + Request.Builder requestBuilder = + request.newBuilder().addHeader(CONTENT_TYPE_HEADER, "application/json") + .addHeader(ACCEPT_HEADER, "application/json"); addHeaders(requestBuilder); request = requestBuilder.build(); return chain.proceed(request); @@ -63,7 +63,7 @@ public Response intercept(Chain chain) throws IOException { * A helper method to add custom headers to the network request. * * @param requestBuilder an instance of {@link Request.Builder} that you can use to add custom - * headers. + * headers. */ public abstract void addHeaders(@NonNull Request.Builder requestBuilder); } diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/serializer/LocalDateSerializer.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/serializer/LocalDateSerializer.java index ae77f91..c9ace6b 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/serializer/LocalDateSerializer.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/serializer/LocalDateSerializer.java @@ -1,4 +1,4 @@ -/** +/* * MIT License *

* Copyright (c) 2017 Wolox S.A @@ -19,7 +19,6 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ - package ar.com.wolox.wolmo.networking.retrofit.serializer; import android.support.annotation.NonNull; @@ -37,6 +36,7 @@ import org.joda.time.format.DateTimeFormatter; import java.lang.reflect.Type; +import java.util.Locale; /** * Transforms a serialized {@link JsonElement} representing a {@link java.util.Date} into @@ -47,7 +47,10 @@ */ public class LocalDateSerializer implements JsonDeserializer, JsonSerializer { - private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; + protected static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; + protected static final Locale DEFAULT_LOCALE = Locale.ENGLISH; + + private DateTimeFormatter mDateTimeFormatter; /** * Transforms a {@link JsonElement} representing a {@link java.util.Date} into a @@ -55,15 +58,18 @@ public class LocalDateSerializer implements JsonDeserializer, JsonSer * This is useful for receiving data over the network. * * @param element a {@link JsonElement} representing a {@link java.util.Date} + * * @return returns an instance of {@link LocalDate} representing a {@link java.util.Date} */ @Override - public LocalDate deserialize(JsonElement element, Type type, - JsonDeserializationContext context) throws JsonParseException { + public LocalDate deserialize(JsonElement element, Type type, JsonDeserializationContext context) + throws JsonParseException { + initFormatter(); String date = element.toString(); - date = date.substring(1, date.length() - 1); - DateTimeFormatter dtf = DateTimeFormat.forPattern(getDateFormat()); - return dtf.parseLocalDate(date); + if (date.startsWith("\"") && date.endsWith("\"")) { + date = date.substring(1, date.length() - 1); + } + return mDateTimeFormatter.parseLocalDate(date); } /** @@ -71,24 +77,50 @@ public LocalDate deserialize(JsonElement element, Type type, * {@link JsonElement}. This is useful for sending the data over the network. * * @param date an instance of {@link LocalDate} to be serialized into a {@link JsonElement} + * * @return an instance of {@link JsonElement} representing a {@link LocalDate} */ @Override public JsonElement serialize(LocalDate date, Type type, JsonSerializationContext context) { - return new JsonParser().parse(date.toString()); + initFormatter(); + return new JsonParser().parse(mDateTimeFormatter.print(date)); + } + + /** + * Instance the formatter if it's null. + */ + private void initFormatter() { + if (mDateTimeFormatter == null) { + mDateTimeFormatter = DateTimeFormat.forPattern(getDateFormat()).withLocale(getLocale()); + } } /** * Override if needed. - * This method returns the format of the {@link java.util.Date} class that is serialized. + * This method returns the format of the Date that will be serialized/deserialized. * Usually, this should match the format that is being received from the API over the network. *

- * The default return value is the constant DEFAULT_DATE_FORMAT, available in this class. + * The default return value is the constant {@link #DEFAULT_DATE_FORMAT}, available in this + * class. * - * @return returns the format of the serialized {@link java.util.Date} + * @return returns the format of the serialized Date */ @NonNull protected String getDateFormat() { return DEFAULT_DATE_FORMAT; } + + /** + * Override if needed. + * This method returns the locale used by JodaTime to serialize/deserialize the date. + * Usually, this should match the format that is being received from the API over the network. + *

+ * The default return value is the constant {@link #DEFAULT_LOCALE}, available in this class. + * + * @return returns the locale of the serialized Date + */ + @NonNull + protected Locale getLocale() { + return DEFAULT_LOCALE; + } } \ No newline at end of file diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/CallUtils.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/CallUtils.java index 07a4df6..65e531b 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/CallUtils.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/CallUtils.java @@ -1,3 +1,24 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ package ar.com.wolox.wolmo.networking.utils; import android.support.annotation.IntRange; @@ -5,11 +26,12 @@ import com.android.internal.util.Predicate; +import ar.com.wolox.wolmo.networking.exception.PollRunOutOfTriesException; + import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; -import ar.com.wolox.wolmo.networking.exception.PollRunOutOfTriesException; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -30,7 +52,7 @@ private CallUtils() {} * @param callback to be notified when polling ends * @param delay to apply in-between calls * @param timeoutUnit to convert delay - + * * @return a {@link Timer} which is controlling the polling */ public static Timer pollWithDelay(@IntRange(from = 1) final int tries, @@ -41,17 +63,20 @@ public static Timer pollWithDelay(@IntRange(from = 1) final int tries, @NonNull TimeUnit timeoutUnit) { // Timer can be used safely since Retrofit callback is called on UiThread Timer pollingTimer = new Timer(); - pollWithDelay(tries, call, pollingCondition, callback, - timeoutUnit.toMillis(delay), pollingTimer); + pollWithDelay(tries, call, pollingCondition, callback, timeoutUnit.toMillis(delay), + pollingTimer); return pollingTimer; } + public static int TRIES = 0; + private static void pollWithDelay(@IntRange(from = 1) final int triesRemaining, - @NonNull final Call call, - @NonNull final Predicate> pollingCondition, - @NonNull final Callback callback, - @IntRange(from = 0) long delayInMillis, - @NonNull final Timer pollingTimer) { + @NonNull final Call call, + @NonNull final Predicate> pollingCondition, + @NonNull final Callback callback, + @IntRange(from = 0) long delayInMillis, + @NonNull final Timer pollingTimer) { + if (triesRemaining <= 0) { callback.onFailure(call, new PollRunOutOfTriesException(call)); return; @@ -68,8 +93,8 @@ public void onResponse(Call call, Response response) { pollingTimer.schedule(new TimerTask() { @Override public void run() { - pollWithDelay(triesRemaining - 1, call.clone(), - pollingCondition, callback, delayInMillis, pollingTimer); + pollWithDelay(triesRemaining - 1, call.clone(), pollingCondition, callback, + delayInMillis, pollingTimer); } }, delayInMillis); } @@ -79,6 +104,6 @@ public void onFailure(Call call, Throwable t) { callback.onFailure(call, t); } }); + TRIES++; } - } diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/Consumer.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/Consumer.java index 38306fa..daa9fc4 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/Consumer.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/Consumer.java @@ -1,3 +1,24 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ package ar.com.wolox.wolmo.networking.utils; /** @@ -9,5 +30,4 @@ public interface Consumer { * @param data to consume */ void accept(T data); - } diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/serializer/BaseGsonBuilder.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/GsonTypeAdapter.java similarity index 56% rename from networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/serializer/BaseGsonBuilder.java rename to networking/src/main/java/ar/com/wolox/wolmo/networking/utils/GsonTypeAdapter.java index bb9dffe..f5417fd 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/retrofit/serializer/BaseGsonBuilder.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/GsonTypeAdapter.java @@ -1,4 +1,4 @@ -/** +/* * MIT License *

* Copyright (c) 2017 Wolox S.A @@ -19,31 +19,32 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ +package ar.com.wolox.wolmo.networking.utils; -package ar.com.wolox.wolmo.networking.retrofit.serializer; - -import android.support.annotation.NonNull; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; -import com.google.gson.FieldNamingPolicy; - -import org.joda.time.LocalDate; +import java.lang.reflect.Type; /** - * This class binds classes with custom serializers and deserializers + * Container util with {@link Gson} TypeAdapters. + * This adapters will be added to Gson using {@link GsonBuilder#registerTypeAdapter(Type, Object)} */ -public class BaseGsonBuilder { +public class GsonTypeAdapter { + + private final Type mType; + private final Object mTypeAdapter; - /** - * Provides a basic {@link com.google.gson.GsonBuilder} that already has bindings - * to perform common serializations and deserializations with Dates using JodaTime library. - * - * @return Returns and instance of {@link com.google.gson.GsonBuilder} with basic bindings - */ - @NonNull - public static com.google.gson.GsonBuilder getBaseGsonBuilder() { - return new com.google.gson.GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .registerTypeAdapter(LocalDate.class, new LocalDateSerializer()); + public GsonTypeAdapter(Type type, Object typeAdapter) { + mType = type; + mTypeAdapter = typeAdapter; } -} \ No newline at end of file + public Type getType() { + return mType; + } + + public Object getTypeAdapter() { + return mTypeAdapter; + } +} diff --git a/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/NetworkCodes.java b/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/NetworkCodes.java index a9ebe34..fe0c1ba 100644 --- a/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/NetworkCodes.java +++ b/networking/src/main/java/ar/com/wolox/wolmo/networking/utils/NetworkCodes.java @@ -1,23 +1,23 @@ /* - * Copyright (c) Wolox S.A - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. */ package ar.com.wolox.wolmo.networking.utils; @@ -28,6 +28,8 @@ */ public class NetworkCodes { + private NetworkCodes() {} + /** * 2XX Success */ @@ -49,5 +51,4 @@ public class NetworkCodes { * 5XX Server errors */ public static final int ERROR_INTERNAL = 500; - } \ No newline at end of file diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/CachingModuleTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/CachingModuleTest.java new file mode 100644 index 0000000..823c796 --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/CachingModuleTest.java @@ -0,0 +1,53 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.di.modules; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import ar.com.wolox.wolmo.networking.optimizations.BaseCallCollapser; +import ar.com.wolox.wolmo.networking.optimizations.ICallCollapser; + +import org.bouncycastle.jcajce.provider.symmetric.ARC4; +import org.junit.Test; + +import okhttp3.OkHttpClient; +import retrofit2.Retrofit; +import retrofit2.Retrofit.Builder; +import retrofit2.converter.gson.GsonConverterFactory; + +public class CachingModuleTest { + + @Test + public void provideBaseCallCollapserShouldReturnNewInstance() { + ICallCollapser callCollapser1 = CachingModule.provideBaseCallCollapser(); + ICallCollapser callCollapser2 = CachingModule.provideBaseCallCollapser(); + + assertThat(callCollapser1).isNotSameAs(callCollapser2); + assertThat(callCollapser1).isNotEqualTo(callCollapser2); + assertThat(callCollapser1).isExactlyInstanceOf(BaseCallCollapser.class); + } +} diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/GsonModuleTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/GsonModuleTest.java new file mode 100644 index 0000000..0ef2402 --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/GsonModuleTest.java @@ -0,0 +1,121 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.di.modules; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.Adapter; + +import ar.com.wolox.wolmo.networking.retrofit.serializer.LocalDateSerializer; +import ar.com.wolox.wolmo.networking.utils.GsonTypeAdapter; + +import org.joda.time.LocalDate; +import org.junit.Test; + +import java.lang.reflect.Type; + +import retrofit2.converter.gson.GsonConverterFactory; + +public class GsonModuleTest { + + @Test + public void provideGsonShouldUseGivenBuilder() { + Gson gson = mock(Gson.class); + GsonBuilder builder = mock(GsonBuilder.class); + when(builder.create()).thenReturn(gson); + + assertThat(GsonModule.provideGson(builder)).isSameAs(gson); + verify(builder, times(1)).create(); + } + + + @Test + public void provideGsonBuilderShouldRegisterAdapters() { + Type type = mock(Type.class); + TypeAdapter adapter = mock(Adapter.class); + GsonBuilder builderMock = mock(GsonBuilder.class); + + GsonTypeAdapter typeAdapter = new GsonTypeAdapter(type, adapter); + GsonTypeAdapter typeAdapter2 = new GsonTypeAdapter(type, adapter); + + GsonBuilder builder = GsonModule.provideGsonBuilder(builderMock, + FieldNamingPolicy.UPPER_CAMEL_CASE, typeAdapter, typeAdapter2); + + assertThat(builder).isSameAs(builderMock); + verify(builderMock, times(1)).setFieldNamingPolicy(any(FieldNamingPolicy.class)); + verify(builderMock, times(1)).setFieldNamingPolicy(eq(FieldNamingPolicy.UPPER_CAMEL_CASE)); + verify(builderMock, times(2)).registerTypeAdapter(eq(type), eq(adapter)); + } + + @Test + public void provideGsonBuilderWithoutAdaptersShouldAddDefaultAdapter() { + GsonBuilder builderMock = mock(GsonBuilder.class); + + GsonBuilder builder = GsonModule.provideGsonBuilder(builderMock, + FieldNamingPolicy.UPPER_CAMEL_CASE); + + assertThat(builder).isSameAs(builderMock); + verify(builderMock, times(1)).setFieldNamingPolicy(any(FieldNamingPolicy.class)); + verify(builderMock, times(1)).setFieldNamingPolicy(eq(FieldNamingPolicy.UPPER_CAMEL_CASE)); + verify(builderMock, times(1)).registerTypeAdapter(eq(LocalDate.class), + any(LocalDateSerializer.class)); + } + + @Test + public void provideGsonBuilderWithNullShouldAddDefaultAdapter() { + GsonBuilder builderMock = mock(GsonBuilder.class); + + GsonBuilder builder = GsonModule.provideGsonBuilder(builderMock, + FieldNamingPolicy.UPPER_CAMEL_CASE, (GsonTypeAdapter[]) null); + + assertThat(builder).isSameAs(builderMock); + verify(builderMock, times(1)).setFieldNamingPolicy(any(FieldNamingPolicy.class)); + verify(builderMock, times(1)).setFieldNamingPolicy(eq(FieldNamingPolicy.UPPER_CAMEL_CASE)); + verify(builderMock, times(1)).registerTypeAdapter(eq(LocalDate.class), + any(LocalDateSerializer.class)); + } + + @Test + public void provideNewGsonBuilderShouldReturnNewInstance() { + GsonBuilder gsonBuilder1 = GsonModule.provideNewGsonBuilder(); + GsonBuilder gsonBuilder2 = GsonModule.provideNewGsonBuilder(); + assertThat(gsonBuilder1).isNotSameAs(gsonBuilder2); + assertThat(gsonBuilder1).isNotEqualTo(gsonBuilder2); + } + + @Test + public void provideGsonConverterFactoryShouldUseProvidedGson() { + Gson gsonMock = mock(Gson.class); + GsonConverterFactory converterFactory = GsonModule.provideGsonConverterFactory(gsonMock); + assertThat(converterFactory).extracting("gson").containsOnly(gsonMock); + } +} diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/NetworkingModuleTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/NetworkingModuleTest.java new file mode 100644 index 0000000..044d49d --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/NetworkingModuleTest.java @@ -0,0 +1,63 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.di.modules; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.junit.Test; + +import okhttp3.OkHttpClient; +import retrofit2.Retrofit; +import retrofit2.Retrofit.Builder; +import retrofit2.converter.gson.GsonConverterFactory; + +public class NetworkingModuleTest { + + @Test + public void provideRetrofitShouldConfigureBuilder() { + Builder builderSpy = spy(new Builder()); + OkHttpClient clientMock = mock(OkHttpClient.class); + GsonConverterFactory converterFactoryMock = mock(GsonConverterFactory.class); + + Retrofit retrofit = NetworkingModule.provideRetrofit(builderSpy, "http://web.com", + converterFactoryMock, clientMock); + + assertThat(retrofit).isNotNull(); + verify(builderSpy, times(1)).baseUrl(eq("http://web.com")); + verify(builderSpy, times(1)).addConverterFactory(eq(converterFactoryMock)); + verify(builderSpy, times(1)).client(eq(clientMock)); + verify(builderSpy, times(1)).build(); + } + + @Test + public void provideRetrofitBuilderShouldReturnNewInstance() { + Retrofit.Builder builder1 = NetworkingModule.provideRetrofitBuilder(); + Retrofit.Builder builder2 = NetworkingModule.provideRetrofitBuilder(); + assertThat(builder1).isNotSameAs(builder2); + assertThat(builder1).isNotEqualTo(builder2); + } +} diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/OkHttpClientModuleTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/OkHttpClientModuleTest.java new file mode 100644 index 0000000..b949d0a --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/di/modules/OkHttpClientModuleTest.java @@ -0,0 +1,81 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.di.modules; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.junit.Test; + +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; + +public class OkHttpClientModuleTest { + + @Test + public void provideOkHttpClientShouldAddInterceptors() { + OkHttpClient.Builder okHttpBuilderSpy = spy(new OkHttpClient.Builder()); + Interceptor interceptorMock = mock(Interceptor.class); + Interceptor interceptorMock2 = mock(Interceptor.class); + + OkHttpClient client = OkHttpClientModule.provideOkHttpClient(okHttpBuilderSpy, + interceptorMock, interceptorMock2); + + assertThat(client).isNotNull(); + verify(okHttpBuilderSpy, times(1)).addInterceptor(eq(interceptorMock)); + verify(okHttpBuilderSpy, times(1)).addInterceptor(eq(interceptorMock2)); + verify(okHttpBuilderSpy, times(1)).build(); + } + + @Test + public void provideOkHttpClientShouldWorkWithoutInterceptors() { + OkHttpClient.Builder okHttpBuilderSpy = spy(new OkHttpClient.Builder()); + + OkHttpClient client = OkHttpClientModule.provideOkHttpClient(okHttpBuilderSpy); + + assertThat(client).isNotNull(); + verify(okHttpBuilderSpy, times(1)).build(); + } + + @Test + public void provideOkHttpClientShouldWorkWithNullInterceptors() { + OkHttpClient.Builder okHttpBuilderSpy = spy(new OkHttpClient.Builder()); + + OkHttpClient client = OkHttpClientModule.provideOkHttpClient(okHttpBuilderSpy, (Interceptor[]) null); + + assertThat(client).isNotNull(); + verify(okHttpBuilderSpy, times(1)).build(); + } + + @Test + public void provideOkHttpClientBuilderShouldReturnNewInstance() { + OkHttpClient.Builder builder1 = OkHttpClientModule.provideOkHttpClientBuilder(); + OkHttpClient.Builder builder2 = OkHttpClientModule.provideOkHttpClientBuilder(); + + assertThat(builder1).isNotEqualTo(builder2); + assertThat(builder1).isNotSameAs(builder2); + } +} diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/offline/RepositoryTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/offline/RepositoryTest.java new file mode 100644 index 0000000..5eb451a --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/offline/RepositoryTest.java @@ -0,0 +1,265 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.offline; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import ar.com.wolox.wolmo.networking.exception.CacheMissException; +import ar.com.wolox.wolmo.networking.exception.NetworkResourceException; +import ar.com.wolox.wolmo.networking.optimizations.ICallCollapser; +import ar.com.wolox.wolmo.networking.test_utils.RetrofitCallMockBuilder; +import ar.com.wolox.wolmo.networking.utils.Consumer; + +import org.junit.Before; +import org.junit.Test; + +import retrofit2.Call; +import retrofit2.Callback; + +public class RepositoryTest { + + private Repository mRepository; + private ICallCollapser mCallCollapserMock; + private Repository.QueryStrategy mQueryStrategyMock; + private String mCache; + + @Before + @SuppressWarnings("unchecked") + public void beforeTest() { + mCache = "Cache"; + mCallCollapserMock = mock(ICallCollapser.class); + mQueryStrategyMock = mock(Repository.QueryStrategy.class); + mRepository = new Repository<>(mCache, mCallCollapserMock); + + // Delegate the Collapser enqueue to the Call + doAnswer(invocation -> { + Call call = invocation.getArgument(0); + call.enqueue(invocation.getArgument(1)); + return null; + }).when(mCallCollapserMock).enqueue(any(Call.class), any(Callback.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void queryDefaultAccessCacheSuccess() { + Call callMock = new RetrofitCallMockBuilder().buildSuccess("Success"); + + Consumer onErrorMock = mock(Consumer.class); + Consumer onSuccessMock = mock(Consumer.class); + + // Cache status + when(mQueryStrategyMock.readLocalSource(any(String.class))).thenReturn("CachedValue"); + + // Do things + Repository.Query query = mRepository.query(callMock, mQueryStrategyMock); + query.onError(onErrorMock).onSuccess(onSuccessMock); + query.run(); + + // Verify cache read + verify(mQueryStrategyMock, times(1)).readLocalSource(eq(mCache)); + verify(onSuccessMock, times(1)).accept(eq("CachedValue")); + + // Verify no network request + verify(mCallCollapserMock, times(0)).enqueue(eq(callMock), any(Callback.class)); + verify(mQueryStrategyMock, times(0)).consumeRemoteSource(eq("Response"), eq(mCache)); + } + + @Test + @SuppressWarnings("unchecked") + public void queryDefaultAccessCacheAndNetworkSuccessful() { + Call callMock = new RetrofitCallMockBuilder().buildSuccess("Response"); + + Consumer onErrorMock = mock(Consumer.class); + Consumer onSuccessMock = mock(Consumer.class); + + // Cache status + when(mQueryStrategyMock.readLocalSource(any(String.class))).thenReturn(null); + + // Do things + Repository.Query query = mRepository.query(callMock, mQueryStrategyMock); + query.onError(onErrorMock).onSuccess(onSuccessMock); + query.run(); + + // Verify cache read + verify(mQueryStrategyMock, times(1)).readLocalSource(eq(mCache)); + + // Verify network request + verify(mCallCollapserMock, times(1)).enqueue(eq(callMock), any(Callback.class)); + verify(mQueryStrategyMock, times(1)).consumeRemoteSource(eq("Response"), eq(mCache)); + verify(onSuccessMock, times(1)).accept(eq("Response")); + } + + @Test + @SuppressWarnings("unchecked") + public void queryDefaultAccessCacheAndNetworkResponseError() { + Call callMock = new RetrofitCallMockBuilder().buildError(404); + + Consumer onErrorMock = mock(Consumer.class); + Consumer onSuccessMock = mock(Consumer.class); + + // Cache status + when(mQueryStrategyMock.readLocalSource(any(String.class))).thenReturn(null); + + // Do things + Repository.Query query = mRepository.query(callMock, mQueryStrategyMock); + query.onError(onErrorMock).onSuccess(onSuccessMock); + query.run(); + + // Verify cache read + verify(mQueryStrategyMock, times(1)).readLocalSource(eq(mCache)); + + // Verify network request + verify(mCallCollapserMock, times(1)).enqueue(eq(callMock), any(Callback.class)); + verify(mQueryStrategyMock, times(0)).consumeRemoteSource(eq("Response"), eq(mCache)); + verify(onErrorMock, times(1)).accept(any(NetworkResourceException.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void queryDefaultAccessCacheAndNetworkFail() { + Exception exception = new Exception(); + Call callMock = new RetrofitCallMockBuilder().buildFailure(exception); + + Consumer onErrorMock = mock(Consumer.class); + Consumer onSuccessMock = mock(Consumer.class); + + // Cache status + when(mQueryStrategyMock.readLocalSource(any(String.class))).thenReturn(null); + + // Do things + Repository.Query query = mRepository.query(callMock, mQueryStrategyMock); + query.onError(onErrorMock).onSuccess(onSuccessMock); + query.run(); + + // Verify cache read + verify(mQueryStrategyMock, times(1)).readLocalSource(eq(mCache)); + + // Verify network request + verify(mCallCollapserMock, times(1)).enqueue(eq(callMock), any(Callback.class)); + verify(mQueryStrategyMock, times(0)).consumeRemoteSource(eq("Response"), eq(mCache)); + verify(onErrorMock, times(1)).accept(eq(exception)); + } + + @Test + @SuppressWarnings("unchecked") + public void queryDefaultAccessCacheAndNetworkFailWithCallback() { + Exception exception = new Exception(); + Call callMock = new RetrofitCallMockBuilder().buildFailure(exception); + IRepositoryCallback repositoryCallbackMock = mock(IRepositoryCallback.class); + + // Cache status + when(mQueryStrategyMock.readLocalSource(any(String.class))).thenReturn(null); + + // Do things + mRepository.query(callMock, mQueryStrategyMock, repositoryCallbackMock); + + // Verify cache read + verify(mQueryStrategyMock, times(1)).readLocalSource(eq(mCache)); + + // Verify network request + verify(mCallCollapserMock, times(1)).enqueue(eq(callMock), any(Callback.class)); + verify(mQueryStrategyMock, times(0)).consumeRemoteSource(eq("Response"), eq(mCache)); + verify(repositoryCallbackMock, times(1)).onError(eq(exception)); + } + + @Test + @SuppressWarnings("unchecked") + public void queryAccessCacheOnly() { + Call callMock = mock(Call.class); + IRepositoryCallback repositoryCallbackMock = mock(IRepositoryCallback.class); + + // Cache status + when(mQueryStrategyMock.readLocalSource(any(String.class))).thenReturn(null); + + // Do things + mRepository.query(Repository.CACHE_ONLY, callMock, mQueryStrategyMock, repositoryCallbackMock); + + // Verify cache read + verify(mQueryStrategyMock, times(1)).readLocalSource(eq(mCache)); + verify(repositoryCallbackMock, times(1)).onError(any(CacheMissException.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void queryAccessCacheNone() { + Call callMock = new RetrofitCallMockBuilder().buildSuccess("Success"); + + Consumer onErrorMock = mock(Consumer.class); + Consumer onSuccessMock = mock(Consumer.class); + + // Cache status + when(mQueryStrategyMock.readLocalSource(any(String.class))).thenReturn(null); + + // Do things + Repository.Query query = mRepository.query(Repository.CACHE_NONE, callMock, mQueryStrategyMock); + query.onError(onErrorMock).onSuccess(onSuccessMock); + query.run(); + + // Verify cache read + verify(mQueryStrategyMock, times(0)).readLocalSource(eq(mCache)); + + // Verify network request + verify(mCallCollapserMock, times(1)).enqueue(eq(callMock), any(Callback.class)); + verify(mQueryStrategyMock, times(1)).consumeRemoteSource(eq("Success"), eq(mCache)); + verify(onSuccessMock, times(1)).accept(eq("Success")); + } + + @Test(expected = IllegalStateException.class) + @SuppressWarnings("unchecked") + public void queryAccessCacheNoneExecutedCall() { + Call callMock = new RetrofitCallMockBuilder().isExecuted(true).buildSuccess("Success"); + + Consumer onErrorMock = mock(Consumer.class); + Consumer onSuccessMock = mock(Consumer.class); + + // Cache status + when(mQueryStrategyMock.readLocalSource(any(String.class))).thenReturn(null); + + // Do things + Repository.Query query = mRepository.query(Repository.CACHE_NONE, callMock, mQueryStrategyMock); + query.onError(onErrorMock).onSuccess(onSuccessMock); + query.run(); + } + + @Test(expected = IllegalStateException.class) + @SuppressWarnings("unchecked") + public void queryAccessCacheNoneCancelledCall() { + Call callMock = new RetrofitCallMockBuilder().isCanceled(true).buildSuccess("Success"); + + Consumer onErrorMock = mock(Consumer.class); + Consumer onSuccessMock = mock(Consumer.class); + + // Cache status + when(mQueryStrategyMock.readLocalSource(any(String.class))).thenReturn(null); + + // Do things + Repository.Query query = mRepository.query(Repository.CACHE_NONE, callMock, mQueryStrategyMock); + query.onError(onErrorMock).onSuccess(onSuccessMock); + query.run(); + } +} diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/offline/TimeResolveQueryStrategyTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/offline/TimeResolveQueryStrategyTest.java new file mode 100644 index 0000000..0d69501 --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/offline/TimeResolveQueryStrategyTest.java @@ -0,0 +1,82 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.offline; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.support.annotation.NonNull; + +import org.junit.Before; +import org.junit.Test; + +public class TimeResolveQueryStrategyTest { + + private static final int REFRESH_DELTA = 10; + + private TimeResolveQueryStrategy mTimeResolveQueryStrategySpy; + + @Before + public void beforeTest() { + mTimeResolveQueryStrategySpy = spy(new TimeResolveQueryStrategy(REFRESH_DELTA) { + + @Override + public void invalidate(@NonNull String cache) { + } + + @Override + public String cleanReadLocalSource(@NonNull String cache) { + return "CleanRead"; + } + + @Override + public void refresh(@NonNull String data, @NonNull String cache) { + } + }); + } + + @Test + public void cacheReadFirstTime() throws Exception { + Thread.sleep(REFRESH_DELTA + 5); + assertThat(mTimeResolveQueryStrategySpy.readLocalSource("Cache")).isNull(); + verify(mTimeResolveQueryStrategySpy, times(1)).invalidate(eq("Cache")); + } + + @Test + public void cacheReadCleanCache() throws Exception { + Thread.sleep(REFRESH_DELTA + 5); + assertThat(mTimeResolveQueryStrategySpy.readLocalSource("Cache")).isNull(); + verify(mTimeResolveQueryStrategySpy, times(1)).invalidate(eq("Cache")); + + // Refresh cache + mTimeResolveQueryStrategySpy.consumeRemoteSource("Data", "RemoteCache"); + + // Do not invalidate again + assertThat(mTimeResolveQueryStrategySpy.readLocalSource("Cache")).isEqualTo("CleanRead"); + verify(mTimeResolveQueryStrategySpy, times(1)).refresh(eq("Data"), eq("RemoteCache")); + verify(mTimeResolveQueryStrategySpy, times(1)).invalidate(eq("Cache")); + verify(mTimeResolveQueryStrategySpy, times(1)).cleanReadLocalSource(eq("Cache")); + } +} diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/optimizations/BaseCallCollapserTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/optimizations/BaseCallCollapserTest.java new file mode 100644 index 0000000..2361899 --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/optimizations/BaseCallCollapserTest.java @@ -0,0 +1,182 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.optimizations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.support.annotation.NonNull; + +import ar.com.wolox.wolmo.networking.test_utils.RetrofitCallMockBuilder; +import ar.com.wolox.wolmo.networking.test_utils.service.RetrofitTestService; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.io.IOException; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class BaseCallCollapserTest { + + private BaseCallCollapser mBaseCallCollapser; + private MockWebServer mMockWebServer; + private Retrofit mRetrofit; + + private Semaphore mSemaphore; + private Callback mCallbackBase; + + @Before + public void beforeTest() throws IOException { + mBaseCallCollapser = new BaseCallCollapser(); + mMockWebServer = new MockWebServer(); + mMockWebServer.start(); + + mRetrofit = new Retrofit.Builder().baseUrl(mMockWebServer.url("")) + .addConverterFactory(GsonConverterFactory.create()).client(new OkHttpClient()) + .build(); + + mSemaphore = new Semaphore(0); + mCallbackBase = new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + mSemaphore.release(); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + mSemaphore.release(); + } + }; + } + + @Test + @SuppressWarnings("unchecked") + public void enqueueGetCall() throws Exception { + mMockWebServer.enqueue(new MockResponse().setBody("\"Hello First\"")); + + Callback callbackMock = mock(Callback.class); + Call callMock = + Mockito.spy(mRetrofit.create(RetrofitTestService.class).retrofitGetMethodString()); + + // Enqueue Two GET requests + mBaseCallCollapser.enqueue(callMock, callbackMock); + mBaseCallCollapser.enqueue(callMock, callbackMock); + + // Wait for a server response and Verify server call + RecordedRequest request = mMockWebServer.takeRequest(); + assertThat(request.getPath()).isEqualTo("/api/get/"); + assertThat(mMockWebServer.getRequestCount()).isEqualTo(1); + verify(callMock, times(1)).enqueue(any(Callback.class)); + + // Verify that both callbacks get called + ArgumentCaptor> responseCaptor = ArgumentCaptor.forClass(Response.class); + verify(callbackMock, times(2)).onResponse(eq(callMock), responseCaptor.capture()); + assertThat(responseCaptor.getValue().body()).isEqualTo("Hello First"); + } + + @Test + @SuppressWarnings("unchecked") + public void enqueueGetFailingCall() throws Exception { + mMockWebServer.enqueue(new MockResponse().setResponseCode(404)); + + Callback callbackSpy = spy(mCallbackBase); + Call callMock = new RetrofitCallMockBuilder() + .runBefore(1, TimeUnit.SECONDS) + .buildFailure(new Exception()); + + // Enqueue Two GET requests + mBaseCallCollapser.enqueue(callMock, callbackSpy); + mBaseCallCollapser.enqueue(callMock, callbackSpy); + + // Wait for a server response and Verify server call + mSemaphore.acquire(2); + assertThat(mMockWebServer.getRequestCount()).isEqualTo(0); + verify(callMock, times(1)).enqueue(any(Callback.class)); + + // Verify that both callbacks get called + verify(callbackSpy, times(2)).onFailure(eq(callMock), any(Exception.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void enqueueGetAndPostCall() throws Exception { + mMockWebServer.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) throws InterruptedException { + if (request.getMethod().equals("GET")) { + return new MockResponse().setBody("\"Hello GET\""); + } else if (request.getMethod().equals("POST")) { + return new MockResponse().setBody("\"Hello POST\""); + } + return new MockResponse(); + } + }); + + // Prepare mocks + Callback getCallbackSpy = spy(mCallbackBase); + Callback postCallbackSpy = spy(mCallbackBase); + Call getCallMock = + Mockito.spy(mRetrofit.create(RetrofitTestService.class).retrofitGetMethodString()); + Call postCallMock = + Mockito.spy(mRetrofit.create(RetrofitTestService.class).retrofitPostMethodString()); + + // Enqueue Two requests + mBaseCallCollapser.enqueue(getCallMock, getCallbackSpy); + mBaseCallCollapser.enqueue(postCallMock, postCallbackSpy); + + // Wait for a server response + mSemaphore.acquire(2); + + // Verify server call + assertThat(mMockWebServer.getRequestCount()).isEqualTo(2); + verify(postCallMock, times(1)).enqueue(any(Callback.class)); + verify(getCallMock, times(1)).enqueue(any(Callback.class)); + + // Verify that both callbacks get called + ArgumentCaptor> responseCaptor = ArgumentCaptor.forClass(Response.class); + verify(getCallbackSpy, times(1)).onResponse(eq(getCallMock), responseCaptor.capture()); + assertThat(responseCaptor.getValue().body()).isEqualTo("Hello GET"); + + ArgumentCaptor> responseCaptor2 = ArgumentCaptor.forClass(Response.class); + verify(postCallbackSpy, times(1)).onResponse(eq(postCallMock), responseCaptor2.capture()); + assertThat(responseCaptor2.getValue().body()).isEqualTo("Hello POST"); + } +} diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/RetrofitServicesTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/RetrofitServicesTest.java new file mode 100644 index 0000000..a07a37d --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/RetrofitServicesTest.java @@ -0,0 +1,64 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package ar.com.wolox.wolmo.networking.retrofit; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; + +import retrofit2.Retrofit; + + +@SuppressWarnings("unchecked") +public class RetrofitServicesTest { + + private Retrofit mRetrofit; + private RetrofitServices mRetrofitServices; + + @Before + public void beforeTest() { + mRetrofit = mock(Retrofit.class); + mRetrofitServices = new RetrofitServices(mRetrofit); + } + + @Test + public void testCachedRetrofitService() { + Object service = new Object(); + when(mRetrofit.create(any(Class.class))).thenReturn(service); + + assertThat(mRetrofitServices.getService(Object.class)).isSameAs(service); + verify(mRetrofit, times(1)).create(eq(Object.class)); + + // Successive calls shouldn't call create() again + assertThat(mRetrofitServices.getService(Object.class)).isSameAs(service); + verify(mRetrofit, times(1)).create(eq(Object.class)); + } + +} \ No newline at end of file diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/callback/NetworkCallbackTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/callback/NetworkCallbackTest.java new file mode 100644 index 0000000..c13808f --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/callback/NetworkCallbackTest.java @@ -0,0 +1,94 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.retrofit.callback; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; + +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Response; + +@SuppressWarnings("unchecked") +public class NetworkCallbackTest { + + private NetworkCallback mNetworkCallbackSpy; + private Response mResponseMock; + private Object mResponseBody; + private Call mCallMock; + + @Before + public void beforeTest() { + mNetworkCallbackSpy = spy(new NetworkCallback() { + @Override + public void onResponseSuccessful(Object response) {} + + @Override + public void onResponseFailed(ResponseBody responseBody, int code) {} + + @Override + public void onCallFailure(Throwable t) {} + }); + + mResponseMock = mock(Response.class); + mCallMock = mock(Call.class); + mResponseBody = new Object(); + + when(mResponseMock.body()).thenReturn(mResponseBody); + when(mResponseMock.code()).thenReturn(444); + when(mResponseMock.errorBody()).thenReturn(mock(ResponseBody.class)); + } + + @Test + public void onResponseShouldNotifySuccessfulResponse() { + when(mResponseMock.isSuccessful()).thenReturn(true); + + mNetworkCallbackSpy.onResponse(mCallMock, mResponseMock); + verify(mNetworkCallbackSpy, times(1)).onResponseSuccessful(eq(mResponseBody)); + } + + @Test + public void onResponseShouldNotifyAuthErrors() { + when(mResponseMock.isSuccessful()).thenReturn(false); + when(mNetworkCallbackSpy.isAuthError(any(Response.class))).thenReturn(true); + + mNetworkCallbackSpy.onResponse(mCallMock, mResponseMock); + verify(mNetworkCallbackSpy, times(1)).handleAuthError(eq(mResponseMock)); + } + + @Test + public void onResponseShouldNotifyResponseFailed() { + when(mResponseMock.isSuccessful()).thenReturn(false); + when(mNetworkCallbackSpy.isAuthError(any(Response.class))).thenReturn(false); + + mNetworkCallbackSpy.onResponse(mCallMock, mResponseMock); + verify(mNetworkCallbackSpy, times(1)).onResponseFailed(any(ResponseBody.class), eq(444)); + } +} \ No newline at end of file diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/interceptor/ApiRestInterceptorTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/interceptor/ApiRestInterceptorTest.java new file mode 100644 index 0000000..395f42b --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/interceptor/ApiRestInterceptorTest.java @@ -0,0 +1,81 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.retrofit.interceptor; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Java6Assertions.entry; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.support.annotation.NonNull; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.io.IOException; +import java.util.Arrays; + +import okhttp3.Interceptor.Chain; +import okhttp3.Request; +import okhttp3.Request.Builder; + +public class ApiRestInterceptorTest { + + private static final String JSON_APP = "application/json"; + + private ApiRestInterceptor mApiRestInterceptor; + + @Before + public void beforeTest() { + mApiRestInterceptor = spy(new ApiRestInterceptor() { + @Override + public void addHeaders(@NonNull Builder requestBuilder) { + } + }); + } + + @Test + public void interceptShouldAddJsonHeaders() throws IOException { + Chain chainMock = mock(Chain.class); + Request request = new Request.Builder().url("http://test.com").build(); + when(chainMock.request()).thenReturn(request); + + mApiRestInterceptor.intercept(chainMock); + + // Capture the request + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class); + verify(chainMock, times(1)).proceed(requestCaptor.capture()); + verify(mApiRestInterceptor, times(1)).addHeaders(any(Request.Builder.class)); + + // Verify that the request has the required headers + Request responseRequest = requestCaptor.getValue(); + assertThat(responseRequest.headers().toMultimap()).hasSize(2) + .contains(entry(ApiRestInterceptor.CONTENT_TYPE_HEADER, Arrays.asList(JSON_APP))) + .contains(entry(ApiRestInterceptor.ACCEPT_HEADER, Arrays.asList(JSON_APP))); + } + +} diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/serializer/LocalDateSerializerTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/serializer/LocalDateSerializerTest.java new file mode 100644 index 0000000..1e6bd9f --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/retrofit/serializer/LocalDateSerializerTest.java @@ -0,0 +1,120 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package ar.com.wolox.wolmo.networking.retrofit.serializer; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.support.annotation.NonNull; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonSerializationContext; + +import org.joda.time.LocalDate; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Type; + +public class LocalDateSerializerTest { + + private LocalDateSerializer mLocalDateSerializer; + + private JsonDeserializationContext mDeserializationContextMock; + private JsonSerializationContext mSerializationContextMock; + private Type mTypeMock; + + @Before + public void beforeTest() { + mLocalDateSerializer = new LocalDateSerializer(); + mDeserializationContextMock = mock(JsonDeserializationContext.class); + mSerializationContextMock = mock(JsonSerializationContext.class); + mTypeMock = mock(Type.class); + } + + + @Test + public void defaultFormatDeserialize() { + JsonElement jsonElementMock = mock(JsonElement.class); + when(jsonElementMock.toString()).thenReturn("2000-01-23"); + + LocalDate localDate = mLocalDateSerializer.deserialize(jsonElementMock, mTypeMock, + mDeserializationContextMock); + assertThat(localDate).isEqualTo(new LocalDate("2000-01-23")); + } + + @Test + public void defaultFormatDeserializeWithBadFormattedDate() { + JsonElement jsonElementMock = mock(JsonElement.class); + when(jsonElementMock.toString()).thenReturn("\"2000-01-23\""); + + LocalDate localDate = mLocalDateSerializer.deserialize(jsonElementMock, mTypeMock, + mDeserializationContextMock); + assertThat(localDate).isEqualTo(new LocalDate("2000-01-23")); + } + + @Test + public void customFormatDeserializeDate() { + mLocalDateSerializer = new LocalDateSerializer() { + @NonNull + @Override + protected String getDateFormat() { + return "MM-yyyy-dd"; + } + }; + + JsonElement jsonElementMock = mock(JsonElement.class); + when(jsonElementMock.toString()).thenReturn("01-2000-23"); + + LocalDate localDate = mLocalDateSerializer.deserialize(jsonElementMock, mTypeMock, + mDeserializationContextMock); + assertThat(localDate).isEqualTo(new LocalDate("2000-01-23")); + } + + @Test + public void defaultFormatSerializeDate() { + LocalDate localDate = new LocalDate("2000-01-23"); + + JsonElement serializedDate = mLocalDateSerializer.serialize(localDate, mTypeMock, + mSerializationContextMock); + assertThat(serializedDate.getAsString()).isEqualTo("2000-01-23"); + } + + @Test + public void customFormatSerializeDate() { + LocalDate localDate = new LocalDate("2000-01-23"); + + mLocalDateSerializer = new LocalDateSerializer() { + @NonNull + @Override + protected String getDateFormat() { + return "MM-yyyy-dd"; + } + }; + + JsonElement serializedDate = mLocalDateSerializer.serialize(localDate, mTypeMock, + mSerializationContextMock); + assertThat(serializedDate.getAsString()).isEqualTo("01-2000-23"); + } +} \ No newline at end of file diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/test_utils/ResponseMockFactory.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/test_utils/ResponseMockFactory.java new file mode 100644 index 0000000..68d33b4 --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/test_utils/ResponseMockFactory.java @@ -0,0 +1,35 @@ +package ar.com.wolox.wolmo.networking.test_utils; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import okhttp3.ResponseBody; +import retrofit2.Response; + +public class ResponseMockFactory { + + private ResponseMockFactory() {} + + @SuppressWarnings("unchecked") + public static Response createSuccessfulResponseMock(T responseBody) { + Response response = mock(Response.class); + when(response.body()).thenReturn(responseBody); + when(response.isSuccessful()).thenReturn(true); + when(response.code()).thenReturn(200); + return response; + } + + @SuppressWarnings("unchecked") + public static Response createFailedResponseMock(ResponseBody errorBody, int code) { + Response response = mock(Response.class); + when(response.errorBody()).thenReturn(errorBody); + when(response.isSuccessful()).thenReturn(false); + when(response.code()).thenReturn(code); + return response; + } + + @SuppressWarnings("unchecked") + public static Response createFailedResponseMock(int code) { + return createFailedResponseMock(mock(ResponseBody.class), code); + } +} diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/test_utils/RetrofitCallMockBuilder.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/test_utils/RetrofitCallMockBuilder.java new file mode 100644 index 0000000..e3eaef4 --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/test_utils/RetrofitCallMockBuilder.java @@ -0,0 +1,241 @@ +package ar.com.wolox.wolmo.networking.test_utils; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.mockito.stubbing.Answer; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; + +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +/** + * Builder to customize a Retrofit's {@link Call} mock. + * + * @param Type of the call. + */ +@ParametersAreNonnullByDefault +public class RetrofitCallMockBuilder { + + public static final String HTTP_METHOD_GET = "GET"; + public static final String HTTP_METHOD_POST = "POST"; + public static final String DEFAULT_URL = "http://test.com/test"; + + private String mHttpMethod; + private String mUrl; + private CallbackMockConsumer mCallbackAnswer; + private Exception mException; + private long mRunBefore; + private boolean mExecuted; + private boolean mCanceled; + + /** + * Sets the HTTP method for the mock. + * If not HTTP method is set, the value {@link #HTTP_METHOD_GET} will be used. + * + * @param httpMethod String http method of the mock. + * + * @return Builder + */ + public RetrofitCallMockBuilder setHttpMethod(String httpMethod) { + mHttpMethod = httpMethod; + return this; + } + + /** + * Sets the URL for the mock. + * If not URL is set, the value {@link #DEFAULT_URL} will be used. + * + * @param url String url of the mock. + * + * @return Builder + */ + public RetrofitCallMockBuilder setUrl(String url) { + mUrl = url; + return this; + } + + /** + * Set the executed status of the mock with the value provided. + * When calling {@link Call#isExecuted()} the value of executed will be returned. + * + * @param executed If the mock was executed or not + * + * @return Builder + */ + public RetrofitCallMockBuilder isExecuted(boolean executed) { + mExecuted = executed; + return this; + } + + /** + * Set the cancel status of the mock with the value provided. + * When calling {@link Call#isCanceled()} ()} the value of canceled will be returned. + * + * @param canceled If the mock was executed or not + * + * @return Builder + */ + public RetrofitCallMockBuilder isCanceled(boolean canceled) { + mCanceled = canceled; + return this; + } + + /** + * Configures the {@link Call} to run before an amount of time. + * Setting this to a number greater than 0 will create a new {@link Thread} to run the call, + * otherwise it'll run in the same thread it was created. + * + * @param time Time to wait before "executing" the call. + * @param timeUnit TimeUnit + * + * @return Builder + */ + public RetrofitCallMockBuilder runBefore(long time, TimeUnit timeUnit) { + mRunBefore = timeUnit.toMillis(time); + return this; + } + + /** + * Builds a Success Call. + * This mocked {@link Call} will make a call to {@link Callback#onResponse(Call, Response)} + * with + * a success {@link Response} and with the given response. + * + * @param response Response to send at the callback. + * + * @return new {@link Call} mock. + */ + public Call buildSuccess(T response) { + return build((call, callback) -> callback.onResponse(call, Response.success(response))); + } + + /** + * Builds an error call. + * This mocked {@link Call} will make a call to {@link Callback#onResponse(Call, Response)} + * with an error {@link Response}. The response will have the httpCode given in the parameter + * and a Mock as the response body. + * + * @param httpCode Response code for the response + * + * @return a new {@link Call} mock + */ + public Call buildError(int httpCode) { + return build((call, callback) -> callback + .onResponse(call, Response.error(httpCode, mock(ResponseBody.class)))); + } + + /** + * Builds a failed call. + * This mocker {@link Call} will call {@link Callback#onFailure(Call, Throwable)} with the + * given + * exception. + * + * @param exception Exception to send to the callback. + * + * @return a new {@link Call} mock + */ + public Call buildFailure(@Nullable Exception exception) { + mException = exception; + return build(); + } + + /** + * Builds a call with a custom behaviour. + * You can pass a {@link CallbackMockConsumer} who receives a {@link Call} and a {@link + * Callback} to + * take custom logic. + * + * @param callbackMockConsumer Consumer for the call. + * + * @return a new {@link Call} mock. + */ + public Call build(CallbackMockConsumer callbackMockConsumer) { + mCallbackAnswer = callbackMockConsumer; + return build(); + } + + /** + * Build a mocked {@link Call} and all the required (mocked) dependencies. + * + * @return a new {@link Call} mock. + */ + @SuppressWarnings("unchecked") + private Call build() { + Call callMock = mock(Call.class); + Request requestMock = buildOkHttpRequest(); + when(callMock.request()).thenReturn(requestMock); + when(callMock.isExecuted()).thenReturn(mExecuted); + when(callMock.isCanceled()).thenReturn(mCanceled); + + // Custom answer + doAnswer(buildCallAnswer(callMock)).when(callMock).enqueue(any(Callback.class)); + when(callMock.clone()).thenReturn(callMock); + return callMock; + } + + private Request buildOkHttpRequest() { + Request okHttpRequestMock = mock(Request.class); + + HttpUrl urlMock = mock(HttpUrl.class); + when(urlMock.toString()).thenReturn(mUrl == null ? DEFAULT_URL : mUrl); + + when(okHttpRequestMock.method()).thenReturn(mHttpMethod == null ? HTTP_METHOD_GET : mHttpMethod); + when(okHttpRequestMock.url()).thenReturn(urlMock); + return okHttpRequestMock; + } + + private Answer buildCallAnswer(final Call callMock) { + CallbackMockConsumer consumer = (call, callback) -> { + if (mException != null) { + callback.onFailure(call, mException); + } else { + mCallbackAnswer.consume(call, callback); + } + }; + + if (mRunBefore > 0) { + return invocation -> { + new Timer().schedule(new TimerTask() { + @Override + public void run() { + Callback callback = invocation.getArgument(0); + try { + consumer.consume(callMock, callback); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, mRunBefore); + return null; + }; + } else { + return invocation -> { + Callback callback = invocation.getArgument(0); + consumer.consume(callMock, callback); + return null; + }; + } + } + + /** + * Helper consumer interface to handle callbacks + * + * @param Type of the call requested + */ + public interface CallbackMockConsumer { + void consume(Call call, Callback callback) throws Exception; + } +} diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/test_utils/service/RetrofitTestService.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/test_utils/service/RetrofitTestService.java new file mode 100644 index 0000000..57195d8 --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/test_utils/service/RetrofitTestService.java @@ -0,0 +1,14 @@ +package ar.com.wolox.wolmo.networking.test_utils.service; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.POST; + +public interface RetrofitTestService { + + @GET("/api/get/") + Call retrofitGetMethodString(); + + @POST("/api/post/") + Call retrofitPostMethodString(); +} diff --git a/networking/src/test/java/ar/com/wolox/wolmo/networking/utils/CallUtilsTest.java b/networking/src/test/java/ar/com/wolox/wolmo/networking/utils/CallUtilsTest.java new file mode 100644 index 0000000..29b77e0 --- /dev/null +++ b/networking/src/test/java/ar/com/wolox/wolmo/networking/utils/CallUtilsTest.java @@ -0,0 +1,112 @@ +/* + * MIT License + *

+ * Copyright (c) 2017 Wolox S.A + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package ar.com.wolox.wolmo.networking.utils; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.android.internal.util.Predicate; + +import ar.com.wolox.wolmo.networking.test_utils.RetrofitCallMockBuilder; + +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +@SuppressWarnings("unchecked") +public class CallUtilsTest { + + private static final int TRIES = 5; + + private Semaphore mSemaphore; + private Callback mCallbackSpy; + + @Before + public void beforeTest() { + mSemaphore = new Semaphore(0); + + mCallbackSpy = spy(new Callback() { + @Override + public void onResponse(Call call, Response response) { + mSemaphore.release(); + } + + @Override + public void onFailure(Call call, Throwable t) { + mSemaphore.release(); + } + }); + } + + @Test + @SuppressWarnings("unchecked") + public void testPollWithDelayUntilTimeout() throws Exception { + Predicate> pollingConditionMock = mock(Predicate.class); + when(pollingConditionMock.apply(any(Response.class))).thenReturn(true); + + Call callMock = new RetrofitCallMockBuilder().build((call, callback) -> { + callback.onResponse(call, mock(Response.class)); + mSemaphore.release(); + }); + + CallUtils.pollWithDelay(TRIES, callMock, pollingConditionMock, mCallbackSpy, 100, + TimeUnit.MILLISECONDS); + mSemaphore.acquire(TRIES + 1); // Tries + 1 Extra permit on final callback + + verify(pollingConditionMock, times(TRIES)).apply(any(Response.class)); + verify(callMock, times(TRIES)).enqueue(any(Callback.class)); + verify(mCallbackSpy, times(1)).onFailure(eq(callMock), any(Exception.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void testPollWithDelaySuccess() throws Exception { + Predicate> pollingConditionMock = mock(Predicate.class); + when(pollingConditionMock.apply(any(Response.class))).thenReturn(true).thenReturn(true) + .thenReturn(false); // 3 Tries + + Call callMock = new RetrofitCallMockBuilder().build((call, callback) -> { + callback.onResponse(call, mock(Response.class)); + mSemaphore.release(); + }); + + CallUtils.pollWithDelay(TRIES, callMock, pollingConditionMock, mCallbackSpy, 100, + TimeUnit.MILLISECONDS); + mSemaphore.acquire(4); // 3 Tries + 1 Extra permit on final callback + + verify(pollingConditionMock, times(3)).apply(any(Response.class)); + verify(callMock, times(3)).enqueue(any(Callback.class)); + verify(mCallbackSpy, times(1)).onResponse(any(Call.class), any(Response.class)); + } +} \ No newline at end of file