diff --git a/Provider/src/main/java/com/spotify/confidence/Confidence.kt b/Provider/src/main/java/com/spotify/confidence/Confidence.kt index a9cbadad..f7b3fdbd 100644 --- a/Provider/src/main/java/com/spotify/confidence/Confidence.kt +++ b/Provider/src/main/java/com/spotify/confidence/Confidence.kt @@ -116,6 +116,8 @@ class Confidence internal constructor( } } +internal const val VISITOR_ID_CONTEXT_KEY = "visitorId" + object ConfidenceFactory { fun create( context: Context, @@ -152,6 +154,8 @@ object ConfidenceFactory { flagResolver = flagResolver, diskStorage = FileDiskStorage.create(context), flagApplierClient = flagApplierClient - ) + ).apply { + putContext(VISITOR_ID_CONTEXT_KEY, ConfidenceValue.String(VisitorUtil.getId(context))) + } } } \ No newline at end of file diff --git a/Provider/src/main/java/com/spotify/confidence/VisitorUtil.kt b/Provider/src/main/java/com/spotify/confidence/VisitorUtil.kt new file mode 100644 index 00000000..1da60877 --- /dev/null +++ b/Provider/src/main/java/com/spotify/confidence/VisitorUtil.kt @@ -0,0 +1,22 @@ +package com.spotify.confidence + +import android.content.Context +import java.util.UUID + +internal const val SHARED_PREFS_NAME = "confidence-visitor" +internal const val VISITOR_ID_SHARED_PREFS_KEY = "visitorId" +internal const val DEFAULT_VALUE = "unable-to-read" + +internal object VisitorUtil { + fun getId(context: Context): String { + return with(context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)) { + if (contains(VISITOR_ID_SHARED_PREFS_KEY)) { + getString(VISITOR_ID_SHARED_PREFS_KEY, DEFAULT_VALUE) ?: DEFAULT_VALUE + } else { + val visitorId = UUID.randomUUID().toString() + edit().putString(VISITOR_ID_SHARED_PREFS_KEY, visitorId).apply() + visitorId + } + } + } +} \ No newline at end of file diff --git a/Provider/src/test/java/com/spotify/confidence/ConfidenceIntegrationTests.kt b/Provider/src/test/java/com/spotify/confidence/ConfidenceIntegrationTests.kt index df754630..707dcbdb 100644 --- a/Provider/src/test/java/com/spotify/confidence/ConfidenceIntegrationTests.kt +++ b/Provider/src/test/java/com/spotify/confidence/ConfidenceIntegrationTests.kt @@ -1,6 +1,7 @@ package com.spotify.confidence import android.content.Context +import android.content.SharedPreferences import com.spotify.confidence.cache.FLAGS_FILE_NAME import com.spotify.confidence.cache.FileDiskStorage import com.spotify.confidence.client.ResolveReason @@ -22,6 +23,7 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import org.mockito.kotlin.any +import org.mockito.kotlin.doNothing import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import java.io.File @@ -30,6 +32,8 @@ import java.util.UUID private const val clientSecret = "21wxcxXpU6tKBRFtEFTXYiH7nDqL86Mm" private val mockContext: Context = mock() +private val mockSharedPrefs: SharedPreferences = mock() +private val mockSharedPrefsEdit: SharedPreferences.Editor = mock() class ConfidenceIntegrationTests { @@ -40,6 +44,10 @@ class ConfidenceIntegrationTests { fun setup() { whenever(mockContext.filesDir).thenReturn(Files.createTempDirectory("tmpTests").toFile()) whenever(mockContext.getDir(any(), any())).thenReturn(Files.createTempDirectory("events").toFile()) + whenever(mockContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)).thenReturn(mockSharedPrefs) + whenever(mockSharedPrefs.edit()).thenReturn(mockSharedPrefsEdit) + whenever(mockSharedPrefsEdit.putString(any(), any())).thenReturn(mockSharedPrefsEdit) + doNothing().whenever(mockSharedPrefsEdit).apply() } @Test diff --git a/Provider/src/test/java/com/spotify/confidence/EventSenderIntegrationTest.kt b/Provider/src/test/java/com/spotify/confidence/EventSenderIntegrationTest.kt index 8afa408a..35784937 100644 --- a/Provider/src/test/java/com/spotify/confidence/EventSenderIntegrationTest.kt +++ b/Provider/src/test/java/com/spotify/confidence/EventSenderIntegrationTest.kt @@ -1,6 +1,7 @@ package com.spotify.confidence import android.content.Context +import android.content.SharedPreferences import com.spotify.confidence.client.SdkMetadata import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking @@ -10,6 +11,8 @@ import kotlinx.coroutines.test.runTest import org.junit.Assert import org.junit.Before import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doNothing import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import java.io.File @@ -17,6 +20,8 @@ import java.nio.file.Files private const val clientSecret = "WciJVLIEiNnRxV8gaYPZNCFF8vbAXOu6" private val mockContext: Context = mock() +private val mockSharedPrefs: SharedPreferences = mock() +private val mockSharedPrefsEdit: SharedPreferences.Editor = mock() @OptIn(ExperimentalCoroutinesApi::class) class EventSenderIntegrationTest { @@ -27,12 +32,29 @@ class EventSenderIntegrationTest { @Before fun setup() { whenever(mockContext.getDir("events", Context.MODE_PRIVATE)).thenReturn(directory) + whenever(mockContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)).thenReturn(mockSharedPrefs) + whenever(mockSharedPrefs.edit()).thenReturn(mockSharedPrefsEdit) + whenever(mockSharedPrefsEdit.putString(any(), any())).thenReturn(mockSharedPrefsEdit) + doNothing().whenever(mockSharedPrefsEdit).apply() eventSender = null for (file in directory.walkFiles()) { file.delete() } } + @Test + fun created_event_sender_has_visitor_id_context() = runTest { + val testDispatcher = UnconfinedTestDispatcher(testScheduler) + eventSender = ConfidenceFactory.create( + mockContext, + clientSecret, + dispatcher = testDispatcher + ) + val context = eventSender?.getContext() + Assert.assertNotNull(context) + Assert.assertTrue(context!!.containsKey(VISITOR_ID_CONTEXT_KEY)) + } + @Test fun emitting_an_event_writes_to_file() = runTest { val eventStorage = EventStorageImpl(mockContext)