diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index bc6992be..50a807cc 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -14,11 +14,9 @@ import io.constructor.injection.component.DaggerAppComponent import io.constructor.injection.module.AppModule import io.constructor.injection.module.NetworkModule import io.constructor.util.broadcastIntent -import io.constructor.util.d -import io.constructor.util.e import io.constructor.util.urlEncode +import io.reactivex.Completable import io.reactivex.Observable -import io.reactivex.disposables.CompositeDisposable import io.reactivex.schedulers.Schedulers import java.util.* @@ -31,7 +29,7 @@ object ConstructorIo { private lateinit var preferenceHelper: PreferencesHelper private lateinit var configMemoryHolder: ConfigMemoryHolder private lateinit var context: Context - private var disposable = CompositeDisposable() + private var broadcast = true var userId: String? get() = configMemoryHolder.userId @@ -46,16 +44,8 @@ object ConstructorIo { .build() } - private var sessionIncrementEventHandler: (String) -> Unit = { - trackSessionStartInternal(it) - } - - private fun trackSessionStartInternal(sessionId: String, errorCallback: ConstructorError = null) { - disposable.add(dataManager.trackSessionStart(arrayOf(Constants.QueryConstants.SESSION to sessionId, - Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_SESSION_START)).subscribeOn(Schedulers.io()).subscribe({}, { - errorCallback?.invoke(it) - d("Error triggering Session Change event") - })) + private var sessionIncrementHandler: (String) -> Unit = { + trackSessionStartInternal() } fun init(context: Context?, constructorIoConfig: ConstructorIoConfig) { @@ -88,14 +78,11 @@ object ConstructorIo { this.dataManager = dataManager this.preferenceHelper = preferenceHelper this.configMemoryHolder = configMemoryHolder - preferenceHelper.token = constructorIoConfig.apiKey - if (preferenceHelper.id.isBlank()) { - preferenceHelper.id = UUID.randomUUID().toString() - } + this.broadcast = false } fun appMovedToForeground() { - preferenceHelper.getSessionId(sessionIncrementEventHandler) + preferenceHelper.getSessionId(sessionIncrementHandler) } fun getAutocompleteResults(query: String): Observable?>> { @@ -107,16 +94,11 @@ object ConstructorIo { } fun getSearchResults(text: String, vararg facets: Pair>, page: Int? = null, perPage: Int? = null, groupId: Int? = null): Observable> { - val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler) + preferenceHelper.getSessionId(sessionIncrementHandler) val encodedParams: ArrayList> = arrayListOf() groupId?.let { encodedParams.add(Constants.QueryConstants.FILTER_GROUP_ID.urlEncode() to it.toString()) } - page?.let { - encodedParams.add(Constants.QueryConstants.PAGE.urlEncode() to page.toString().urlEncode()) - } - perPage?.let { - encodedParams.add(Constants.QueryConstants.PER_PAGE.urlEncode() to perPage.toString().urlEncode()) - } - encodedParams.add(Constants.QueryConstants.SESSION.urlEncode() to sessionId.toString().urlEncode()) + page?.let { encodedParams.add(Constants.QueryConstants.PAGE.urlEncode() to page.toString().urlEncode()) } + perPage?.let { encodedParams.add(Constants.QueryConstants.PER_PAGE.urlEncode() to perPage.toString().urlEncode()) } facets.forEach { facet -> facet.second.forEach { encodedParams.add(Constants.QueryConstants.FILTER_FACET.format(facet.first).urlEncode() to it.urlEncode()) @@ -125,106 +107,112 @@ object ConstructorIo { return dataManager.getSearchResults(text, encodedParams = encodedParams.toTypedArray()) } - fun trackAutocompleteSelect(searchTerm: String, originalQuery: String, sectionName: String, group: Group? = null, errorCallback: ConstructorError = null) { - val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler) + /** + * Tracks Session Start Events + */ + internal fun trackSessionStartInternal (): Completable { + return dataManager.trackSessionStart( + arrayOf(Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_SESSION_START) + ) + } + + /** + * Tracks input focus events + */ + fun trackInputFocus(term: String?): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + return dataManager.trackInputFocus(term, arrayOf( + Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_INPUT_FOCUS + )); + } + + /** + * Tracks autocomplete select events + */ + fun trackAutocompleteSelect(searchTerm: String, originalQuery: String, sectionName: String, group: Group? = null): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) val encodedParams: ArrayList> = arrayListOf() group?.groupId?.let { encodedParams.add(Constants.QueryConstants.GROUP_ID.urlEncode() to it) } group?.displayName?.let { encodedParams.add(Constants.QueryConstants.GROUP_DISPLAY_NAME.urlEncode() to it.urlEncode()) } - disposable.add(dataManager.trackAutocompleteSelect(searchTerm, - arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(), - Constants.QueryConstants.AUTOCOMPLETE_SECTION to sectionName, - Constants.QueryConstants.ORIGINAL_QUERY to originalQuery, - Constants.QueryConstants.EVENT to Constants.QueryValues.EVENT_CLICK), - encodedParams.toTypedArray()) - .subscribe({ - context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to searchTerm) - }, { t -> - t.printStackTrace() - errorCallback?.invoke(t) - e("Autocomplete Select event error: ${t.message}") - })) + val completable = dataManager.trackAutocompleteSelect(searchTerm, arrayOf( + Constants.QueryConstants.AUTOCOMPLETE_SECTION to sectionName, + Constants.QueryConstants.ORIGINAL_QUERY to originalQuery, + Constants.QueryConstants.EVENT to Constants.QueryValues.EVENT_CLICK + ), encodedParams.toTypedArray()).subscribeOn(Schedulers.io()) + + if (this.broadcast) { + completable.subscribeOn(Schedulers.io()).subscribe { + context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to searchTerm) + } + } + + return completable } - fun trackSearchSubmit(searchTerm: String, originalQuery: String, group: Group?, errorCallback: ConstructorError = null) { - val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler) + /** + * Tracks search submit events + */ + fun trackSearchSubmit(searchTerm: String, originalQuery: String, group: Group?): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) val encodedParams: ArrayList> = arrayListOf() group?.groupId?.let { encodedParams.add(Constants.QueryConstants.GROUP_ID.urlEncode() to it) } group?.displayName?.let { encodedParams.add(Constants.QueryConstants.GROUP_DISPLAY_NAME.urlEncode() to it.urlEncode()) } - disposable.add(dataManager.trackSearchSubmit(searchTerm, - arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(), - Constants.QueryConstants.ORIGINAL_QUERY to originalQuery, - Constants.QueryConstants.EVENT to Constants.QueryValues.EVENT_SEARCH), encodedParams.toTypedArray()) - .subscribe({ - context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to searchTerm) - }, { - it.printStackTrace() - errorCallback?.invoke(it) - e("Search Submit event error: ${it.message}") - })) + val completable = dataManager.trackSearchSubmit(searchTerm, arrayOf( + Constants.QueryConstants.ORIGINAL_QUERY to originalQuery, + Constants.QueryConstants.EVENT to Constants.QueryValues.EVENT_SEARCH + ), encodedParams.toTypedArray()) + + if (this.broadcast) { + completable.subscribeOn(Schedulers.io()).subscribe { + context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to searchTerm) + } + } + + return completable } - fun trackConversion(itemName: String, customerId: String, revenue: Double?, searchTerm: String = Constants.QueryConstants.TERM_UNKNOWN, sectionName: String? = null, errorCallback: ConstructorError = null) { - val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler) - val revenueString = revenue?.let { "%.2f".format(revenue) } - disposable.add(dataManager.trackConversion(searchTerm, itemName, customerId, revenueString, - arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(), - Constants.QueryConstants.AUTOCOMPLETE_SECTION to (sectionName ?: preferenceHelper.defaultItemSection))).subscribeOn(Schedulers.io()) - .subscribe({}, { t -> - t.printStackTrace() - errorCallback?.invoke(t) - e("Conversion event error: ${t.message}") - })) + /** + * Tracks search results loaded (a.k.a. search results viewed) events + */ + fun trackSearchResultsLoaded(term: String, resultCount: Int): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + return dataManager.trackSearchResultsLoaded(term, resultCount, arrayOf( + Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_SEARCH_RESULTS + )) } - fun trackSearchResultClick(itemName: String, customerId: String, searchTerm: String = Constants.QueryConstants.TERM_UNKNOWN, sectionName: String? = null, errorCallback: ConstructorError = null) { - val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler) + /** + * Tracks search result click events + */ + fun trackSearchResultClick(itemName: String, customerId: String, searchTerm: String = Constants.QueryConstants.TERM_UNKNOWN, sectionName: String? = null): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) val sName = sectionName ?: preferenceHelper.defaultItemSection - disposable.add(dataManager.trackSearchResultClick(itemName, customerId, searchTerm, - arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(), - Constants.QueryConstants.AUTOCOMPLETE_SECTION to sName)).subscribeOn(Schedulers.io()) - .subscribe({}, { t -> - t.printStackTrace() - errorCallback?.invoke(t) - e("Search Result Click event error: ${t.message}") - })) - } + return dataManager.trackSearchResultClick(itemName, customerId, searchTerm, arrayOf( + Constants.QueryConstants.AUTOCOMPLETE_SECTION to sName + )) - fun trackSearchResultsLoaded(term: String, resultCount: Int, errorCallback: ConstructorError = null) { - val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler) - disposable.add(dataManager.trackSearchResultsLoaded(term, resultCount, - arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(), - Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_SEARCH_RESULTS)).subscribeOn(Schedulers.io()) - .subscribe({}, { t -> - t.printStackTrace() - errorCallback?.invoke(t) - e("Search Results Loaded event error: ${t.message}") - })) } - fun trackInputFocus(term: String?, errorCallback: ConstructorError = null) { - val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler) - disposable.add(dataManager.trackInputFocus(term, - arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(), - Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_INPUT_FOCUS)).subscribeOn(Schedulers.io()) - .subscribe({}, { t -> - t.printStackTrace() - errorCallback?.invoke(t) - e("Input Focus event error: ${t.message}") - })) + /** + * Tracks conversion (a.k.a add to cart) events + */ + fun trackConversion(itemName: String, customerId: String, revenue: Double?, searchTerm: String = Constants.QueryConstants.TERM_UNKNOWN, sectionName: String? = null): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + val revenueString = revenue?.let { "%.2f".format(revenue) } + return dataManager.trackConversion(searchTerm, itemName, customerId, revenueString, arrayOf( + Constants.QueryConstants.AUTOCOMPLETE_SECTION to (sectionName ?: preferenceHelper.defaultItemSection) + )) } - fun trackPurchase(clientIds: Array, revenue: Double?, sectionName: String? = null, errorCallback: ConstructorError = null) { - val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler) + /** + * Tracks purchase events + */ + fun trackPurchase(clientIds: Array, revenue: Double?, sectionName: String? = null): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) val sectionNameParam = sectionName ?: preferenceHelper.defaultItemSection val revenueString = revenue?.let { "%.2f".format(revenue) } - val params = mutableListOf(Constants.QueryConstants.SESSION to sessionId.toString(), - Constants.QueryConstants.AUTOCOMPLETE_SECTION to sectionNameParam) - disposable.add(dataManager.trackPurchase(clientIds.toList(), revenueString, params.toTypedArray()).subscribeOn(Schedulers.io()) - .subscribe({}, { t -> - t.printStackTrace() - errorCallback?.invoke(t) - e("Purchase event error: ${t.message}") - })) + val params = mutableListOf(Constants.QueryConstants.AUTOCOMPLETE_SECTION to sectionNameParam) + return dataManager.trackPurchase(clientIds.toList(), revenueString, params.toTypedArray()) } } \ No newline at end of file diff --git a/library/src/main/java/io/constructor/data/interceptor/TokenInterceptor.kt b/library/src/main/java/io/constructor/data/interceptor/TokenInterceptor.kt index 98e5cc09..7415e218 100755 --- a/library/src/main/java/io/constructor/data/interceptor/TokenInterceptor.kt +++ b/library/src/main/java/io/constructor/data/interceptor/TokenInterceptor.kt @@ -5,29 +5,39 @@ import io.constructor.BuildConfig import io.constructor.core.Constants import io.constructor.data.local.PreferencesHelper import io.constructor.data.memory.ConfigMemoryHolder +import io.constructor.util.urlEncode import okhttp3.Interceptor import okhttp3.Response - +/** + * Adds common request query parameters to all API requests + */ class TokenInterceptor(val context: Context, private val preferencesHelper: PreferencesHelper, private val configMemoryHolder: ConfigMemoryHolder) : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - var request = chain.request() + val request = chain.request() val builder = request.url().newBuilder() - .addQueryParameter(Constants.QueryConstants.API_KEY, preferencesHelper.token) - .addQueryParameter(Constants.QueryConstants.IDENTITY, preferencesHelper.id) - .addQueryParameter(Constants.QueryConstants.TIMESTAMP, System.currentTimeMillis().toString()) - .addQueryParameter(Constants.QueryConstants.CLIENT, BuildConfig.CLIENT_VERSION) + .addQueryParameter(Constants.QueryConstants.API_KEY, preferencesHelper.token) + .addQueryParameter(Constants.QueryConstants.IDENTITY, preferencesHelper.id) + + // TODO : Urlencode + configMemoryHolder.userId?.let { + builder.addQueryParameter(Constants.QueryConstants.USER_ID, it) + } + + builder.addQueryParameter(Constants.QueryConstants.SESSION, preferencesHelper.getSessionId().toString()) + + // TODO : Urlencode configMemoryHolder.testCellParams.forEach { it?.let { builder.addQueryParameter(it.first, it.second) } } - configMemoryHolder.userId?.let { - builder.addQueryParameter(Constants.QueryConstants.USER_ID, it) - } + + builder.addQueryParameter(Constants.QueryConstants.CLIENT, BuildConfig.CLIENT_VERSION) + builder.addQueryParameter(Constants.QueryConstants.TIMESTAMP, System.currentTimeMillis().toString()) + val url = builder.build() - request = request.newBuilder().url(url).build() - return chain.proceed(request) + val newRequest = request.newBuilder().url(url).build() + return chain.proceed(newRequest) } } \ No newline at end of file diff --git a/library/src/main/java/io/constructor/injection/module/NetworkModule.kt b/library/src/main/java/io/constructor/injection/module/NetworkModule.kt index 6f4dc333..11ded22d 100755 --- a/library/src/main/java/io/constructor/injection/module/NetworkModule.kt +++ b/library/src/main/java/io/constructor/injection/module/NetworkModule.kt @@ -37,7 +37,7 @@ class NetworkModule(private val context: Context) { val httpClientBuilder = OkHttpClient.Builder() httpClientBuilder.addInterceptor(tokenInterceptor) if (BuildConfig.DEBUG) { - httpClientBuilder.addInterceptor(httpLoggingInterceptor) + // httpClientBuilder.addInterceptor(httpLoggingInterceptor) } return httpClientBuilder.build() diff --git a/library/src/main/java/io/constructor/util/Extensions.kt b/library/src/main/java/io/constructor/util/Extensions.kt index 43f7aa26..41b8cae2 100755 --- a/library/src/main/java/io/constructor/util/Extensions.kt +++ b/library/src/main/java/io/constructor/util/Extensions.kt @@ -37,8 +37,6 @@ fun String.urlEncode() = URLEncoder.encode(this, "UTF-8").replace("+", "%20") fun Any.d(msg: String) = Log.d(this::class.qualifiedName, msg) -fun Any.e(msg: String) = Log.e(this::class.qualifiedName, msg) - fun String.base64Encode(): String? { return String(Base64.encode(toByteArray(), Base64.NO_WRAP or Base64.NO_PADDING)) } diff --git a/library/src/test/java/io/constructor/core/ConstructorIoAutocompleteTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoAutocompleteTest.kt new file mode 100644 index 00000000..9374f6e0 --- /dev/null +++ b/library/src/test/java/io/constructor/core/ConstructorIoAutocompleteTest.kt @@ -0,0 +1,116 @@ +package io.constructor.core + +import android.content.Context +import io.constructor.data.local.PreferencesHelper +import io.constructor.data.memory.ConfigMemoryHolder +import io.constructor.test.createTestDataManager +import io.constructor.util.RxSchedulersOverrideRule +import io.constructor.util.TestDataLoader +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.util.concurrent.TimeUnit + +class ConstructorIoAutocompleteTest { + + @Rule + @JvmField + val overrideSchedulersRule = RxSchedulersOverrideRule() + + private lateinit var mockServer: MockWebServer + private var constructorIo = ConstructorIo + private val ctx = mockk() + private val preferencesHelper = mockk() + private val configMemoryHolder = mockk() + + @Before + fun setup() { + every { ctx.applicationContext } returns ctx + every { preferencesHelper.token } returns "golden-key" + every { preferencesHelper.id } returns "guido-the-guid" + every { preferencesHelper.getSessionId(any(), any()) } returns 79 + every { configMemoryHolder.autocompleteResultCount } returns null + every { configMemoryHolder.testCellParams = any() } just Runs + every { configMemoryHolder.userId } returns "player-one" + every { configMemoryHolder.testCellParams } returns emptyList() + + mockServer = MockWebServer() + mockServer.start() + val config = ConstructorIoConfig("dummyKey", testCells = listOf("flavor" to "chocolate", "topping" to "sprinkles")) + val dataManager = createTestDataManager(mockServer, preferencesHelper, configMemoryHolder, ctx) + + constructorIo.testInit(ctx, config, dataManager, preferencesHelper, configMemoryHolder) + } + + @Test + fun getAutocompleteResults() { + val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("autocomplete_response.json")) + mockServer.enqueue(mockResponse) + val observer = constructorIo.getAutocompleteResults("titanic").test() + observer.assertComplete().assertValue { + it.get()!!.isNotEmpty() && it.get()!!.size == 5 + } + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic?key=golden-key&i=guido-the-guid&ui=player-one&s=79&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } + + @Test + fun getAutocompleteResultsWithServerError() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockServer.enqueue(mockResponse) + val observer = constructorIo.getAutocompleteResults("titanic").test() + observer.assertComplete().assertValue { + it.networkError + } + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic?key=golden-key&i=guido-the-guid&ui=player-one&s=79&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } + + @Test + fun getAutocompleteResultsWithTimeout() { + val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("autocomplete_response.json")) + mockResponse.throttleBody(128, 5, TimeUnit.SECONDS) + mockServer.enqueue(mockResponse) + val observer = constructorIo.getAutocompleteResults("titanic").test() + observer.assertComplete().assertValue { + it.isError + } + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic?key=golden-key&i=guido-the-guid&ui=player-one&s=79&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } + + @Test + fun getAutocompleteResultsWithUnexpectedResponse() { + val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("autocomplete_response_with_unexpected_data.json")) + mockServer.enqueue(mockResponse) + val observer = constructorIo.getAutocompleteResults("titanic").test() + observer.assertComplete().assertValue { + it.get()!!.isNotEmpty() && it.get()!!.size == 5 + } + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic?key=golden-key&i=guido-the-guid&ui=player-one&s=79&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } + + @Test + fun getAutocompleteResultsWithEmptyResponse() { + val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("autocomplete_response_empty.json")) + mockServer.enqueue(mockResponse) + val observer = constructorIo.getAutocompleteResults("titanic").test() + observer.assertComplete().assertValue { + it.isEmpty + } + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic?key=golden-key&i=guido-the-guid&ui=player-one&s=79&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } +} \ No newline at end of file diff --git a/library/src/test/java/io/constructor/core/ConstructorIoTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoTest.kt deleted file mode 100755 index 66277685..00000000 --- a/library/src/test/java/io/constructor/core/ConstructorIoTest.kt +++ /dev/null @@ -1,281 +0,0 @@ -package io.constructor.core - -import android.content.Context -import io.constructor.BuildConfig -import io.constructor.data.DataManager -import io.constructor.data.interceptor.TokenInterceptor -import io.constructor.data.local.PreferencesHelper -import io.constructor.data.memory.ConfigMemoryHolder -import io.constructor.data.model.Group -import io.constructor.data.model.SuggestionViewModel -import io.constructor.util.RxSchedulersOverrideRule -import io.constructor.util.broadcastIntent -import io.constructor.util.urlEncode -import io.mockk.* -import io.reactivex.Completable -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import kotlin.test.assertEquals - -class ConstructorIoTest { - - @Rule - @JvmField val overrideSchedulersRule = RxSchedulersOverrideRule() - - private val ctx = mockk() - private val pref = mockk() - private val configMemoryHolder = mockk() - private val data = mockk() - private var constructorIo = ConstructorIo - private val sampleMillis = "1520000000000" - private val dummySuggestion = SuggestionViewModel("", Group("123", "Test name", null), "", null) - - @Before - fun setUp() { - every { ctx.applicationContext } returns ctx - every { pref.token = any() } returns Unit - every { pref.id } returns "1" - every { pref.getSessionId() } returns 1 - every { pref.getSessionId(any()) } returns 1 - every { pref.getSessionId(any(), any()) } returns 1 - constructorIo.testInit(ctx, ConstructorIoConfig("dummyKey", - testCells = listOf("1" to "2", "3" to "4")), data, pref, configMemoryHolder) - } - - @After - fun tearDown() { - } - - @Test - fun verifySelectUrl() { - val expected = "https://ac.cnstrc.com/autocomplete/hot%20dogs/select?s=1&i=1&_dt=1520000000000&autocomplete_section=Search%20Suggestions&original_query=dog&group%5Bgroup_id%5D=Meat%20%26%20Seafood&group%5Bdisplay_name%5D=Meat%20%26%20Seafood&tr=click&c=cioand-${BuildConfig.VERSION_NAME}&key=testKey" - val searchQuery = "dog" - val term = "hot dogs" - val urlBuilder = HttpUrl.Builder().scheme("https") - .host("ac.cnstrc.com") - .addPathSegment("autocomplete") - .addPathSegment(term) - .addPathSegment("select") - .addQueryParameter(Constants.QueryConstants.SESSION, "1") - .addQueryParameter(Constants.QueryConstants.IDENTITY, "1") - .addQueryParameter(Constants.QueryConstants.TIMESTAMP, sampleMillis) - .addQueryParameter(Constants.QueryConstants.AUTOCOMPLETE_SECTION, Constants.QueryValues.SEARCH_SUGGESTIONS) - .addQueryParameter(Constants.QueryConstants.ORIGINAL_QUERY, searchQuery) - .addEncodedQueryParameter(Constants.QueryConstants.GROUP_ID.urlEncode(), "Meat%20%26%20Seafood") - .addEncodedQueryParameter(Constants.QueryConstants.GROUP_DISPLAY_NAME.urlEncode(), "Meat & Seafood".urlEncode()) - .addQueryParameter(Constants.QueryConstants.EVENT, Constants.QueryValues.EVENT_CLICK) - .addQueryParameter(Constants.QueryConstants.CLIENT, BuildConfig.CLIENT_VERSION) - .addQueryParameter(Constants.QueryConstants.API_KEY, "testKey") - val urlString = urlBuilder.build().url().toString() - assertEquals(expected, urlString) - } - - @Test - fun verifyGetSuggestionsUrl() { - val expected = "https://ac.cnstrc.com/autocomplete/dog?key=testKey&_dt=1520000000000" - val searchQuery = "dog" - val urlBuilder = HttpUrl.Builder().scheme("https") - .host("ac.cnstrc.com") - .addPathSegment("autocomplete") - .addPathSegment(searchQuery) - .addQueryParameter(Constants.QueryConstants.API_KEY, "testKey") - .addQueryParameter(Constants.QueryConstants.TIMESTAMP, sampleMillis) - val urlString = urlBuilder.build().url().toString() - assertEquals(expected, urlString) - } - - @Test - fun verifySessionStartEventUrl() { - val expected = "https://ac.cnstrc.com/behavior?c=${BuildConfig.CLIENT_VERSION}&s=1&action=session_start&key=testKey&_dt=1520000000000" - val urlBuilder = HttpUrl.Builder().scheme("https") - .host("ac.cnstrc.com") - .addPathSegment("behavior") - .addQueryParameter(Constants.QueryConstants.CLIENT, BuildConfig.CLIENT_VERSION) - .addQueryParameter(Constants.QueryConstants.SESSION, "1") - .addQueryParameter(Constants.QueryConstants.ACTION, "session_start") - .addQueryParameter(Constants.QueryConstants.API_KEY, "testKey") - .addQueryParameter(Constants.QueryConstants.TIMESTAMP, sampleMillis) - val urlString = urlBuilder.build().url().toString() - assertEquals(expected, urlString) - } - - @Test - fun verifySearchResultClickEventUrl() { - val expected = "https://ac.cnstrc.com/autocomplete/term/click_through?c=${BuildConfig.CLIENT_VERSION}&s=1&autocomplete_section=Products&key=testKey&_dt=1520000000000" - val urlBuilder = HttpUrl.Builder().scheme("https") - .host("ac.cnstrc.com") - .addPathSegment("autocomplete") - .addPathSegment("term") - .addPathSegment("click_through") - .addQueryParameter(Constants.QueryConstants.CLIENT, BuildConfig.CLIENT_VERSION) - .addQueryParameter(Constants.QueryConstants.SESSION, "1") - .addQueryParameter(Constants.QueryConstants.AUTOCOMPLETE_SECTION, "Products") - .addQueryParameter(Constants.QueryConstants.API_KEY, "testKey") - .addQueryParameter(Constants.QueryConstants.TIMESTAMP, sampleMillis) - val urlString = urlBuilder.build().url().toString() - assertEquals(expected, urlString) - } - - @Test - fun verifySearchResultsLoadedEventUrl() { - val expected = "https://ac.cnstrc.com/behavior?c=${BuildConfig.CLIENT_VERSION}&s=1&action=search-results&key=testKey&_dt=1520000000000" - val urlBuilder = HttpUrl.Builder().scheme("https") - .host("ac.cnstrc.com") - .addPathSegment("behavior") - .addQueryParameter(Constants.QueryConstants.CLIENT, BuildConfig.CLIENT_VERSION) - .addQueryParameter(Constants.QueryConstants.SESSION, "1") - .addQueryParameter(Constants.QueryConstants.ACTION, Constants.QueryValues.EVENT_SEARCH_RESULTS) - .addQueryParameter(Constants.QueryConstants.API_KEY, "testKey") - .addQueryParameter(Constants.QueryConstants.TIMESTAMP, sampleMillis) - val urlString = urlBuilder.build().url().toString() - assertEquals(expected, urlString) - } - - @Test - fun verifyInputFocusEvent() { - val expected = "https://ac.cnstrc.com/behavior?c=${BuildConfig.CLIENT_VERSION}&i=user_id&s=1&action=focus&key=testKey&_dt=1520000000000" - val urlBuilder = HttpUrl.Builder().scheme("https") - .host("ac.cnstrc.com") - .addPathSegment("behavior") - .addQueryParameter(Constants.QueryConstants.CLIENT, BuildConfig.CLIENT_VERSION) - .addQueryParameter(Constants.QueryConstants.IDENTITY, "user_id") - .addQueryParameter(Constants.QueryConstants.SESSION, "1") - .addQueryParameter(Constants.QueryConstants.ACTION, Constants.QueryValues.EVENT_INPUT_FOCUS) - .addQueryParameter(Constants.QueryConstants.API_KEY, "testKey") - .addQueryParameter(Constants.QueryConstants.TIMESTAMP, sampleMillis) - val urlString = urlBuilder.build().url().toString() - assertEquals(expected, urlString) - } - - @Test - fun trackAutocompleteSelectSuccess() { - staticMockk("io.constructor.util.ExtensionsKt").use { - every { ctx.broadcastIntent(any(), any()) } returns Unit - every { data.trackAutocompleteSelect(any(), any(), any()) } returns Completable.complete() - constructorIo.trackAutocompleteSelect("doggy dog", "dog", "section1", dummySuggestion.group) - verify(exactly = 1) { ctx.broadcastIntent(any(), any()) } - } - } - - @Test - fun trackAutocompleteSelectError() { - staticMockk("io.constructor.util.ExtensionsKt").use { - every { ctx.broadcastIntent(any(), any()) } returns Unit - every { data.trackAutocompleteSelect(any(), any(), any()) } returns Completable.error(Exception()) - constructorIo.trackAutocompleteSelect("doggy dog", "dog", "section1", dummySuggestion.group) - verify(exactly = 0) { ctx.broadcastIntent(any(), any()) } - } - } - - @Test - fun trackSearchSubmitSuccess() { - staticMockk("io.constructor.util.ExtensionsKt").use { - every { ctx.broadcastIntent(any(), any()) } returns Unit - every { data.trackSearchSubmit(any(), any(), any()) } returns Completable.complete() - constructorIo.trackSearchSubmit("doggy dog", "dog", dummySuggestion.group) - verify(exactly = 1) { ctx.broadcastIntent(any(), any()) } - } - } - - @Test - fun verifySearchUrl() { - val expected = "https://ac.cnstrc.com/autocomplete/hot%20dogs/search?s=1&i=1&_dt=1520000000000&original_query=dog&group%5Bgroup_id%5D=Meat%20%26%20Seafood&group%5Bdisplay_name%5D=Meat%20%26%20Seafood&tr=search&c=cioand-${BuildConfig.VERSION_NAME}&key=testKey" - val originalQuery = "dog" - val term = "hot dogs" - val urlBuilder = HttpUrl.Builder().scheme("https") - .host("ac.cnstrc.com") - .addPathSegment("autocomplete") - .addPathSegment(term) - .addPathSegment("search") - .addQueryParameter(Constants.QueryConstants.SESSION, "1") - .addQueryParameter(Constants.QueryConstants.IDENTITY, "1") - .addQueryParameter(Constants.QueryConstants.TIMESTAMP, sampleMillis) - .addQueryParameter(Constants.QueryConstants.ORIGINAL_QUERY, originalQuery) - .addEncodedQueryParameter(Constants.QueryConstants.GROUP_ID.urlEncode(), "Meat%20%26%20Seafood") - .addEncodedQueryParameter(Constants.QueryConstants.GROUP_DISPLAY_NAME.urlEncode(), "Meat & Seafood".urlEncode()) - .addQueryParameter(Constants.QueryConstants.EVENT, Constants.QueryValues.EVENT_SEARCH) - .addQueryParameter(Constants.QueryConstants.CLIENT, BuildConfig.CLIENT_VERSION) - .addQueryParameter(Constants.QueryConstants.API_KEY, "testKey") - val urlString = urlBuilder.build().url().toString() - assertEquals(expected, urlString) - } - - @Test - fun verifyTestCellParamsAddedToRequest() { - val mockServer = MockWebServer() - every { pref.token } returns "123" - every { pref.id } returns "1" - every { configMemoryHolder.testCellParams = any() } just Runs - every { configMemoryHolder.userId } returns "uid" - every { configMemoryHolder.autocompleteResultCount } returns mapOf(Constants.QueryValues.SEARCH_SUGGESTIONS to 10, Constants.QueryValues.PRODUCTS to 0) - every { configMemoryHolder.testCellParams } returns listOf("ef-1" to "2", "ef-3" to "4") - mockServer.start() - mockServer.enqueue(MockResponse()) - var client = OkHttpClient.Builder().addInterceptor(TokenInterceptor(ctx, pref, configMemoryHolder)).build() - client.newCall(Request.Builder().url(mockServer.url("/")).build()).execute() - var recordedRequest = mockServer.takeRequest() - assert(recordedRequest.path.contains("ef-1=2")) - assert(recordedRequest.path.contains("ui=uid")) - } - - @Test - fun trackSearchSubmitError() { - staticMockk("io.constructor.util.ExtensionsKt").use { - every { ctx.broadcastIntent(any(), any()) } returns Unit - every { data.trackSearchSubmit(any(), any(), any()) } returns Completable.error(Exception()) - constructorIo.trackSearchSubmit("doggy dog", "dog", dummySuggestion.group) - verify(exactly = 0) { ctx.broadcastIntent(any(), any()) } - } - } - - @Test - fun trackConversion() { - every { pref.defaultItemSection } returns "Products" - every { data.trackConversion(any(), any(), any(), any(), any()) } returns Completable.complete() - constructorIo.trackConversion("corn", "id1", 11.99) - verify(exactly = 1) { data.trackConversion("TERM_UNKNOWN", any(), any(), any(), any()) } - } - - @Test - fun trackSearchResultClick() { - every { pref.defaultItemSection } returns "Products" - every { data.trackSearchResultClick(any(), any(), any(), any()) } returns Completable.complete() - constructorIo.trackSearchResultClick("1", "1") - verify(exactly = 1) { data.trackSearchResultClick(any(), any(), any(), any()) } - } - - @Test - fun getSessionId() { - constructorIo.getSessionId() - verify(exactly = 1) { pref.getSessionId() } - } - - @Test - fun getClientId() { - constructorIo.getClientId() - verify(exactly = 2) { pref.id } - } - - @Test - fun trackInputFocus() { - every { data.trackInputFocus(any(), any()) } returns Completable.complete() - constructorIo.trackInputFocus("1") - verify(exactly = 1) { data.trackInputFocus(any(), any()) } - } - - @Test - fun trackPurchase() { - every { pref.defaultItemSection } returns "Products" - every { data.trackPurchase(any(), any(), any()) } returns Completable.complete() - constructorIo.trackPurchase(arrayOf("id1"), 12.99) - verify(exactly = 1) { data.trackPurchase(any(), any(), any()) } - } - -} \ No newline at end of file diff --git a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt new file mode 100755 index 00000000..47c68036 --- /dev/null +++ b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt @@ -0,0 +1,322 @@ +package io.constructor.core + +import android.content.Context +import io.constructor.data.local.PreferencesHelper +import io.constructor.data.memory.ConfigMemoryHolder +import io.constructor.test.createTestDataManager +import io.constructor.util.RxSchedulersOverrideRule +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.net.SocketTimeoutException +import java.util.concurrent.TimeUnit + +class ConstructorIoTest { + + @Rule + @JvmField val overrideSchedulersRule = RxSchedulersOverrideRule() + + private lateinit var mockServer: MockWebServer + private var constructorIo = ConstructorIo + private val ctx = mockk() + private val preferencesHelper = mockk() + private val configMemoryHolder = mockk() + + @Before + fun setup() { + every { ctx.applicationContext } returns ctx + every { preferencesHelper.token } returns "copper-key" + every { preferencesHelper.id } returns "wacko-the-guid" + every { preferencesHelper.getSessionId(any(), any()) } returns 67 + every { preferencesHelper.defaultItemSection } returns "Products" + every { configMemoryHolder.autocompleteResultCount } returns null + every { configMemoryHolder.testCellParams = any() } just Runs + every { configMemoryHolder.userId } returns "player-three" + every { configMemoryHolder.testCellParams } returns emptyList() + + mockServer = MockWebServer() + mockServer.start() + val config = ConstructorIoConfig("dummyKey", testCells = listOf("flavor" to "peaches", "topping" to "cream")) + val dataManager = createTestDataManager(mockServer, preferencesHelper, configMemoryHolder, ctx) + + constructorIo.testInit(ctx, config, dataManager, preferencesHelper, configMemoryHolder) + } + + @Test + fun trackAutocompleteSelect() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackAutocompleteSelect("titanic", "tit", "Search Suggestions").test() + observer.assertComplete() + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic/select?autocomplete_section=Search%20Suggestions&original_query=tit&tr=click&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } + + @Test + fun trackAutocompleteSelectWithServerError() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackAutocompleteSelect("titanic", "tit", "Search Suggestions").test() + observer.assertError { true } + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic/select?autocomplete_section=Search%20Suggestions&original_query=tit&tr=click&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } + + @Test + fun trackAutocompleteSelectWithTimeout() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackAutocompleteSelect("titanic", "tit", "Search Suggestions").test() + observer.assertError(SocketTimeoutException::class.java) + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic/select?autocomplete_section=Search%20Suggestions&original_query=tit&tr=click&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } + + @Test + fun trackSearchSubmit() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSearchSubmit("titanic", "tit", null).test() + observer.assertComplete() + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic/search?original_query=tit&tr=search&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackSearchSubmit500() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSearchSubmit("titanic", "tit", null).test() + observer.assertError { true } + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic/search?original_query=tit&tr=search&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackSearchSubmitTimeout() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSearchSubmit("titanic", "tit", null).test() + observer.assertError(SocketTimeoutException::class.java) + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic/search?original_query=tit&tr=search&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackSessionStart() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = constructorIo.trackSessionStartInternal().test() + observer.assertComplete() + val request = mockServer.takeRequest() + val path = "/behavior?action=session_start&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackSessionStart500() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSessionStartInternal().test() + observer.assertError { true } + val request = mockServer.takeRequest() + val path = "/behavior?action=session_start&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackSessionStartTimeout() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSessionStartInternal().test() + observer.assertError(SocketTimeoutException::class.java) + val request = mockServer.takeRequest() + val path = "/behavior?action=session_start&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackConversion() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackConversion("titanic replica", "TIT-REP-1997", 89.00).test() + observer.assertComplete() + val request = mockServer.takeRequest() + val path = "/autocomplete/TERM_UNKNOWN/conversion?name=titanic%20replica&customer_id=TIT-REP-1997&revenue=89.00&autocomplete_section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackConversion500() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackConversion("titanic replica", "TIT-REP-1997", 89.00).test() + observer.assertError { true } + val request = mockServer.takeRequest() + val path = "/autocomplete/TERM_UNKNOWN/conversion?name=titanic%20replica&customer_id=TIT-REP-1997&revenue=89.00&autocomplete_section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackConversionTimeout() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackConversion("titanic replica", "TIT-REP-1997", 89.00).test() + observer.assertError(SocketTimeoutException::class.java) + val request = mockServer.takeRequest() + val path = "/autocomplete/TERM_UNKNOWN/conversion?name=titanic%20replica&customer_id=TIT-REP-1997&revenue=89.00&autocomplete_section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackSearchResultClick() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSearchResultClick("titanic replica", "TIT-REP-1997", "titanic").test() + observer.assertComplete() + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic/click_through?name=titanic%20replica&customer_id=TIT-REP-1997&autocomplete_section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackSearchResultClick500() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSearchResultClick("titanic replica", "TIT-REP-1997", "titanic").test() + observer.assertError { true } + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic/click_through?name=titanic%20replica&customer_id=TIT-REP-1997&autocomplete_section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackSearchResultClickTimeout() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSearchResultClick("titanic replica", "TIT-REP-1997", "titanic").test() + observer.assertError(SocketTimeoutException::class.java) + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic/click_through?name=titanic%20replica&customer_id=TIT-REP-1997&autocomplete_section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackSearchResultLoaded() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSearchResultsLoaded("titanic", 10).test() + observer.assertComplete() + val request = mockServer.takeRequest() + val path = "/behavior?term=titanic&num_results=10&action=search-results&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackSearchResultLoaded500() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSearchResultsLoaded("titanic", 10).test() + observer.assertError { true } + val request = mockServer.takeRequest() + val path = "/behavior?term=titanic&num_results=10&action=search-results&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackSearchResultLoadedTimeout() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSearchResultsLoaded("titanic", 10).test() + observer.assertError(SocketTimeoutException::class.java) + val request = mockServer.takeRequest() + val path = "/behavior?term=titanic&num_results=10&action=search-results&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackInputFocus() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackInputFocus("tita").test() + observer.assertComplete() + val request = mockServer.takeRequest() + val path = "/behavior?term=tita&action=focus&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackInputFocus500() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackInputFocus("tita").test() + observer.assertError { true } + val request = mockServer.takeRequest() + val path = "/behavior?term=tita&action=focus&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackInputFocusTimeout() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackInputFocus("tita").test() + observer.assertError(SocketTimeoutException::class.java) + val request = mockServer.takeRequest() + val path = "/behavior?term=tita&action=focus&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackPurchase() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackPurchase(arrayOf("TIT-REP-1997", "QE2-REP-1969"), 12.99, "Products").test() + observer.assertComplete() + val request = mockServer.takeRequest() + val path = "/autocomplete/TERM_UNKNOWN/purchase?customer_ids=TIT-REP-1997&customer_ids=QE2-REP-1969&revenue=12.99&autocomplete_section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackPurchase500() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackPurchase(arrayOf("TIT-REP-1997", "QE2-REP-1969"), 12.99, "Products").test() + observer.assertError { true } + val request = mockServer.takeRequest() + val path = "/autocomplete/TERM_UNKNOWN/purchase?customer_ids=TIT-REP-1997&customer_ids=QE2-REP-1969&revenue=12.99&autocomplete_section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } + + @Test + fun trackPurchaseTimeout() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackPurchase(arrayOf("TIT-REP-1997", "QE2-REP-1969"), 12.99, "Products").test() + observer.assertError(SocketTimeoutException::class.java) + val request = mockServer.takeRequest() + val path = "/autocomplete/TERM_UNKNOWN/purchase?customer_ids=TIT-REP-1997&customer_ids=QE2-REP-1969&revenue=12.99&autocomplete_section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-1.3.0&_dt="; + assert(request.path.startsWith(path)) + } +} \ No newline at end of file diff --git a/library/src/test/java/io/constructor/core/ConstructorioSearchTest.kt b/library/src/test/java/io/constructor/core/ConstructorioSearchTest.kt new file mode 100644 index 00000000..ece0995c --- /dev/null +++ b/library/src/test/java/io/constructor/core/ConstructorioSearchTest.kt @@ -0,0 +1,116 @@ +package io.constructor.core + +import android.content.Context +import io.constructor.data.local.PreferencesHelper +import io.constructor.data.memory.ConfigMemoryHolder +import io.constructor.test.createTestDataManager +import io.constructor.util.RxSchedulersOverrideRule +import io.constructor.util.TestDataLoader +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.util.concurrent.TimeUnit + +class ConstructorIoSearchTest { + + @Rule + @JvmField + val overrideSchedulersRule = RxSchedulersOverrideRule() + + private lateinit var mockServer: MockWebServer + private var constructorIo = ConstructorIo + private val ctx = mockk() + private val preferencesHelper = mockk() + private val configMemoryHolder = mockk() + + @Before + fun setup() { + every { ctx.applicationContext } returns ctx + every { preferencesHelper.token } returns "silver-key" + every { preferencesHelper.id } returns "guapo-the-guid" + every { preferencesHelper.getSessionId(any(), any()) } returns 92 + every { configMemoryHolder.autocompleteResultCount } returns null + every { configMemoryHolder.testCellParams = any() } just Runs + every { configMemoryHolder.userId } returns "player-two" + every { configMemoryHolder.testCellParams } returns emptyList() + + mockServer = MockWebServer() + mockServer.start() + val config = ConstructorIoConfig("dummyKey", testCells = listOf("flavor" to "vanilla", "topping" to "whipped-cream")) + val dataManager = createTestDataManager(mockServer, preferencesHelper, configMemoryHolder, ctx) + + constructorIo.testInit(ctx, config, dataManager, preferencesHelper, configMemoryHolder) + } + + @Test + fun getSearchResults() { + val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("search_response.json")) + mockServer.enqueue(mockResponse) + val observer = constructorIo.getSearchResults("corn").test() + observer.assertComplete().assertValue { + it.get()!!.searchData.results!!.size == 20 + } + val request = mockServer.takeRequest() + val path = "/search/corn?key=silver-key&i=guapo-the-guid&ui=player-two&s=92&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } + + @Test + fun getSearchResultsWithServerError() { + val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") + mockServer.enqueue(mockResponse) + val observer = constructorIo.getSearchResults("corn").test() + observer.assertComplete().assertValue { + it.networkError + } + val request = mockServer.takeRequest() + val path = "/search/corn?key=silver-key&i=guapo-the-guid&ui=player-two&s=92&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } + + @Test + fun getSearchResultsWithTimeout() { + val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("search_response.json")) + mockResponse.throttleBody(128, 5, TimeUnit.SECONDS) + mockServer.enqueue(mockResponse) + val observer = constructorIo.getSearchResults("corn").test() + observer.assertComplete().assertValue { + it.isError + } + val request = mockServer.takeRequest() + val path = "/search/corn?key=silver-key&i=guapo-the-guid&ui=player-two&s=92&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } + + @Test + fun getSearchResultsWithUnexpectedResponse() { + val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("search_response_unexpected_data.json")) + mockServer.enqueue(mockResponse) + val observer = constructorIo.getSearchResults("corn").test() + observer.assertComplete().assertValue { + it.get()!!.searchData.resultCount == 23 + } + val request = mockServer.takeRequest() + val path = "/search/corn?key=silver-key&i=guapo-the-guid&ui=player-two&s=92&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } + + @Test + fun getSearchResultsWithEmptyResponse() { + val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("search_response_empty.json")) + mockServer.enqueue(mockResponse) + val observer = constructorIo.getSearchResults("corn").test() + observer.assertComplete().assertValue { + it.get()!!.searchData.results!!.isEmpty() + } + val request = mockServer.takeRequest() + val path = "/search/corn?key=silver-key&i=guapo-the-guid&ui=player-two&s=92&c=cioand-1.3.0&_dt=" + assert(request.path.startsWith(path)) + } +} \ No newline at end of file diff --git a/library/src/test/java/io/constructor/data/DataManagerHttpTest.kt b/library/src/test/java/io/constructor/data/DataManagerHttpTest.kt deleted file mode 100755 index 76524626..00000000 --- a/library/src/test/java/io/constructor/data/DataManagerHttpTest.kt +++ /dev/null @@ -1,513 +0,0 @@ -package io.constructor.data - -import android.content.Context -import com.squareup.moshi.KotlinJsonAdapterFactory -import com.squareup.moshi.Moshi -import io.constructor.core.Constants -import io.constructor.data.interceptor.TokenInterceptor -import io.constructor.data.local.PreferencesHelper -import io.constructor.data.memory.ConfigMemoryHolder -import io.constructor.data.remote.ApiPaths -import io.constructor.data.remote.ConstructorApi -import io.constructor.util.RxSchedulersOverrideRule -import io.constructor.util.TestDataLoader -import io.mockk.Runs -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import retrofit2.Retrofit -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory -import retrofit2.converter.moshi.MoshiConverterFactory -import java.net.SocketTimeoutException -import java.util.concurrent.TimeUnit - - - - -class DataManagerHttpTest { - - @Rule - @JvmField val overrideSchedulersRule = RxSchedulersOverrideRule() - - private lateinit var constructorApi: ConstructorApi - - private val ctx = mockk() - private val pref = mockk() - private val configMemoryHolder = mockk() - - private lateinit var dataManager: DataManager - - private lateinit var mockServer: MockWebServer - - @Before - fun setup() { - every { pref.token } returns "123" - every { pref.id } returns "1" - every { configMemoryHolder.testCellParams = any() } just Runs - every { configMemoryHolder.userId } returns "id1" - every { configMemoryHolder.testCellParams } returns emptyList() - mockServer = MockWebServer() - mockServer.start() - val basePath = mockServer.url("" ) - - val client = OkHttpClient.Builder().addInterceptor(TokenInterceptor(ctx, pref, configMemoryHolder)) - .addInterceptor { chain -> - var request = chain.request() - if (!request.url().toString().startsWith(basePath.toString())) { - val requestUrl = request.url() - val newRequestUrl = HttpUrl.Builder().scheme(basePath.scheme()) - .encodedQuery(requestUrl.encodedQuery()) - .host(basePath.host()) - .port(basePath.port()) - .encodedPath(requestUrl.encodedPath()).build() - request = request.newBuilder() - .url(newRequestUrl) - .build() - } - chain.proceed(request) - } - .readTimeout(4, TimeUnit.SECONDS).build() - - - val moshi = Moshi - .Builder() - .add(KotlinJsonAdapterFactory()) - .build() - - // Get an instance of Retrofit - val retrofit = Retrofit.Builder() - .baseUrl(basePath.toString()) - .client(client) - .addConverterFactory(MoshiConverterFactory.create(moshi)) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .build() - - constructorApi = retrofit.create(ConstructorApi::class.java) - dataManager = DataManager(constructorApi, moshi) - } - - @Test - fun getAutocompleteResults() { - val path = "/" + ApiPaths.URL_AUTOCOMPLETE.replace("{value}", "titanic") - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("autocomplete_response.json")) - mockServer.enqueue(mockResponse) - val observer = dataManager.getAutocompleteResults("titanic").test() - observer.assertComplete().assertValue { - it.get()!!.isNotEmpty() && it.get()!!.size == 5 - } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - } - - @Test - fun getAutocompleteResultsBadServerResponse() { - val path = "/" + ApiPaths.URL_AUTOCOMPLETE.replace("{value}", "titanic") - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockServer.enqueue(mockResponse) - val observer = dataManager.getAutocompleteResults("titanic").test() - observer.assertComplete().assertValue { - it.networkError - } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - } - - @Test - fun getAutocompleteResultsTimeoutException() { - val path = "/" + ApiPaths.URL_AUTOCOMPLETE.replace("{value}", "titanic") - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("autocomplete_response.json")) - mockResponse.throttleBody(128, 5, TimeUnit.SECONDS) - mockServer.enqueue(mockResponse) - val observer = dataManager.getAutocompleteResults("titanic").test() - observer.assertComplete().assertValue { - it.isError - } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - } - - @Test - fun getAutocompleteResultsUnexpectedDataResponse() { - val path = "/" + ApiPaths.URL_AUTOCOMPLETE.replace("{value}", "titanic") - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("autocomplete_response_with_unexpected_data.json")) - mockServer.enqueue(mockResponse) - val observer = dataManager.getAutocompleteResults("titanic").test() - observer.assertComplete().assertValue { - it.get()!!.isNotEmpty() && it.get()!!.size == 5 - } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - } - - @Test - fun getAutocompleteResultsEmptyResponse() { - val path = "/" + ApiPaths.URL_AUTOCOMPLETE.replace("{value}", "titanic") - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("autocomplete_response_empty.json")) - mockServer.enqueue(mockResponse) - val observer = dataManager.getAutocompleteResults("titanic").test() - observer.assertComplete().assertValue { - it.isEmpty - } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - } - - @Test - fun trackAutocompleteSelect() { - val path = "/" + ApiPaths.URL_AUTOCOMPLETE_SELECT_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(204) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackAutocompleteSelect("titanic").test() - observer.assertComplete() - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - } - - @Test - fun trackAutocompleteSelect500() { - val path = "/" + ApiPaths.URL_AUTOCOMPLETE_SELECT_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockServer.enqueue(mockResponse) - val observer = dataManager.trackAutocompleteSelect("titanic").test() - observer.assertError { true } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - } - - @Test - fun trackAutocompleteTimeout() { - val path = "/" + ApiPaths.URL_AUTOCOMPLETE_SELECT_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackAutocompleteSelect("titanic").test() - observer.assertError(SocketTimeoutException::class.java) - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - } - - @Test - fun trackSearchSubmit() { - val path = "/" + ApiPaths.URL_SEARCH_SUBMIT_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(204) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSearchSubmit("titanic").test() - observer.assertComplete() - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - } - - @Test - fun trackSearchSubmit500() { - val path = "/" + ApiPaths.URL_SEARCH_SUBMIT_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSearchSubmit("titanic").test() - observer.assertError { true } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - } - - @Test - fun trackSearchSubmitTimeout() { - val path = "/" + ApiPaths.URL_SEARCH_SUBMIT_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSearchSubmit("titanic").test() - observer.assertError(SocketTimeoutException::class.java) - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - } - - @Test - fun trackSessionStart() { - val path = "/" + ApiPaths.URL_SESSION_START_EVENT - val mockResponse = MockResponse().setResponseCode(204) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSessionStart(arrayOf(Constants.QueryConstants.SESSION to "1")).test() - observer.assertComplete() - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("s=1")) - } - - @Test - fun trackSessionStart500() { - val path = "/" + ApiPaths.URL_SESSION_START_EVENT - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSessionStart(arrayOf(Constants.QueryConstants.SESSION to "1")).test() - observer.assertError { true } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("s=1")) - } - - @Test - fun trackSessionStartTimeout() { - val path = "/" + ApiPaths.URL_SESSION_START_EVENT - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSessionStart(arrayOf(Constants.QueryConstants.SESSION to "1")).test() - observer.assertError(SocketTimeoutException::class.java) - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("s=1")) - } - - @Test - fun trackConversion() { - val path = "/" + ApiPaths.URL_CONVERSION_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(204) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackConversion("titanic", "ship", "cid").test() - observer.assertComplete() - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("name=ship")) - assert(request.path.contains("customer_id=cid")) - } - - @Test - fun trackConversion500() { - val path = "/" + ApiPaths.URL_CONVERSION_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockServer.enqueue(mockResponse) - val observer = dataManager.trackConversion("titanic", "ship", "cid").test() - observer.assertError { true } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("name=ship")) - assert(request.path.contains("customer_id=cid")) - } - - @Test - fun trackConversionTimeout() { - val path = "/" + ApiPaths.URL_CONVERSION_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackConversion("titanic", "ship", "cid").test() - observer.assertError(SocketTimeoutException::class.java) - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("name=ship")) - assert(request.path.contains("customer_id=cid")) - } - - @Test - fun trackSearchResultClick() { - val path = "/" + ApiPaths.URL_SEARCH_RESULT_CLICK_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(204) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSearchResultClick("ship", "cid", "titanic").test() - observer.assertComplete() - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("name=ship")) - assert(request.path.contains("customer_id=cid")) - } - - @Test - fun trackSearchResultClick500() { - val path = "/" + ApiPaths.URL_SEARCH_RESULT_CLICK_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSearchResultClick("ship", "cid", "titanic").test() - observer.assertError { true } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("name=ship")) - assert(request.path.contains("customer_id=cid")) - } - - @Test - fun trackSearchResultClickTimeout() { - val path = "/" + ApiPaths.URL_SEARCH_RESULT_CLICK_EVENT.replace("{term}", "titanic") - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSearchResultClick("ship", "cid", "titanic").test() - observer.assertError(SocketTimeoutException::class.java) - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("name=ship")) - assert(request.path.contains("customer_id=cid")) - } - - @Test - fun trackSearchResultLoaded() { - val path = "/" + ApiPaths.URL_BEHAVIOR - val mockResponse = MockResponse().setResponseCode(204) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSearchResultsLoaded("titanic", 10, arrayOf(Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_SEARCH_RESULTS)).test() - observer.assertComplete() - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("${Constants.QueryConstants.ACTION}=${Constants.QueryValues.EVENT_SEARCH_RESULTS}")) - } - - @Test - fun trackSearchResultLoaded500() { - val path = "/" + ApiPaths.URL_BEHAVIOR - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSearchResultsLoaded("titanic", 10, arrayOf(Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_SEARCH_RESULTS)).test() - observer.assertError { true } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("${Constants.QueryConstants.ACTION}=${Constants.QueryValues.EVENT_SEARCH_RESULTS}")) - } - - @Test - fun trackSearchResultLoadedTimeout() { - val path = "/" + ApiPaths.URL_BEHAVIOR - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackSearchResultsLoaded("titanic", 10, arrayOf(Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_SEARCH_RESULTS)).test() - observer.assertError(SocketTimeoutException::class.java) - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("${Constants.QueryConstants.ACTION}=${Constants.QueryValues.EVENT_SEARCH_RESULTS}")) - } - - @Test - fun trackInputFocus() { - val path = "/" + ApiPaths.URL_BEHAVIOR - val mockResponse = MockResponse().setResponseCode(204) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackInputFocus("titanic", arrayOf(Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_INPUT_FOCUS)).test() - observer.assertComplete() - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("${Constants.QueryConstants.ACTION}=${Constants.QueryValues.EVENT_INPUT_FOCUS}")) - } - - @Test - fun trackInputFocus500() { - val path = "/" + ApiPaths.URL_BEHAVIOR - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockServer.enqueue(mockResponse) - val observer = dataManager.trackInputFocus("titanic", arrayOf(Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_INPUT_FOCUS)).test() - observer.assertError { true } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("${Constants.QueryConstants.ACTION}=${Constants.QueryValues.EVENT_INPUT_FOCUS}")) - } - - @Test - fun trackInputFocusTimeout() { - val path = "/" + ApiPaths.URL_BEHAVIOR - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackInputFocus("titanic", arrayOf(Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_INPUT_FOCUS)).test() - observer.assertError(SocketTimeoutException::class.java) - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("${Constants.QueryConstants.ACTION}=${Constants.QueryValues.EVENT_INPUT_FOCUS}")) - } - - @Test - fun trackPurchase() { - val path = "/" + ApiPaths.URL_PURCHASE - val mockResponse = MockResponse().setResponseCode(204) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackPurchase(listOf("1", "2"), "12.99", arrayOf()).test() - observer.assertComplete() - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("${Constants.QueryConstants.CUSTOMER_ID}=1")) - assert(request.path.contains("${Constants.QueryConstants.CUSTOMER_ID}=2")) - } - - @Test - fun trackPurchase500() { - val path = "/" + ApiPaths.URL_PURCHASE - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockServer.enqueue(mockResponse) - val observer = dataManager.trackPurchase(listOf("1", "2"), "12.99", arrayOf()).test() - observer.assertError { true } - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("${Constants.QueryConstants.CUSTOMER_ID}=1")) - assert(request.path.contains("${Constants.QueryConstants.CUSTOMER_ID}=2")) - } - - @Test - fun trackPurchaseTimeout() { - val path = "/" + ApiPaths.URL_PURCHASE - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockResponse.throttleBody(0, 5, TimeUnit.SECONDS) - mockServer.enqueue(mockResponse) - val observer = dataManager.trackPurchase(listOf("1", "2"), "12.99", arrayOf()).test() - observer.assertError(SocketTimeoutException::class.java) - val request = mockServer.takeRequest() - assert(request.path.startsWith(path)) - assert(request.path.contains("${Constants.QueryConstants.CUSTOMER_ID}=1")) - assert(request.path.contains("${Constants.QueryConstants.CUSTOMER_ID}=2")) - } - - @Test - fun getSearchResult() { - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("search_response.json")) - mockServer.enqueue(mockResponse) - val observer = dataManager.getSearchResults("corn").test() - observer.assertComplete().assertValue { - it.get()!!.searchData.results!!.size == 20 - } - } - - @Test - fun getSearchResultsBadServerResponse() { - val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") - mockServer.enqueue(mockResponse) - val observer = dataManager.getSearchResults("corn").test() - observer.assertComplete().assertValue { - it.networkError - } - } - - @Test - fun getSearchResultsTimeoutException() { - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("search_response.json")) - mockResponse.throttleBody(128, 5, TimeUnit.SECONDS) - mockServer.enqueue(mockResponse) - val observer = dataManager.getSearchResults("corn").test() - observer.assertComplete().assertValue { - it.isError - } - } - - @Test - fun getSearchUnexpectedDataResponse() { - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("search_response_unexpected_data.json")) - mockServer.enqueue(mockResponse) - val observer = dataManager.getSearchResults("corn").test() - observer.assertComplete().assertValue { - it.get()!!.searchData.resultCount == 23 - } - } - - @Test - fun getSearchResultsEmptyResponse() { - val path = "/" + ApiPaths.URL_SEARCH.format("corn") - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("search_response_empty.json")) - mockServer.enqueue(mockResponse) - val observer = dataManager.getSearchResults("corn").test() - observer.assertComplete().assertValue { - it.get()!!.searchData.results!!.isEmpty() - } - } - -} \ No newline at end of file diff --git a/library/src/test/java/io/constructor/test/helpers.kt b/library/src/test/java/io/constructor/test/helpers.kt new file mode 100644 index 00000000..4bfe9bfa --- /dev/null +++ b/library/src/test/java/io/constructor/test/helpers.kt @@ -0,0 +1,45 @@ +package io.constructor.test + +import android.content.Context +import io.constructor.data.DataManager +import io.constructor.data.local.PreferencesHelper +import io.constructor.data.memory.ConfigMemoryHolder +import io.constructor.data.remote.ConstructorApi +import io.constructor.injection.module.NetworkModule +import okhttp3.HttpUrl +import okhttp3.mockwebserver.MockWebServer +import java.util.concurrent.TimeUnit + +/** + * Creates a data manager that communicates with a Mock Web Server + */ +fun createTestDataManager(mockServer : MockWebServer, + preferencesHelper: PreferencesHelper, + configMemoryHolder: ConfigMemoryHolder, + ctx: Context +): DataManager { + + val basePath = mockServer.url("") + val networkModule = NetworkModule(ctx); + val loggingInterceptor = networkModule.provideHttpLoggingInterceptor() + val tokenInterceptor = networkModule.provideTokenInterceptor(preferencesHelper, configMemoryHolder) + val moshi = networkModule.provideMoshi() + + // Intercept all requests to the Constructor API and point them to a mock web server + val okHttpClient = networkModule.provideOkHttpClient(loggingInterceptor, tokenInterceptor).newBuilder().addInterceptor { chain -> + var request = chain.request() + val requestUrl = request.url() + val newRequestUrl = HttpUrl.Builder().scheme(basePath.scheme()) + .encodedQuery(requestUrl.encodedQuery()) + .host(basePath.host()) + .port(basePath.port()) + .encodedPath(requestUrl.encodedPath()).build() + request = request.newBuilder() + .url(newRequestUrl) + .build() + chain.proceed(request) + }.readTimeout(1, TimeUnit.SECONDS).build() + val retrofit = networkModule.provideRetrofit(okHttpClient, moshi) + val constructorApi = retrofit.create(ConstructorApi::class.java) + return DataManager(constructorApi, moshi); +} \ No newline at end of file