diff --git a/build.gradle b/build.gradle index 57530186..c030adcc 100755 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.3.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'org.jacoco:org.jacoco.core:0.8.3' + classpath 'org.jacoco:org.jacoco.core:0.8.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' } } diff --git a/library/build.gradle b/library/build.gradle index 428af783..82d21b3b 100755 --- a/library/build.gradle +++ b/library/build.gradle @@ -8,7 +8,7 @@ apply plugin: 'jacoco' group='com.github.Constructor-io' jacoco { - toolVersion = '0.8.3' + toolVersion = '0.8.1' } tasks.withType(Test) { @@ -158,12 +158,12 @@ apply from: 'dependencies.gradle' dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' - implementation 'io.reactivex.rxjava2:rxjava:2.1.8' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' + implementation 'io.reactivex.rxjava2:rxjava:2.1.13' implementation 'io.reactivex.rxjava2:rxkotlin:2.2.0' testImplementation 'io.mockk:mockk:1.9.kotlin12' testImplementation 'org.robolectric:robolectric:3.6.1' - testImplementation 'com.squareup.okhttp3:mockwebserver:3.11.0' + testImplementation 'com.squareup.okhttp3:mockwebserver:3.14.1' implementation supportLibs implementation networkLibs implementation otherLibs diff --git a/library/dependencies.gradle b/library/dependencies.gradle index fc99e893..574c9589 100755 --- a/library/dependencies.gradle +++ b/library/dependencies.gradle @@ -2,7 +2,7 @@ ext { versions = [ support : "28.0.0", moshi : "1.5.0", - okHttp : "3.9.0", + okHttp : "3.14.1", retrofit: '2.3.0', dagger : '2.14.1', junit : '4.12', diff --git a/library/src/main/java/io/constructor/core/Constants.kt b/library/src/main/java/io/constructor/core/Constants.kt index a0e9167b..bf1aafbd 100755 --- a/library/src/main/java/io/constructor/core/Constants.kt +++ b/library/src/main/java/io/constructor/core/Constants.kt @@ -27,6 +27,10 @@ class Constants { const val GROUP_DISPLAY_NAME = "group[display_name]" const val USER_ID = "ui" const val TERM_UNKNOWN = "TERM_UNKNOWN" + const val PAGE = "page" + const val PER_PAGE = "num_results_per_page" + const val FILTER_GROUP_ID = "filters[group_id]" + const val FILTER_FACET = "filters[%s]" } object QueryValues { diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index df83ba94..bc6992be 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -8,6 +8,7 @@ import io.constructor.data.local.PreferencesHelper import io.constructor.data.memory.ConfigMemoryHolder import io.constructor.data.model.Group import io.constructor.data.model.Suggestion +import io.constructor.data.model.search.SearchResponse import io.constructor.injection.component.AppComponent import io.constructor.injection.component.DaggerAppComponent import io.constructor.injection.module.AppModule @@ -105,6 +106,25 @@ object ConstructorIo { return dataManager.getAutocompleteResults(query, params.toTypedArray()) } + fun getSearchResults(text: String, vararg facets: Pair>, page: Int? = null, perPage: Int? = null, groupId: Int? = null): Observable> { + val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler) + 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()) + facets.forEach { facet -> + facet.second.forEach { + encodedParams.add(Constants.QueryConstants.FILTER_FACET.format(facet.first).urlEncode() to it.urlEncode()) + } + } + 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) val encodedParams: ArrayList> = arrayListOf() @@ -121,7 +141,7 @@ object ConstructorIo { }, { t -> t.printStackTrace() errorCallback?.invoke(t) - e("trigger select error: ${t.message}") //To change body of created functions use File | Settings | File Templates. + e("Autocomplete Select event error: ${t.message}") })) } @@ -139,7 +159,7 @@ object ConstructorIo { }, { it.printStackTrace() errorCallback?.invoke(it) - e("trigger search error: ${it.message}") + e("Search Submit event error: ${it.message}") })) } @@ -165,7 +185,7 @@ object ConstructorIo { .subscribe({}, { t -> t.printStackTrace() errorCallback?.invoke(t) - e("Search result click event error: ${t.message}") + e("Search Result Click event error: ${t.message}") })) } @@ -177,7 +197,7 @@ object ConstructorIo { .subscribe({}, { t -> t.printStackTrace() errorCallback?.invoke(t) - e("Conversion event error: ${t.message}") + e("Search Results Loaded event error: ${t.message}") })) } @@ -189,7 +209,7 @@ object ConstructorIo { .subscribe({}, { t -> t.printStackTrace() errorCallback?.invoke(t) - e("Input focus event error: ${t.message}") + e("Input Focus event error: ${t.message}") })) } @@ -203,7 +223,7 @@ object ConstructorIo { .subscribe({}, { t -> t.printStackTrace() errorCallback?.invoke(t) - e("Input focus event error: ${t.message}") + e("Purchase event error: ${t.message}") })) } diff --git a/library/src/main/java/io/constructor/data/DataManager.kt b/library/src/main/java/io/constructor/data/DataManager.kt index 80c44395..80fb1c6e 100755 --- a/library/src/main/java/io/constructor/data/DataManager.kt +++ b/library/src/main/java/io/constructor/data/DataManager.kt @@ -1,6 +1,10 @@ package io.constructor.data +import com.squareup.moshi.Moshi +import io.constructor.BuildConfig import io.constructor.data.model.Suggestion +import io.constructor.data.model.search.SearchResponse +import io.constructor.data.remote.ApiPaths import io.constructor.data.remote.ConstructorApi import io.reactivex.Completable import io.reactivex.Observable @@ -9,9 +13,9 @@ import javax.inject.Singleton @Singleton class DataManager @Inject -constructor(private val constructorApi: ConstructorApi) { +constructor(private val constructorApi: ConstructorApi, private val moshi: Moshi) { - fun getAutocompleteResults(text: String, params: Array> = arrayOf()): Observable?>> = constructorApi.getSuggestions(text, params.toMap()).map { + fun getAutocompleteResults(text: String, params: Array> = arrayOf()): Observable?>> = constructorApi.getAutocompleteResults(text, params.toMap()).map { if (!it.isError) { it.response()?.let { if (it.isSuccessful) { @@ -25,6 +29,30 @@ constructor(private val constructorApi: ConstructorApi) { } }.toObservable() + fun getSearchResults(text: String, encodedParams: Array> = arrayOf()): Observable> { + var dynamicUrl = BuildConfig.BASE_API_URL + "/${ApiPaths.URL_SEARCH.format(text)}" + encodedParams.forEachIndexed { index, pair -> + dynamicUrl += "${if (index != 0) "&" else "?" }${pair.first}=${pair.second}" + } + return constructorApi.getSearchResults(dynamicUrl).map { result -> + if (!result.isError) { + result.response()?.let { + if (it.isSuccessful){ + val adapter = moshi.adapter(SearchResponse::class.java) + val response = it.body()?.string() + val result = response?.let { adapter.fromJson(it) } + result?.rawData = response + ConstructorData.of(result!!) + } else { + ConstructorData.networkError(it.errorBody()?.string()) + } + } ?: ConstructorData.error(result.error()) + } else { + ConstructorData.error(result.error()) + } + }.toObservable() + } + fun trackAutocompleteSelect(term: String, params: Array> = arrayOf(), encodedParams: Array> = arrayOf()): Completable { return constructorApi.trackAutocompleteSelect(term, params.toMap(), encodedParams.toMap()) } diff --git a/library/src/main/java/io/constructor/data/model/search/FacetOption.kt b/library/src/main/java/io/constructor/data/model/search/FacetOption.kt new file mode 100644 index 00000000..3900e7c5 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/search/FacetOption.kt @@ -0,0 +1,3 @@ +package io.constructor.data.model.search + +data class FacetOption(val count: Int, val value: String?) \ No newline at end of file diff --git a/library/src/main/java/io/constructor/data/model/search/Result.kt b/library/src/main/java/io/constructor/data/model/search/Result.kt new file mode 100644 index 00000000..364f7f26 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/search/Result.kt @@ -0,0 +1,5 @@ +package io.constructor.data.model.search + +import com.squareup.moshi.Json + +data class Result(@Json(name = "data") val result: ResultData, @Json(name = "matched_terms") val matchedTerms: List?, val value: String) diff --git a/library/src/main/java/io/constructor/data/model/search/ResultData.kt b/library/src/main/java/io/constructor/data/model/search/ResultData.kt new file mode 100644 index 00000000..5595199e --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/search/ResultData.kt @@ -0,0 +1,12 @@ +package io.constructor.data.model.search + +import com.squareup.moshi.Json +import io.constructor.data.model.Group + +data class ResultData(val description: String?, + val id: String, + @Json(name = "image_url") val imageUrl: String?, + val url: String?, + val facets: List?, + val groups: List?, + var metadata: Map?) \ No newline at end of file diff --git a/library/src/main/java/io/constructor/data/model/search/ResultFacet.kt b/library/src/main/java/io/constructor/data/model/search/ResultFacet.kt new file mode 100644 index 00000000..d92e7986 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/search/ResultFacet.kt @@ -0,0 +1,3 @@ +package io.constructor.data.model.search + +data class ResultFacet(val name: String, val values: List?) diff --git a/library/src/main/java/io/constructor/data/model/search/SearchData.kt b/library/src/main/java/io/constructor/data/model/search/SearchData.kt new file mode 100644 index 00000000..f40a9a98 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/search/SearchData.kt @@ -0,0 +1,6 @@ +package io.constructor.data.model.search + +import com.squareup.moshi.Json + + +data class SearchData(val facets: List?, val groups: List?, val results: List?, @Json(name = "total_num_results") val resultCount: Int) diff --git a/library/src/main/java/io/constructor/data/model/search/SearchFacet.kt b/library/src/main/java/io/constructor/data/model/search/SearchFacet.kt new file mode 100644 index 00000000..fa861f52 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/search/SearchFacet.kt @@ -0,0 +1,11 @@ +package io.constructor.data.model.search + +import com.squareup.moshi.Json + +data class SearchFacet(val name: String, + @Json(name = "display_name") val displayName: String?, + val status: Map?, + val type: String?, + val min: Int?, + val max: Int?, + val options: List?) diff --git a/library/src/main/java/io/constructor/data/model/search/SearchGroup.kt b/library/src/main/java/io/constructor/data/model/search/SearchGroup.kt new file mode 100644 index 00000000..8dbe9fa2 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/search/SearchGroup.kt @@ -0,0 +1,9 @@ +package io.constructor.data.model.search + +import com.squareup.moshi.Json + +data class SearchGroup(@Json(name = "children") val children: List?, + @Json(name = "parents") val parents: List?, + val count: Int, + @Json(name = "display_name") val displayName: String, + @Json(name = "group_id") val groupId: Long) \ No newline at end of file diff --git a/library/src/main/java/io/constructor/data/model/search/SearchResponse.kt b/library/src/main/java/io/constructor/data/model/search/SearchResponse.kt new file mode 100644 index 00000000..96d4ae86 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/search/SearchResponse.kt @@ -0,0 +1,5 @@ +package io.constructor.data.model.search + +import com.squareup.moshi.Json + +data class SearchResponse(@Json(name = "response") val searchData: SearchData, @Json(name = "result_id") val resultId: String, var rawData: String?) \ No newline at end of file diff --git a/library/src/main/java/io/constructor/data/remote/ApiPaths.kt b/library/src/main/java/io/constructor/data/remote/ApiPaths.kt index 2264b226..f55fac04 100755 --- a/library/src/main/java/io/constructor/data/remote/ApiPaths.kt +++ b/library/src/main/java/io/constructor/data/remote/ApiPaths.kt @@ -1,7 +1,7 @@ package io.constructor.data.remote object ApiPaths { - const val URL_GET_SUGGESTIONS = "autocomplete/{value}" + const val URL_AUTOCOMPLETE = "autocomplete/{value}" const val URL_AUTOCOMPLETE_SELECT_EVENT = "autocomplete/{term}/select" const val URL_SEARCH_SUBMIT_EVENT = "autocomplete/{term}/search" const val URL_SESSION_START_EVENT = "behavior" @@ -9,5 +9,6 @@ object ApiPaths { const val URL_SEARCH_RESULT_CLICK_EVENT = "autocomplete/{term}/click_through" const val URL_BEHAVIOR = "behavior" const val URL_PURCHASE = "autocomplete/TERM_UNKNOWN/purchase" + const val URL_SEARCH = "search/%s" } \ No newline at end of file diff --git a/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt b/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt index 128c4381..4cf5dae1 100755 --- a/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt +++ b/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt @@ -4,16 +4,14 @@ import io.constructor.core.Constants import io.constructor.data.model.AutocompleteResult import io.reactivex.Completable import io.reactivex.Single +import okhttp3.ResponseBody import retrofit2.adapter.rxjava2.Result -import retrofit2.http.GET -import retrofit2.http.Path -import retrofit2.http.Query -import retrofit2.http.QueryMap +import retrofit2.http.* interface ConstructorApi { - @GET(ApiPaths.URL_GET_SUGGESTIONS) - fun getSuggestions(@Path("value") value: String, @QueryMap data: Map): Single> + @GET(ApiPaths.URL_AUTOCOMPLETE) + fun getAutocompleteResults(@Path("value") value: String, @QueryMap data: Map): Single> @GET(ApiPaths.URL_AUTOCOMPLETE_SELECT_EVENT) fun trackAutocompleteSelect(@Path("term") term: String, @QueryMap data: Map, @QueryMap(encoded = true) encodedData: Map): Completable @@ -40,4 +38,8 @@ interface ConstructorApi { fun trackPurchase(@Query(Constants.QueryConstants.CUSTOMER_ID) customerIds: List, @Query("revenue") revenue: String?, @QueryMap params: Map): Completable + + @GET + fun getSearchResults(@Url searchUrl: String): Single> + } \ 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 index 1c2ad871..76524626 100755 --- a/library/src/test/java/io/constructor/data/DataManagerHttpTest.kt +++ b/library/src/test/java/io/constructor/data/DataManagerHttpTest.kt @@ -15,6 +15,7 @@ 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 @@ -27,6 +28,9 @@ import retrofit2.converter.moshi.MoshiConverterFactory import java.net.SocketTimeoutException import java.util.concurrent.TimeUnit + + + class DataManagerHttpTest { @Rule @@ -51,8 +55,26 @@ class DataManagerHttpTest { 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 client = OkHttpClient.Builder().addInterceptor(TokenInterceptor(ctx, pref, configMemoryHolder)).readTimeout(4, TimeUnit.SECONDS).build() val moshi = Moshi .Builder() @@ -61,20 +83,20 @@ class DataManagerHttpTest { // Get an instance of Retrofit val retrofit = Retrofit.Builder() - .baseUrl(mockServer.url("").toString()) + .baseUrl(basePath.toString()) .client(client) .addConverterFactory(MoshiConverterFactory.create(moshi)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build() constructorApi = retrofit.create(ConstructorApi::class.java) - dataManager = DataManager(constructorApi) + dataManager = DataManager(constructorApi, moshi) } @Test fun getAutocompleteResults() { - val path = "/" + ApiPaths.URL_GET_SUGGESTIONS.replace("{value}", "titanic") - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("response.json")) + 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 { @@ -86,7 +108,7 @@ class DataManagerHttpTest { @Test fun getAutocompleteResultsBadServerResponse() { - val path = "/" + ApiPaths.URL_GET_SUGGESTIONS.replace("{value}", "titanic") + 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() @@ -99,8 +121,8 @@ class DataManagerHttpTest { @Test fun getAutocompleteResultsTimeoutException() { - val path = "/" + ApiPaths.URL_GET_SUGGESTIONS.replace("{value}", "titanic") - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("response.json")) + 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() @@ -113,8 +135,8 @@ class DataManagerHttpTest { @Test fun getAutocompleteResultsUnexpectedDataResponse() { - val path = "/" + ApiPaths.URL_GET_SUGGESTIONS.replace("{value}", "titanic") - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("response_with_unexpected_data.json")) + 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 { @@ -126,8 +148,8 @@ class DataManagerHttpTest { @Test fun getAutocompleteResultsEmptyResponse() { - val path = "/" + ApiPaths.URL_GET_SUGGESTIONS.replace("{value}", "titanic") - val mockResponse = MockResponse().setResponseCode(200).setBody(TestDataLoader.loadAsString("empty_response.json")) + 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 { @@ -436,4 +458,56 @@ class DataManagerHttpTest { 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/data/DataManagerTest.kt b/library/src/test/java/io/constructor/data/DataManagerTest.kt index b834292b..6a65a4d8 100755 --- a/library/src/test/java/io/constructor/data/DataManagerTest.kt +++ b/library/src/test/java/io/constructor/data/DataManagerTest.kt @@ -1,5 +1,7 @@ package io.constructor.data +import com.squareup.moshi.KotlinJsonAdapterFactory +import com.squareup.moshi.Moshi import io.constructor.data.model.AutocompleteResult import io.constructor.data.remote.ConstructorApi import io.constructor.util.RxSchedulersOverrideRule @@ -23,11 +25,16 @@ class DataManagerTest { private var constructorApi = mockk() - private var dataManager = DataManager(constructorApi) + private var moshi = Moshi + .Builder() + .add(KotlinJsonAdapterFactory()) + .build() + + private var dataManager = DataManager(constructorApi, moshi) @Test - fun getSuggestions() { - every { constructorApi.getSuggestions("titanic", any()) } returns Single.just(Result.response(Response.success(TestDataLoader.loadResponse()))) + fun getAutocompleteResults() { + every { constructorApi.getAutocompleteResults("titanic", any()) } returns Single.just(Result.response(Response.success(TestDataLoader.loadResponse()))) val observer = dataManager.getAutocompleteResults("titanic").test() observer.assertComplete().assertValue { it.get()!!.isNotEmpty() && it.get()!!.size == 5 @@ -35,8 +42,8 @@ class DataManagerTest { } @Test - fun getSuggestionsBadServerResponse() { - every { constructorApi.getSuggestions("titanic", any()) } returns Single.just(Result.response(Response.error(500, ResponseBody.create(MediaType.parse("text/plain"), "Error")))) + fun getAutocompleteResultsBadServerResponse() { + every { constructorApi.getAutocompleteResults("titanic", any()) } returns Single.just(Result.response(Response.error(500, ResponseBody.create(MediaType.parse("text/plain"), "Error")))) val observer = dataManager.getAutocompleteResults("titanic").test() observer.assertComplete().assertValue { it.networkError @@ -44,8 +51,8 @@ class DataManagerTest { } @Test - fun getSuggestionsException() { - every { constructorApi.getSuggestions("titanic", any()) } returns Single.just(Result.error(Exception())) + fun getAutocompleteResultsException() { + every { constructorApi.getAutocompleteResults("titanic", any()) } returns Single.just(Result.error(Exception())) val observer = dataManager.getAutocompleteResults("titanic").test() observer.assertComplete().assertValue { it.isError @@ -53,8 +60,8 @@ class DataManagerTest { } @Test - fun getSuggestionsUnexpectedDataResponse() { - every { constructorApi.getSuggestions("titanic", any()) } returns Single.just(Result.response(Response.success(TestDataLoader.loadResponseWithUnexpectedData()))) + fun getAutocompleteResultsUnexpectedDataResponse() { + every { constructorApi.getAutocompleteResults("titanic", any()) } returns Single.just(Result.response(Response.success(TestDataLoader.loadResponseWithUnexpectedData()))) val observer = dataManager.getAutocompleteResults("titanic").test() observer.assertComplete().assertValue { it.get()!!.isNotEmpty() && it.get()!!.size == 5 @@ -62,8 +69,8 @@ class DataManagerTest { } @Test - fun getSuggestionsEmptyResponse() { - every { constructorApi.getSuggestions("titanic", any() + fun getAutocompleteResultsEmptyResponse() { + every { constructorApi.getAutocompleteResults("titanic", any() ) } returns Single.just(Result.response(Response.success(TestDataLoader.loadEmptyResponse()))) val observer = dataManager.getAutocompleteResults("titanic").test() observer.assertComplete().assertValue { @@ -191,4 +198,32 @@ class DataManagerTest { verify(exactly = 1) { constructorApi.trackPurchase(any(), any(), any()) } } + @Test + fun getSearchResults() { + val rb = ResponseBody.create(MediaType.get("application/json"), TestDataLoader.loadAsString("search_response.json")) + every { constructorApi.getSearchResults(any()) } returns Single.just(Result.response(Response.success(rb))) + val observer = dataManager.getSearchResults("corn").test() + observer.assertComplete().assertValue { + it.get()!!.searchData.resultCount == 23 + } + } + + @Test + fun getSearchResultsBadServerResponse() { + every { constructorApi.getSearchResults("https://ac.cnstrc.com/search/corn") } returns Single.just(Result.response(Response.error(500, ResponseBody.create(MediaType.parse("text/plain"), "Error")))) + val observer = dataManager.getSearchResults("corn").test() + observer.assertComplete().assertValue { + it.networkError + } + } + + @Test + fun getSearchResultsException() { + every { constructorApi.getSearchResults("https://ac.cnstrc.com/search/corn") } returns Single.just(Result.error(Exception())) + val observer = dataManager.getSearchResults("corn").test() + observer.assertComplete().assertValue { + it.isError + } + } + } \ No newline at end of file diff --git a/library/src/test/java/io/constructor/util/TestDataLoader.kt b/library/src/test/java/io/constructor/util/TestDataLoader.kt index 140a0693..684130c0 100755 --- a/library/src/test/java/io/constructor/util/TestDataLoader.kt +++ b/library/src/test/java/io/constructor/util/TestDataLoader.kt @@ -3,6 +3,7 @@ package io.constructor.util import com.squareup.moshi.KotlinJsonAdapterFactory import com.squareup.moshi.Moshi import io.constructor.data.model.AutocompleteResult +import io.constructor.data.model.search.SearchResponse import okio.Buffer import java.io.File import java.io.FileInputStream @@ -12,11 +13,11 @@ import java.nio.charset.Charset object TestDataLoader { - fun loadResponse() : AutocompleteResult? = loadResult("response.json") + fun loadResponse() : AutocompleteResult? = loadResult("autocomplete_response.json") - fun loadResponseWithUnexpectedData() : AutocompleteResult? = loadResult("response_with_unexpected_data.json") + fun loadResponseWithUnexpectedData() : AutocompleteResult? = loadResult("autocomplete_response_with_unexpected_data.json") - fun loadEmptyResponse() : AutocompleteResult? = loadResult("empty_response.json") + fun loadEmptyResponse() : AutocompleteResult? = loadResult("autocomplete_response_empty.json") private fun loadResult(fileName: String): AutocompleteResult? { val file = File(TestDataLoader::class.java.classLoader.getResource(fileName).path) @@ -31,6 +32,18 @@ object TestDataLoader { return result } + private fun convertToSearchResult(stringResponse: String): SearchResponse? { + val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() + val jsonAdapter = moshi.adapter(SearchResponse::class.java) + var result: SearchResponse? = null + try { + result = jsonAdapter.fromJson(stringResponse) + } catch (e: IOException) { + e.printStackTrace() + } + return result + } + fun loadAsString(fileName: String): String { var result = "" try { diff --git a/library/src/test/resources/response.json b/library/src/test/resources/autocomplete_response.json similarity index 100% rename from library/src/test/resources/response.json rename to library/src/test/resources/autocomplete_response.json diff --git a/library/src/test/resources/empty_response.json b/library/src/test/resources/autocomplete_response_empty.json similarity index 100% rename from library/src/test/resources/empty_response.json rename to library/src/test/resources/autocomplete_response_empty.json diff --git a/library/src/test/resources/response_with_unexpected_data.json b/library/src/test/resources/autocomplete_response_with_unexpected_data.json similarity index 100% rename from library/src/test/resources/response_with_unexpected_data.json rename to library/src/test/resources/autocomplete_response_with_unexpected_data.json diff --git a/library/src/test/resources/search_response.json b/library/src/test/resources/search_response.json new file mode 100755 index 00000000..1cce0cbe --- /dev/null +++ b/library/src/test/resources/search_response.json @@ -0,0 +1,598 @@ +{ + "request": { + "ef-11": "22", + "ef-ab": "cd", + "fmt_options": { + "groups_max_depth": 1, + "groups_start": "current" + }, + "num_results_per_page": 20, + "page": 1, + "section": "Products", + "sort_by": "relevance", + "sort_order": "descending", + "term": "corn" + }, + "response": { + "facets": [], + "groups": [ + { + "children": [], + "count": 9, + "display_name": "Horror", + "group_id": "27", + "parents": [] + }, + { + "children": [], + "count": 7, + "display_name": "Thriller", + "group_id": "53", + "parents": [] + }, + { + "children": [], + "count": 4, + "display_name": "Drama", + "group_id": "18", + "parents": [] + }, + { + "children": [], + "count": 3, + "display_name": "Documentary", + "group_id": "99", + "parents": [] + }, + { + "children": [], + "count": 1, + "display_name": "Mystery", + "group_id": "9648", + "parents": [] + }, + { + "children": [], + "count": 1, + "display_name": "Fantasy", + "group_id": "14", + "parents": [] + }, + { + "children": [], + "count": 1, + "display_name": "Animation", + "group_id": "16", + "parents": [] + }, + { + "children": [], + "count": 6, + "display_name": "Comedy", + "group_id": "35", + "parents": [] + }, + { + "children": [], + "count": 3, + "display_name": "Crime", + "group_id": "80", + "parents": [] + }, + { + "children": [], + "count": 3, + "display_name": "Action", + "group_id": "28", + "parents": [] + }, + { + "children": [], + "count": 2, + "display_name": "Romance", + "group_id": "10749", + "parents": [] + }, + { + "children": [], + "count": 1, + "display_name": "Science Fiction", + "group_id": "878", + "parents": [] + } + ], + "results": [ + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn iv: the gathering", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/tRjeV9AZgCXGTqyvlp7Ui55Yb3l.jpg", + "url": "https://www.top250.tv/movies/25750" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn IV: The Gathering" + }, + { + "data": { + "groups": [ + { + "display_name": "Drama", + "group_id": "18", + "path": null, + "path_list": [] + }, + { + "display_name": "Fantasy", + "group_id": "14", + "path": null, + "path_list": [] + }, + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + }, + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/zH77CDSRPeYfZZJyyKSt84j62m8.jpg", + "url": "https://www.top250.tv/movies/10823" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn ii: the final sacrifice", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/lqFb8Mnx9tFPUevnfbz9o2adLFw.jpg", + "url": "https://www.top250.tv/movies/25748" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn II: The Final Sacrifice" + }, + { + "data": { + "groups": [ + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + }, + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn iii: urban harvest", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/Ajp5lVNAW0Kfi3uUlCCpIri28B8.jpg", + "url": "https://www.top250.tv/movies/25749" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn III: Urban Harvest" + }, + { + "data": { + "groups": [ + { + "display_name": "Drama", + "group_id": "18", + "path": null, + "path_list": [] + } + ], + "id": "the corn is green", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/zBN62KUP4WGys96vmJUFRKc43B9.jpg", + "url": "https://www.top250.tv/movies/43492" + }, + "matched_terms": [ + "corn" + ], + "value": "The Corn Is Green" + }, + { + "data": { + "groups": [ + { + "display_name": "Documentary", + "group_id": "99", + "path": null, + "path_list": [] + } + ], + "id": "king corn", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/pvqjdmu5IdzUPQgqwylpPrUSKSd.jpg", + "url": "https://www.top250.tv/movies/15281" + }, + "matched_terms": [ + "corn" + ], + "value": "King Corn" + }, + { + "data": { + "groups": [ + { + "display_name": "Drama", + "group_id": "18", + "path": null, + "path_list": [] + } + ], + "id": "corn island", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/zM4ZZ7IpKQA266ynNWOvn3LfKE.jpg", + "url": "https://www.top250.tv/movies/282376" + }, + "matched_terms": [ + "corn" + ], + "value": "Corn Island" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + }, + { + "display_name": "Mystery", + "group_id": "9648", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn 666: isaac's return", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/cAoLi0dxRZwA20LUzTTGN3Xn39Y.jpg", + "url": "https://www.top250.tv/movies/25752" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn 666: Isaac's Return" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + }, + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn v: fields of terror", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/w3ZOi0jbHNEQ26MEt1X3XCJzBYe.jpg", + "url": "https://www.top250.tv/movies/25751" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn V: Fields of Terror" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + }, + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn: genesis", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/94Cc2YJMsCtezYRcmL1PyBNhE1y.jpg", + "url": "https://www.top250.tv/movies/70575" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn: Genesis" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn: revelation", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/sL3ZaPFwgkfn9KuIsh861zsPX0Y.jpg", + "url": "https://www.top250.tv/movies/25753" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn: Revelation" + }, + { + "data": { + "groups": [ + { + "display_name": "Animation", + "group_id": "16", + "path": null, + "path_list": [] + } + ], + "id": "corn on the cop", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/1ifAyPrAJW4WDVqmvBmZo3hDhrD.jpg", + "url": "https://www.top250.tv/movies/234377" + }, + "matched_terms": [ + "corn" + ], + "value": "Corn on the Cop" + }, + { + "data": { + "groups": [ + { + "display_name": "Action", + "group_id": "28", + "path": null, + "path_list": [] + }, + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + }, + { + "display_name": "Crime", + "group_id": "80", + "path": null, + "path_list": [] + } + ], + "id": "con air", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/yhaOQ7xXw0PLHLvg1w0M9zlPdg6.jpg", + "url": "https://www.top250.tv/movies/1701" + }, + "matched_terms": [ + "con" + ], + "value": "Con Air" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + }, + { + "display_name": "Science Fiction", + "group_id": "878", + "path": null, + "path_list": [] + }, + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + }, + { + "display_name": "Action", + "group_id": "28", + "path": null, + "path_list": [] + } + ], + "id": "def-con 4", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/wmCLwtkwzrrphDhR1smtFLZrQxG.jpg", + "url": "https://www.top250.tv/movies/42033" + }, + "matched_terms": [ + "con" + ], + "value": "Def-Con 4" + }, + { + "data": { + "groups": [ + { + "display_name": "Romance", + "group_id": "10749", + "path": null, + "path_list": [] + }, + { + "display_name": "Comedy", + "group_id": "35", + "path": null, + "path_list": [] + }, + { + "display_name": "Crime", + "group_id": "80", + "path": null, + "path_list": [] + }, + { + "display_name": "Drama", + "group_id": "18", + "path": null, + "path_list": [] + } + ], + "id": "the con", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/fDcPgFzVioueAcp13nLw8TGiVOC.jpg", + "url": "https://www.top250.tv/movies/131729" + }, + "matched_terms": [ + "con" + ], + "value": "The Con" + }, + { + "data": { + "groups": [ + { + "display_name": "Documentary", + "group_id": "99", + "path": null, + "path_list": [] + } + ], + "id": "comic-con episode iv: a fan's hope", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/axltQJKHxolLfbGwuTKZZlLbBsZ.jpg", + "url": "https://www.top250.tv/movies/91356" + }, + "matched_terms": [ + "con" + ], + "value": "Comic-Con Episode IV: A Fan's Hope" + }, + { + "data": { + "groups": [ + { + "display_name": "Comedy", + "group_id": "35", + "path": null, + "path_list": [] + }, + { + "display_name": "Documentary", + "group_id": "99", + "path": null, + "path_list": [] + } + ], + "id": "chronic-con, episode 420: a new dope", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/16pPnt4ce0i3zU7QOoHDD4JN9Oe.jpg", + "url": "https://www.top250.tv/movies/347528" + }, + "matched_terms": [ + "con" + ], + "value": "Chronic-Con, Episode 420: A New Dope" + }, + { + "data": { + "groups": [ + { + "display_name": "Comedy", + "group_id": "35", + "path": null, + "path_list": [] + } + ], + "id": "vaya con dios", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/pIV1xgavkLYL33agVmRxpwq9CE4.jpg", + "url": "https://www.top250.tv/movies/6318" + }, + "matched_terms": [ + "con" + ], + "value": "Vaya con Dios" + }, + { + "data": { + "groups": [ + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + }, + { + "display_name": "Action", + "group_id": "28", + "path": null, + "path_list": [] + }, + { + "display_name": "Crime", + "group_id": "80", + "path": null, + "path_list": [] + } + ], + "id": "the con artists", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/rn2xTdw2pyRTcjcwHy5yEfxkfyQ.jpg", + "url": "https://www.top250.tv/movies/300433" + }, + "matched_terms": [ + "con" + ], + "value": "The Con Artists" + }, + { + "data": { + "groups": [ + { + "display_name": "Comedy", + "group_id": "35", + "path": null, + "path_list": [] + } + ], + "id": "tempo instabile con probabili schiarite", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/iaOhlyHXqAIhsoGXqD9S4I066zo.jpg", + "url": "https://www.top250.tv/movies/333888" + }, + "matched_terms": [ + "con" + ], + "value": "Tempo instabile con probabili schiarite" + } + ], + "sort_options": [], + "total_num_results": 23 + }, + "result_id": "5b19c365-1c85-4c2d-b627-0509d60ce2d5" +} diff --git a/library/src/test/resources/search_response_empty.json b/library/src/test/resources/search_response_empty.json new file mode 100644 index 00000000..5f82e2cc --- /dev/null +++ b/library/src/test/resources/search_response_empty.json @@ -0,0 +1,24 @@ +{ + "request": { + "ef-11": "22", + "ef-ab": "cd", + "fmt_options": { + "groups_max_depth": 1, + "groups_start": "current" + }, + "num_results_per_page": 20, + "page": 1, + "section": "Products", + "sort_by": "relevance", + "sort_order": "descending", + "term": "cornucopiasofcorndogs" + }, + "response": { + "facets": [], + "groups": [], + "results": [], + "sort_options": [], + "total_num_results": 0 + }, + "result_id": "2ea93527-91d6-4dfa-86b0-6a0e8158bfd1" +} diff --git a/library/src/test/resources/search_response_unexpected_data.json b/library/src/test/resources/search_response_unexpected_data.json new file mode 100755 index 00000000..7275c3f9 --- /dev/null +++ b/library/src/test/resources/search_response_unexpected_data.json @@ -0,0 +1,599 @@ +{ + "request": { + "ef-11": "22", + "ef-ab": "cd", + "fmt_options": { + "groups_max_depth": 1, + "groups_start": "current" + }, + "num_results_per_page": 20, + "page": 1, + "section": "Products", + "sort_by": "relevance", + "sort_order": "descending", + "term": "corn" + }, + "response": { + "facets": [], + "groups": [ + { + "children": [], + "count": 9, + "display_name": "Horror", + "group_id": "27", + "parents": [] + }, + { + "children": [], + "count": 7, + "display_name": "Thriller", + "group_id": "53", + "parents": [], + "unknown_name": "New name" + }, + { + "children": [], + "count": 4, + "display_name": "Drama", + "group_id": "18", + "parents": [] + }, + { + "children": [], + "count": 3, + "display_name": "Documentary", + "group_id": "99", + "parents": [] + }, + { + "children": [], + "count": 1, + "display_name": "Mystery", + "group_id": "9648", + "parents": [] + }, + { + "children": [], + "count": 1, + "display_name": "Fantasy", + "group_id": "14", + "parents": [] + }, + { + "children": [], + "count": 1, + "display_name": "Animation", + "group_id": "16", + "parents": [] + }, + { + "children": [], + "count": 6, + "display_name": "Comedy", + "group_id": "35", + "parents": [] + }, + { + "children": [], + "count": 3, + "display_name": "Crime", + "group_id": "80", + "parents": [] + }, + { + "children": [], + "count": 3, + "display_name": "Action", + "group_id": "28", + "parents": [] + }, + { + "children": [], + "count": 2, + "display_name": "Romance", + "group_id": "10749", + "parents": [] + }, + { + "children": [], + "count": 1, + "display_name": "Science Fiction", + "group_id": "878", + "parents": [] + } + ], + "results": [ + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn iv: the gathering", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/tRjeV9AZgCXGTqyvlp7Ui55Yb3l.jpg", + "url": "https://www.top250.tv/movies/25750" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn IV: The Gathering" + }, + { + "data": { + "groups": [ + { + "display_name": "Drama", + "group_id": "18", + "path": null, + "path_list": [] + }, + { + "display_name": "Fantasy", + "group_id": "14", + "path": null, + "path_list": [] + }, + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + }, + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/zH77CDSRPeYfZZJyyKSt84j62m8.jpg", + "url": "https://www.top250.tv/movies/10823" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn ii: the final sacrifice", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/lqFb8Mnx9tFPUevnfbz9o2adLFw.jpg", + "url": "https://www.top250.tv/movies/25748" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn II: The Final Sacrifice" + }, + { + "data": { + "groups": [ + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + }, + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn iii: urban harvest", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/Ajp5lVNAW0Kfi3uUlCCpIri28B8.jpg", + "url": "https://www.top250.tv/movies/25749" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn III: Urban Harvest" + }, + { + "data": { + "groups": [ + { + "display_name": "Drama", + "group_id": "18", + "path": null, + "path_list": [] + } + ], + "id": "the corn is green", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/zBN62KUP4WGys96vmJUFRKc43B9.jpg", + "url": "https://www.top250.tv/movies/43492" + }, + "matched_terms": [ + "corn" + ], + "value": "The Corn Is Green" + }, + { + "data": { + "groups": [ + { + "display_name": "Documentary", + "group_id": "99", + "path": null, + "path_list": [] + } + ], + "id": "king corn", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/pvqjdmu5IdzUPQgqwylpPrUSKSd.jpg", + "url": "https://www.top250.tv/movies/15281" + }, + "matched_terms": [ + "corn" + ], + "value": "King Corn" + }, + { + "data": { + "groups": [ + { + "display_name": "Drama", + "group_id": "18", + "path": null, + "path_list": [] + } + ], + "id": "corn island", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/zM4ZZ7IpKQA266ynNWOvn3LfKE.jpg", + "url": "https://www.top250.tv/movies/282376" + }, + "matched_terms": [ + "corn" + ], + "value": "Corn Island" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + }, + { + "display_name": "Mystery", + "group_id": "9648", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn 666: isaac's return", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/cAoLi0dxRZwA20LUzTTGN3Xn39Y.jpg", + "url": "https://www.top250.tv/movies/25752" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn 666: Isaac's Return" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + }, + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn v: fields of terror", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/w3ZOi0jbHNEQ26MEt1X3XCJzBYe.jpg", + "url": "https://www.top250.tv/movies/25751" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn V: Fields of Terror" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + }, + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn: genesis", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/94Cc2YJMsCtezYRcmL1PyBNhE1y.jpg", + "url": "https://www.top250.tv/movies/70575" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn: Genesis" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + } + ], + "id": "children of the corn: revelation", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/sL3ZaPFwgkfn9KuIsh861zsPX0Y.jpg", + "url": "https://www.top250.tv/movies/25753" + }, + "matched_terms": [ + "corn" + ], + "value": "Children of the Corn: Revelation" + }, + { + "data": { + "groups": [ + { + "display_name": "Animation", + "group_id": "16", + "path": null, + "path_list": [] + } + ], + "id": "corn on the cop", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/1ifAyPrAJW4WDVqmvBmZo3hDhrD.jpg", + "url": "https://www.top250.tv/movies/234377" + }, + "matched_terms": [ + "corn" + ], + "value": "Corn on the Cop" + }, + { + "data": { + "groups": [ + { + "display_name": "Action", + "group_id": "28", + "path": null, + "path_list": [] + }, + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + }, + { + "display_name": "Crime", + "group_id": "80", + "path": null, + "path_list": [] + } + ], + "id": "con air", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/yhaOQ7xXw0PLHLvg1w0M9zlPdg6.jpg", + "url": "https://www.top250.tv/movies/1701" + }, + "matched_terms": [ + "con" + ], + "value": "Con Air" + }, + { + "data": { + "groups": [ + { + "display_name": "Horror", + "group_id": "27", + "path": null, + "path_list": [] + }, + { + "display_name": "Science Fiction", + "group_id": "878", + "path": null, + "path_list": [] + }, + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + }, + { + "display_name": "Action", + "group_id": "28", + "path": null, + "path_list": [] + } + ], + "id": "def-con 4", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/wmCLwtkwzrrphDhR1smtFLZrQxG.jpg", + "url": "https://www.top250.tv/movies/42033" + }, + "matched_terms": [ + "con" + ], + "value": "Def-Con 4" + }, + { + "data": { + "groups": [ + { + "display_name": "Romance", + "group_id": "10749", + "path": null, + "path_list": [] + }, + { + "display_name": "Comedy", + "group_id": "35", + "path": null, + "path_list": [] + }, + { + "display_name": "Crime", + "group_id": "80", + "path": null, + "path_list": [] + }, + { + "display_name": "Drama", + "group_id": "18", + "path": null, + "path_list": [] + } + ], + "id": "the con", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/fDcPgFzVioueAcp13nLw8TGiVOC.jpg", + "url": "https://www.top250.tv/movies/131729" + }, + "matched_terms": [ + "con" + ], + "value": "The Con" + }, + { + "data": { + "groups": [ + { + "display_name": "Documentary", + "group_id": "99", + "path": null, + "path_list": [] + } + ], + "id": "comic-con episode iv: a fan's hope", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/axltQJKHxolLfbGwuTKZZlLbBsZ.jpg", + "url": "https://www.top250.tv/movies/91356" + }, + "matched_terms": [ + "con" + ], + "value": "Comic-Con Episode IV: A Fan's Hope" + }, + { + "data": { + "groups": [ + { + "display_name": "Comedy", + "group_id": "35", + "path": null, + "path_list": [] + }, + { + "display_name": "Documentary", + "group_id": "99", + "path": null, + "path_list": [] + } + ], + "id": "chronic-con, episode 420: a new dope", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/16pPnt4ce0i3zU7QOoHDD4JN9Oe.jpg", + "url": "https://www.top250.tv/movies/347528" + }, + "matched_terms": [ + "con" + ], + "value": "Chronic-Con, Episode 420: A New Dope" + }, + { + "data": { + "groups": [ + { + "display_name": "Comedy", + "group_id": "35", + "path": null, + "path_list": [] + } + ], + "id": "vaya con dios", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/pIV1xgavkLYL33agVmRxpwq9CE4.jpg", + "url": "https://www.top250.tv/movies/6318" + }, + "matched_terms": [ + "con" + ], + "value": "Vaya con Dios" + }, + { + "data": { + "groups": [ + { + "display_name": "Thriller", + "group_id": "53", + "path": null, + "path_list": [] + }, + { + "display_name": "Action", + "group_id": "28", + "path": null, + "path_list": [] + }, + { + "display_name": "Crime", + "group_id": "80", + "path": null, + "path_list": [] + } + ], + "id": "the con artists", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/rn2xTdw2pyRTcjcwHy5yEfxkfyQ.jpg", + "url": "https://www.top250.tv/movies/300433" + }, + "matched_terms": [ + "con" + ], + "value": "The Con Artists" + }, + { + "data": { + "groups": [ + { + "display_name": "Comedy", + "group_id": "35", + "path": null, + "path_list": [] + } + ], + "id": "tempo instabile con probabili schiarite", + "image_url": "https://image.tmdb.org/t/p/w185_and_h278_bestv2/iaOhlyHXqAIhsoGXqD9S4I066zo.jpg", + "url": "https://www.top250.tv/movies/333888" + }, + "matched_terms": [ + "con" + ], + "value": "Tempo instabile con probabili schiarite" + } + ], + "sort_options": [], + "total_num_results": 23 + }, + "result_id": "5b19c365-1c85-4c2d-b627-0509d60ce2d5" +} diff --git a/sample/build.gradle b/sample/build.gradle index 35ddde42..c99a9db0 100755 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -38,6 +38,9 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:recyclerview-v7:28.0.0' implementation "com.android.support:cardview-v7:28.0.0" + implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' + implementation 'io.reactivex.rxjava2:rxjava:2.1.13' + implementation 'io.reactivex.rxjava2:rxkotlin:2.2.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' } diff --git a/sample/src/main/java/io/constructor/sample/MainActivity.kt b/sample/src/main/java/io/constructor/sample/MainActivity.kt index 0a963b54..de7a872a 100755 --- a/sample/src/main/java/io/constructor/sample/MainActivity.kt +++ b/sample/src/main/java/io/constructor/sample/MainActivity.kt @@ -4,6 +4,7 @@ import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import io.constructor.core.ConstructorIo +import io.reactivex.schedulers.Schedulers import kotlinx.android.synthetic.main.activity_main.* import java.util.* @@ -18,5 +19,9 @@ class MainActivity : AppCompatActivity() { button3.setOnClickListener { ConstructorIo.trackConversion("testId", "id", 11.0) } button4.setOnClickListener { ConstructorIo.trackSearchResultClick("testTerm", "testId", "1") } button5.setOnClickListener { ConstructorIo.trackSearchResultsLoaded("testTerm", Random().nextInt(99) + 1) } + button6.setOnClickListener { ConstructorIo.getSearchResults("corn").subscribeOn(Schedulers.io()).subscribe { + + } } + } } diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index ea623bb5..03103d6d 100755 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -55,4 +55,14 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/button4" /> + +