diff --git a/gradle.properties b/gradle.properties index 84aa839..1d04510 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m org.gradle.jvmargs=-Xmx3072m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 - +org.gradle.caching=true # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/sampleApp/build.gradle b/sampleApp/build.gradle index 9c8d649..3635f36 100644 --- a/sampleApp/build.gradle +++ b/sampleApp/build.gradle @@ -23,10 +23,11 @@ android { defaultConfig { applicationId "com.zeyad.usecase.accesslayer" - minSdkVersion 17 + minSdkVersion 21 targetSdkVersion 25 versionCode 1 versionName "1.0" + testInstrumentationRunner "com.zeyad.usecases.app.UseCasesTestRunner" } signingConfigs { @@ -47,6 +48,7 @@ android { buildTypes { debug { + ext.alwaysUpdateBuildId = false minifyEnabled false shrinkResources false debuggable true @@ -92,6 +94,17 @@ android { exclude 'META-INF/NOTICE' exclude 'META-INF/rxjava.properties' } + + if (project.hasProperty('devBuild')) { + splits.abi.enable = false + splits.density.enable = false + aaptOptions.cruncherEnabled = false + } + sourceSets { + main { + assets.srcDirs = ['src/main/assets', 'src/androidTest/java/com/zeyad/usecases/app/assets/'] + } + } } ext { @@ -155,6 +168,11 @@ dependencies { androidTestCompile 'org.mockito:mockito-core:1.10.19' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' + androidTestCompile('com.jakewharton.espresso:okhttp3-idling-resource:1.0.0') { + exclude group: 'com.android.support' + } + androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.8.0' + androidTestCompile 'com.github.andrzejchm.RESTMock:android:0.1.4' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' diff --git a/sampleApp/src/androidTest/java/com/zeyad/usecases/app/MockWebServerRule.java b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/MockWebServerRule.java new file mode 100644 index 0000000..528038f --- /dev/null +++ b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/MockWebServerRule.java @@ -0,0 +1,30 @@ +package com.zeyad.usecases.app; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import okhttp3.internal.tls.SslClient; +import okhttp3.mockwebserver.MockWebServer; + +/** + * @author by ZIaDo on 6/15/17. + */ + +public class MockWebServerRule implements TestRule { + public final MockWebServer server = new MockWebServer(); + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + server.useHttps(SslClient.localhost().socketFactory, false); + server.start(); + base.evaluate(); + server.shutdown(); + } + }; + } + +} diff --git a/sampleApp/src/androidTest/java/com/zeyad/usecases/app/OkHttpIdlingResourceRule.java b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/OkHttpIdlingResourceRule.java new file mode 100644 index 0000000..6163a2e --- /dev/null +++ b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/OkHttpIdlingResourceRule.java @@ -0,0 +1,32 @@ +package com.zeyad.usecases.app; + +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.Espresso; +import android.support.test.espresso.IdlingResource; + +import com.jakewharton.espresso.OkHttp3IdlingResource; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * @author by ZIaDo on 6/15/17. + */ +public class OkHttpIdlingResourceRule implements TestRule { + @Override + public Statement apply(final Statement base, Description description) { + TestGenericApplication app = (TestGenericApplication) + InstrumentationRegistry.getTargetContext().getApplicationContext(); + return new Statement() { + @Override + public void evaluate() throws Throwable { + IdlingResource idlingResource = OkHttp3IdlingResource.create( + "okhttp", app.getOkHttpBuilder().build()); + Espresso.registerIdlingResources(idlingResource); + base.evaluate(); + Espresso.unregisterIdlingResources(idlingResource); + } + }; + } +} \ No newline at end of file diff --git a/sampleApp/src/androidTest/java/com/zeyad/usecases/app/TestGenericApplication.java b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/TestGenericApplication.java new file mode 100644 index 0000000..118fe7b --- /dev/null +++ b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/TestGenericApplication.java @@ -0,0 +1,31 @@ +package com.zeyad.usecases.app; + +import android.support.annotation.NonNull; + +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; + +import io.appflate.restmock.RESTMockServer; +import okhttp3.internal.tls.SslClient; + +/** + * @author by ZIaDo on 6/15/17. + */ + +public class TestGenericApplication extends GenericApplication { + @NonNull + @Override + public String getApiBaseUrl() { + return RESTMockServer.getUrl(); + } + + @Override + X509TrustManager getX509TrustManager() { + return SslClient.localhost().trustManager; + } + + @Override + SSLSocketFactory getSSlSocketFactory() { + return SslClient.localhost().socketFactory; + } +} diff --git a/sampleApp/src/androidTest/java/com/zeyad/usecases/app/UseCasesTestRunner.java b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/UseCasesTestRunner.java new file mode 100644 index 0000000..2aed230 --- /dev/null +++ b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/UseCasesTestRunner.java @@ -0,0 +1,17 @@ +package com.zeyad.usecases.app; + +import android.app.Application; +import android.content.Context; + +import io.appflate.restmock.android.RESTMockTestRunner; + +/** + * @author by ZIaDo on 6/15/17. + */ +public class UseCasesTestRunner extends RESTMockTestRunner { + @Override + public Application newApplication(ClassLoader cl, String className, Context context) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return super.newApplication(cl, TestGenericApplication.class.getName(), context); + } +} diff --git a/sampleApp/src/androidTest/java/com/zeyad/usecases/app/assets/users/userList.json b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/assets/users/userList.json new file mode 100644 index 0000000..e40ec7f --- /dev/null +++ b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/assets/users/userList.json @@ -0,0 +1,4 @@ +{ + "login": "octocat", + "followers": 1500 +} \ No newline at end of file diff --git a/sampleApp/src/androidTest/java/com/zeyad/usecases/app/screens/user/list/UserListActivityTest.java b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/screens/user/list/UserListActivityTest.java new file mode 100644 index 0000000..12aca2b --- /dev/null +++ b/sampleApp/src/androidTest/java/com/zeyad/usecases/app/screens/user/list/UserListActivityTest.java @@ -0,0 +1,86 @@ +package com.zeyad.usecases.app.screens.user.list; + +import android.support.test.rule.ActivityTestRule; + +import com.zeyad.usecases.app.OkHttpIdlingResourceRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import io.appflate.restmock.RESTMockServer; +import io.appflate.restmock.RequestsVerifier; +import okhttp3.mockwebserver.MockResponse; + +import static io.appflate.restmock.utils.RequestMatchers.pathEndsWith; +import static io.appflate.restmock.utils.RequestMatchers.pathStartsWith; + +/** + * @author by ZIaDo on 6/15/17. + */ +public class UserListActivityTest { + private static final String USER_LIST_BODY = "{ \"login\" : \"octocat\", \"followers\" : 1500 }"; + private final String urlPart = "users?since=0"; + @Rule + public ActivityTestRule activityRule + = new ActivityTestRule<>(UserListActivity.class, true, false); + + @Rule + public OkHttpIdlingResourceRule okHttpIdlingResourceRule = new OkHttpIdlingResourceRule(); + +// @Rule +// public MockWebServerRule mockWebServerRule = new MockWebServerRule(); + + @Before + public void before() { + RESTMockServer.reset(); + } + + @Test + public void followers() throws IOException, InterruptedException { + RESTMockServer.whenGET(pathEndsWith(urlPart)) + .thenReturnFile("users/userList.json"); + + activityRule.launchActivity(null); + +// onView(withId(R.id.followers)) +// .check(matches(withText("1500"))); + + RequestsVerifier.verifyGET(pathStartsWith("/" + urlPart)).invoked(); + } + + @Test + public void status404() throws IOException { + RESTMockServer.whenGET(pathEndsWith(urlPart)) + .thenReturnEmpty(404); + + activityRule.launchActivity(null); + +// onView(withId(R.id.followers)) +// .check(matches(withText("404"))); + } + + @Test + public void malformedJson() throws IOException { + RESTMockServer.whenGET(pathEndsWith(urlPart)).thenReturn(new MockResponse().setBody("Jason")); + + activityRule.launchActivity(null); + +// onView(withId(R.id.followers)) +// .check(matches(withText("IOException"))); + } + + @Test + public void timeout() throws IOException { + RESTMockServer.whenGET(pathEndsWith(urlPart)).thenReturn( + new MockResponse().setBody(USER_LIST_BODY).throttleBody(1, 1, TimeUnit.SECONDS)); + + activityRule.launchActivity(null); + +// onView(withId(R.id.followers)) +// .check(matches(withText("SocketTimeoutException"))); + } +} \ No newline at end of file diff --git a/sampleApp/src/main/AndroidManifest.xml b/sampleApp/src/main/AndroidManifest.xml index ca4f92f..7195135 100644 --- a/sampleApp/src/main/AndroidManifest.xml +++ b/sampleApp/src/main/AndroidManifest.xml @@ -7,10 +7,12 @@ + diff --git a/sampleApp/src/main/java/com/zeyad/usecases/app/GenericApplication.java b/sampleApp/src/main/java/com/zeyad/usecases/app/GenericApplication.java index f2e20ae..b955b11 100644 --- a/sampleApp/src/main/java/com/zeyad/usecases/app/GenericApplication.java +++ b/sampleApp/src/main/java/com/zeyad/usecases/app/GenericApplication.java @@ -8,6 +8,7 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.StrictMode; +import android.support.annotation.NonNull; import android.util.Base64; import android.util.Log; @@ -17,14 +18,21 @@ import com.zeyad.usecases.api.DataServiceFactory; import java.security.MessageDigest; +import java.util.Arrays; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; + import io.flowup.FlowUp; import io.reactivex.Completable; import io.reactivex.schedulers.Schedulers; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.rx.RealmObservableFactory; +import okhttp3.CertificatePinner; +import okhttp3.ConnectionSpec; +import okhttp3.OkHttpClient; import static com.zeyad.usecases.app.utils.Constants.URLS.API_BASE_URL; @@ -105,12 +113,36 @@ public void onCreate() { }, Throwable::printStackTrace); initializeRealm(); DataServiceFactory.init(new DataServiceConfig.Builder(this) - .baseUrl(API_BASE_URL) + .baseUrl(getApiBaseUrl()) + .okHttpBuilder(getOkHttpBuilder()) .withCache(3, TimeUnit.MINUTES) .withRealm() .build()); } + @NonNull + OkHttpClient.Builder getOkHttpBuilder() { + OkHttpClient.Builder builder = new OkHttpClient.Builder() + .certificatePinner(new CertificatePinner.Builder() + .add(API_BASE_URL, + "sha256/6wJsqVDF8K19zxfLxV5DGRneLyzso9adVdUN/exDacw") + .add(API_BASE_URL, + "sha256/k2v657xBsOVe1PQRwOsHsw3bsGT2VzIqz5K+59sNQws=") + .add(API_BASE_URL, + "sha256/WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=") + .build()) + .connectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, + ConnectionSpec.COMPATIBLE_TLS)); + if (getSSlSocketFactory() != null && getX509TrustManager() != null) + builder.sslSocketFactory(getSSlSocketFactory(), getX509TrustManager()); + return builder; + } + + @NonNull + String getApiBaseUrl() { + return API_BASE_URL; + } + private void initializeStrictMode() { if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy( @@ -143,4 +175,12 @@ && verifyInstaller(context) && checkEmulator() && checkDebuggable(context); } + + X509TrustManager getX509TrustManager() { + return null; + } + + SSLSocketFactory getSSlSocketFactory() { + return null; + } } diff --git a/usecases/build.gradle b/usecases/build.gradle index db0587d..f008f44 100644 --- a/usecases/build.gradle +++ b/usecases/build.gradle @@ -103,6 +103,12 @@ android { exclude 'META-INF/NOTICE' exclude 'META-INF/rxjava.properties' } + + if (project.hasProperty('devBuild')) { + splits.abi.enable = false + splits.density.enable = false + aaptOptions.cruncherEnabled = false + } } ext { @@ -149,6 +155,14 @@ dependencies { testCompile "org.powermock:powermock-module-junit4-rule:$powerMock" testCompile "org.powermock:powermock-api-mockito:$powerMock" testCompile "org.powermock:powermock-classloading-xstream:$powerMock" + + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') { + exclude group: 'com.android.support' + } + androidTestCompile('com.jakewharton.espresso:okhttp3-idling-resource:1.0.0') { + exclude group: 'com.android.support' + } + androidTestCompile "com.squareup.okhttp3:mockwebserver:$okhttpVersion" } task createPom { diff --git a/sampleApp/src/androidTest/java/com/zeyad/usecases/app/ApplicationTest.java b/usecases/src/androidTest/java/com/zeyad/usecases/app/ApplicationTest.java similarity index 100% rename from sampleApp/src/androidTest/java/com/zeyad/usecases/app/ApplicationTest.java rename to usecases/src/androidTest/java/com/zeyad/usecases/app/ApplicationTest.java diff --git a/usecases/src/main/java/com/zeyad/usecases/Config.java b/usecases/src/main/java/com/zeyad/usecases/Config.java index f278263..080f694 100644 --- a/usecases/src/main/java/com/zeyad/usecases/Config.java +++ b/usecases/src/main/java/com/zeyad/usecases/Config.java @@ -139,10 +139,6 @@ public static void setCloudStore(CloudStore cloudStore) { Config.cloudStore = cloudStore; } - public static boolean isWithSQLite() { - return withSQLite; - } - public static void setWithSQLite(boolean withSQLite) { Config.withSQLite = withSQLite; }