Skip to content

Commit fe91bc1

Browse files
committed
cover client with tests
1 parent ebee2a6 commit fe91bc1

File tree

3 files changed

+138
-5
lines changed

3 files changed

+138
-5
lines changed

attributed-metrics/attributed-metrics-impl/src/main/java/com/duckduckgo/app/attributed/metrics/impl/AttributedMetricsState.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,18 @@ class RealAttributedMetricsState @Inject constructor(
111111
dataStore.setActive(true)
112112
}
113113
}
114+
logClientStatus()
114115
}
115116
}
116117

117118
override fun onPrivacyConfigDownloaded() {
118119
appCoroutineScope.launch(dispatcherProvider.io()) {
119120
val toggleEnabledState = attributedMetricsConfigFeature.self().isEnabled()
120121
logcat(tag = "AttributedMetrics") {
121-
"Privacy config downloaded, attributed metrics enabled: $toggleEnabledState," +
122-
" client state: ${dataStore.isActive()}-${dataStore.getInitializationDate()}"
122+
"Privacy config downloaded, update client toggle state: $toggleEnabledState"
123123
}
124124
dataStore.setEnabled(toggleEnabledState)
125+
logClientStatus()
125126
}
126127
}
127128

@@ -139,12 +140,18 @@ class RealAttributedMetricsState @Inject constructor(
139140

140141
val daysSinceInit = attributedMetricsDateUtils.daysSince(initDate)
141142
val isWithinPeriod = daysSinceInit <= COLLECTION_PERIOD_DAYS
142-
val isClientActive = isWithinPeriod && dataStore.isActive()
143+
val newClientActiveState = isWithinPeriod && dataStore.isActive()
143144

144145
logcat(tag = "AttributedMetrics") {
145-
"Updating client state to $isClientActive, within period? $isWithinPeriod, is client active? ${dataStore.isActive()}"
146+
"Updating client state to $newClientActiveState result of -> within period? $isWithinPeriod, client active? ${dataStore.isActive()}"
146147
}
147-
dataStore.setActive(isClientActive)
148+
dataStore.setActive(newClientActiveState)
149+
logClientStatus()
150+
}
151+
152+
private suspend fun logClientStatus() = logcat(tag = "AttributedMetrics") {
153+
"Client status running: ${isActive()} -> isActive: ${dataStore.isActive()}, isEnabled: ${dataStore.isEnabled()}," +
154+
" initializationDate: ${dataStore.getInitializationDate()}"
148155
}
149156

