Skip to content

Commit 493a1ea

Browse files
authored
Feature/craig/map auction variant (#710)
* Add EU auction detection to referrer logic * Add unit test to verify no duplicate variant keys exist * Quieten down log statements * Rename reserved EU auction variant * Fix typo in variable name * Add custom t= param for users sourced from the EU auction * Hardcode expected url key/params into test to make it change-resilient * Update test names for more clarity
1 parent 476fee6 commit 493a1ea

File tree

13 files changed

+144
-35
lines changed

13 files changed

+144
-35
lines changed

app/src/androidTest/java/com/duckduckgo/app/browser/DuckDuckGoRequestRewriterTest.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.duckduckgo.app.browser
1818

1919
import android.net.Uri
2020
import com.duckduckgo.app.global.AppUrl.ParamKey
21+
import com.duckduckgo.app.referral.AppReferrerDataStore
2122
import com.duckduckgo.app.statistics.VariantManager
2223
import com.duckduckgo.app.statistics.model.Atb
2324
import com.duckduckgo.app.statistics.store.StatisticsDataStore
@@ -30,14 +31,16 @@ import org.junit.Test
3031
class DuckDuckGoRequestRewriterTest {
3132

3233
private lateinit var testee: DuckDuckGoRequestRewriter
33-
private var mockStatisticsStore: StatisticsDataStore = mock()
34-
private var mockVariantManager: VariantManager = mock()
34+
private val mockStatisticsStore: StatisticsDataStore = mock()
35+
private val mockVariantManager: VariantManager = mock()
36+
private val mockAppReferrerDataStore: AppReferrerDataStore = mock()
3537
private lateinit var builder: Uri.Builder
3638

3739
@Before
3840
fun before() {
3941
whenever(mockVariantManager.getVariant()).thenReturn(VariantManager.DEFAULT_VARIANT)
40-
testee = DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector(), mockStatisticsStore, mockVariantManager)
42+
whenever(mockAppReferrerDataStore.installedFromEuAuction).thenReturn(false)
43+
testee = DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector(), mockStatisticsStore, mockVariantManager, mockAppReferrerDataStore)
4144
builder = Uri.Builder()
4245
}
4346

@@ -49,6 +52,15 @@ class DuckDuckGoRequestRewriterTest {
4952
assertEquals("ddg_android", uri.getQueryParameter(ParamKey.SOURCE))
5053
}
5154

55+
@Test
56+
fun whenAddingCustomParamsAndUserSourcedFromEuAuctionThenEuSourceParameterIsAdded() {
57+
whenever(mockAppReferrerDataStore.installedFromEuAuction).thenReturn(true)
58+
testee.addCustomQueryParams(builder)
59+
val uri = builder.build()
60+
assertTrue(uri.queryParameterNames.contains(ParamKey.SOURCE))
61+
assertEquals("ddg_androideu", uri.getQueryParameter(ParamKey.SOURCE))
62+
}
63+
5264
@Test
5365
fun whenAddingCustomParamsIfStoreContainsAtbIsAdded() {
5466
whenever(mockStatisticsStore.atb).thenReturn(Atb("v105-2ma"))

app/src/androidTest/java/com/duckduckgo/app/browser/QueryUrlConverterTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.duckduckgo.app.browser
1818

1919
import android.net.Uri
2020
import com.duckduckgo.app.browser.omnibar.QueryUrlConverter
21+
import com.duckduckgo.app.referral.AppReferrerDataStore
2122
import com.duckduckgo.app.statistics.VariantManager
2223
import com.duckduckgo.app.statistics.store.StatisticsDataStore
2324
import com.nhaarman.mockitokotlin2.mock
@@ -28,7 +29,8 @@ class QueryUrlConverterTest {
2829

2930
private var mockStatisticsStore: StatisticsDataStore = mock()
3031
private val variantManager: VariantManager = mock()
31-
private val requestRewriter = DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector(), mockStatisticsStore, variantManager)
32+
private val mockAppReferrerDataStore: AppReferrerDataStore = mock()
33+
private val requestRewriter = DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector(), mockStatisticsStore, variantManager, mockAppReferrerDataStore)
3234
private val testee: QueryUrlConverter = QueryUrlConverter(requestRewriter)
3335

3436
@Test

