diff --git a/ThirdPartyAdapters/pangle/CHANGELOG.md b/ThirdPartyAdapters/pangle/CHANGELOG.md index fd4f2caf0..376c5a15c 100644 --- a/ThirdPartyAdapters/pangle/CHANGELOG.md +++ b/ThirdPartyAdapters/pangle/CHANGELOG.md @@ -1,5 +1,9 @@ ## Pangle Android Mediation Adapter Changelog +#### Next Version +- Adapter now automatically forwards GDPR consent found inside `IABTCF_AddtlConsent` to the Pangle SDK if GDPR applies. + - The `setGDPRConsent()` and `getGDPRConsent()` methods have been removed from the `PangleMediationAdapter` class. + #### Version 7.8.0.8.0 - Verified compatibility with Pangle SDK version 7.8.0.8. diff --git a/ThirdPartyAdapters/pangle/pangle/build.gradle b/ThirdPartyAdapters/pangle/pangle/build.gradle index 0cb6835a9..c2ce04dbf 100644 --- a/ThirdPartyAdapters/pangle/pangle/build.gradle +++ b/ThirdPartyAdapters/pangle/pangle/build.gradle @@ -105,7 +105,7 @@ task jacocoTestReport(type: JacocoReport, excludes: fileFilter ) - def mainSrc = "$project.projectDir/src/main/kotlin" + def mainSrc = "$project.projectDir/src/main/java" getSourceDirectories().setFrom(files([mainSrc])) getClassDirectories().setFrom(files([debugTree, debugKotlinTree])) diff --git a/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleConstants.java b/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleConstants.java index 434fa4e8f..34c90957e 100644 --- a/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleConstants.java +++ b/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleConstants.java @@ -30,6 +30,16 @@ public class PangleConstants { public static final String ERROR_DOMAIN = "com.google.ads.mediation.pangle"; public static final String PANGLE_SDK_ERROR_DOMAIN = "com.pangle.ads"; + /** Represents the result of a consent check for advertising purposes. */ + public enum ConsentResult { + /** The consent status could not be determined, or consent does not apply. */ + UNKNOWN, + /** The user has given their consent. */ + TRUE, + /** The user has explicitly declined consent. */ + FALSE + } + /** * Error message when the user is a child. * diff --git a/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleInitializer.java b/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleInitializer.java index 56c471f18..a62a831d9 100644 --- a/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleInitializer.java +++ b/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleInitializer.java @@ -15,14 +15,17 @@ package com.google.ads.mediation.pangle; import static com.google.ads.mediation.pangle.PangleConstants.ERROR_INVALID_SERVER_PARAMETERS; +import static com.google.ads.mediation.pangle.PangleMediationAdapter.AD_TECHNOLOGY_PROVIDER_ID; import android.content.Context; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import com.bytedance.sdk.openadsdk.api.PAGConstant.PAGGDPRConsentType; import com.bytedance.sdk.openadsdk.api.init.PAGConfig; import com.bytedance.sdk.openadsdk.api.init.PAGSdk.PAGInitCallback; +import com.google.ads.mediation.pangle.PangleConstants.ConsentResult; import com.google.android.gms.ads.AdError; import java.util.ArrayList; @@ -86,6 +89,15 @@ public void initialize( return; } + int gdprConsent = PAGGDPRConsentType.PAG_GDPR_CONSENT_TYPE_DEFAULT; + ConsentResult consentResult = + PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID); + if (consentResult == ConsentResult.TRUE) { + gdprConsent = PAGGDPRConsentType.PAG_GDPR_CONSENT_TYPE_CONSENT; + } else if (consentResult == ConsentResult.FALSE) { + gdprConsent = PAGGDPRConsentType.PAG_GDPR_CONSENT_TYPE_NO_CONSENT; + } + isInitializing = true; initListeners.add(listener); @@ -95,7 +107,7 @@ public void initialize( .createPAGConfigBuilder() .appId(appId) .setAdxId(PangleConstants.ADX_ID) - .setGDPRConsent(PangleMediationAdapter.getGDPRConsent()) + .setGDPRConsent(gdprConsent) .setUserData( String.format( "[{\"name\":\"mediation\",\"value\":\"google\"},{\"name\":\"adapter_version\",\"value\":\"%s\"}]", diff --git a/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleMediationAdapter.java b/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleMediationAdapter.java index 92365e213..ef12252ff 100644 --- a/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleMediationAdapter.java +++ b/ThirdPartyAdapters/pangle/pangle/src/main/java/com/google/ads/mediation/pangle/PangleMediationAdapter.java @@ -18,16 +18,18 @@ import static com.google.ads.mediation.pangle.PangleConstants.isChildUser; import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; +import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.bytedance.sdk.openadsdk.api.PAGConstant; -import com.bytedance.sdk.openadsdk.api.PAGConstant.PAGGDPRConsentType; import com.bytedance.sdk.openadsdk.api.bidding.PAGBiddingRequest; import com.bytedance.sdk.openadsdk.api.init.BiddingTokenCallback; import com.bytedance.sdk.openadsdk.api.init.PAGConfig; +import com.google.ads.mediation.pangle.PangleConstants.ConsentResult; import com.google.ads.mediation.pangle.PangleInitializer.Listener; import com.google.ads.mediation.pangle.renderer.PangleAppOpenAd; import com.google.ads.mediation.pangle.renderer.PangleBannerAd; @@ -57,6 +59,7 @@ import com.google.android.gms.ads.mediation.rtb.RtbAdapter; import com.google.android.gms.ads.mediation.rtb.RtbSignalData; import com.google.android.gms.ads.mediation.rtb.SignalCallbacks; +import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -64,6 +67,12 @@ public class PangleMediationAdapter extends RtbAdapter { public static final String TAG = PangleMediationAdapter.class.getSimpleName(); + /** + * Pangle ad technology provider ID from + * https://storage.googleapis.com/tcfac/additional-consent-providers.csv + */ + public static final int AD_TECHNOLOGY_PROVIDER_ID = 3100; + @VisibleForTesting static final String ERROR_MESSAGE_MISSING_OR_INVALID_APP_ID = "Missing or invalid App ID."; @@ -77,8 +86,6 @@ public class PangleMediationAdapter extends RtbAdapter { private PangleNativeAd nativeAd; private PangleRewardedAd rewardedAd; - private static int gdpr = -1; - public PangleMediationAdapter() { pangleInitializer = PangleInitializer.getInstance(); pangleSdkWrapper = new PangleSdkWrapper(); @@ -293,34 +300,136 @@ public void loadRewardedAd( } /** - * Set the GDPR setting in Pangle SDK. + * Checks whether the user provided consent to a Google Ad Tech Provider (ATP) in Google’s + * Additional Consent technical specification. For more details, see Google’s Additional Consent technical + * specification. * - * @param gdpr an {@code Integer} value that indicates whether the user consents the use of - * personal data to serve ads under GDPR. See Pangle's - * documentation for more information about what values may be provided. + *

