diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/Configuration.kt b/core/src/main/java/com/segment/analytics/kotlin/core/Configuration.kt index a7ee8895..8ef1c8d2 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/Configuration.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/Configuration.kt @@ -33,7 +33,7 @@ data class Configuration( var flushAt: Int = 20, var flushInterval: Int = 30, var flushPolicies: List = emptyList(), - var defaultSettings: Settings = Settings(), + var defaultSettings: Settings? = null, var autoAddSegmentDestination: Boolean = true, var apiHost: String = DEFAULT_API_HOST, var cdnHost: String = DEFAULT_CDN_HOST, diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/Settings.kt b/core/src/main/java/com/segment/analytics/kotlin/core/Settings.kt index 197dafa4..073b7cbf 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/Settings.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/Settings.kt @@ -92,6 +92,7 @@ suspend fun Analytics.checkSettings() { val settingsObj: Settings? = fetchSettings(writeKey, cdnHost) withContext(analyticsDispatcher) { + settingsObj?.let { log("Dispatching update settings on ${Thread.currentThread().name}") store.dispatch(System.UpdateSettingsAction(settingsObj), System::class) @@ -108,22 +109,27 @@ internal fun Analytics.fetchSettings( writeKey: String, cdnHost: String ): Settings? = try { - val connection = HTTPClient(writeKey, this.configuration.requestFactory).settings(cdnHost) - val settingsString = - connection.inputStream?.bufferedReader()?.use(BufferedReader::readText) ?: "" - log("Fetched Settings: $settingsString") - LenientJson.decodeFromString(settingsString) - } catch (ex: Exception) { - reportErrorWithMetrics( - this, - AnalyticsError.SettingsFail(AnalyticsError.NetworkUnknown(URL("https://$cdnHost/projects/$writeKey/settings"), ex)), - "Failed to fetch settings", - Telemetry.INVOKE_ERROR_METRIC, - ex.stackTraceToString() - ) { - it["error"] = ex.toString() - it["writekey"] = writeKey - it["message"] = "Error retrieving settings" - } - configuration.defaultSettings - } \ No newline at end of file + val connection = HTTPClient(writeKey, this.configuration.requestFactory).settings(cdnHost) + val settingsString = + connection.inputStream?.bufferedReader()?.use(BufferedReader::readText) ?: "" + log("Fetched Settings: $settingsString") + LenientJson.decodeFromString(settingsString) +} catch (ex: Exception) { + reportErrorWithMetrics( + this, + AnalyticsError.SettingsFail( + AnalyticsError.NetworkUnknown( + URL("https://$cdnHost/projects/$writeKey/settings"), + ex + ) + ), + "Failed to fetch settings", + Telemetry.INVOKE_ERROR_METRIC, + ex.stackTraceToString() + ) { + it["error"] = ex.toString() + it["writekey"] = writeKey + it["message"] = "Error retrieving settings" + } + null +} \ No newline at end of file diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/State.kt b/core/src/main/java/com/segment/analytics/kotlin/core/State.kt index 21275a88..374c56c5 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/State.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/State.kt @@ -25,14 +25,38 @@ data class System( companion object { fun defaultState(configuration: Configuration, storage: Storage): System { - val settings = try { - Json.decodeFromString( - Settings.serializer(), - storage.read(Storage.Constants.Settings) ?: "" - ) - } catch (ignored: Exception) { - configuration.defaultSettings + val storedSettings = storage.read(Storage.Constants.Settings) + val defaultSettings = configuration.defaultSettings ?: Settings( + integrations = buildJsonObject { + put( + "Segment.io", + buildJsonObject { + put( + "apiKey", + configuration.writeKey + ) + put("apiHost", Constants.DEFAULT_API_HOST) + }) + }, + plan = emptyJsonObject, + edgeFunction = emptyJsonObject, + middlewareSettings = emptyJsonObject + ) + + // Use stored settings or fallback to default settings + val settings = if (storedSettings == null || storedSettings == "" || storedSettings == "{}") { + defaultSettings + } else { + try { + Json.decodeFromString( + Settings.serializer(), + storedSettings + ) + } catch (ignored: Exception) { + defaultSettings + } } + return System( configuration = configuration, settings = settings, diff --git a/core/src/main/java/com/segment/analytics/kotlin/core/platform/Plugin.kt b/core/src/main/java/com/segment/analytics/kotlin/core/platform/Plugin.kt index 4fb337a2..0be7decb 100644 --- a/core/src/main/java/com/segment/analytics/kotlin/core/platform/Plugin.kt +++ b/core/src/main/java/com/segment/analytics/kotlin/core/platform/Plugin.kt @@ -89,7 +89,7 @@ abstract class DestinationPlugin : EventPlugin { override val type: Plugin.Type = Plugin.Type.Destination private val timeline: Timeline = Timeline() override lateinit var analytics: Analytics - internal var enabled = false + internal var enabled = true abstract val key: String override fun setup(analytics: Analytics) { diff --git a/core/src/test/kotlin/com/segment/analytics/kotlin/core/SettingsTests.kt b/core/src/test/kotlin/com/segment/analytics/kotlin/core/SettingsTests.kt index 56a1bda8..d67d6963 100644 --- a/core/src/test/kotlin/com/segment/analytics/kotlin/core/SettingsTests.kt +++ b/core/src/test/kotlin/com/segment/analytics/kotlin/core/SettingsTests.kt @@ -88,7 +88,9 @@ class SettingsTests { // no settings available, should not be called analytics.add(mockPlugin) - + verify (exactly = 0){ + mockPlugin.update(any(), any()) + } // load settings mockHTTPClient() @@ -102,7 +104,7 @@ class SettingsTests { // load settings again mockHTTPClient() analytics.checkSettings() - verify (exactly = 2) { + verify (exactly = 1) { mockPlugin.update(any(), Plugin.UpdateType.Refresh) } } @@ -230,69 +232,67 @@ class SettingsTests { @Test fun `fetchSettings returns null when Settings string is invalid`() { - val emptySettings = analytics.fetchSettings("emptySettingsObject", "cdn-settings.segment.com/v1") // Null on invalid JSON mockHTTPClient("") var settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) // Null on invalid JSON mockHTTPClient("hello") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) // Null on invalid JSON mockHTTPClient("#! /bin/sh") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) // Null on invalid JSON mockHTTPClient("") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) // Null on invalid JSON mockHTTPClient("true") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) // Null on invalid JSON mockHTTPClient("[]") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) // Null on invalid JSON mockHTTPClient("}{") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) // Null on invalid JSON mockHTTPClient("{{{{}}}}") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) // Null on invalid JSON mockHTTPClient("{null:\"bar\"}") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) } @Test fun `fetchSettings returns null when parameters are invalid`() { - val emptySettings = analytics.fetchSettings("emptySettingsObject", "cdn-settings.segment.com/v1") mockHTTPClient("{\"integrations\":{}, \"plan\":{}, \"edgeFunction\": {}, \"middlewareSettings\": {}}") // empty host var settings = analytics.fetchSettings("foo", "") - assertEquals(emptySettings, settings) + assertNull(settings) // not a host name settings = analytics.fetchSettings("foo", "http://blah") - assertEquals(emptySettings, settings) + assertNull(settings) // emoji settings = analytics.fetchSettings("foo", "😃") - assertEquals(emptySettings, settings) + assertNull(settings) } @Test @@ -300,32 +300,27 @@ class SettingsTests { // Null if integrations is null mockHTTPClient("{\"integrations\":null}") var settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertTrue(settings?.integrations?.isEmpty() ?: true, "Integrations should be empty") - assertTrue(settings?.plan?.isEmpty() ?: true, "Plan should be empty") - assertTrue(settings?.edgeFunction?.isEmpty() ?: true, "EdgeFunction should be empty") - assertTrue(settings?.middlewareSettings?.isEmpty() ?: true, "MiddlewareSettings should be empty") - assertTrue(settings?.metrics?.isEmpty() ?: true, "Metrics should be empty") - assertTrue(settings?.consentSettings?.isEmpty() ?: true, "ConsentSettings should be empty") - -// // Null if plan is null -// mockHTTPClient("{\"plan\":null}") -// settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") -// assertNull(settings) -// -// // Null if edgeFunction is null -// mockHTTPClient("{\"edgeFunction\":null}") -// settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") -// assertNull(settings) -// -// // Null if middlewareSettings is null -// mockHTTPClient("{\"middlewareSettings\":null}") -// settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") -// assertNull(settings) + assertNull(settings) + + // Null if plan is null + mockHTTPClient("{\"plan\":null}") + settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") + assertNull(settings) + + // Null if edgeFunction is null + mockHTTPClient("{\"edgeFunction\":null}") + settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") + assertNull(settings) + + // Null if middlewareSettings is null + mockHTTPClient("{\"middlewareSettings\":null}") + settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") + assertNull(settings) } @Test fun `known Settings property types must match json type`() { - val emptySettings = analytics.fetchSettings("emptySettingsObject", "cdn-settings.segment.com/v1") + // integrations must be a JSON object mockHTTPClient("{\"integrations\":{}}") var settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") @@ -334,21 +329,21 @@ class SettingsTests { // Null if integrations is a number mockHTTPClient("{\"integrations\":123}") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) // Null if integrations is a string mockHTTPClient("{\"integrations\":\"foo\"}") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) // Null if integrations is an array mockHTTPClient("{\"integrations\":[\"foo\"]}") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) // Null if integrations is an emoji (UTF-8 string) mockHTTPClient("{\"integrations\": 😃}") settings = analytics.fetchSettings("foo", "cdn-settings.segment.com/v1") - assertEquals(emptySettings, settings) + assertNull(settings) } } \ No newline at end of file