Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,57 +1,75 @@
package com.onesignal.debug.internal.crash

import android.content.Context
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
import com.onesignal.debug.internal.logging.otel.android.AndroidOtelLogger
import com.onesignal.otel.IOtelCrashHandler
import com.onesignal.otel.IOtelLogger
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.types.shouldBeInstanceOf
import io.mockk.mockk
import org.robolectric.annotation.Config

@RobolectricTest
@Config(sdk = [Build.VERSION_CODES.O])
class OneSignalCrashHandlerFactoryTest : FunSpec({
var appContext: Context? = null
var logger: AndroidOtelLogger? = null
lateinit var appContext: Context
lateinit var logger: AndroidOtelLogger
// Save original handler to restore after tests
val originalHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler()

beforeAny {
if (appContext == null) {
appContext = ApplicationProvider.getApplicationContext()
logger = AndroidOtelLogger()
}
appContext = ApplicationProvider.getApplicationContext()
logger = AndroidOtelLogger()
}

afterEach {
// Restore original uncaught exception handler after each test
Thread.setDefaultUncaughtExceptionHandler(originalHandler)
}

test("createCrashHandler should return IOtelCrashHandler") {
val handler = OneSignalCrashHandlerFactory.createCrashHandler(
appContext!!,
logger!!
)
val handler = OneSignalCrashHandlerFactory.createCrashHandler(appContext, logger)

handler.shouldBeInstanceOf<IOtelCrashHandler>()
}

test("createCrashHandler should create Otel handler for SDK 26+") {
// Note: SDK version check is handled at runtime by the factory
// This test verifies the handler can be created and initialized
val handler = OneSignalCrashHandlerFactory.createCrashHandler(
appContext!!,
logger!!
)
test("createCrashHandler should create handler that can be initialized") {
val handler = OneSignalCrashHandlerFactory.createCrashHandler(appContext, logger)

handler shouldNotBe null
// Should be able to initialize
handler.initialize()
}

test("createCrashHandler should return no-op handler for SDK < 26") {
// Note: SDK version check is handled at runtime by the factory
// This test verifies the handler can be created and initialized
val handler = OneSignalCrashHandlerFactory.createCrashHandler(
appContext!!,
logger!!
)
test("createCrashHandler should accept mock logger") {
val mockLogger = mockk<IOtelLogger>(relaxed = true)

val handler = OneSignalCrashHandlerFactory.createCrashHandler(appContext, mockLogger)

handler shouldNotBe null
handler.shouldBeInstanceOf<IOtelCrashHandler>()
}

test("handler should be idempotent when initialized multiple times") {
val handler = OneSignalCrashHandlerFactory.createCrashHandler(appContext, logger)

handler.initialize()
handler.initialize() // Should not throw

handler shouldNotBe null
handler.initialize() // Should not crash
}

test("createCrashHandler should work with different contexts") {
val context1: Context = ApplicationProvider.getApplicationContext()
val context2: Context = ApplicationProvider.getApplicationContext()

val handler1 = OneSignalCrashHandlerFactory.createCrashHandler(context1, logger)
val handler2 = OneSignalCrashHandlerFactory.createCrashHandler(context2, logger)

handler1 shouldNotBe null
handler2 shouldNotBe null
}
})
Original file line number Diff line number Diff line change
@@ -1,100 +1,104 @@
package com.onesignal.debug.internal.crash

import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
import com.onesignal.core.internal.application.IApplicationService
import com.onesignal.core.internal.config.ConfigModel
import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
import com.onesignal.core.internal.preferences.PreferenceStores
import com.onesignal.core.internal.startup.IStartableService
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.types.shouldBeInstanceOf
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import org.json.JSONArray
import org.json.JSONObject
import org.robolectric.annotation.Config
import com.onesignal.core.internal.config.CONFIG_NAME_SPACE as configNameSpace

@RobolectricTest
@Config(sdk = [Build.VERSION_CODES.O])
class OneSignalCrashUploaderWrapperTest : FunSpec({

var appContext: Context? = null
lateinit var appContext: Context
lateinit var sharedPreferences: SharedPreferences

beforeAny {
if (appContext == null) {
appContext = ApplicationProvider.getApplicationContext()
}
appContext = ApplicationProvider.getApplicationContext()
sharedPreferences = appContext.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
}

afterEach {
sharedPreferences.edit().clear().commit()
}

test("should implement IStartableService") {
// Given
test("should implement IStartableService interface") {
val mockApplicationService = mockk<IApplicationService>(relaxed = true)
every { mockApplicationService.appContext } returns appContext!!
every { mockApplicationService.appContext } returns appContext

// When
val wrapper = OneSignalCrashUploaderWrapper(mockApplicationService)

// Then
wrapper.shouldBeInstanceOf<IStartableService>()
}

test("should create uploader lazily when start is called") {
// Given
test("start should complete without error when remote logging is disabled") {
// Configure remote logging as disabled (NONE)
val remoteLoggingParams = JSONObject().put("logLevel", "NONE")
val configModel = JSONObject().put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
sharedPreferences.edit()
.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, JSONArray().put(configModel).toString())
.commit()

val mockApplicationService = mockk<IApplicationService>(relaxed = true)
every { mockApplicationService.appContext } returns appContext!!
every { mockApplicationService.appContext } returns appContext

val wrapper = OneSignalCrashUploaderWrapper(mockApplicationService)

// When
runBlocking {
wrapper.start()
}

// Then - should not throw, uploader should be created
// We can't directly verify the uploader was created, but if start() completes without error,
// it means the uploader was created and started successfully
// Should return early without error when remote logging is disabled
runBlocking { wrapper.start() }
}

test("should call uploader.start() when start() is called") {
// Given
test("start should complete without error when no crash reports exist") {
// Configure remote logging as enabled
val remoteLoggingParams = JSONObject().put("logLevel", "ERROR")
val configModel = JSONObject().put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
sharedPreferences.edit()
.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, JSONArray().put(configModel).toString())
.commit()

val mockApplicationService = mockk<IApplicationService>(relaxed = true)
every { mockApplicationService.appContext } returns appContext!!
every { mockApplicationService.appContext } returns appContext

val wrapper = OneSignalCrashUploaderWrapper(mockApplicationService)

// When
runBlocking {
wrapper.start()
}

// Then - start() should complete without throwing
// The actual uploader.start() is called internally via runBlocking
// If it throws, this test would fail
// Should complete without error even when no crash reports exist
runBlocking { wrapper.start() }
}

test("should handle errors gracefully when uploader fails") {
// Given
test("start can be called multiple times safely") {
val mockApplicationService = mockk<IApplicationService>(relaxed = true)
every { mockApplicationService.appContext } returns appContext!!
every { mockApplicationService.appContext } returns appContext

val wrapper = OneSignalCrashUploaderWrapper(mockApplicationService)

// When/Then - start() should handle errors gracefully
// If remote logging is disabled, it should return early without error
// If there are no crash reports, it should complete without error
// Multiple calls should not throw
runBlocking {
// This should not throw even if there are no crash reports or if remote logging is disabled
wrapper.start()
wrapper.start()
}
}

test("should create wrapper with applicationService dependency") {
// Given
test("wrapper should be non-null after creation") {
val mockApplicationService = mockk<IApplicationService>(relaxed = true)
every { mockApplicationService.appContext } returns appContext!!
every { mockApplicationService.appContext } returns appContext

// When
val wrapper = OneSignalCrashUploaderWrapper(mockApplicationService)

// Then
wrapper shouldNotBe null
wrapper.shouldBeInstanceOf<OneSignalCrashUploaderWrapper>()
}
})
Loading
Loading