From 5b5c89063b3b6ec904723bad4cf0bce9399d35c0 Mon Sep 17 00:00:00 2001 From: typotter Date: Mon, 22 Jun 2026 15:39:10 -0600 Subject: [PATCH 1/3] FFL-2510: allocationKey as top-level field on ResolutionDetails; thread to OF flagMetadata --- .../internal/adapters/Converters.kt | 5 +- .../internal/adapters/ConvertersTest.kt | 27 +++++++++-- .../flags/internal/DatadogFlagsClient.kt | 4 +- .../android/flags/model/ResolutionDetails.kt | 4 +- .../flags/internal/DatadogFlagsClientTest.kt | 48 ++++--------------- 5 files changed, 38 insertions(+), 50 deletions(-) diff --git a/features/dd-sdk-android-flags-openfeature/src/main/kotlin/com/datadog/android/flags/openfeature/internal/adapters/Converters.kt b/features/dd-sdk-android-flags-openfeature/src/main/kotlin/com/datadog/android/flags/openfeature/internal/adapters/Converters.kt index 1faab3cb8c..f42e799e6a 100644 --- a/features/dd-sdk-android-flags-openfeature/src/main/kotlin/com/datadog/android/flags/openfeature/internal/adapters/Converters.kt +++ b/features/dd-sdk-android-flags-openfeature/src/main/kotlin/com/datadog/android/flags/openfeature/internal/adapters/Converters.kt @@ -41,11 +41,12 @@ internal fun ResolutionDetails.toProviderEvaluation(): ProviderEval reason = this.reason?.name, errorCode = this.errorCode?.toOpenFeatureErrorCode(), errorMessage = this.errorMessage, - metadata = this.flagMetadata.toEvaluationMetadata() + metadata = flagMetadata.toEvaluationMetadata(allocationKey) ) -private fun Map.toEvaluationMetadata(): EvaluationMetadata { +private fun Map.toEvaluationMetadata(allocationKey: String? = null): EvaluationMetadata { val builder = Builder() + allocationKey?.let { builder.putString("allocationKey", it) } forEach { (key, value) -> when (value) { is String -> builder.putString(key, value) diff --git a/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/internal/adapters/ConvertersTest.kt b/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/internal/adapters/ConvertersTest.kt index 68f86d6af9..939cf6c2d8 100644 --- a/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/internal/adapters/ConvertersTest.kt +++ b/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/internal/adapters/ConvertersTest.kt @@ -135,16 +135,17 @@ internal class ConvertersTest { assertThat(result.reason).isEqualTo("ERROR") } + @Test - fun `M surface allocationKey in metadata W toProviderEvaluation() {flagMetadata contains allocationKey}`( - @BoolForgery fakeValue: Boolean, - @StringForgery fakeAllocationKey: String + fun `M surface allocationKey in metadata W toProviderEvaluation() {resolution with allocationKey}`( + @StringForgery fakeAllocationKey: String, + @BoolForgery fakeValue: Boolean ) { // Given val resolution = ResolutionDetails( value = fakeValue, reason = ResolutionReason.TARGETING_MATCH, - flagMetadata = mapOf("allocationKey" to fakeAllocationKey) + allocationKey = fakeAllocationKey ) // When @@ -173,6 +174,24 @@ internal class ConvertersTest { assertThat(result.metadata.getString("count")).isEqualTo("42") } + @Test + fun `M not surface allocationKey in metadata W toProviderEvaluation() {resolution with null allocationKey}`( + @BoolForgery fakeValue: Boolean + ) { + // Given + val resolution = ResolutionDetails( + value = fakeValue, + reason = ResolutionReason.DEFAULT, + allocationKey = null + ) + + // When + val result = resolution.toProviderEvaluation() + + // Then + assertThat(result.metadata.getString("allocationKey")).isNull() + } + // endregion // region toOpenFeatureErrorCode diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt index a394131d39..ae47c41203 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt @@ -398,6 +398,7 @@ internal class DatadogFlagsClient( reason = parseReason(precomputedFlag.reason), errorCode = null, errorMessage = null, + allocationKey = precomputedFlag.allocationKey.takeIf { it.isNotBlank() }, flagMetadata = buildMetadata(precomputedFlag) ) @@ -409,9 +410,6 @@ internal class DatadogFlagsClient( is String, is Number, is Boolean -> metadata[key] = value } } - if (precomputedFlag.allocationKey.isNotBlank()) { - metadata["allocationKey"] = precomputedFlag.allocationKey - } return metadata } diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/model/ResolutionDetails.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/model/ResolutionDetails.kt index 68660db1ad..2f98153267 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/model/ResolutionDetails.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/model/ResolutionDetails.kt @@ -18,7 +18,7 @@ package com.datadog.android.flags.model * @property reason Optional reason code explaining why this value was resolved. * @property errorCode Optional error code if the resolution failed. Null indicates successful resolution. * @property errorMessage Optional human-readable error message providing additional context about failures. - * @property flagMetadata Map of arbitrary metadata associated with the flag (string keys, primitive values). Empty if no metadata. + * @property allocationKey The allocation key from the flag assignment. Empty string if not available. */ data class ResolutionDetails( val value: T, @@ -26,5 +26,5 @@ data class ResolutionDetails( val reason: ResolutionReason? = null, val errorCode: ErrorCode? = null, val errorMessage: String? = null, - val flagMetadata: Map = emptyMap() + val allocationKey: String = "" ) diff --git a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/DatadogFlagsClientTest.kt b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/DatadogFlagsClientTest.kt index d1f0e888cb..b63186c4b5 100644 --- a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/DatadogFlagsClientTest.kt +++ b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/DatadogFlagsClientTest.kt @@ -832,42 +832,12 @@ internal class DatadogFlagsClientTest { assertThat(result.reason).isEqualTo(ResolutionReason.valueOf(fakeReason)) assertThat(result.errorCode).isNull() assertThat(result.errorMessage).isNull() - assertThat(result.flagMetadata).isNotNull + assertThat(result.allocationKey).isEqualTo(fakeAllocationKey) assertThat(result.flagMetadata).containsKeys("version", "environment") - assertThat(result.flagMetadata["allocationKey"]).isEqualTo(fakeAllocationKey) } @Test - fun `M typed allocationKey wins W resolve() { extraLogging also contains allocationKey }`(forge: Forge) { - // Given - val fakeFlagKey = forge.anAlphabeticalString() - val fakeDefaultValue = forge.aBool() - val fakeFlagValue = !fakeDefaultValue - val fakeAllocationKey = forge.anAlphabeticalString() - val fakeExtraLoggingAllocationKey = forge.anAlphabeticalString() - val fakeFlag = forge.getForgery().copy( - variationType = VariationType.BOOLEAN.value, - variationValue = fakeFlagValue.toString(), - allocationKey = fakeAllocationKey, - extraLogging = JSONObject().apply { - put("allocationKey", fakeExtraLoggingAllocationKey) - } - ) - val fakeContext = EvaluationContext( - targetingKey = forge.anAlphabeticalString(), - attributes = emptyMap() - ) - whenever(mockFlagsRepository.getPrecomputedFlagWithContext(fakeFlagKey)) doReturn (fakeFlag to fakeContext) - - // When - val result = testedClient.resolve(fakeFlagKey, fakeDefaultValue) - - // Then - typed allocationKey wins over any "allocationKey" entry from extraLogging - assertThat(result.flagMetadata["allocationKey"]).isEqualTo(fakeAllocationKey) - } - - @Test - fun `M allocationKey excluded from metadata W resolve() { empty allocationKey }`(forge: Forge) { + fun `M allocationKey null W resolve() { empty allocationKey }`(forge: Forge) { // Given val fakeFlagKey = forge.anAlphabeticalString() val fakeDefaultValue = forge.aBool() @@ -889,11 +859,11 @@ internal class DatadogFlagsClientTest { // Then assertThat(result.value).isEqualTo(fakeFlagValue) - assertThat(result.flagMetadata).doesNotContainKey("allocationKey") + assertThat(result.allocationKey).isNull() } @Test - fun `M allocationKey excluded from metadata W resolve() { whitespace allocationKey }`(forge: Forge) { + fun `M allocationKey null W resolve() { whitespace allocationKey }`(forge: Forge) { // Given val fakeFlagKey = forge.anAlphabeticalString() val fakeDefaultValue = forge.aBool() @@ -915,7 +885,7 @@ internal class DatadogFlagsClientTest { // Then assertThat(result.value).isEqualTo(fakeFlagValue) - assertThat(result.flagMetadata).doesNotContainKey("allocationKey") + assertThat(result.allocationKey).isNull() } @Test @@ -1004,7 +974,7 @@ internal class DatadogFlagsClientTest { assertThat(result.errorCode).isEqualTo(ErrorCode.TYPE_MISMATCH) assertThat(result.errorMessage).contains("Flag '$fakeFlagKey'") assertThat(result.errorMessage).contains("has type 'string' but Boolean was requested") - assertThat(result.flagMetadata).isEmpty() + assertThat(result.allocationKey).isEmpty() // Verify no exposure tracked for type mismatch verifyNoInteractions(mockProcessor) @@ -1027,7 +997,7 @@ internal class DatadogFlagsClientTest { assertThat(result.errorCode).isEqualTo(ErrorCode.FLAG_NOT_FOUND) assertThat(result.errorMessage).contains("Flag '$fakeFlagKey'") assertThat(result.errorMessage).contains("Flag not found") - assertThat(result.flagMetadata).isEmpty() + assertThat(result.allocationKey).isEmpty() // Verify no exposure tracked when flag not found verifyNoInteractions(mockProcessor) @@ -1050,7 +1020,7 @@ internal class DatadogFlagsClientTest { assertThat(result.errorCode).isEqualTo(ErrorCode.PROVIDER_NOT_READY) assertThat(result.errorMessage).contains("Flag '$fakeFlagKey'") assertThat(result.errorMessage).contains("Provider not ready") - assertThat(result.flagMetadata).isEmpty() + assertThat(result.allocationKey).isEmpty() // Verify no exposure tracked when provider not ready verifyNoInteractions(mockProcessor) @@ -1084,7 +1054,7 @@ internal class DatadogFlagsClientTest { assertThat(result.errorCode).isEqualTo(ErrorCode.PARSE_ERROR) assertThat(result.errorMessage).contains("Flag '$fakeFlagKey'") assertThat(result.errorMessage).contains("Failed to parse value") - assertThat(result.flagMetadata).isEmpty() + assertThat(result.allocationKey).isEmpty() // Verify no exposure tracked for parse error verifyNoInteractions(mockProcessor) From 0216bb5db432b9e72519cfb0f54947aac95c1d7f Mon Sep 17 00:00:00 2001 From: typotter Date: Mon, 22 Jun 2026 16:38:29 -0600 Subject: [PATCH 2/3] FFL-2510: keep flagMetadata+extraLogging on ResolutionDetails (backwards compat) --- features/dd-sdk-android-flags/api/apiSurface | 2 +- .../api/dd-sdk-android-flags.api | 12 +++++++----- .../android/flags/internal/DatadogFlagsClient.kt | 16 ++++++++++++++++ .../android/flags/model/ResolutionDetails.kt | 6 ++++-- .../flags/internal/DatadogFlagsClientTest.kt | 12 ++++++++---- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/features/dd-sdk-android-flags/api/apiSurface b/features/dd-sdk-android-flags/api/apiSurface index 8f084a7096..c154602339 100644 --- a/features/dd-sdk-android-flags/api/apiSurface +++ b/features/dd-sdk-android-flags/api/apiSurface @@ -56,7 +56,7 @@ sealed class com.datadog.android.flags.model.FlagsClientState data class Error : FlagsClientState constructor(Throwable? = null) data class com.datadog.android.flags.model.ResolutionDetails - constructor(T, String? = null, ResolutionReason? = null, ErrorCode? = null, String? = null, Map = emptyMap()) + constructor(T, String? = null, ResolutionReason? = null, ErrorCode? = null, String? = null, String? = null, Map = emptyMap()) enum com.datadog.android.flags.model.ResolutionReason - STATIC - DEFAULT diff --git a/features/dd-sdk-android-flags/api/dd-sdk-android-flags.api b/features/dd-sdk-android-flags/api/dd-sdk-android-flags.api index d73cd91762..6d36dee787 100644 --- a/features/dd-sdk-android-flags/api/dd-sdk-android-flags.api +++ b/features/dd-sdk-android-flags/api/dd-sdk-android-flags.api @@ -457,17 +457,19 @@ public final class com/datadog/android/flags/model/FlagsClientState$Stale : com/ } public final class com/datadog/android/flags/model/ResolutionDetails { - public fun (Ljava/lang/Object;Ljava/lang/String;Lcom/datadog/android/flags/model/ResolutionReason;Lcom/datadog/android/flags/model/ErrorCode;Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/Object;Ljava/lang/String;Lcom/datadog/android/flags/model/ResolutionReason;Lcom/datadog/android/flags/model/ErrorCode;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/Object;Ljava/lang/String;Lcom/datadog/android/flags/model/ResolutionReason;Lcom/datadog/android/flags/model/ErrorCode;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/Object;Ljava/lang/String;Lcom/datadog/android/flags/model/ResolutionReason;Lcom/datadog/android/flags/model/ErrorCode;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Object; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lcom/datadog/android/flags/model/ResolutionReason; public final fun component4 ()Lcom/datadog/android/flags/model/ErrorCode; public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Ljava/util/Map; - public final fun copy (Ljava/lang/Object;Ljava/lang/String;Lcom/datadog/android/flags/model/ResolutionReason;Lcom/datadog/android/flags/model/ErrorCode;Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/flags/model/ResolutionDetails; - public static synthetic fun copy$default (Lcom/datadog/android/flags/model/ResolutionDetails;Ljava/lang/Object;Ljava/lang/String;Lcom/datadog/android/flags/model/ResolutionReason;Lcom/datadog/android/flags/model/ErrorCode;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/flags/model/ResolutionDetails; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/util/Map; + public final fun copy (Ljava/lang/Object;Ljava/lang/String;Lcom/datadog/android/flags/model/ResolutionReason;Lcom/datadog/android/flags/model/ErrorCode;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/flags/model/ResolutionDetails; + public static synthetic fun copy$default (Lcom/datadog/android/flags/model/ResolutionDetails;Ljava/lang/Object;Ljava/lang/String;Lcom/datadog/android/flags/model/ResolutionReason;Lcom/datadog/android/flags/model/ErrorCode;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/flags/model/ResolutionDetails; public fun equals (Ljava/lang/Object;)Z + public final fun getAllocationKey ()Ljava/lang/String; public final fun getErrorCode ()Lcom/datadog/android/flags/model/ErrorCode; public final fun getErrorMessage ()Ljava/lang/String; public final fun getFlagMetadata ()Ljava/util/Map; diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt index ae47c41203..86e320eb5e 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt @@ -444,6 +444,22 @@ internal class DatadogFlagsClient( } } + private fun extractMetadata(extraLogging: org.json.JSONObject): Map { + if (extraLogging.length() == 0) { + return emptyMap() + } + + val metadata = mutableMapOf() + extraLogging.keys().forEach { key -> + val value = extraLogging.opt(key) + when (value) { + is String, is Number, is Boolean -> metadata[key] = value + } + } + + return metadata + } + private fun trackResolution(resolution: InternalResolution.Success) { trackResolution(resolution.flagKey, resolution.flag, resolution.context) } diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/model/ResolutionDetails.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/model/ResolutionDetails.kt index 2f98153267..40f2bb8461 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/model/ResolutionDetails.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/model/ResolutionDetails.kt @@ -18,7 +18,8 @@ package com.datadog.android.flags.model * @property reason Optional reason code explaining why this value was resolved. * @property errorCode Optional error code if the resolution failed. Null indicates successful resolution. * @property errorMessage Optional human-readable error message providing additional context about failures. - * @property allocationKey The allocation key from the flag assignment. Empty string if not available. + * @property allocationKey The allocation key from the flag assignment. Null if not available. + * @property flagMetadata Map of arbitrary metadata associated with the flag (string keys, primitive values). Empty if no metadata. */ data class ResolutionDetails( val value: T, @@ -26,5 +27,6 @@ data class ResolutionDetails( val reason: ResolutionReason? = null, val errorCode: ErrorCode? = null, val errorMessage: String? = null, - val allocationKey: String = "" + val allocationKey: String? = null, + val flagMetadata: Map = emptyMap() ) diff --git a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/DatadogFlagsClientTest.kt b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/DatadogFlagsClientTest.kt index b63186c4b5..bffd4498e3 100644 --- a/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/DatadogFlagsClientTest.kt +++ b/features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/DatadogFlagsClientTest.kt @@ -974,7 +974,8 @@ internal class DatadogFlagsClientTest { assertThat(result.errorCode).isEqualTo(ErrorCode.TYPE_MISMATCH) assertThat(result.errorMessage).contains("Flag '$fakeFlagKey'") assertThat(result.errorMessage).contains("has type 'string' but Boolean was requested") - assertThat(result.allocationKey).isEmpty() + assertThat(result.allocationKey).isNull() + assertThat(result.flagMetadata).isEmpty() // Verify no exposure tracked for type mismatch verifyNoInteractions(mockProcessor) @@ -997,7 +998,8 @@ internal class DatadogFlagsClientTest { assertThat(result.errorCode).isEqualTo(ErrorCode.FLAG_NOT_FOUND) assertThat(result.errorMessage).contains("Flag '$fakeFlagKey'") assertThat(result.errorMessage).contains("Flag not found") - assertThat(result.allocationKey).isEmpty() + assertThat(result.allocationKey).isNull() + assertThat(result.flagMetadata).isEmpty() // Verify no exposure tracked when flag not found verifyNoInteractions(mockProcessor) @@ -1020,7 +1022,8 @@ internal class DatadogFlagsClientTest { assertThat(result.errorCode).isEqualTo(ErrorCode.PROVIDER_NOT_READY) assertThat(result.errorMessage).contains("Flag '$fakeFlagKey'") assertThat(result.errorMessage).contains("Provider not ready") - assertThat(result.allocationKey).isEmpty() + assertThat(result.allocationKey).isNull() + assertThat(result.flagMetadata).isEmpty() // Verify no exposure tracked when provider not ready verifyNoInteractions(mockProcessor) @@ -1054,7 +1057,8 @@ internal class DatadogFlagsClientTest { assertThat(result.errorCode).isEqualTo(ErrorCode.PARSE_ERROR) assertThat(result.errorMessage).contains("Flag '$fakeFlagKey'") assertThat(result.errorMessage).contains("Failed to parse value") - assertThat(result.allocationKey).isEmpty() + assertThat(result.allocationKey).isNull() + assertThat(result.flagMetadata).isEmpty() // Verify no exposure tracked for parse error verifyNoInteractions(mockProcessor) From d9b4185ac7921bca44c3b571930db9e6ddd81b61 Mon Sep 17 00:00:00 2001 From: typotter Date: Tue, 23 Jun 2026 22:43:00 -0600 Subject: [PATCH 3/3] FFL-2510: fix dead extractMetadata, allocationKey collision, add tests Remove dead extractMetadata (duplicate of buildMetadata, no callers). In toEvaluationMetadata, iterate flagMetadata first then write allocationKey last so typed field always wins over any flagMetadata "allocationKey" entry. Add combined allocationKey+flagMetadata test and collision test to ConvertersTest. --- .../internal/adapters/Converters.kt | 2 +- .../internal/adapters/ConvertersTest.kt | 43 ++++++++++++++++++- .../flags/internal/DatadogFlagsClient.kt | 16 ------- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/features/dd-sdk-android-flags-openfeature/src/main/kotlin/com/datadog/android/flags/openfeature/internal/adapters/Converters.kt b/features/dd-sdk-android-flags-openfeature/src/main/kotlin/com/datadog/android/flags/openfeature/internal/adapters/Converters.kt index f42e799e6a..81bc37d0f3 100644 --- a/features/dd-sdk-android-flags-openfeature/src/main/kotlin/com/datadog/android/flags/openfeature/internal/adapters/Converters.kt +++ b/features/dd-sdk-android-flags-openfeature/src/main/kotlin/com/datadog/android/flags/openfeature/internal/adapters/Converters.kt @@ -46,7 +46,6 @@ internal fun ResolutionDetails.toProviderEvaluation(): ProviderEval private fun Map.toEvaluationMetadata(allocationKey: String? = null): EvaluationMetadata { val builder = Builder() - allocationKey?.let { builder.putString("allocationKey", it) } forEach { (key, value) -> when (value) { is String -> builder.putString(key, value) @@ -56,6 +55,7 @@ private fun Map.toEvaluationMetadata(allocationKey: String? = null) else -> builder.putString(key, value.toString()) } } + allocationKey?.let { builder.putString("allocationKey", it) } return builder.build() } diff --git a/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/internal/adapters/ConvertersTest.kt b/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/internal/adapters/ConvertersTest.kt index 939cf6c2d8..96adb2430a 100644 --- a/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/internal/adapters/ConvertersTest.kt +++ b/features/dd-sdk-android-flags-openfeature/src/test/kotlin/com/datadog/android/flags/openfeature/internal/adapters/ConvertersTest.kt @@ -135,7 +135,6 @@ internal class ConvertersTest { assertThat(result.reason).isEqualTo("ERROR") } - @Test fun `M surface allocationKey in metadata W toProviderEvaluation() {resolution with allocationKey}`( @StringForgery fakeAllocationKey: String, @@ -192,6 +191,48 @@ internal class ConvertersTest { assertThat(result.metadata.getString("allocationKey")).isNull() } + @Test + fun `M surface allocationKey and flagMetadata W toProviderEvaluation() {both present}`( + @StringForgery fakeAllocationKey: String, + @BoolForgery fakeValue: Boolean + ) { + // Given + val resolution = ResolutionDetails( + value = fakeValue, + reason = ResolutionReason.TARGETING_MATCH, + allocationKey = fakeAllocationKey, + flagMetadata = mapOf("env" to "prod") + ) + + // When + val result = resolution.toProviderEvaluation() + + // Then — allocationKey and flagMetadata entries both surfaced + assertThat(result.metadata.getString("allocationKey")).isEqualTo(fakeAllocationKey) + assertThat(result.metadata.getString("env")).isEqualTo("prod") + } + + @Test + fun `M typed allocationKey wins W toProviderEvaluation() {flagMetadata also contains allocationKey}`( + @StringForgery fakeAllocationKey: String, + @StringForgery fakeMetadataAllocationKey: String, + @BoolForgery fakeValue: Boolean + ) { + // Given — flagMetadata has an "allocationKey" entry that should be overridden by the typed field + val resolution = ResolutionDetails( + value = fakeValue, + reason = ResolutionReason.TARGETING_MATCH, + allocationKey = fakeAllocationKey, + flagMetadata = mapOf("allocationKey" to fakeMetadataAllocationKey) + ) + + // When + val result = resolution.toProviderEvaluation() + + // Then — typed allocationKey field wins over flagMetadata entry + assertThat(result.metadata.getString("allocationKey")).isEqualTo(fakeAllocationKey) + } + // endregion // region toOpenFeatureErrorCode diff --git a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt index 86e320eb5e..ae47c41203 100644 --- a/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt +++ b/features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/DatadogFlagsClient.kt @@ -444,22 +444,6 @@ internal class DatadogFlagsClient( } } - private fun extractMetadata(extraLogging: org.json.JSONObject): Map { - if (extraLogging.length() == 0) { - return emptyMap() - } - - val metadata = mutableMapOf() - extraLogging.keys().forEach { key -> - val value = extraLogging.opt(key) - when (value) { - is String, is Number, is Boolean -> metadata[key] = value - } - } - - return metadata - } - private fun trackResolution(resolution: InternalResolution.Success) { trackResolution(resolution.flagKey, resolution.flag, resolution.context) }