150157
companion object {

attributed-metrics/attributed-metrics-impl/src/main/java/com/duckduckgo/app/attributed/metrics/impl/RealAttributedMetricClient.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class RealAttributedMetricClient @Inject constructor(
6666
eventRepository.getEventStats(eventName, days)
6767
}
6868

69+
// TODO: Pending adding default attributed metrics and removing default prefix from pixel names
6970
override fun emitMetric(metric: AttributedMetric) {
7071
appCoroutineScope.launch(dispatcherProvider.io()) {
7172
if (!metricsState.isActive()) return@launch
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.attributed.metrics.impl
18+
19+
import androidx.test.ext.junit.runners.AndroidJUnit4
20+
import com.duckduckgo.app.attributed.metrics.api.AttributedMetric
21+
import com.duckduckgo.app.attributed.metrics.api.EventStats
22+
import com.duckduckgo.app.attributed.metrics.store.EventRepository
23+
import com.duckduckgo.app.statistics.pixels.Pixel
24+
import com.duckduckgo.common.test.CoroutineTestRule
25+
import kotlinx.coroutines.test.runTest
26+
import org.junit.Assert.assertEquals
27+
import org.junit.Before
28+
import org.junit.Rule
29+
import org.junit.Test
30+
import org.junit.runner.RunWith
31+
import org.mockito.Mockito.verifyNoInteractions
32+
import org.mockito.kotlin.any
33+
import org.mockito.kotlin.mock
34+
import org.mockito.kotlin.never
35+
import org.mockito.kotlin.verify
36+
import org.mockito.kotlin.whenever
37+
38+
@RunWith(AndroidJUnit4::class)
39+
class RealAttributedMetricClientTest {
40+
41+
@get:Rule
42+
val coroutineTestRule = CoroutineTestRule()
43+
44+
private val mockEventRepository: EventRepository = mock()
45+
private val mockPixel: Pixel = mock()
46+
private val mockMetricsState: AttributedMetricsState = mock()
47+
48+
private lateinit var testee: RealAttributedMetricClient
49+
50+
@Before
51+
fun setup() {
52+
testee = RealAttributedMetricClient(
53+
appCoroutineScope = coroutineTestRule.testScope,
54+
dispatcherProvider = coroutineTestRule.testDispatcherProvider,
55+
eventRepository = mockEventRepository,
56+
pixel = mockPixel,
57+
metricsState = mockMetricsState,
58+
)
59+
}
60+
61+
@Test
62+
fun whenCollectEventAndClientActiveEventIsCollected() = runTest {
63+
whenever(mockMetricsState.isActive()).thenReturn(true)
64+
65+
testee.collectEvent("test_event")
66+
67+
verify(mockEventRepository).collectEvent("test_event")
68+
}
69+
70+
@Test
71+
fun whenCollectEventAndClientNotActiveEventIsNotCollected() = runTest {
72+
whenever(mockMetricsState.isActive()).thenReturn(false)
73+
74+
testee.collectEvent("test_event")
75+
76+
verify(mockEventRepository, never()).collectEvent(any())
77+
}
78+
79+
@Test
80+
fun whenGetEventStatsAndClientActiveStatsAreReturned() = runTest {
81+
val expectedStats = EventStats(daysWithEvents = 5, rollingAverage = 2.5, totalEvents = 10)
82+
whenever(mockMetricsState.isActive()).thenReturn(true)
83+
whenever(mockEventRepository.getEventStats("test_event", 7)).thenReturn(expectedStats)
84+
85+
val result = testee.getEventStats("test_event", 7)
86+
87+
assertEquals(expectedStats, result)
88+
verify(mockEventRepository).getEventStats("test_event", 7)
89+
}
90+
91+
@Test
92+
fun whenGetEventStatsAndClientNotActiveEmptyStatsAreReturned() = runTest {
93+
whenever(mockMetricsState.isActive()).thenReturn(false)
94+
95+
val result = testee.getEventStats("test_event", 7)
96+
97+
assertEquals(EventStats(daysWithEvents = 0, rollingAverage = 0.0, totalEvents = 0), result)
98+
verify(mockEventRepository, never()).getEventStats(any(), any())
99+
}
100+
101+
@Test
102+
fun whenEmitMetricAndClientActiveMetricIsEmitted() = runTest {
103+
val testMetric = TestAttributedMetric()
104+
whenever(mockMetricsState.isActive()).thenReturn(true)
105+
106+
testee.emitMetric(testMetric)
107+
108+
verify(mockPixel).fire(pixelName = "test_pixel", parameters = mapOf("param" to "value"))
109+
}
110+
111+
@Test
112+
fun whenEmitMetricAndClientNotActiveMetricIsNotEmitted() = runTest {
113+
val testMetric = TestAttributedMetric()
114+
whenever(mockMetricsState.isActive()).thenReturn(false)
115+
116+
testee.emitMetric(testMetric)
117+
118+
verifyNoInteractions(mockPixel)
119+
}
120+
121+
private class TestAttributedMetric : AttributedMetric {
122+
override fun getPixelName(): String = "test_pixel"
123+
override suspend fun getMetricParameters(): Map<String, String> = mapOf("param" to "value")
124+
}
125+
}

0 commit comments

Comments
 (0)