app/src/androidTest/java/com/duckduckgo/app/launch/LaunchViewModelTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class LaunchViewModelTest {
120120
override suspend fun waitForReferrerCode(): ParsedReferrerResult {
121121
if (mockDelayMs > 0) delay(mockDelayMs)
122122

123-
return ParsedReferrerResult.ReferrerFound(referrer)
123+
return ParsedReferrerResult.CampaignReferrerFound(referrer)
124124
}
125125

126126
override fun initialiseReferralRetrieval() {

app/src/androidTest/java/com/duckduckgo/app/referral/QueryParamReferrerParserTest.kt

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616

1717
package com.duckduckgo.app.referral
1818

19-
import com.duckduckgo.app.referral.ParsedReferrerResult.ReferrerFound
19+
import com.duckduckgo.app.referral.ParsedReferrerResult.CampaignReferrerFound
20+
import com.duckduckgo.app.referral.ParsedReferrerResult.EuAuctionReferrerFound
2021
import org.junit.Assert.assertEquals
2122
import org.junit.Assert.assertTrue
2223
import org.junit.Test
@@ -33,13 +34,13 @@ class QueryParamReferrerParserTest {
3334
@Test
3435
fun whenReferrerContainsTargetAndLongSuffixThenShortenedReferrerFound() {
3536
val result = testee.parse("DDGRAABC")
36-
verifyReferrerFound("AB", result)
37+
verifyCampaignReferrerFound("AB", result)
3738
}
3839

3940
@Test
4041
fun whenReferrerContainsTargetAndTwoCharSuffixThenReferrerFound() {
4142
val result = testee.parse("DDGRAXY")
42-
verifyReferrerFound("XY", result)
43+
verifyCampaignReferrerFound("XY", result)
4344
}
4445

4546
@Test
@@ -62,27 +63,57 @@ class QueryParamReferrerParserTest {
6263
@Test
6364
fun whenReferrerContainsTargetAsFirstParamThenReferrerFound() {
6465
val result = testee.parse("key1=DDGRAAB&key2=foo&key3=bar")
65-
verifyReferrerFound("AB", result)
66+
verifyCampaignReferrerFound("AB", result)
6667
}
6768

6869
@Test
6970
fun whenReferrerContainsTargetAsLastParamThenReferrerFound() {
7071
val result = testee.parse("key1=foo&key2=bar&key3=DDGRAAB")
71-
verifyReferrerFound("AB", result)
72+
verifyCampaignReferrerFound("AB", result)
7273
}
7374

7475
@Test
7576
fun whenReferrerContainsTargetWithDifferentCaseThenNoReferrerFound() {
7677
verifyReferrerNotFound(testee.parse("ddgraAB"))
7778
}
7879

79-
private fun verifyReferrerFound(expectedReferrer: String, result: ParsedReferrerResult) {
80-
assertTrue(result is ReferrerFound)
81-
val value = (result as ReferrerFound).campaignSuffix
80+
@Test
81+
fun whenReferrerContainsEuAuctionDataThenEuActionReferrerFound() {
82+
val result = testee.parse("$INSTALLATION_SOURCE_KEY=$INSTALLATION_SOURCE_EU_AUCTION_VALUE")
83+
assertTrue(result is EuAuctionReferrerFound)
84+
}
85+
86+
@Test
87+
fun whenReferrerContainsBothEuAuctionAndCampaignReferrerDataThenEuActionReferrerFound() {
88+
val result = testee.parse("key1=DDGRAAB&key2=foo&key3=bar&$INSTALLATION_SOURCE_KEY=$INSTALLATION_SOURCE_EU_AUCTION_VALUE")
89+
assertTrue(result is EuAuctionReferrerFound)
90+
}
91+
92+
@Test
93+
fun whenReferrerContainsInstallationSourceKeyButNotMatchingValueThenNoReferrerFound() {
94+
val result = testee.parse("$INSTALLATION_SOURCE_KEY=bar")
95+
verifyReferrerNotFound(result)
96+
}
97+
98+
@Test
99+
fun whenReferrerContainsInstallationSourceKeyAndNoEuAuctionValueButHasCampaignReferrerDataThenCampaignReferrerFound() {
100+
val result = testee.parse("key1=DDGRAAB&key2=foo&key3=bar&$INSTALLATION_SOURCE_KEY=bar")
101+
verifyCampaignReferrerFound("AB", result)
102+
}
103+
104+
private fun verifyCampaignReferrerFound(expectedReferrer: String, result: ParsedReferrerResult) {
105+
assertTrue(result is CampaignReferrerFound)
106+
val value = (result as CampaignReferrerFound).campaignSuffix
82107
assertEquals(expectedReferrer, value)
83108
}
84109

85110
private fun verifyReferrerNotFound(result: ParsedReferrerResult) {
86111
assertTrue(result is ParsedReferrerResult.ReferrerNotFound)
87112
}
113+
114+
companion object {
115+
private const val INSTALLATION_SOURCE_KEY = "utm_source"
116+
private const val INSTALLATION_SOURCE_EU_AUCTION_VALUE = "eea-search-choice"
117+
}
118+
88119
}

app/src/androidTest/java/com/duckduckgo/app/statistics/AtbInitializerTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,6 @@ class AtbInitializerTest {
8888

8989
private suspend fun referrerAnswer(delayMs: Long): Answer<ParsedReferrerResult> {
9090
delay(delayMs)
91-
return Answer { ParsedReferrerResult.ReferrerFound("") }
91+
return Answer { ParsedReferrerResult.CampaignReferrerFound("") }
9292
}
9393
}

app/src/androidTest/java/com/duckduckgo/app/statistics/VariantManagerTest.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
package com.duckduckgo.app.statistics
1818

1919
import com.duckduckgo.app.statistics.VariantManager.VariantFeature.*
20-
import org.junit.Assert.assertEquals
21-
import org.junit.Assert.assertTrue
22-
import org.junit.Assert.fail
20+
import org.junit.Assert.*
2321
import org.junit.Test
2422

2523
class VariantManagerTest {
@@ -131,6 +129,17 @@ class VariantManagerTest {
131129
assertTrue(variant.hasFeature(ConceptTest))
132130
}
133131

132+
@Test
133+
fun verifyNoDuplicateVariantNames() {
134+
val existingNames = mutableSetOf<String>()
135+
variants.forEach {
136+
if (!existingNames.add(it.key)) {
137+
fail("Duplicate variant name found: ${it.key}")
138+
}
139+
}
140+
}
141+
142+
134143
@Suppress("SameParameterValue")
135144
private fun assertEqualsDouble(expected: Double, actual: Double) {
136145
val comparison = expected.compareTo(actual)

app/src/main/java/com/duckduckgo/app/browser/DuckDuckGoRequestRewriter.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.duckduckgo.app.browser
1919
import android.net.Uri
2020
import com.duckduckgo.app.global.AppUrl.ParamKey
2121
import com.duckduckgo.app.global.AppUrl.ParamValue
22+
import com.duckduckgo.app.referral.AppReferrerDataStore
2223
import com.duckduckgo.app.statistics.VariantManager
2324
import com.duckduckgo.app.statistics.store.StatisticsDataStore
2425
import timber.log.Timber
@@ -32,7 +33,8 @@ interface RequestRewriter {
3233
class DuckDuckGoRequestRewriter(
3334
private val duckDuckGoUrlDetector: DuckDuckGoUrlDetector,
3435
private val statisticsStore: StatisticsDataStore,
35-
private val variantManager: VariantManager
36+
private val variantManager: VariantManager,
37+
private val appReferrerDataStore: AppReferrerDataStore
3638
) : RequestRewriter {
3739

3840
override fun rewriteRequestWithCustomQueryParams(request: Uri): Uri {
@@ -67,6 +69,8 @@ class DuckDuckGoRequestRewriter(
6769
if (atb != null) {
6870
builder.appendQueryParameter(ParamKey.ATB, atb.formatWithVariant(variantManager.getVariant()))
6971
}
70-
builder.appendQueryParameter(ParamKey.SOURCE, ParamValue.SOURCE)
72+
73+
val sourceValue = if (appReferrerDataStore.installedFromEuAuction) ParamValue.SOURCE_EU_AUCTION else ParamValue.SOURCE
74+
builder.appendQueryParameter(ParamKey.SOURCE, sourceValue)
7175
}
7276
}

app/src/main/java/com/duckduckgo/app/browser/di/BrowserModule.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import com.duckduckgo.app.global.file.FileDeleter
3939
import com.duckduckgo.app.global.install.AppInstallStore
4040
import com.duckduckgo.app.httpsupgrade.HttpsUpgrader
4141
import com.duckduckgo.app.privacy.db.PrivacyProtectionCountDao
42+
import com.duckduckgo.app.referral.AppReferrerDataStore
4243
import com.duckduckgo.app.statistics.VariantManager
4344
import com.duckduckgo.app.statistics.pixels.Pixel
4445
import com.duckduckgo.app.statistics.store.OfflinePixelCountDataStore
@@ -57,9 +58,10 @@ class BrowserModule {
5758
fun duckDuckGoRequestRewriter(
5859
urlDetector: DuckDuckGoUrlDetector,
5960
statisticsStore: StatisticsDataStore,
60-
variantManager: VariantManager
61+
variantManager: VariantManager,
62+
appReferrerDataStore: AppReferrerDataStore
6163
): RequestRewriter {
62-
return DuckDuckGoRequestRewriter(urlDetector, statisticsStore, variantManager)
64+
return DuckDuckGoRequestRewriter(urlDetector, statisticsStore, variantManager, appReferrerDataStore)
6365
}
6466

6567
@Provides

app/src/main/java/com/duckduckgo/app/global/AppUrl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@ class AppUrl {
4040

4141
object ParamValue {
4242
const val SOURCE = "ddg_android"
43+
const val SOURCE_EU_AUCTION = "ddg_androideu"
4344
}
4445
}

app/src/main/java/com/duckduckgo/app/referral/AppInstallationReferrerParser.kt

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616

1717
package com.duckduckgo.app.referral
1818

19-
import com.duckduckgo.app.referral.ParsedReferrerResult.ReferrerFound
20-
import com.duckduckgo.app.referral.ParsedReferrerResult.ReferrerNotFound
19+
import com.duckduckgo.app.referral.ParsedReferrerResult.*
2120
import timber.log.Timber
2221

2322

@@ -33,16 +32,41 @@ class QueryParamReferrerParser : AppInstallationReferrerParser {
3332
val referrerParts = splitIntoConstituentParts(referrer)
3433
if (referrerParts.isNullOrEmpty()) return ReferrerNotFound(fromCache = false)
3534

35+
val auctionReferrer = extractEuAuctionReferrer(referrerParts)
36+
if (auctionReferrer is EuAuctionReferrerFound) {
37+
return auctionReferrer
38+
}
39+
40+
return extractCampaignReferrer(referrerParts)
41+
}
42+
43+
private fun extractEuAuctionReferrer(referrerParts: List<String>): ParsedReferrerResult {
44+
Timber.d("Looking for Google EU Auction referrer data")
45+
for (part in referrerParts) {
46+
47+
Timber.v("Analysing query param part: $part")
48+
if (part.startsWith(INSTALLATION_SOURCE_KEY) && part.endsWith(INSTALLATION_SOURCE_EU_AUCTION_VALUE)) {
49+
Timber.i("App installed as a result of the EU auction")
50+
return EuAuctionReferrerFound()
51+
}
52+
}
53+
54+
Timber.d("App not installed as a result of EU auction")
55+
return ReferrerNotFound()
56+
}
57+
58+
private fun extractCampaignReferrer(referrerParts: List<String>): ParsedReferrerResult {
59+
Timber.d("Looking for regular referrer data")
3660
for (part in referrerParts) {
37-
Timber.d("Analysing query param part: $part")
3861

62+
Timber.v("Analysing query param part: $part")
3963
if (part.contains(CAMPAIGN_NAME_PREFIX)) {
4064
return extractCampaignNameSuffix(part, CAMPAIGN_NAME_PREFIX)
4165
}
4266
}
4367

44-
Timber.i("Referrer information does not contain inspected campaign names")
45-
return ReferrerNotFound(fromCache = false)
68+
Timber.d("Referrer information does not contain inspected campaign names")
69+
return ReferrerNotFound()
4670
}
4771

4872
private fun extractCampaignNameSuffix(part: String, prefix: String): ParsedReferrerResult {
@@ -56,7 +80,7 @@ class QueryParamReferrerParser : AppInstallationReferrerParser {
5680

5781
val condensedSuffix = suffix.take(2)
5882
Timber.i("Found suffix $condensedSuffix (looking for ${prefix}, found in $part)")
59-
return ReferrerFound(condensedSuffix)
83+
return CampaignReferrerFound(condensedSuffix)
6084
}
6185

6286
private fun stripCampaignName(fullCampaignName: String, prefix: String): String {
@@ -69,11 +93,15 @@ class QueryParamReferrerParser : AppInstallationReferrerParser {
6993

7094
companion object {
7195
private const val CAMPAIGN_NAME_PREFIX = "DDGRA"
96+
97+
private const val INSTALLATION_SOURCE_KEY = "utm_source"
98+
private const val INSTALLATION_SOURCE_EU_AUCTION_VALUE = "eea-search-choice"
7299
}
73100
}
74101

75102
sealed class ParsedReferrerResult(open val fromCache: Boolean = false) {
76-
data class ReferrerFound(val campaignSuffix: String, override val fromCache: Boolean = false) : ParsedReferrerResult(fromCache)
103+
data class EuAuctionReferrerFound(override val fromCache: Boolean = false) : ParsedReferrerResult(fromCache)
104+
data class CampaignReferrerFound(val campaignSuffix: String, override val fromCache: Boolean = false) : ParsedReferrerResult(fromCache)
77105
data class ReferrerNotFound(override val fromCache: Boolean = false) : ParsedReferrerResult(fromCache)
78106
data class ParseFailure(val reason: ParseFailureReason) : ParsedReferrerResult()
79107
object ReferrerInitialising : ParsedReferrerResult()

0 commit comments

Comments
 (0)