Skip to content

Commit

Permalink
Merge pull request #1666 from OneSignal/user-model/unit-test-initial
Browse files Browse the repository at this point in the history
[User Model] Unit Test Initial
  • Loading branch information
brismithers authored Oct 17, 2022
2 parents 44dff8b + 1d612cf commit cff11ab
Show file tree
Hide file tree
Showing 20 changed files with 1,645 additions and 40 deletions.
3 changes: 2 additions & 1 deletion OneSignalSDK/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ buildscript {
huaweiAgconnectVersion = '1.6.2.300'
huaweiHMSPushVersion = '6.3.0.304'
huaweiHMSLocationVersion = '4.0.0.300'
kotlinVersion = '1.6.20'
kotlinVersion = '1.6.21'
kotestVersion = '5.5.0'
ktlintVersion = '11.0.0'
detektVersion = '1.21.0'
onesignalGradlePluginVersion = '[0.14.0, 0.99.99]'
Expand Down
38 changes: 35 additions & 3 deletions OneSignalSDK/onesignal/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ext {

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'org.jlleitschuh.gradle.ktlint'
apply plugin: 'io.gitlab.arturbosch.detekt'

Expand All @@ -14,6 +15,8 @@ android {
defaultConfig {
minSdkVersion buildVersions.minSdkVersion
consumerProguardFiles 'consumer-proguard-rules.pro'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testOptions.unitTests.includeAndroidResources = true
}

buildTypes {
Expand All @@ -28,14 +31,30 @@ android {
minifyEnabled false
}
}

testOptions {
unitTests.all {
maxParallelForks 1
maxHeapSize '2048m'
}
unitTests {
includeAndroidResources = true
}
}
// Forced downgrade to Java 8 so SDK is backwards compatible in consuming projects
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

tasks.withType(Test) {
testLogging {
exceptionFormat "full"
events "started", "skipped", "passed", "failed"
showStandardStreams false // Enable to have logging print
}
}

// api || implementation = compile and runtime

// KEEP: version ranges, these get used in the released POM file for maven central
Expand All @@ -47,8 +66,8 @@ dependencies {
compileOnly('com.amazon.device:amazon-appstore-sdk:[3.0.1, 3.0.99]')

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
implementation 'androidx.work:work-runtime-ktx:2.7.1'

// play-services-location:16.0.0 is the last version before going to AndroidX
Expand Down Expand Up @@ -114,6 +133,19 @@ dependencies {
prefer '2.7.1'
}
}

testImplementation("junit:junit:4.13.2")
testImplementation("io.kotest:kotest-runner-junit4:$kotestVersion")
testImplementation("io.kotest:kotest-runner-junit4-jvm:$kotestVersion")
testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
testImplementation("io.kotest:kotest-property:$kotestVersion")
testImplementation("org.robolectric:robolectric:4.8.1")
testImplementation("androidx.test:core-ktx:1.4.0")
testImplementation("androidx.test:core:1.4.0")
testImplementation("io.mockk:mockk:1.13.2")
testImplementation("org.json:json:20180813")
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
}

apply from: 'maven-push.gradle'

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import com.onesignal.core.internal.device.IDeviceService
import com.onesignal.core.internal.device.impl.DeviceService
import com.onesignal.core.internal.http.IHttpClient
import com.onesignal.core.internal.http.impl.HttpClient
import com.onesignal.core.internal.http.impl.HttpConnectionFactory
import com.onesignal.core.internal.http.impl.IHttpConnectionFactory
import com.onesignal.core.internal.influence.IInfluenceManager
import com.onesignal.core.internal.influence.impl.InfluenceManager
import com.onesignal.core.internal.language.ILanguageContext
Expand Down Expand Up @@ -74,6 +76,7 @@ internal object CoreModule {
builder.register<PreferencesService>()
.provides<IPreferencesService>()
.provides<IStartableService>()
builder.register<HttpConnectionFactory>().provides<IHttpConnectionFactory>()
builder.register<HttpClient>().provides<IHttpClient>()
builder.register<ApplicationService>().provides<IApplicationService>()
builder.register<DeviceService>().provides<IDeviceService>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ import com.onesignal.notification.internal.common.NotificationConstants
import java.lang.IllegalStateException
import java.util.ArrayList

internal class OSDatabase(
internal open class OSDatabase(
private val _outcomeTableProvider: OutcomeTableProvider,
context: Context?
) : SQLiteOpenHelper(context, DATABASE_NAME, null, dbVersion), IDatabase {
context: Context?,
version: Int = dbVersion
) : SQLiteOpenHelper(context, DATABASE_NAME, null, version), IDatabase {

/**
* Should be used in the event that we don't want to retry getting the a [SQLiteDatabase] instance
Expand Down Expand Up @@ -260,7 +261,7 @@ internal class OSDatabase(
Logging.debug("OneSignal Database onUpgrade from: $oldVersion to: $newVersion")

try {
internalOnUpgrade(db, oldVersion)
internalOnUpgrade(db, oldVersion, newVersion)
} catch (e: SQLiteException) {
// This could throw if rolling back then forward again.
// However this shouldn't happen as we clearing the database on onDowngrade
Expand All @@ -269,16 +270,16 @@ internal class OSDatabase(
}

@Synchronized
private fun internalOnUpgrade(db: SQLiteDatabase, oldVersion: Int) {
if (oldVersion < 2) upgradeToV2(db)
if (oldVersion < 3) upgradeToV3(db)
if (oldVersion < 4) upgradeToV4(db)
if (oldVersion < 5) upgradeToV5(db)
private fun internalOnUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion < 2 && newVersion >= 2) upgradeToV2(db)
if (oldVersion < 3 && newVersion >= 3) upgradeToV3(db)
if (oldVersion < 4 && newVersion >= 4) upgradeToV4(db)
if (oldVersion < 5 && newVersion >= 5) upgradeToV5(db)

// Specifically running only when going from 5 to 6+ is intentional
if (oldVersion == 5) upgradeFromV5ToV6(db)
if (oldVersion < 7) upgradeToV7(db)
if (oldVersion < 8) upgradeToV8(db)
if (oldVersion == 5 && newVersion >= 6) upgradeFromV5ToV6(db)
if (oldVersion < 7 && newVersion >= 7) upgradeToV7(db)
if (oldVersion < 8 && newVersion >= 8) upgradeToV8(db)
}

// Add collapse_id field and index
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ import com.onesignal.core.internal.preferences.IPreferencesService
import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
import com.onesignal.core.internal.preferences.PreferenceStores
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.withContext
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import org.json.JSONObject
import java.io.IOException
import java.net.ConnectException
import java.net.HttpURLConnection
import java.net.URL
import java.net.UnknownHostException
import java.util.Scanner
import javax.net.ssl.HttpsURLConnection

internal class HttpClient(
private val _connectionFactory: IHttpConnectionFactory,
private val _prefs: IPreferencesService,
private val _configModelStore: ConfigModelStore
) : IHttpClient {
Expand Down Expand Up @@ -80,7 +80,9 @@ internal class HttpClient(
timeout: Int,
cacheKey: String?
): HttpResponse {
return withContext(Dispatchers.IO) {
var retVal: HttpResponse? = null

val job = GlobalScope.launch(Dispatchers.IO) {
var httpResponse = -1
var con: HttpURLConnection? = null

Expand All @@ -89,8 +91,8 @@ internal class HttpClient(
}

try {
Logging.debug("HttpClient: Making request to: $BASE_URL$url")
con = newHttpURLConnection(url)
Logging.debug("HttpClient: Making request to: $url")
con = _connectionFactory.newHttpURLConnection(url)

// https://github.com/OneSignal/OneSignal-Android-SDK/issues/1465
// Android 4.4 and older devices fail to register to onesignal.com to due it's TLS1.2+ requirement
Expand Down Expand Up @@ -139,18 +141,18 @@ internal class HttpClient(

// Network request is made from getResponseCode()
httpResponse = con.responseCode
Logging.verbose("HttpClient: After con.getResponseCode to: " + BASE_URL + url)
Logging.verbose("HttpClient: After con.getResponseCode to: $url")

when (httpResponse) {
HttpURLConnection.HTTP_NOT_MODIFIED -> {
val cachedResponse = _prefs.getString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.PREFS_OS_HTTP_CACHE_PREFIX + cacheKey)
Logging.debug("HttpClient: " + (method ?: "GET") + " - Using Cached response due to 304: " + cachedResponse)

// TODO: SHOULD RETURN OK INSTEAD OF NOT_MODIFIED TO MAKE TRANSPARENT?
return@withContext HttpResponse(httpResponse, cachedResponse)
retVal = HttpResponse(httpResponse, cachedResponse)
}
HttpURLConnection.HTTP_ACCEPTED, HttpURLConnection.HTTP_OK -> {
Logging.debug("HttpClient: Successfully finished request to: $BASE_URL$url")
Logging.debug("HttpClient: Successfully finished request to: $url")

val inputStream = con.inputStream
val scanner = Scanner(inputStream, "UTF-8")
Expand All @@ -168,10 +170,10 @@ internal class HttpClient(
}
}

return@withContext HttpResponse(httpResponse, json)
retVal = HttpResponse(httpResponse, json)
}
else -> {
Logging.debug("HttpClient: Failed request to: $BASE_URL$url")
Logging.debug("HttpClient: Failed request to: $url")

var inputStream = con.errorStream
if (inputStream == null) {
Expand All @@ -189,7 +191,7 @@ internal class HttpClient(
Logging.warn("HttpClient: $method HTTP Code: $httpResponse No response body!")
}

return@withContext HttpResponse(httpResponse, jsonResponse)
retVal = HttpResponse(httpResponse, jsonResponse)
}
}
} catch (t: Throwable) {
Expand All @@ -199,16 +201,14 @@ internal class HttpClient(
Logging.warn("HttpClient: $method Error thrown from network stack. ", t)
}

return@withContext HttpResponse(httpResponse, null, t)
retVal = HttpResponse(httpResponse, null, t)
} finally {
con?.disconnect()
}
}
}

@Throws(IOException::class)
private fun newHttpURLConnection(url: String): HttpURLConnection {
return URL(BASE_URL + url).openConnection() as HttpURLConnection
job.join()
return retVal!!
}

private fun getThreadTimeout(timeout: Int): Int {
Expand All @@ -218,7 +218,6 @@ internal class HttpClient(
companion object {
private const val OS_API_VERSION = "1"
private const val OS_ACCEPT_HEADER = "application/vnd.onesignal.v$OS_API_VERSION+json"
private const val BASE_URL = "https://api.onesignal.com/"
private const val GET_TIMEOUT = 60000
private const val TIMEOUT = 120000
private const val THREAD_ID = 10000
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.onesignal.core.internal.http.impl

import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL

internal class HttpConnectionFactory : IHttpConnectionFactory {
@Throws(IOException::class)
override fun newHttpURLConnection(url: String): HttpURLConnection {
return URL(BASE_URL + url).openConnection() as HttpURLConnection
}

companion object {
private const val BASE_URL = "https://api.onesignal.com/"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.onesignal.core.internal.http.impl

import java.io.IOException
import java.net.HttpURLConnection

internal interface IHttpConnectionFactory {
@Throws(IOException::class)
fun newHttpURLConnection(url: String): HttpURLConnection
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import com.onesignal.core.internal.modeling.SimpleModelStore
import com.onesignal.core.internal.modeling.SingletonModelStore
import com.onesignal.core.internal.preferences.IPreferencesService

internal class ConfigModelStore(prefs: IPreferencesService) : SingletonModelStore<ConfigModel>(SimpleModelStore({ ConfigModel() }, "config", prefs))
internal class SessionModelStore(prefs: IPreferencesService) : SingletonModelStore<SessionModel>(SimpleModelStore({ SessionModel() }, "session", prefs))
internal class IdentityModelStore(prefs: IPreferencesService) : SingletonModelStore<IdentityModel>(SimpleModelStore({ IdentityModel() }, "identity", prefs))
internal class PropertiesModelStore(prefs: IPreferencesService) : SingletonModelStore<PropertiesModel>(SimpleModelStore({ PropertiesModel() }, "properties", prefs))
internal class SubscriptionModelStore(prefs: IPreferencesService) : SimpleModelStore<SubscriptionModel>({ SubscriptionModel() }, "subscriptions", prefs)
internal class TriggerModelStore : SimpleModelStore<TriggerModel>({ TriggerModel() })
internal open class ConfigModelStore(prefs: IPreferencesService) : SingletonModelStore<ConfigModel>(SimpleModelStore({ ConfigModel() }, "config", prefs))
internal open class SessionModelStore(prefs: IPreferencesService) : SingletonModelStore<SessionModel>(SimpleModelStore({ SessionModel() }, "session", prefs))
internal open class IdentityModelStore(prefs: IPreferencesService) : SingletonModelStore<IdentityModel>(SimpleModelStore({ IdentityModel() }, "identity", prefs))
internal open class PropertiesModelStore(prefs: IPreferencesService) : SingletonModelStore<PropertiesModel>(SimpleModelStore({ PropertiesModel() }, "properties", prefs))
internal open class SubscriptionModelStore(prefs: IPreferencesService) : SimpleModelStore<SubscriptionModel>({ SubscriptionModel() }, "subscriptions", prefs)
internal open class TriggerModelStore : SimpleModelStore<TriggerModel>({ TriggerModel() })
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Code taken from https://github.com/kotest/kotest-extensions-robolectric with no changes.
*
* LICENSE: https://github.com/kotest/kotest-extensions-robolectric/blob/master/LICENSE
*/
package com.onesignal.core.tests.extensions

import org.junit.runners.model.FrameworkMethod
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.internal.bytecode.InstrumentationConfiguration
import org.robolectric.pluginapi.config.ConfigurationStrategy
import org.robolectric.plugins.ConfigConfigurer
import java.lang.reflect.Method

internal class ContainedRobolectricRunner(
private val config: Config?
) : RobolectricTestRunner(PlaceholderTest::class.java, injector) {
private val placeHolderMethod: FrameworkMethod = children[0]
val sdkEnvironment = getSandbox(placeHolderMethod).also {
configureSandbox(it, placeHolderMethod)
}
private val bootStrapMethod = sdkEnvironment.bootstrappedClass<Any>(testClass.javaClass)
.getMethod(PlaceholderTest::bootStrapMethod.name)

fun containedBefore() {
super.beforeTest(sdkEnvironment, placeHolderMethod, bootStrapMethod)
}

fun containedAfter() {
super.afterTest(placeHolderMethod, bootStrapMethod)
super.finallyAfterTest(placeHolderMethod)
}

override fun createClassLoaderConfig(method: FrameworkMethod?): InstrumentationConfiguration {
return InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
.doNotAcquirePackage("io.kotest")
.build()
}

override fun getConfig(method: Method?): Config {
val defaultConfiguration = injector.getInstance(ConfigurationStrategy::class.java)
.getConfig(testClass.javaClass, method)

if (config != null) {
val configConfigurer = injector.getInstance(ConfigConfigurer::class.java)
return configConfigurer.merge(defaultConfiguration[Config::class.java], config)
}

return super.getConfig(method)
}

class PlaceholderTest {
@org.junit.Test
fun testPlaceholder() {
}

fun bootStrapMethod() {
}
}

companion object {
private val injector = defaultInjector().build()
}
}
Loading

0 comments on commit cff11ab

Please sign in to comment.