Skip to content

Commit

Permalink
Add retention statistics
Browse files Browse the repository at this point in the history
* Add install and retention statistics
* Added stub statistics module for tests
* Move app config to correct package
* Update dependencies
  • Loading branch information
subsymbolic authored Feb 27, 2018
1 parent 63ad354 commit 4c0be54
Show file tree
Hide file tree
Showing 48 changed files with 809 additions and 163 deletions.
12 changes: 6 additions & 6 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ dependencies {
implementation "com.android.support:appcompat-v7:$supportLibrary"
implementation "com.android.support:design:$supportLibrary"
implementation "com.android.support.constraint:constraint-layout:1.0.2"
implementation "com.squareup.okhttp3:okhttp:3.9.1"
implementation "com.squareup.okhttp3:okhttp:3.10.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit"
implementation "com.squareup.retrofit2:converter-moshi:$retrofit"
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit"
implementation "io.reactivex.rxjava2:rxjava:2.1.8"
implementation "io.reactivex.rxjava2:rxandroid:2.0.1"
implementation "com.jakewharton.timber:timber:4.6.0"
implementation "io.reactivex.rxjava2:rxjava:2.1.10"
implementation "io.reactivex.rxjava2:rxandroid:2.0.2"
implementation "com.jakewharton.timber:timber:4.6.1"
implementation "com.google.dagger:dagger-android:$dagger"
implementation "com.google.dagger:dagger-android-support:$dagger"
releaseImplementation 'com.faendir:acra:4.10.0'
Expand Down Expand Up @@ -127,14 +127,14 @@ dependencies {
kaptAndroidTest "com.google.dagger:dagger-android-processor:$dagger"
kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger"

testImplementation "org.mockito:mockito-core:2.13.0"
testImplementation "org.mockito:mockito-core:2.15.0"
testImplementation "com.nhaarman:mockito-kotlin-kt1.1:1.5.0"
testImplementation "junit:junit:4.12"

androidTestImplementation "com.android.support.test:runner:1.0.1"
androidTestUtil "com.android.support.test:orchestrator:1.0.1"
androidTestImplementation "com.android.support.test.espresso:espresso-core:3.0.1"
androidTestImplementation "org.mockito:mockito-android:2.13.0"
androidTestImplementation "org.mockito:mockito-android:2.15.0"
androidTestImplementation "com.nhaarman:mockito-kotlin-kt1.1:1.5.0"

}
24 changes: 24 additions & 0 deletions app/src/androidTest/java/com/duckduckgo/app/FileUtilities.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2018 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app

object FileUtilities {

fun loadText(resourceName: String): String =
javaClass.classLoader.getResource(resourceName).openStream().bufferedReader().use { it.readText() }

}
55 changes: 55 additions & 0 deletions app/src/androidTest/java/com/duckduckgo/app/ImmediateTestRule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2018 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app


import io.reactivex.android.plugins.RxAndroidPlugins
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.Schedulers
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement


class InstantSchedulersRule : TestRule {

override fun apply(base: Statement, description: Description): Statement {

return object : Statement() {

@Throws(Throwable::class)
override fun evaluate() {

RxJavaPlugins.reset()
RxAndroidPlugins.reset()

RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() }
RxAndroidPlugins.setMainThreadSchedulerHandler { Schedulers.trampoline() }

base.evaluate()

RxJavaPlugins.reset()
RxAndroidPlugins.reset()

}

}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ import com.duckduckgo.app.privacymonitor.db.NetworkPercent
import com.duckduckgo.app.privacymonitor.model.PrivacyGrade
import com.duckduckgo.app.privacymonitor.store.PrivacyMonitorRepository
import com.duckduckgo.app.privacymonitor.store.TermsOfServiceStore
import com.duckduckgo.app.settings.db.AppConfigurationDao
import com.duckduckgo.app.settings.db.AppConfigurationEntity
import com.duckduckgo.app.global.db.AppConfigurationDao
import com.duckduckgo.app.global.db.AppConfigurationEntity
import com.duckduckgo.app.settings.db.SettingsDataStore
import com.duckduckgo.app.statistics.api.StatisticsUpdater
import com.duckduckgo.app.trackerdetection.model.TrackerNetwork
import com.duckduckgo.app.trackerdetection.model.TrackerNetworks
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
Expand Down Expand Up @@ -74,6 +75,9 @@ class BrowserViewModelTest {
}
}

@Mock
private lateinit var mockStatisticsUpdater: StatisticsUpdater

@Mock
private lateinit var mockQueryObserver: Observer<String>

Expand Down Expand Up @@ -116,6 +120,7 @@ class BrowserViewModelTest {
appConfigurationDao = db.appConfigurationDao()

testee = BrowserViewModel(
statisticsUpdater = mockStatisticsUpdater,
queryUrlConverter = mockOmnibarConverter,
duckDuckGoUrlDetector = DuckDuckGoUrlDetector(),
termsOfServiceStore = mockTermsOfServiceStore,
Expand Down Expand Up @@ -235,6 +240,12 @@ class BrowserViewModelTest {
assertEquals("test", testee.viewState.value!!.omnibarText)
}

@Test
fun whenUrlChangedWithDuckDuckGoUrlContainingQueryThenAtbRefreshed() {
testee.urlChanged("http://duckduckgo.com?q=test")
verify(mockStatisticsUpdater).refreshRetentionAtb()
}

@Test
fun whenUrlChangedWithDuckDuckGoUrlNotContainingQueryThenFullUrlShown() {
testee.urlChanged("http://duckduckgo.com")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,58 @@
package com.duckduckgo.app.browser

import android.net.Uri
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import com.duckduckgo.app.global.AppUrl.ParamKey
import com.duckduckgo.app.statistics.store.StatisticsDataStore
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test

class DuckDuckGoRequestRewriterTest {

private lateinit var testee: DuckDuckGoRequestRewriter
private var mockStatisticsStore: StatisticsDataStore = mock()
private lateinit var builder: Uri.Builder

@Before
fun before() {
testee = DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector())
testee = DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector(), mockStatisticsStore)
builder = Uri.Builder()
}

@Test
fun whenAddingCustomParamsSourceParameterIsAdded() {
testee.addCustomQueryParams(builder)
val uri = builder.build()
assertTrue(uri.queryParameterNames.contains("t"))
assertEquals("ddg_android", uri.getQueryParameter("t"))
assertTrue(uri.queryParameterNames.contains(ParamKey.SOURCE))
assertEquals("ddg_android", uri.getQueryParameter(ParamKey.SOURCE))
}

@Test
fun whenAddingCustomParamsAppVersionParameterIsAdded() {
testee.addCustomQueryParams(builder)
val uri = builder.build()
assertTrue(uri.queryParameterNames.contains("tappv"))
assertTrue(uri.queryParameterNames.contains(ParamKey.APP_VERSION))
assertEquals("android_${BuildConfig.VERSION_NAME.replace(".", "_")}", uri.getQueryParameter("tappv"))
}

@Test
fun whenAddingCustomParamsIfStoreContainsAtbIsAdded() {
whenever(mockStatisticsStore.atb).thenReturn("v105-2ma")
testee.addCustomQueryParams(builder)
val uri = builder.build()
assertTrue(uri.queryParameterNames.contains(ParamKey.ATB))
assertEquals("v105-2ma", uri.getQueryParameter(ParamKey.ATB))
}

@Test
fun whenAddingCustomParamsIfIsStoreMissingAtbThenAtbIsNotAdded() {
whenever(mockStatisticsStore.atb).thenReturn(null)

testee.addCustomQueryParams(builder)
val uri = builder.build()
assertFalse(uri.queryParameterNames.contains(ParamKey.ATB))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,29 @@ class DuckDuckGoUrlDetectorTest {

@Test
fun whenDDGUrlContainsQueryThenQueryCanBeExtracted() {
val query = testee.extractQuery("https://duckduck.com?q=test%20search")
val query = testee.extractQuery("https://duckduckgo.com?q=test%20search")
assertEquals("test search", query)
}

@Test
fun whenDDGUrlDoesNotContainsQueryThenQueryIsNull() {
val query = testee.extractQuery("https://duckduck.com")
val query = testee.extractQuery("https://duckduckgo.com")
assertNull(query)
}

@Test
fun whenDDGUrlContainsQueryThenQueryDetected() {
assertTrue(testee.hasQuery("https://duckduck.com?q=test%20search"))
assertTrue(testee.isDuckDuckGoQueryUrl("https://duckduckgo.com?q=test%20search"))
}

@Test
fun whenDDGUrlDoesNotContainsQueryThenQueryIsNotDetected() {
assertFalse(testee.hasQuery("https://duckduck.com"))
assertFalse(testee.isDuckDuckGoQueryUrl("https://duckduckgo.com"))
}

@Test
fun whenNonDDGUrlContainsQueryThenQueryIsNotDetected() {
assertFalse(testee.isDuckDuckGoQueryUrl("https://example.com?q=test%20search"))
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ package com.duckduckgo.app.browser
import android.net.Uri
import android.support.test.runner.AndroidJUnit4
import com.duckduckgo.app.browser.omnibar.QueryUrlConverter
import com.duckduckgo.app.statistics.store.StatisticsDataStore
import com.nhaarman.mockito_kotlin.mock
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class QueryUrlConverterTest {

val testee: QueryUrlConverter = QueryUrlConverter(DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector()))
private var mockStatisticsStore: StatisticsDataStore = mock()
private val testee: QueryUrlConverter = QueryUrlConverter(DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector(), mockStatisticsStore))

@Test
fun whenUserIsPresentThenIsWebUrlIsFalse() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2018 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.di

import com.duckduckgo.app.statistics.api.StatisticsService
import com.duckduckgo.app.statistics.api.StatisticsUpdater
import dagger.Module
import dagger.Provides
import retrofit2.Retrofit

@Module
class StubStatisticsModule {

@Provides
fun statisticsService(retrofit: Retrofit): StatisticsService =
retrofit.create(StatisticsService::class.java)

@Provides
fun stubStatisticsUpdater(): StatisticsUpdater {
return object : StatisticsUpdater {
override fun refreshRetentionAtb() {
}

override fun initializeAtb() {
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import javax.inject.Singleton
StubDatabaseModule::class,
StubJobSchedulerModule::class,
StubAppConfigurationDownloadModule::class,
StubStatisticsModule::class,

/* real modules */
(ApplicationModule::class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import com.duckduckgo.app.browser.omnibar.QueryUrlConverter
import com.duckduckgo.app.global.db.AppDatabase
import com.duckduckgo.app.migration.legacy.LegacyDb
import com.duckduckgo.app.migration.legacy.LegacyDbContracts
import com.duckduckgo.app.statistics.store.StatisticsDataStore
import com.nhaarman.mockito_kotlin.mock
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
Expand All @@ -36,11 +38,12 @@ import org.junit.Test
class LegacyMigrationTest {

// target context else we can't write a db file
val context = InstrumentationRegistry.getTargetContext()
val urlConverter = QueryUrlConverter(DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector()))
private val context = InstrumentationRegistry.getTargetContext()
private var mockStatisticsStore: StatisticsDataStore = mock()
private val urlConverter = QueryUrlConverter(DuckDuckGoRequestRewriter(DuckDuckGoUrlDetector(), mockStatisticsStore))

var appDatabase: AppDatabase = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()
var bookmarksDao = StubBookmarksDao()
private var appDatabase: AppDatabase = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()
private var bookmarksDao = StubBookmarksDao()

@After
fun after() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.duckduckgo.app.privacymonitor.api

import com.duckduckgo.app.FileUtilities.loadText
import com.duckduckgo.app.privacymonitor.model.TermsOfService
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonDataException
Expand All @@ -32,14 +33,14 @@ class TermsOfServiceListAdapterTest {

@Test
fun whenFormatIsValidThenDataIsCreated() {
val json = json("json/tosdr.json")
val json = loadText("json/tosdr.json")
val terms = jsonAdapter.fromJson(json)
assertNotNull(terms)
}

@Test
fun whenFormatIsValidThenDataIsConvertedCorrectly() {
val json = json("json/tosdr.json")
val json = loadText("json/tosdr.json")
val terms = jsonAdapter.fromJson(json)

val firstTerm = terms.first { it.name == "example.com" }
Expand All @@ -56,11 +57,8 @@ class TermsOfServiceListAdapterTest {
}

@Test(expected = JsonDataException::class)
fun whenDisconnectFormatIsMismatchedThenExceptionIsThrown() {
val json = json("json/tosdr_mismatched.json")
fun whenFormatIsMismatchedThenExceptionIsThrown() {
val json = loadText("json/tosdr_mismatched.json")
jsonAdapter.fromJson(json)
}

private fun json(resourceName: String): String =
javaClass.classLoader.getResource(resourceName).openStream().bufferedReader().use { it.readText() }
}
Loading

0 comments on commit 4c0be54

Please sign in to comment.