From 5ddbd6a18e35e4fb6ba545b766d0d7659ae26b2c Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Fri, 5 Dec 2025 16:56:23 -0500 Subject: [PATCH 1/7] exposing suspend accessors --- .../sdktest/application/MainApplicationKT.kt | 1 - .../src/main/java/com/onesignal/OneSignal.kt | 171 +++++- .../com/onesignal/OneSignalAccessorsTests.kt | 561 ++++++++++++++++++ 3 files changed, 726 insertions(+), 7 deletions(-) create mode 100644 OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalAccessorsTests.kt diff --git a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplicationKT.kt b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplicationKT.kt index 123e747499..c9c61be6a5 100644 --- a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplicationKT.kt +++ b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplicationKT.kt @@ -39,7 +39,6 @@ import com.onesignal.user.state.IUserStateObserver import com.onesignal.user.state.UserChangedState import com.onesignal.user.state.UserState import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt index ff15ce0f1d..708bbe08f8 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt @@ -152,6 +152,140 @@ object OneSignal { return oneSignal.initWithContextSuspend(context, appId) } + /** + * Get the user manager without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [User] property accessor. + * + * @return The user manager for accessing user-scoped management. + */ + @JvmStatic + suspend fun getUserSuspend(): IUserManager { + return oneSignal.getUser() + } + + /** + * Get the session manager without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [Session] property accessor. + * + * @return The session manager for accessing session-scoped management. + */ + @JvmStatic + suspend fun getSessionSuspend(): ISessionManager { + return oneSignal.getSession() + } + + /** + * Get the notifications manager without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [Notifications] property accessor. + * + * @return The notification manager for accessing device-scoped notification management. + */ + @JvmStatic + suspend fun getNotificationsSuspend(): INotificationsManager { + return oneSignal.getNotifications() + } + + /** + * Get the location manager without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [Location] property accessor. + * + * @return The location manager for accessing device-scoped location management. + */ + @JvmStatic + suspend fun getLocationSuspend(): ILocationManager { + return oneSignal.getLocation() + } + + /** + * Get the in-app messages manager without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [InAppMessages] property accessor. + * + * @return The in-app messaging manager for accessing device-scoped IAM management. + */ + @JvmStatic + suspend fun getInAppMessagesSuspend(): IInAppMessagesManager { + return oneSignal.getInAppMessages() + } + + /** + * Get the consent required flag in a thread-safe manner without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [consentRequired] property accessor. + * + * @return Whether a user must consent to privacy prior to their user data being sent to OneSignal. + */ + @JvmStatic + suspend fun getConsentRequiredSuspend(): Boolean { + return oneSignal.getConsentRequired() + } + + /** + * Set the consent required flag in a thread-safe manner without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [consentRequired] property setter. + * + * @param required Whether a user must consent to privacy prior to their user data being sent to OneSignal. + * Should be set to `true` prior to the invocation of [initWithContext] to ensure compliance. + */ + @JvmStatic + suspend fun setConsentRequiredSuspend(required: Boolean) { + oneSignal.setConsentRequired(required) + } + + /** + * Get the consent given flag in a thread-safe manner without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [consentGiven] property accessor. + * + * @return Whether privacy consent has been granted. This field is only relevant when + * the application has opted into data privacy protections. See [consentRequired]. + */ + @JvmStatic + suspend fun getConsentGivenSuspend(): Boolean { + return oneSignal.getConsentGiven() + } + + /** + * Set the consent given flag in a thread-safe manner without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [consentGiven] property setter. + * + * @param value Whether privacy consent has been granted. + */ + @JvmStatic + suspend fun setConsentGivenSuspend(value: Boolean) { + oneSignal.setConsentGiven(value) + } + + /** + * Get the disable GMS missing prompt flag in a thread-safe manner without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [disableGMSMissingPrompt] property accessor. + * + * @return Whether to disable the "GMS is missing" prompt to the user. + */ + @JvmStatic + suspend fun getDisableGMSMissingPromptSuspend(): Boolean { + return oneSignal.getDisableGMSMissingPrompt() + } + + /** + * Set the disable GMS missing prompt flag in a thread-safe manner without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [disableGMSMissingPrompt] property setter. + * + * @param value Whether to disable the "GMS is missing" prompt to the user. + */ + @JvmStatic + suspend fun setDisableGMSMissingPromptSuspend(value: Boolean) { + oneSignal.setDisableGMSMissingPrompt(value) + } + /** * Login to OneSignal under the user identified by the [externalId] provided. The act of * logging a user into the OneSignal SDK will switch the [User] context to that specific user. @@ -226,24 +360,49 @@ object OneSignal { } /** - * Login a user with external ID and optional JWT token (suspend version). + * Login a user with external ID and optional JWT token without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [login] method. + * + * The act of logging a user into the OneSignal SDK will switch the [User] context to that specific user. * - * @param externalId External user ID for login - * @param jwtBearerToken Optional JWT token for authentication + * * If the [externalId] exists the user will be retrieved and the context set from that + * user information. If operations have already been performed under a guest user, they + * *will not* be applied to the now logged in user (they will be lost). + * * If the [externalId] does not exist the user will be created and the context set from + * the current local state. If operations have already been performed under a guest user + * those operations *will* be applied to the newly created user. + * + * *Push Notifications and In App Messaging* + * Logging in a new user will automatically transfer push notification and in app messaging + * subscriptions from the current user (if there is one) to the newly logged in user. This is + * because both Push and IAM are owned by the device. + * + * @param externalId The external ID of the user that is to be logged in. + * @param jwtBearerToken The optional JWT bearer token generated by your backend to establish + * trust for the login operation. Required when identity verification has been enabled. See + * [Identity Verification | OneSignal](https://documentation.onesignal.com/docs/identity-verification) */ @JvmStatic suspend fun loginSuspend( externalId: String, jwtBearerToken: String? = null, ) { - oneSignal.login(externalId, jwtBearerToken) + oneSignal.loginSuspend(externalId, jwtBearerToken) } /** - * Logout the current user (suspend version). + * Logout the current user without blocking the calling thread. + * Suspends until the SDK is initialized if initialization is in progress. + * This is the suspend-safe version of the [logout] method. + * + * The [User] property now references a new device-scoped user. A device-scoped user has no + * user identity that can later be retrieved, except through this device as long as the app + * remains installed and the app data is not cleared. */ + @JvmStatic suspend fun logoutSuspend() { - oneSignal.logout() + oneSignal.logoutSuspend() } /** diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalAccessorsTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalAccessorsTests.kt new file mode 100644 index 0000000000..a5d39f0049 --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalAccessorsTests.kt @@ -0,0 +1,561 @@ +package com.onesignal + +import android.content.Context +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest +import com.onesignal.debug.LogLevel +import com.onesignal.debug.internal.logging.Logging +import com.onesignal.inAppMessages.IInAppMessagesManager +import com.onesignal.location.ILocationManager +import com.onesignal.notifications.INotificationsManager +import com.onesignal.session.ISessionManager +import com.onesignal.user.IUserManager +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import kotlinx.coroutines.runBlocking + +@RobolectricTest +class OneSignalAccessorsTests : FunSpec({ + + beforeAny { + Logging.logLevel = LogLevel.NONE + } + + afterAny { + val context = getApplicationContext() + val prefs = context.getSharedPreferences("OneSignal", Context.MODE_PRIVATE) + prefs.edit().clear().commit() + val otherPrefs = context.getSharedPreferences("com.onesignal", Context.MODE_PRIVATE) + otherPrefs.edit().clear().commit() + Thread.sleep(50) + } + + context("suspend accessor methods exist and return correct types") { + test("getUserSuspend returns IUserManager after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val user = OneSignal.getUserSuspend() + + // Then + user.shouldBeInstanceOf() + } + } + + test("getSessionSuspend returns ISessionManager after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val session = OneSignal.getSessionSuspend() + + // Then + session.shouldBeInstanceOf() + } + } + + test("getNotificationsSuspend returns INotificationsManager after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val notifications = OneSignal.getNotificationsSuspend() + + // Then + notifications.shouldBeInstanceOf() + } + } + + test("getLocationSuspend returns ILocationManager after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val location = OneSignal.getLocationSuspend() + + // Then + location.shouldBeInstanceOf() + } + } + + test("getInAppMessagesSuspend returns IInAppMessagesManager after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val inAppMessages = OneSignal.getInAppMessagesSuspend() + + // Then + inAppMessages.shouldBeInstanceOf() + } + } + } + + context("suspend configuration property accessors") { + test("getConsentRequiredSuspend and setConsentRequiredSuspend work correctly") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When - set to true + OneSignal.setConsentRequiredSuspend(true) + + // Then + OneSignal.getConsentRequiredSuspend() shouldBe true + + // When - set to false + OneSignal.setConsentRequiredSuspend(false) + + // Then + OneSignal.getConsentRequiredSuspend() shouldBe false + } + } + + test("getConsentGivenSuspend and setConsentGivenSuspend work correctly") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When - set to true + OneSignal.setConsentGivenSuspend(true) + + // Then + OneSignal.getConsentGivenSuspend() shouldBe true + + // When - set to false + OneSignal.setConsentGivenSuspend(false) + + // Then + OneSignal.getConsentGivenSuspend() shouldBe false + } + } + + test("getDisableGMSMissingPromptSuspend and setDisableGMSMissingPromptSuspend work correctly") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When - set to true + OneSignal.setDisableGMSMissingPromptSuspend(true) + + // Then + OneSignal.getDisableGMSMissingPromptSuspend() shouldBe true + + // When - set to false + OneSignal.setDisableGMSMissingPromptSuspend(false) + + // Then + OneSignal.getDisableGMSMissingPromptSuspend() shouldBe false + } + } + } + + context("suspend accessors match non-suspend properties") { + test("getUserSuspend returns same instance as User property") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val userSuspend = OneSignal.getUserSuspend() + val userProperty = OneSignal.User + + // Then + userSuspend shouldBe userProperty + } + } + + test("getSessionSuspend returns same instance as Session property") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val sessionSuspend = OneSignal.getSessionSuspend() + val sessionProperty = OneSignal.Session + + // Then + sessionSuspend shouldBe sessionProperty + } + } + + test("getNotificationsSuspend returns same instance as Notifications property") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val notificationsSuspend = OneSignal.getNotificationsSuspend() + val notificationsProperty = OneSignal.Notifications + + // Then + notificationsSuspend shouldBe notificationsProperty + } + } + + test("getLocationSuspend returns same instance as Location property") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val locationSuspend = OneSignal.getLocationSuspend() + val locationProperty = OneSignal.Location + + // Then + locationSuspend shouldBe locationProperty + } + } + + test("getInAppMessagesSuspend returns same instance as InAppMessages property") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val inAppMessagesSuspend = OneSignal.getInAppMessagesSuspend() + val inAppMessagesProperty = OneSignal.InAppMessages + + // Then + inAppMessagesSuspend shouldBe inAppMessagesProperty + } + } + + test("suspend configuration accessors match non-suspend properties") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When - set via suspend methods + OneSignal.setConsentRequiredSuspend(true) + OneSignal.setConsentGivenSuspend(true) + OneSignal.setDisableGMSMissingPromptSuspend(true) + + // Then - verify via both suspend and non-suspend accessors + OneSignal.getConsentRequiredSuspend() shouldBe OneSignal.consentRequired + OneSignal.getConsentGivenSuspend() shouldBe OneSignal.consentGiven + OneSignal.getDisableGMSMissingPromptSuspend() shouldBe OneSignal.disableGMSMissingPrompt + + // When - set via non-suspend properties + OneSignal.consentRequired = false + OneSignal.consentGiven = false + OneSignal.disableGMSMissingPrompt = false + + // Then - verify via suspend accessors + OneSignal.getConsentRequiredSuspend() shouldBe false + OneSignal.getConsentGivenSuspend() shouldBe false + OneSignal.getDisableGMSMissingPromptSuspend() shouldBe false + } + } + } + + context("loginSuspend and logoutSuspend methods") { + test("loginSuspend can be called after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When/Then - should not throw + OneSignal.loginSuspend("testExternalId") + } + } + + test("logoutSuspend can be called after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When/Then - should not throw + OneSignal.logoutSuspend() + } + } + + test("loginSuspend with JWT token can be called after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When/Then - should not throw + OneSignal.loginSuspend("testExternalId", "testJwtToken") + } + } + } + + context("non-suspend accessor properties exist and return correct types") { + test("User property returns IUserManager after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val user = OneSignal.User + + // Then + user.shouldBeInstanceOf() + } + } + + test("Session property returns ISessionManager after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val session = OneSignal.Session + + // Then + session.shouldBeInstanceOf() + } + } + + test("Notifications property returns INotificationsManager after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val notifications = OneSignal.Notifications + + // Then + notifications.shouldBeInstanceOf() + } + } + + test("Location property returns ILocationManager after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val location = OneSignal.Location + + // Then + location.shouldBeInstanceOf() + } + } + + test("InAppMessages property returns IInAppMessagesManager after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val inAppMessages = OneSignal.InAppMessages + + // Then + inAppMessages.shouldBeInstanceOf() + } + } + } + + context("non-suspend configuration properties") { + test("consentRequired property can be get and set") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When - set to true + OneSignal.consentRequired = true + + // Then + OneSignal.consentRequired shouldBe true + + // When - set to false + OneSignal.consentRequired = false + + // Then + OneSignal.consentRequired shouldBe false + } + } + + test("consentGiven property can be get and set") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When - set to true + OneSignal.consentGiven = true + + // Then + OneSignal.consentGiven shouldBe true + + // When - set to false + OneSignal.consentGiven = false + + // Then + OneSignal.consentGiven shouldBe false + } + } + + test("disableGMSMissingPrompt property can be get and set") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When - set to true + OneSignal.disableGMSMissingPrompt = true + + // Then + OneSignal.disableGMSMissingPrompt shouldBe true + + // When - set to false + OneSignal.disableGMSMissingPrompt = false + + // Then + OneSignal.disableGMSMissingPrompt shouldBe false + } + } + } + + context("non-suspend methods") { + test("initWithContext method exists and can be called") { + // Given + val context = getApplicationContext() + + // When/Then - should not throw + OneSignal.initWithContext(context, "testAppId") + } + + test("login method exists and can be called after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When/Then - should not throw + OneSignal.login("testExternalId") + } + } + + test("login method with JWT token exists and can be called after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When/Then - should not throw + OneSignal.login("testExternalId", "testJwtToken") + } + } + + test("logout method exists and can be called after initialization") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When/Then - should not throw + OneSignal.logout() + } + } + } + + context("non-suspend properties match suspend accessors") { + test("User property returns same instance as getUserSuspend") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val userProperty = OneSignal.User + val userSuspend = OneSignal.getUserSuspend() + + // Then + userProperty shouldBe userSuspend + } + } + + test("Session property returns same instance as getSessionSuspend") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val sessionProperty = OneSignal.Session + val sessionSuspend = OneSignal.getSessionSuspend() + + // Then + sessionProperty shouldBe sessionSuspend + } + } + + test("Notifications property returns same instance as getNotificationsSuspend") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val notificationsProperty = OneSignal.Notifications + val notificationsSuspend = OneSignal.getNotificationsSuspend() + + // Then + notificationsProperty shouldBe notificationsSuspend + } + } + + test("Location property returns same instance as getLocationSuspend") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val locationProperty = OneSignal.Location + val locationSuspend = OneSignal.getLocationSuspend() + + // Then + locationProperty shouldBe locationSuspend + } + } + + test("InAppMessages property returns same instance as getInAppMessagesSuspend") { + runBlocking { + // Given + val context = getApplicationContext() + OneSignal.initWithContextSuspend(context, "testAppId") + + // When + val inAppMessagesProperty = OneSignal.InAppMessages + val inAppMessagesSuspend = OneSignal.getInAppMessagesSuspend() + + // Then + inAppMessagesProperty shouldBe inAppMessagesSuspend + } + } + } +}) From ddd0bbe494e4dc8cd8a07cfc9d84e2f9f0b23068 Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Mon, 8 Dec 2025 12:32:16 -0500 Subject: [PATCH 2/7] hanging tests --- .../internal/summary/NotificationSummaryManagerTests.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt index 75bf2aa479..8889ee160c 100644 --- a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt @@ -17,6 +17,7 @@ import io.mockk.coVerify import io.mockk.just import io.mockk.mockk import io.mockk.runs +import kotlinx.coroutines.runBlocking import org.robolectric.annotation.Config @Config( @@ -182,7 +183,9 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - notificationSummaryManager.clearNotificationOnSummaryClick("groupId") + runBlocking { + notificationSummaryManager.clearNotificationOnSummaryClick("groupId") + } // Then coVerify(exactly = 1) { mockNotificationRepository.getAndroidIdForGroup("groupId", false) } @@ -211,7 +214,9 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - notificationSummaryManager.clearNotificationOnSummaryClick("groupId") + runBlocking { + notificationSummaryManager.clearNotificationOnSummaryClick("groupId") + } // Then coVerify(exactly = 1) { mockNotificationRepository.getAndroidIdForGroup("groupId", false) } From 8c844933404b77742337ed7d368c52fee101b8ea Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Mon, 8 Dec 2025 12:57:50 -0500 Subject: [PATCH 3/7] hanging tests --- .../summary/NotificationSummaryManagerTests.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt index 8889ee160c..1d91d0e753 100644 --- a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt @@ -50,7 +50,9 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) + runBlocking { + notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) + } // Then coVerify(exactly = 1) { mockNotificationRepository.getGroupId(1) } @@ -77,7 +79,9 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) + runBlocking { + notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) + } // Then coVerify(exactly = 1) { mockNotificationRepository.getGroupId(1) } @@ -113,7 +117,9 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) + runBlocking { + notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) + } // Then coVerify(exactly = 1) { mockNotificationRepository.getGroupId(1) } @@ -146,7 +152,9 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) + runBlocking { + notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) + } // Then coVerify(exactly = 1) { mockNotificationRepository.getGroupId(1) } From 6895df12b25933ed7277397afc933cb99c216429 Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Mon, 8 Dec 2025 18:49:25 -0500 Subject: [PATCH 4/7] deleting tests --- .../com/onesignal/OneSignalAccessorsTests.kt | 561 ------------------ 1 file changed, 561 deletions(-) delete mode 100644 OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalAccessorsTests.kt diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalAccessorsTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalAccessorsTests.kt deleted file mode 100644 index a5d39f0049..0000000000 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalAccessorsTests.kt +++ /dev/null @@ -1,561 +0,0 @@ -package com.onesignal - -import android.content.Context -import androidx.test.core.app.ApplicationProvider.getApplicationContext -import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest -import com.onesignal.debug.LogLevel -import com.onesignal.debug.internal.logging.Logging -import com.onesignal.inAppMessages.IInAppMessagesManager -import com.onesignal.location.ILocationManager -import com.onesignal.notifications.INotificationsManager -import com.onesignal.session.ISessionManager -import com.onesignal.user.IUserManager -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe -import io.kotest.matchers.types.shouldBeInstanceOf -import kotlinx.coroutines.runBlocking - -@RobolectricTest -class OneSignalAccessorsTests : FunSpec({ - - beforeAny { - Logging.logLevel = LogLevel.NONE - } - - afterAny { - val context = getApplicationContext() - val prefs = context.getSharedPreferences("OneSignal", Context.MODE_PRIVATE) - prefs.edit().clear().commit() - val otherPrefs = context.getSharedPreferences("com.onesignal", Context.MODE_PRIVATE) - otherPrefs.edit().clear().commit() - Thread.sleep(50) - } - - context("suspend accessor methods exist and return correct types") { - test("getUserSuspend returns IUserManager after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val user = OneSignal.getUserSuspend() - - // Then - user.shouldBeInstanceOf() - } - } - - test("getSessionSuspend returns ISessionManager after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val session = OneSignal.getSessionSuspend() - - // Then - session.shouldBeInstanceOf() - } - } - - test("getNotificationsSuspend returns INotificationsManager after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val notifications = OneSignal.getNotificationsSuspend() - - // Then - notifications.shouldBeInstanceOf() - } - } - - test("getLocationSuspend returns ILocationManager after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val location = OneSignal.getLocationSuspend() - - // Then - location.shouldBeInstanceOf() - } - } - - test("getInAppMessagesSuspend returns IInAppMessagesManager after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val inAppMessages = OneSignal.getInAppMessagesSuspend() - - // Then - inAppMessages.shouldBeInstanceOf() - } - } - } - - context("suspend configuration property accessors") { - test("getConsentRequiredSuspend and setConsentRequiredSuspend work correctly") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - set to true - OneSignal.setConsentRequiredSuspend(true) - - // Then - OneSignal.getConsentRequiredSuspend() shouldBe true - - // When - set to false - OneSignal.setConsentRequiredSuspend(false) - - // Then - OneSignal.getConsentRequiredSuspend() shouldBe false - } - } - - test("getConsentGivenSuspend and setConsentGivenSuspend work correctly") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - set to true - OneSignal.setConsentGivenSuspend(true) - - // Then - OneSignal.getConsentGivenSuspend() shouldBe true - - // When - set to false - OneSignal.setConsentGivenSuspend(false) - - // Then - OneSignal.getConsentGivenSuspend() shouldBe false - } - } - - test("getDisableGMSMissingPromptSuspend and setDisableGMSMissingPromptSuspend work correctly") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - set to true - OneSignal.setDisableGMSMissingPromptSuspend(true) - - // Then - OneSignal.getDisableGMSMissingPromptSuspend() shouldBe true - - // When - set to false - OneSignal.setDisableGMSMissingPromptSuspend(false) - - // Then - OneSignal.getDisableGMSMissingPromptSuspend() shouldBe false - } - } - } - - context("suspend accessors match non-suspend properties") { - test("getUserSuspend returns same instance as User property") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val userSuspend = OneSignal.getUserSuspend() - val userProperty = OneSignal.User - - // Then - userSuspend shouldBe userProperty - } - } - - test("getSessionSuspend returns same instance as Session property") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val sessionSuspend = OneSignal.getSessionSuspend() - val sessionProperty = OneSignal.Session - - // Then - sessionSuspend shouldBe sessionProperty - } - } - - test("getNotificationsSuspend returns same instance as Notifications property") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val notificationsSuspend = OneSignal.getNotificationsSuspend() - val notificationsProperty = OneSignal.Notifications - - // Then - notificationsSuspend shouldBe notificationsProperty - } - } - - test("getLocationSuspend returns same instance as Location property") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val locationSuspend = OneSignal.getLocationSuspend() - val locationProperty = OneSignal.Location - - // Then - locationSuspend shouldBe locationProperty - } - } - - test("getInAppMessagesSuspend returns same instance as InAppMessages property") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val inAppMessagesSuspend = OneSignal.getInAppMessagesSuspend() - val inAppMessagesProperty = OneSignal.InAppMessages - - // Then - inAppMessagesSuspend shouldBe inAppMessagesProperty - } - } - - test("suspend configuration accessors match non-suspend properties") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - set via suspend methods - OneSignal.setConsentRequiredSuspend(true) - OneSignal.setConsentGivenSuspend(true) - OneSignal.setDisableGMSMissingPromptSuspend(true) - - // Then - verify via both suspend and non-suspend accessors - OneSignal.getConsentRequiredSuspend() shouldBe OneSignal.consentRequired - OneSignal.getConsentGivenSuspend() shouldBe OneSignal.consentGiven - OneSignal.getDisableGMSMissingPromptSuspend() shouldBe OneSignal.disableGMSMissingPrompt - - // When - set via non-suspend properties - OneSignal.consentRequired = false - OneSignal.consentGiven = false - OneSignal.disableGMSMissingPrompt = false - - // Then - verify via suspend accessors - OneSignal.getConsentRequiredSuspend() shouldBe false - OneSignal.getConsentGivenSuspend() shouldBe false - OneSignal.getDisableGMSMissingPromptSuspend() shouldBe false - } - } - } - - context("loginSuspend and logoutSuspend methods") { - test("loginSuspend can be called after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When/Then - should not throw - OneSignal.loginSuspend("testExternalId") - } - } - - test("logoutSuspend can be called after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When/Then - should not throw - OneSignal.logoutSuspend() - } - } - - test("loginSuspend with JWT token can be called after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When/Then - should not throw - OneSignal.loginSuspend("testExternalId", "testJwtToken") - } - } - } - - context("non-suspend accessor properties exist and return correct types") { - test("User property returns IUserManager after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val user = OneSignal.User - - // Then - user.shouldBeInstanceOf() - } - } - - test("Session property returns ISessionManager after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val session = OneSignal.Session - - // Then - session.shouldBeInstanceOf() - } - } - - test("Notifications property returns INotificationsManager after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val notifications = OneSignal.Notifications - - // Then - notifications.shouldBeInstanceOf() - } - } - - test("Location property returns ILocationManager after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val location = OneSignal.Location - - // Then - location.shouldBeInstanceOf() - } - } - - test("InAppMessages property returns IInAppMessagesManager after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val inAppMessages = OneSignal.InAppMessages - - // Then - inAppMessages.shouldBeInstanceOf() - } - } - } - - context("non-suspend configuration properties") { - test("consentRequired property can be get and set") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - set to true - OneSignal.consentRequired = true - - // Then - OneSignal.consentRequired shouldBe true - - // When - set to false - OneSignal.consentRequired = false - - // Then - OneSignal.consentRequired shouldBe false - } - } - - test("consentGiven property can be get and set") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - set to true - OneSignal.consentGiven = true - - // Then - OneSignal.consentGiven shouldBe true - - // When - set to false - OneSignal.consentGiven = false - - // Then - OneSignal.consentGiven shouldBe false - } - } - - test("disableGMSMissingPrompt property can be get and set") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - set to true - OneSignal.disableGMSMissingPrompt = true - - // Then - OneSignal.disableGMSMissingPrompt shouldBe true - - // When - set to false - OneSignal.disableGMSMissingPrompt = false - - // Then - OneSignal.disableGMSMissingPrompt shouldBe false - } - } - } - - context("non-suspend methods") { - test("initWithContext method exists and can be called") { - // Given - val context = getApplicationContext() - - // When/Then - should not throw - OneSignal.initWithContext(context, "testAppId") - } - - test("login method exists and can be called after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When/Then - should not throw - OneSignal.login("testExternalId") - } - } - - test("login method with JWT token exists and can be called after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When/Then - should not throw - OneSignal.login("testExternalId", "testJwtToken") - } - } - - test("logout method exists and can be called after initialization") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When/Then - should not throw - OneSignal.logout() - } - } - } - - context("non-suspend properties match suspend accessors") { - test("User property returns same instance as getUserSuspend") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val userProperty = OneSignal.User - val userSuspend = OneSignal.getUserSuspend() - - // Then - userProperty shouldBe userSuspend - } - } - - test("Session property returns same instance as getSessionSuspend") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val sessionProperty = OneSignal.Session - val sessionSuspend = OneSignal.getSessionSuspend() - - // Then - sessionProperty shouldBe sessionSuspend - } - } - - test("Notifications property returns same instance as getNotificationsSuspend") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val notificationsProperty = OneSignal.Notifications - val notificationsSuspend = OneSignal.getNotificationsSuspend() - - // Then - notificationsProperty shouldBe notificationsSuspend - } - } - - test("Location property returns same instance as getLocationSuspend") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val locationProperty = OneSignal.Location - val locationSuspend = OneSignal.getLocationSuspend() - - // Then - locationProperty shouldBe locationSuspend - } - } - - test("InAppMessages property returns same instance as getInAppMessagesSuspend") { - runBlocking { - // Given - val context = getApplicationContext() - OneSignal.initWithContextSuspend(context, "testAppId") - - // When - val inAppMessagesProperty = OneSignal.InAppMessages - val inAppMessagesSuspend = OneSignal.getInAppMessagesSuspend() - - // Then - inAppMessagesProperty shouldBe inAppMessagesSuspend - } - } - } -}) From 1c935edeba391e2e9f9c085de9bdf1b1808aab63 Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Mon, 8 Dec 2025 18:57:48 -0500 Subject: [PATCH 5/7] reverted --- .../NotificationSummaryManagerTests.kt | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt index 1d91d0e753..75bf2aa479 100644 --- a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/summary/NotificationSummaryManagerTests.kt @@ -17,7 +17,6 @@ import io.mockk.coVerify import io.mockk.just import io.mockk.mockk import io.mockk.runs -import kotlinx.coroutines.runBlocking import org.robolectric.annotation.Config @Config( @@ -50,9 +49,7 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - runBlocking { - notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) - } + notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) // Then coVerify(exactly = 1) { mockNotificationRepository.getGroupId(1) } @@ -79,9 +76,7 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - runBlocking { - notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) - } + notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) // Then coVerify(exactly = 1) { mockNotificationRepository.getGroupId(1) } @@ -117,9 +112,7 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - runBlocking { - notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) - } + notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) // Then coVerify(exactly = 1) { mockNotificationRepository.getGroupId(1) } @@ -152,9 +145,7 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - runBlocking { - notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) - } + notificationSummaryManager.updatePossibleDependentSummaryOnDismiss(1) // Then coVerify(exactly = 1) { mockNotificationRepository.getGroupId(1) } @@ -191,9 +182,7 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - runBlocking { - notificationSummaryManager.clearNotificationOnSummaryClick("groupId") - } + notificationSummaryManager.clearNotificationOnSummaryClick("groupId") // Then coVerify(exactly = 1) { mockNotificationRepository.getAndroidIdForGroup("groupId", false) } @@ -222,9 +211,7 @@ class NotificationSummaryManagerTests : FunSpec({ ) // When - runBlocking { - notificationSummaryManager.clearNotificationOnSummaryClick("groupId") - } + notificationSummaryManager.clearNotificationOnSummaryClick("groupId") // Then coVerify(exactly = 1) { mockNotificationRepository.getAndroidIdForGroup("groupId", false) } From 87e14239d9c9b71c775e954e333d1430e99cac10 Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Tue, 9 Dec 2025 10:52:17 -0500 Subject: [PATCH 6/7] run blocking for tests --- .../main/java/com/onesignal/mocks/IOMockHelper.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/IOMockHelper.kt b/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/IOMockHelper.kt index 7296be941e..177980ab1a 100644 --- a/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/IOMockHelper.kt +++ b/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/IOMockHelper.kt @@ -15,11 +15,9 @@ import io.mockk.mockkStatic import io.mockk.unmockkObject import io.mockk.unmockkStatic import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout import java.util.concurrent.atomic.AtomicInteger @@ -82,7 +80,7 @@ object IOMockHelper : BeforeSpecListener, AfterSpecListener, BeforeTestListener, mockkObject(OneSignalDispatchers) // Helper function to track async work (suspendifyOnIO, launchOnIO, launchOnDefault) - // Note: We use Dispatchers.Unconfined to execute immediately and deterministically + // Note: We use runBlocking with Dispatchers.Unconfined to execute synchronously and deterministically // instead of suspendifyWithCompletion to avoid circular dependency // (suspendifyWithCompletion calls OneSignalDispatchers.launchOnIO which we're mocking) fun trackAsyncWork(block: suspend () -> Unit) { @@ -92,9 +90,10 @@ object IOMockHelper : BeforeSpecListener, AfterSpecListener, BeforeTestListener, ioWaiter = CompletableDeferred() } - // Execute the block using Unconfined dispatcher to run immediately and deterministically - // This makes tests deterministic and avoids the need for delays - CoroutineScope(SupervisorJob() + Dispatchers.Unconfined).launch { + // Execute the block synchronously using runBlocking with Dispatchers.Unconfined + // Unconfined executes on the current thread, making tests deterministic + // runBlocking ensures the block completes before returning + runBlocking(Dispatchers.Unconfined) { try { block() } catch (e: Exception) { From 0ee60ef8a4e87b2c2c5749252265e3dc74df3621 Mon Sep 17 00:00:00 2001 From: AR Abdul Azeez Date: Tue, 9 Dec 2025 11:04:49 -0500 Subject: [PATCH 7/7] methods existing --- .../OneSignalSuspendMethodsExistTest.kt | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalSuspendMethodsExistTest.kt diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalSuspendMethodsExistTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalSuspendMethodsExistTest.kt new file mode 100644 index 0000000000..d277361055 --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/OneSignalSuspendMethodsExistTest.kt @@ -0,0 +1,188 @@ +package com.onesignal + +import com.onesignal.inAppMessages.IInAppMessagesManager +import com.onesignal.location.ILocationManager +import com.onesignal.notifications.INotificationsManager +import com.onesignal.session.ISessionManager +import com.onesignal.user.IUserManager +import io.kotest.core.spec.style.FunSpec +import kotlin.reflect.full.memberFunctions + +/** + * Simple compilation tests to verify that all suspend methods exist in OneSignal class + * with correct signatures. These tests verify the API surface but don't execute the methods. + */ +class OneSignalSuspendMethodsExistTest : FunSpec({ + + test("initWithContextSuspend exists with correct signature") { + // This test compiles only if the method exists with correct signature + val method: suspend (android.content.Context, String) -> Boolean = OneSignal::initWithContextSuspend + + // Verify using reflection that it's actually a suspend function + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "initWithContextSuspend" } + + assert(kFunction != null) { "initWithContextSuspend not found" } + assert(kFunction!!.isSuspend) { "initWithContextSuspend is not a suspend function" } + } + + test("getUserSuspend exists and returns IUserManager") { + // Compilation check - this fails if method doesn't exist or has wrong return type + val method: suspend () -> IUserManager = OneSignal::getUserSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "getUserSuspend" } + + assert(kFunction != null) { "getUserSuspend not found" } + assert(kFunction!!.isSuspend) { "getUserSuspend is not a suspend function" } + } + + test("getSessionSuspend exists and returns ISessionManager") { + val method: suspend () -> ISessionManager = OneSignal::getSessionSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "getSessionSuspend" } + + assert(kFunction != null) { "getSessionSuspend not found" } + assert(kFunction!!.isSuspend) { "getSessionSuspend is not a suspend function" } + } + + test("getNotificationsSuspend exists and returns INotificationsManager") { + val method: suspend () -> INotificationsManager = OneSignal::getNotificationsSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "getNotificationsSuspend" } + + assert(kFunction != null) { "getNotificationsSuspend not found" } + assert(kFunction!!.isSuspend) { "getNotificationsSuspend is not a suspend function" } + } + + test("getLocationSuspend exists and returns ILocationManager") { + val method: suspend () -> ILocationManager = OneSignal::getLocationSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "getLocationSuspend" } + + assert(kFunction != null) { "getLocationSuspend not found" } + assert(kFunction!!.isSuspend) { "getLocationSuspend is not a suspend function" } + } + + test("getInAppMessagesSuspend exists and returns IInAppMessagesManager") { + val method: suspend () -> IInAppMessagesManager = OneSignal::getInAppMessagesSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "getInAppMessagesSuspend" } + + assert(kFunction != null) { "getInAppMessagesSuspend not found" } + assert(kFunction!!.isSuspend) { "getInAppMessagesSuspend is not a suspend function" } + } + + test("getConsentRequiredSuspend exists and returns Boolean") { + val method: suspend () -> Boolean = OneSignal::getConsentRequiredSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "getConsentRequiredSuspend" } + + assert(kFunction != null) { "getConsentRequiredSuspend not found" } + assert(kFunction!!.isSuspend) { "getConsentRequiredSuspend is not a suspend function" } + } + + test("setConsentRequiredSuspend exists with Boolean parameter") { + val method: suspend (Boolean) -> Unit = OneSignal::setConsentRequiredSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "setConsentRequiredSuspend" } + + assert(kFunction != null) { "setConsentRequiredSuspend not found" } + assert(kFunction!!.isSuspend) { "setConsentRequiredSuspend is not a suspend function" } + } + + test("getConsentGivenSuspend exists and returns Boolean") { + val method: suspend () -> Boolean = OneSignal::getConsentGivenSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "getConsentGivenSuspend" } + + assert(kFunction != null) { "getConsentGivenSuspend not found" } + assert(kFunction!!.isSuspend) { "getConsentGivenSuspend is not a suspend function" } + } + + test("setConsentGivenSuspend exists with Boolean parameter") { + val method: suspend (Boolean) -> Unit = OneSignal::setConsentGivenSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "setConsentGivenSuspend" } + + assert(kFunction != null) { "setConsentGivenSuspend not found" } + assert(kFunction!!.isSuspend) { "setConsentGivenSuspend is not a suspend function" } + } + + test("getDisableGMSMissingPromptSuspend exists and returns Boolean") { + val method: suspend () -> Boolean = OneSignal::getDisableGMSMissingPromptSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "getDisableGMSMissingPromptSuspend" } + + assert(kFunction != null) { "getDisableGMSMissingPromptSuspend not found" } + assert(kFunction!!.isSuspend) { "getDisableGMSMissingPromptSuspend is not a suspend function" } + } + + test("setDisableGMSMissingPromptSuspend exists with Boolean parameter") { + val method: suspend (Boolean) -> Unit = OneSignal::setDisableGMSMissingPromptSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "setDisableGMSMissingPromptSuspend" } + + assert(kFunction != null) { "setDisableGMSMissingPromptSuspend not found" } + assert(kFunction!!.isSuspend) { "setDisableGMSMissingPromptSuspend is not a suspend function" } + } + + test("loginSuspend exists with String and optional String parameters") { + // Verify the method exists with correct signature using reflection + // Note: There's only one loginSuspend with a default parameter for jwtBearerToken + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "loginSuspend" } + + assert(kFunction != null) { "loginSuspend not found" } + assert(kFunction!!.isSuspend) { "loginSuspend is not a suspend function" } + assert(kFunction.parameters.size >= 2) { "loginSuspend should have at least 2 parameters (receiver + externalId)" } + } + + test("logoutSuspend exists with no parameters") { + val method: suspend () -> Unit = OneSignal::logoutSuspend + + val kFunction = OneSignal::class.memberFunctions + .find { it.name == "logoutSuspend" } + + assert(kFunction != null) { "logoutSuspend not found" } + assert(kFunction!!.isSuspend) { "logoutSuspend is not a suspend function" } + } + + test("all suspend methods are marked with @JvmStatic") { + // Get all suspend methods we added + val suspendMethodNames = listOf( + "getUserSuspend", + "getSessionSuspend", + "getNotificationsSuspend", + "getLocationSuspend", + "getInAppMessagesSuspend", + "getConsentRequiredSuspend", + "setConsentRequiredSuspend", + "getConsentGivenSuspend", + "setConsentGivenSuspend", + "getDisableGMSMissingPromptSuspend", + "setDisableGMSMissingPromptSuspend", + "loginSuspend", + "logoutSuspend" + ) + + // Verify each exists and is a static method (accessible via companion object) + suspendMethodNames.forEach { methodName -> + val kFunction = OneSignal::class.memberFunctions + .find { it.name == methodName } + + assert(kFunction != null) { "$methodName not found" } + assert(kFunction!!.isSuspend) { "$methodName is not a suspend function" } + } + } +})