Returns {@link ConsentResult#UNKNOWN} if GDPR does not apply or if positive or negative + * consent was not explicitly detected. + * + * @param context {@link Context} object of your application + * @param vendorId a Google Ad Tech Provider (ATP) ID from + * https://storage.googleapis.com/tcfac/additional-consent-providers.csv + * @return A {@link ConsentResult} indicating consent for the given ATP. */ - public static void setGDPRConsent(@PAGGDPRConsentType int gdpr) { - setGDPRConsent(gdpr, new PangleSdkWrapper()); - } + static @NonNull ConsentResult hasACConsent(@NonNull Context context, int vendorId) { + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); + + int gdprApplies = -1; + try { + gdprApplies = sharedPref.getInt("IABTCF_gdprApplies", -1); + } catch (ClassCastException exception) { + Log.w( + TAG, + "Could not parse IABTCF_gdprApplies as an integer. Did your CMP write it correctly?", + exception); + } - @VisibleForTesting - static void setGDPRConsent(@PAGGDPRConsentType int gdpr, PangleSdkWrapper pangleSdkWrapper) { - if (gdpr != PAGGDPRConsentType.PAG_GDPR_CONSENT_TYPE_CONSENT - && gdpr != PAGGDPRConsentType.PAG_GDPR_CONSENT_TYPE_NO_CONSENT - && gdpr != PAGGDPRConsentType.PAG_GDPR_CONSENT_TYPE_DEFAULT) { - // no-op - Log.w(TAG, "Invalid GDPR value. Pangle SDK only accepts -1, 0 or 1."); - return; + if (gdprApplies != 1) { + return ConsentResult.UNKNOWN; } - if (pangleSdkWrapper.isInitSuccess() && !isChildUser()) { - pangleSdkWrapper.setGdprConsent(gdpr); + + String additionalConsentString = ""; + try { + additionalConsentString = sharedPref.getString("IABTCF_AddtlConsent", ""); + } catch (ClassCastException exception) { + Log.w( + TAG, + "Could not parse IABTCF_AddtlConsent as a string. Did your CMP write it correctly?", + exception); } - PangleMediationAdapter.gdpr = gdpr; - } - public static int getGDPRConsent() { - return gdpr; + if (TextUtils.isEmpty(additionalConsentString)) { + return ConsentResult.UNKNOWN; + } + + String vendorIdString = String.valueOf(vendorId); + String[] additionalConsentParts = additionalConsentString.split("~"); + + int version; + try { + version = Integer.parseInt(String.valueOf(additionalConsentString.charAt(0))); + } catch (Exception exception) { + Log.w( + TAG, + "Could not parse the IABTCF_AddtlConsent spec version. Did your CMP write it correctly?", + exception); + return ConsentResult.UNKNOWN; + } + + if (version == 1) { + // Spec version 1 + Log.w( + TAG, + "The IABTCF_AddtlConsent string uses version 1 of Google’s Additional Consent spec." + + " Version 1 does not report vendors to whom the user denied consent. To detect" + + " vendors that the user denied consent, upgrade to a CMP that supports version 2 of" + + " Google's Additional Consent technical specification."); + + if (additionalConsentParts.length == 1) { + // The AC string had no consented vendor. + return ConsentResult.UNKNOWN; + } else if (additionalConsentParts.length == 2) { + String[] consentedIds = additionalConsentParts[1].split("\\."); + if (Arrays.asList(consentedIds).contains(vendorIdString)) { + return ConsentResult.TRUE; + } + } else { + String errorMessage = + String.format( + "Could not parse the IABTCF_AddtlConsent string: \"%s\". String had more parts than" + + " expected. Did your CMP write IABTCF_AddtlConsent correctly?", + additionalConsentString); + Log.w(TAG, errorMessage); + return ConsentResult.UNKNOWN; + } + + return ConsentResult.UNKNOWN; + } else if (version >= 2) { + // Spec version 2 + if (additionalConsentParts.length < 3) { + String errorMessage = + String.format( + "Could not parse the IABTCF_AddtlConsent string: \"%s\". String had less parts than" + + " expected. Did your CMP write IABTCF_AddtlConsent correctly?", + additionalConsentString); + Log.w(TAG, errorMessage); + return ConsentResult.UNKNOWN; + } + + String[] disclosedIds = additionalConsentParts[2].split("\\."); + if (!disclosedIds[0].equals("dv")) { + String errorMessage = + String.format( + "Could not parse the IABTCF_AddtlConsent string: \"%s\". Expected disclosed vendors" + + " part to have the string \"dv.\". Did your CMP write IABTCF_AddtlConsent" + + " correctly?", + additionalConsentString); + Log.w(TAG, errorMessage); + return ConsentResult.UNKNOWN; + } + + String[] consentedIds = additionalConsentParts[1].split("\\."); + if (Arrays.asList(consentedIds).contains(vendorIdString)) { + return ConsentResult.TRUE; + } + + if (Arrays.asList(disclosedIds).contains(vendorIdString)) { + return ConsentResult.FALSE; + } + + return ConsentResult.UNKNOWN; + } else { + // Unknown spec version + String errorMessage = + String.format( + "Could not parse the IABTCF_AddtlConsent string: \"%s\". Spec version was unexpected." + + " Did your CMP write IABTCF_AddtlConsent correctly?", + additionalConsentString); + Log.w(TAG, errorMessage); + return ConsentResult.UNKNOWN; + } } /** diff --git a/ThirdPartyAdapters/pangle/pangle/src/test/kotlin/com/google/ads/mediation/pangle/PangleInitializerTest.kt b/ThirdPartyAdapters/pangle/pangle/src/test/kotlin/com/google/ads/mediation/pangle/PangleInitializerTest.kt index bfe265a07..46623236e 100644 --- a/ThirdPartyAdapters/pangle/pangle/src/test/kotlin/com/google/ads/mediation/pangle/PangleInitializerTest.kt +++ b/ThirdPartyAdapters/pangle/pangle/src/test/kotlin/com/google/ads/mediation/pangle/PangleInitializerTest.kt @@ -3,8 +3,6 @@ package com.google.ads.mediation.pangle import android.content.Context import androidx.test.core.app.ApplicationProvider import com.bytedance.sdk.openadsdk.api.init.PAGConfig -import com.google.ads.mediation.pangle.utils.GDPRConsentTypesProvider -import com.google.testing.junit.testparameterinjector.TestParameter import org.junit.After import org.junit.Before import org.junit.Test @@ -146,17 +144,6 @@ class PangleInitializerTest { verify(pagConfigBuilder).appId(APP_ID) } - @Test - fun initialize_configuresPangleSdkWithCorrectGDPRConsent( - @TestParameter(valuesProvider = GDPRConsentTypesProvider::class) gdprConsent: Int - ) { - PangleMediationAdapter.setGDPRConsent(gdprConsent) - - pangleInitializer.initialize(context, APP_ID, initializerListener) - - verify(pagConfigBuilder).setGDPRConsent(gdprConsent) - } - companion object { private const val APP_ID = "appId" } diff --git a/ThirdPartyAdapters/pangle/pangle/src/test/kotlin/com/google/ads/mediation/pangle/PangleMediationAdapterTest.kt b/ThirdPartyAdapters/pangle/pangle/src/test/kotlin/com/google/ads/mediation/pangle/PangleMediationAdapterTest.kt index 9e800bf85..21676f715 100644 --- a/ThirdPartyAdapters/pangle/pangle/src/test/kotlin/com/google/ads/mediation/pangle/PangleMediationAdapterTest.kt +++ b/ThirdPartyAdapters/pangle/pangle/src/test/kotlin/com/google/ads/mediation/pangle/PangleMediationAdapterTest.kt @@ -1,19 +1,18 @@ package com.google.ads.mediation.pangle import android.content.Context +import android.content.SharedPreferences import androidx.core.os.bundleOf -import androidx.test.core.app.ApplicationProvider -import com.bytedance.sdk.openadsdk.api.PAGConstant.PAGGDPRConsentType import com.bytedance.sdk.openadsdk.api.PAGConstant.PAGPAConsentType import com.bytedance.sdk.openadsdk.api.init.BiddingTokenCallback import com.google.ads.mediation.pangle.PangleConstants.ERROR_INVALID_SERVER_PARAMETERS +import com.google.ads.mediation.pangle.PangleMediationAdapter.AD_TECHNOLOGY_PROVIDER_ID import com.google.ads.mediation.pangle.PangleMediationAdapter.ERROR_MESSAGE_MISSING_OR_INVALID_APP_ID import com.google.ads.mediation.pangle.renderer.PangleAppOpenAd import com.google.ads.mediation.pangle.renderer.PangleBannerAd import com.google.ads.mediation.pangle.renderer.PangleInterstitialAd import com.google.ads.mediation.pangle.renderer.PangleNativeAd import com.google.ads.mediation.pangle.renderer.PangleRewardedAd -import com.google.ads.mediation.pangle.utils.GDPRConsentTypesProvider import com.google.ads.mediation.pangle.utils.TestConstants.APP_ID_VALUE import com.google.ads.mediation.pangle.utils.TestConstants.PANGLE_INIT_FAILURE_MESSAGE import com.google.ads.mediation.pangle.utils.mockPangleSdkInitializationFailure @@ -41,7 +40,6 @@ import com.google.android.gms.ads.mediation.UnifiedNativeAdMapper import com.google.android.gms.ads.mediation.rtb.RtbSignalData import com.google.android.gms.ads.mediation.rtb.SignalCallbacks import com.google.common.truth.Truth.assertThat -import com.google.testing.junit.testparameterinjector.TestParameter import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -62,7 +60,8 @@ class PangleMediationAdapterTest { // Test subject. private lateinit var pangleMediationAdapter: PangleMediationAdapter - private val context: Context = ApplicationProvider.getApplicationContext() + private val context: Context = mock() + private var sharedPreferences: SharedPreferences = mock() private val pangleInitializer: PangleInitializer = mock() private val pangleSdkWrapper: PangleSdkWrapper = mock() private val appOpenAd: PangleAppOpenAd = mock() @@ -101,12 +100,13 @@ class PangleMediationAdapterTest { @Before fun setUp() { - // Resetting the GDPR and the PA Consent Information to their default value. - PangleMediationAdapter.setGDPRConsent(PAGGDPRConsentType.PAG_GDPR_CONSENT_TYPE_DEFAULT) + // Resetting the PA Consent Information to their default value. PangleMediationAdapter.setPAConsent(PAGPAConsentType.PAG_PA_CONSENT_TYPE_CONSENT) pangleMediationAdapter = PangleMediationAdapter(pangleInitializer, pangleSdkWrapper, pangleFactory) + + whenever(context.getSharedPreferences(any(), any())).thenReturn(sharedPreferences) } @Test @@ -192,47 +192,165 @@ class PangleMediationAdapterTest { verify(initializationCompleteCallback).onInitializationFailed(PANGLE_INIT_FAILURE_MESSAGE) } + // region hasACConsent() Tests @Test - fun getGDPRConsent_returnsTheUpdatedValueWhenCalled() { - // Given the initial PangleMediationAdapter state - // When the GDPRConsent is updated - PangleMediationAdapter.setGDPRConsent(PAGGDPRConsentType.PAG_GDPR_CONSENT_TYPE_NO_CONSENT) + fun hasACConsent_withNegativeGDPRApplies_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(-1) + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) - // Then getGDPRConsent() must return the updated value. - assertThat(PangleMediationAdapter.getGDPRConsent()) - .isEqualTo(PAGGDPRConsentType.PAG_GDPR_CONSENT_TYPE_NO_CONSENT) + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) } @Test - fun setGDPRConsent_ignoresValuesOutsideTheThreeAccepted() { - // Given the initial PangleMdiationAdapter state - // When the GDPRConsent is updated to a different range of values that are not allowed. - PangleMediationAdapter.setGDPRConsent(-2) - // Then the value is only updated when valid options are sent (-1, 0 or 1). - assertThat(PangleMediationAdapter.getGDPRConsent()) - .isEqualTo(PAGGDPRConsentType.PAG_GDPR_CONSENT_TYPE_DEFAULT) - PangleMediationAdapter.setGDPRConsent(2) - assertThat(PangleMediationAdapter.getGDPRConsent()) - .isEqualTo(PAGGDPRConsentType.PAG_GDPR_CONSENT_TYPE_DEFAULT) - PangleMediationAdapter.setGDPRConsent(-1) - assertThat(PangleMediationAdapter.getGDPRConsent()).isEqualTo(-1) - PangleMediationAdapter.setGDPRConsent(0) - assertThat(PangleMediationAdapter.getGDPRConsent()).isEqualTo(0) - PangleMediationAdapter.setGDPRConsent(1) - assertThat(PangleMediationAdapter.getGDPRConsent()).isEqualTo(1) + fun hasACConsent_withZeroGDPRApplies_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(0) + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) + } + + @Test + fun hasACConsent_withInvalidGDPRApplies_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())) + .thenThrow(ClassCastException::class.java) + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) } @Test - fun setGDPRConsent_ifPangleSDKIsInitialized_setsGDPRConsentOnPangleSdk( - @TestParameter(valuesProvider = GDPRConsentTypesProvider::class) gdprConsent: Int - ) { - whenever(pangleSdkWrapper.isInitSuccess()).thenReturn(true) + fun hasACConsent_withInvalidAdditionalConsent_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())) + .thenThrow(ClassCastException::class.java) - PangleMediationAdapter.setGDPRConsent(gdprConsent, pangleSdkWrapper) + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) - verify(pangleSdkWrapper).setGdprConsent(gdprConsent) + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) } + @Test + fun hasACConsent_withUnknownSpecVersion_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())) + .thenReturn("0~3100.1~dv.2.3") + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) + } + + @Test + fun hasACConsent_withInvalidSpecVersion_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())) + .thenReturn("a~3100.1~dv.2.3") + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) + } + + @Test + fun hasACConsent_withVersionOneSpec_withNoConsentedVendor_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())).thenReturn("1~") + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) + } + + @Test + fun hasACConsent_withVersionOneSpec_withPangleIncludedInAdditionalConsent_returnsTrue() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())).thenReturn("1~1.3100") + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.TRUE) + } + + @Test + fun hasACConsent_withVersionOneSpec_withPangleNotIncludedInAdditionalConsent_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())).thenReturn("1~1.2") + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) + } + + @Test + fun hasACConsent_withVersionOneSpec_withUnexpectedParts_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())) + .thenReturn("1~3100.1~dv.2.3") + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) + } + + @Test + fun hasACConsent_withVersionTwoSpec_withInvalidDisclosedFormat_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())) + .thenReturn("2~3100.1~ax.2.3") + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) + } + + @Test + fun hasACConsent_withVersionTwoSpec_withUnexpectedParts_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())).thenReturn("2~3100.1") + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) + } + + @Test + fun hasACConsent_withVersionTwoSpec_withPangleIncludedInAdditionalConsent_returnsTrue() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())) + .thenReturn("2~1.3100~dv.2.3") + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.TRUE) + } + + @Test + fun hasACConsent_withVersionTwoSpec_withPangleDisclosedInAdditionalConsent_returnsFalse() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())) + .thenReturn("2~1.2~dv.3100.3") + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.FALSE) + } + + @Test + fun hasACConsent_withVersionTwoSpec_withPangleMissingInAdditionalConsent_returnsUnknown() { + whenever(sharedPreferences.getInt(eq("IABTCF_gdprApplies"), any())).thenReturn(1) + whenever(sharedPreferences.getString(eq("IABTCF_AddtlConsent"), any())) + .thenReturn("2~1.2~dv.3.4") + + val consentResult = PangleMediationAdapter.hasACConsent(context, AD_TECHNOLOGY_PROVIDER_ID) + + assertThat(consentResult).isEqualTo(PangleConstants.ConsentResult.UNKNOWN) + } + + // endregion + @Test fun getVersionInfo_ifAdapterVersionHasLessThanFourParts_returnsZeros() { // "3.1.4" is invalid because adapter version should contain at least four parts delimited by