diff --git a/CHANGELOG.md b/CHANGELOG.md index eeb24ba7f..bdb64cac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,18 @@ ## 1.0.0-beta.30-SNAPSHOT +### Breaking changes +- [CORE] `IndexableDataProvider.add()`, and `IndexableDataProvider.update()` have been replaced with a new function `IndexableDataProvider.upsert()`. +- [CORE] `IndexableDataProvider.addAll()` has been renamed to `IndexableDataProvider.upsertAll()`. +- [CORE] `IndexableDataProviderEngine.add()`, and `IndexableDataProviderEngine.update()` have been replaced with a new function `IndexableDataProviderEngine.upsert()`. +- [CORE] `IndexableDataProviderEngine.addAll()` has been renamed to `IndexableDataProviderEngine.upsertAll()`. +- [CORE] `IndexableDataProviderEngine.executeBatchUpdate()` has been removed along with `IndexableDataProviderEngine.BatchUpdateOperation`. Now `IndexableDataProviderEngine` is a thread-safe entity. If you need multiple operations to be executed one after another, call them in a needed order on the same thread. +- [CORE] `AnalyticsService.sendRawFeedbackEvent()` has been removed. Events should de sent immediately. +- [CORE] `AnalyticsService.createRawFeedbackEvent()` functions have been made asynchronous and marked as deprecated. These functions return raw events in a very specific format and should not be used. + ### Mapbox dependencies -- Search Native SDK `0.52.0` -- Common SDK `21.3.0-rc.2` -- Telemetry SDK `8.1.1` +- Search Native SDK `0.54.1` +- Common SDK `21.3.0` - Kotlin `1.5.31` diff --git a/LICENSE.md b/LICENSE.md index 57fa0cc1b..334b74c25 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -107,12 +107,6 @@ License: [The Apache Software License, Version 2.0](http://www.apache.org/licens =========================================================================== -Mapbox Search Android uses portions of the Android Support Library core utils. -URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html) -License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -=========================================================================== - Mapbox Search Android uses portions of the Android Support Library Cursor Adapter. URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html) License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -125,12 +119,6 @@ License: [The Apache Software License, Version 2.0](http://www.apache.org/licens =========================================================================== -Mapbox Search Android uses portions of the Android Support Library Document File. -URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html) -License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -=========================================================================== - Mapbox Search Android uses portions of the Android Support Library Drawer Layout. URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html) License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -155,18 +143,6 @@ License: [The Apache Software License, Version 2.0](http://www.apache.org/licens =========================================================================== -Mapbox Search Android uses portions of the Android Support Library Local Broadcast Manager. -URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html) -License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -=========================================================================== - -Mapbox Search Android uses portions of the Android Support Library Print. -URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html) -License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -=========================================================================== - Mapbox Search Android uses portions of the Android Support Library View Pager. URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html) License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -292,12 +268,6 @@ License: [Mapbox Terms of Service](https://www.mapbox.com/legal/tos) =========================================================================== -Mapbox Search Android uses portions of the Mapbox Telemetry for Android. -URL: [https://github.com/mapbox/mapbox-events-android](https://github.com/mapbox/mapbox-events-android) -License: [MIT](https://mit-license.org) - -=========================================================================== - Mapbox Search Android uses portions of the okhttp. URL: [https://square.github.io/okhttp/](https://square.github.io/okhttp/) License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -672,12 +642,6 @@ License: [Mapbox Terms of Service](https://www.mapbox.com/legal/tos) =========================================================================== -Mapbox Search Android uses portions of the Mapbox Telemetry for Android. -URL: [https://github.com/mapbox/mapbox-events-android](https://github.com/mapbox/mapbox-events-android) -License: [MIT](https://mit-license.org) - -=========================================================================== - Mapbox Search Android uses portions of the Material Components for Android. URL: [https://github.com/material-components/material-components-android](https://github.com/material-components/material-components-android) License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) diff --git a/MapboxSearch/gradle/versions.gradle b/MapboxSearch/gradle/versions.gradle index 824a172c3..719584d01 100644 --- a/MapboxSearch/gradle/versions.gradle +++ b/MapboxSearch/gradle/versions.gradle @@ -41,13 +41,12 @@ ext { pitest_version = '1.6.7' - mapbox_maps_version = "10.4.0" - telemetry_version = '8.1.1' - common_sdk_version = '21.3.0-rc.2' + mapbox_maps_version = "10.5.0" + common_sdk_version = '21.3.0' mapbox_base_version = '0.6.0' mapbox_android_core_version = '5.0.1' - search_native_version = '0.52.0' + search_native_version = '0.54.1' detekt_version = "1.19.0" diff --git a/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/FavoritesTest.kt b/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/FavoritesTest.kt index ea00eaa9d..60d4e7f7c 100644 --- a/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/FavoritesTest.kt +++ b/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/FavoritesTest.kt @@ -6,6 +6,7 @@ import com.mapbox.search.sample.Constants.Assets.RANELAGH_ROYAL_SPA_RESULT_ASSET import com.mapbox.search.sample.Constants.Assets.RANELAGH_SUGGESTIONS_ASSET import com.mapbox.search.sample.extensions.enqueue import com.mapbox.search.sample.extensions.noInternetConnectionError +import com.mapbox.search.sample.extensions.unknownError import com.mapbox.search.sample.robots.addressSearchView import com.mapbox.search.sample.robots.editFavoriteView import com.mapbox.search.sample.robots.favoriteActionsDialog @@ -383,7 +384,9 @@ class FavoritesTest : MockServerSearchActivityTest() { typeQuery(CLEMENS_STREET_43_NAME) awaitResults() verifySearchResults { - noInternetConnectionError() + // TODO(https://github.com/mapbox/mapbox-search-sdk/issues/870) + // noInternetConnectionError() should be here + unknownError() } retryFromError() selectSearchResult( diff --git a/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/MockServerSearchActivityTest.kt b/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/MockServerSearchActivityTest.kt index 0db19a5fa..6435db460 100644 --- a/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/MockServerSearchActivityTest.kt +++ b/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/MockServerSearchActivityTest.kt @@ -97,7 +97,7 @@ abstract class MockServerSearchActivityTest { protected fun createNoNetworkConnectionResponse(): MockResponse { return MockResponse() - .setSocketPolicy(SocketPolicy.NO_RESPONSE) + .setSocketPolicy(SocketPolicy.DISCONNECT_AT_START) } protected fun MockWebServer.enqueueSuccessfulResponses( diff --git a/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/extensions/Builders.kt b/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/extensions/Builders.kt index cee6d5cd9..f2f0c3d80 100644 --- a/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/extensions/Builders.kt +++ b/MapboxSearch/sample/src/androidTest/java/com/mapbox/search/sample/extensions/Builders.kt @@ -8,3 +8,10 @@ fun SearchResultsRecyclerBuilder.noInternetConnectionError() { errorSubtitle = "You’re offline. Try to reconnect." ) } + +fun SearchResultsRecyclerBuilder.unknownError() { + error( + errorTitle = "Error", + errorSubtitle = "Something went wrong." + ) +} diff --git a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/CustomIndexableDataProviderJavaExample.java b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/CustomIndexableDataProviderJavaExample.java index ca7d3e5a4..01bbe714f 100644 --- a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/CustomIndexableDataProviderJavaExample.java +++ b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/CustomIndexableDataProviderJavaExample.java @@ -180,7 +180,7 @@ public AsyncOperationTask registerIndexableDataProviderEngine( @NonNull Executor executor, @NonNull CompletionCallback callback ) { - dataProviderEngine.addAll(records.values()); + dataProviderEngine.upsertAll(records.values()); dataProviderEngines.add(dataProviderEngine); executor.execute(() -> callback.onComplete(Unit.INSTANCE)); return CompletedAsyncOperationTask.getInstance(); @@ -268,13 +268,13 @@ public AsyncOperationTask contains(@NonNull String id, @NonNull CompletionCallba @NonNull @Override - public AsyncOperationTask add( + public AsyncOperationTask upsert( @NonNull R record, @NonNull Executor executor, @NonNull CompletionCallback callback ) { for (IndexableDataProviderEngine engine : dataProviderEngines) { - engine.add(record); + engine.upsert(record); } records.put(record.getId(), record); executor.execute(() -> callback.onComplete(Unit.INSTANCE)); @@ -283,19 +283,19 @@ public AsyncOperationTask add( @NonNull @Override - public AsyncOperationTask add(@NonNull R record, @NonNull CompletionCallback callback) { - return add(record, mainThreadExecutor, callback); + public AsyncOperationTask upsert(@NonNull R record, @NonNull CompletionCallback callback) { + return upsert(record, mainThreadExecutor, callback); } @NonNull @Override - public AsyncOperationTask addAll( + public AsyncOperationTask upsertAll( @NonNull List records, @NonNull Executor executor, @NonNull CompletionCallback callback ) { for (IndexableDataProviderEngine engine : dataProviderEngines) { - engine.addAll(records); + engine.upsertAll(records); } for (R record : records) { this.records.put(record.getId(), record); @@ -306,29 +306,8 @@ public AsyncOperationTask addAll( @NonNull @Override - public AsyncOperationTask addAll(@NonNull List records, @NonNull CompletionCallback callback) { - return addAll(records, mainThreadExecutor, callback); - } - - @NonNull - @Override - public AsyncOperationTask update( - @NonNull R record, - @NonNull Executor executor, - @NonNull CompletionCallback callback - ) { - for (IndexableDataProviderEngine engine : dataProviderEngines) { - engine.update(record); - } - records.put(record.getId(), record); - executor.execute(() -> callback.onComplete(Unit.INSTANCE)); - return CompletedAsyncOperationTask.getInstance(); - } - - @NonNull - @Override - public AsyncOperationTask update(@NonNull R record, @NonNull CompletionCallback callback) { - return update(record, mainThreadExecutor, callback); + public AsyncOperationTask upsertAll(@NonNull List records, @NonNull CompletionCallback callback) { + return upsertAll(records, mainThreadExecutor, callback); } @NonNull diff --git a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/CustomIndexableDataProviderKotlinExample.kt b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/CustomIndexableDataProviderKotlinExample.kt index 39802def8..fc0f168ee 100644 --- a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/CustomIndexableDataProviderKotlinExample.kt +++ b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/CustomIndexableDataProviderKotlinExample.kt @@ -146,7 +146,7 @@ class CustomIndexableDataProviderKotlinExample : AppCompatActivity() { executor: Executor, callback: CompletionCallback ): AsyncOperationTask { - dataProviderEngine.addAll(records.values.toList()) + dataProviderEngine.upsertAll(records.values.toList()) dataProviderEngines.add(dataProviderEngine) executor.execute { callback.onComplete(Unit) @@ -198,9 +198,9 @@ class CustomIndexableDataProviderKotlinExample : AppCompatActivity() { return CompletedAsyncOperationTask } - override fun add(record: R, executor: Executor, callback: CompletionCallback): AsyncOperationTask { + override fun upsert(record: R, executor: Executor, callback: CompletionCallback): AsyncOperationTask { dataProviderEngines.forEach { - it.add(record) + it.upsert(record) } records[record.id] = record executor.execute { @@ -209,13 +209,13 @@ class CustomIndexableDataProviderKotlinExample : AppCompatActivity() { return CompletedAsyncOperationTask } - override fun addAll( + override fun upsertAll( records: List, executor: Executor, callback: CompletionCallback ): AsyncOperationTask { dataProviderEngines.forEach { - it.addAll(records) + it.upsertAll(records) } for (record in records) { this.records[record.id] = record @@ -226,17 +226,6 @@ class CustomIndexableDataProviderKotlinExample : AppCompatActivity() { return CompletedAsyncOperationTask } - override fun update(record: R, executor: Executor, callback: CompletionCallback): AsyncOperationTask { - dataProviderEngines.forEach { - it.update(record) - } - records[record.id] = record - executor.execute { - callback.onComplete(Unit) - } - return CompletedAsyncOperationTask - } - override fun remove(id: String, executor: Executor, callback: CompletionCallback): AsyncOperationTask { dataProviderEngines.forEach { it.remove(id) diff --git a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/FavoritesDataProviderJavaExample.java b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/FavoritesDataProviderJavaExample.java index ebe966d21..432f2b5dd 100644 --- a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/FavoritesDataProviderJavaExample.java +++ b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/FavoritesDataProviderJavaExample.java @@ -76,7 +76,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { null ); - task = favoritesDataProvider.add(newFavorite, addFavoriteCallback); + task = favoritesDataProvider.upsert(newFavorite, addFavoriteCallback); } @Override diff --git a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/FavoritesDataProviderKotlinExample.kt b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/FavoritesDataProviderKotlinExample.kt index a8b59d5fb..a128f155b 100644 --- a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/FavoritesDataProviderKotlinExample.kt +++ b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/FavoritesDataProviderKotlinExample.kt @@ -66,7 +66,7 @@ class FavoritesDataProviderKotlinExample : AppCompatActivity() { metadata = null ) - task = favoritesDataProvider.add(newFavorite, addFavoriteCallback) + task = favoritesDataProvider.upsert(newFavorite, addFavoriteCallback) } override fun onDestroy() { diff --git a/MapboxSearch/sdk-common/src/main/java/com/mapbox/search/common/CommonErrorsReporter.kt b/MapboxSearch/sdk-common/src/main/java/com/mapbox/search/common/CommonErrorsReporter.kt index dd9650b93..fb7f67411 100644 --- a/MapboxSearch/sdk-common/src/main/java/com/mapbox/search/common/CommonErrorsReporter.kt +++ b/MapboxSearch/sdk-common/src/main/java/com/mapbox/search/common/CommonErrorsReporter.kt @@ -1,6 +1,5 @@ package com.mapbox.search.common -// TODO should ErrorsReporter functionality be moved to the common-sdk module? object CommonErrorsReporter { var reporter: ((Throwable) -> Unit)? = null } diff --git a/MapboxSearch/sdk/api/api-metalava.txt b/MapboxSearch/sdk/api/api-metalava.txt index 5299f25eb..e3464aff8 100644 --- a/MapboxSearch/sdk/api/api-metalava.txt +++ b/MapboxSearch/sdk/api/api-metalava.txt @@ -894,14 +894,13 @@ package com.mapbox.search { package com.mapbox.search.analytics { public interface AnalyticsService { - method public String createRawFeedbackEvent(com.mapbox.search.result.SearchResult searchResult, com.mapbox.search.ResponseInfo responseInfo); - method public String createRawFeedbackEvent(com.mapbox.search.result.SearchSuggestion searchSuggestion, com.mapbox.search.ResponseInfo responseInfo); + method @Deprecated public void createRawFeedbackEvent(com.mapbox.search.result.SearchResult searchResult, com.mapbox.search.ResponseInfo responseInfo, java.util.concurrent.Executor executor, com.mapbox.search.CompletionCallback callback); + method @Deprecated public void createRawFeedbackEvent(com.mapbox.search.result.SearchSuggestion searchSuggestion, com.mapbox.search.ResponseInfo responseInfo, java.util.concurrent.Executor executor, com.mapbox.search.CompletionCallback callback); method public void sendFeedback(com.mapbox.search.result.SearchResult searchResult, com.mapbox.search.ResponseInfo responseInfo, com.mapbox.search.analytics.FeedbackEvent event); method public void sendFeedback(com.mapbox.search.result.SearchSuggestion searchSuggestion, com.mapbox.search.ResponseInfo responseInfo, com.mapbox.search.analytics.FeedbackEvent event); method public void sendFeedback(com.mapbox.search.record.HistoryRecord historyRecord, com.mapbox.search.analytics.FeedbackEvent event); method public void sendFeedback(com.mapbox.search.record.FavoriteRecord favoriteRecord, com.mapbox.search.analytics.FeedbackEvent event); method public void sendMissingResultFeedback(com.mapbox.search.analytics.MissingResultFeedbackEvent event); - method public void sendRawFeedbackEvent(String rawFeedbackEvent, com.mapbox.search.analytics.FeedbackEvent event); } public final class FeedbackEvent { @@ -1110,10 +1109,6 @@ package com.mapbox.search.record { } public interface IndexableDataProvider { - method public com.mapbox.search.AsyncOperationTask add(R record, java.util.concurrent.Executor executor, com.mapbox.search.CompletionCallback callback); - method public default com.mapbox.search.AsyncOperationTask add(R record, com.mapbox.search.CompletionCallback callback); - method public com.mapbox.search.AsyncOperationTask addAll(java.util.List records, java.util.concurrent.Executor executor, com.mapbox.search.CompletionCallback callback); - method public default com.mapbox.search.AsyncOperationTask addAll(java.util.List records, com.mapbox.search.CompletionCallback callback); method public com.mapbox.search.AsyncOperationTask clear(java.util.concurrent.Executor executor, com.mapbox.search.CompletionCallback callback); method public default com.mapbox.search.AsyncOperationTask clear(com.mapbox.search.CompletionCallback callback); method public com.mapbox.search.AsyncOperationTask contains(String id, java.util.concurrent.Executor executor, com.mapbox.search.CompletionCallback callback); @@ -1130,24 +1125,20 @@ package com.mapbox.search.record { method public default com.mapbox.search.AsyncOperationTask remove(String id, com.mapbox.search.CompletionCallback callback); method public com.mapbox.search.AsyncOperationTask unregisterIndexableDataProviderEngine(com.mapbox.search.record.IndexableDataProviderEngine dataProviderEngine, java.util.concurrent.Executor executor, com.mapbox.search.CompletionCallback callback); method public default com.mapbox.search.AsyncOperationTask unregisterIndexableDataProviderEngine(com.mapbox.search.record.IndexableDataProviderEngine dataProviderEngine, com.mapbox.search.CompletionCallback callback); - method public com.mapbox.search.AsyncOperationTask update(R record, java.util.concurrent.Executor executor, com.mapbox.search.CompletionCallback callback); - method public default com.mapbox.search.AsyncOperationTask update(R record, com.mapbox.search.CompletionCallback callback); + method public com.mapbox.search.AsyncOperationTask upsert(R record, java.util.concurrent.Executor executor, com.mapbox.search.CompletionCallback callback); + method public default com.mapbox.search.AsyncOperationTask upsert(R record, com.mapbox.search.CompletionCallback callback); + method public com.mapbox.search.AsyncOperationTask upsertAll(java.util.List records, java.util.concurrent.Executor executor, com.mapbox.search.CompletionCallback callback); + method public default com.mapbox.search.AsyncOperationTask upsertAll(java.util.List records, com.mapbox.search.CompletionCallback callback); property public abstract String dataProviderName; property public abstract int priority; } public interface IndexableDataProviderEngine { - method public void add(com.mapbox.search.record.IndexableRecord record); - method public void addAll(Iterable records); method public void clear(); - method public void executeBatchUpdate(com.mapbox.search.record.IndexableDataProviderEngine.BatchUpdateOperation batchUpdateOperation); method public void remove(String id); method public void removeAll(Iterable ids); - method public void update(com.mapbox.search.record.IndexableRecord record); - } - - public static fun interface IndexableDataProviderEngine.BatchUpdateOperation { - method public void execute(com.mapbox.search.record.IndexableDataProviderEngine engine); + method public void upsert(com.mapbox.search.record.IndexableRecord record); + method public void upsertAll(Iterable records); } public interface IndexableRecord extends android.os.Parcelable { @@ -1487,5 +1478,8 @@ package com.mapbox.search.utils.extension { public final class PackageInfoKt { } + public final class SearchResponseErrorKt { + } + } diff --git a/MapboxSearch/sdk/api/sdk.api b/MapboxSearch/sdk/api/sdk.api index 23f8cec27..b4e97413d 100644 --- a/MapboxSearch/sdk/api/sdk.api +++ b/MapboxSearch/sdk/api/sdk.api @@ -977,14 +977,13 @@ public abstract interface class com/mapbox/search/ViewportProvider { } public abstract interface class com/mapbox/search/analytics/AnalyticsService { - public abstract fun createRawFeedbackEvent (Lcom/mapbox/search/result/SearchResult;Lcom/mapbox/search/ResponseInfo;)Ljava/lang/String; - public abstract fun createRawFeedbackEvent (Lcom/mapbox/search/result/SearchSuggestion;Lcom/mapbox/search/ResponseInfo;)Ljava/lang/String; + public abstract fun createRawFeedbackEvent (Lcom/mapbox/search/result/SearchResult;Lcom/mapbox/search/ResponseInfo;Ljava/util/concurrent/Executor;Lcom/mapbox/search/CompletionCallback;)V + public abstract fun createRawFeedbackEvent (Lcom/mapbox/search/result/SearchSuggestion;Lcom/mapbox/search/ResponseInfo;Ljava/util/concurrent/Executor;Lcom/mapbox/search/CompletionCallback;)V public abstract fun sendFeedback (Lcom/mapbox/search/record/FavoriteRecord;Lcom/mapbox/search/analytics/FeedbackEvent;)V public abstract fun sendFeedback (Lcom/mapbox/search/record/HistoryRecord;Lcom/mapbox/search/analytics/FeedbackEvent;)V public abstract fun sendFeedback (Lcom/mapbox/search/result/SearchResult;Lcom/mapbox/search/ResponseInfo;Lcom/mapbox/search/analytics/FeedbackEvent;)V public abstract fun sendFeedback (Lcom/mapbox/search/result/SearchSuggestion;Lcom/mapbox/search/ResponseInfo;Lcom/mapbox/search/analytics/FeedbackEvent;)V public abstract fun sendMissingResultFeedback (Lcom/mapbox/search/analytics/MissingResultFeedbackEvent;)V - public abstract fun sendRawFeedbackEvent (Ljava/lang/String;Lcom/mapbox/search/analytics/FeedbackEvent;)V } public final class com/mapbox/search/analytics/FeedbackEvent { @@ -1149,8 +1148,6 @@ public final class com/mapbox/search/record/FavoritesDataProvider$Companion { } public final class com/mapbox/search/record/FavoritesDataProvider$DefaultImpls { - public static fun add (Lcom/mapbox/search/record/FavoritesDataProvider;Lcom/mapbox/search/record/FavoriteRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public static fun addAll (Lcom/mapbox/search/record/FavoritesDataProvider;Ljava/util/List;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun addOnDataChangedListener (Lcom/mapbox/search/record/FavoritesDataProvider;Lcom/mapbox/search/record/LocalDataProvider$OnDataChangedListener;)V public static fun addOnDataProviderEngineRegisterListener (Lcom/mapbox/search/record/FavoritesDataProvider;Lcom/mapbox/search/record/LocalDataProvider$OnDataProviderEngineRegisterListener;)V public static fun clear (Lcom/mapbox/search/record/FavoritesDataProvider;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; @@ -1160,7 +1157,8 @@ public final class com/mapbox/search/record/FavoritesDataProvider$DefaultImpls { public static fun registerIndexableDataProviderEngine (Lcom/mapbox/search/record/FavoritesDataProvider;Lcom/mapbox/search/record/IndexableDataProviderEngine;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun remove (Lcom/mapbox/search/record/FavoritesDataProvider;Ljava/lang/String;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun unregisterIndexableDataProviderEngine (Lcom/mapbox/search/record/FavoritesDataProvider;Lcom/mapbox/search/record/IndexableDataProviderEngine;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public static fun update (Lcom/mapbox/search/record/FavoritesDataProvider;Lcom/mapbox/search/record/FavoriteRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public static fun upsert (Lcom/mapbox/search/record/FavoritesDataProvider;Lcom/mapbox/search/record/FavoriteRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public static fun upsertAll (Lcom/mapbox/search/record/FavoritesDataProvider;Ljava/util/List;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; } public abstract interface class com/mapbox/search/record/HistoryDataProvider : com/mapbox/search/record/LocalDataProvider { @@ -1175,8 +1173,6 @@ public final class com/mapbox/search/record/HistoryDataProvider$Companion { } public final class com/mapbox/search/record/HistoryDataProvider$DefaultImpls { - public static fun add (Lcom/mapbox/search/record/HistoryDataProvider;Lcom/mapbox/search/record/HistoryRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public static fun addAll (Lcom/mapbox/search/record/HistoryDataProvider;Ljava/util/List;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun addOnDataChangedListener (Lcom/mapbox/search/record/HistoryDataProvider;Lcom/mapbox/search/record/LocalDataProvider$OnDataChangedListener;)V public static fun addOnDataProviderEngineRegisterListener (Lcom/mapbox/search/record/HistoryDataProvider;Lcom/mapbox/search/record/LocalDataProvider$OnDataProviderEngineRegisterListener;)V public static fun clear (Lcom/mapbox/search/record/HistoryDataProvider;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; @@ -1186,7 +1182,8 @@ public final class com/mapbox/search/record/HistoryDataProvider$DefaultImpls { public static fun registerIndexableDataProviderEngine (Lcom/mapbox/search/record/HistoryDataProvider;Lcom/mapbox/search/record/IndexableDataProviderEngine;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun remove (Lcom/mapbox/search/record/HistoryDataProvider;Ljava/lang/String;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun unregisterIndexableDataProviderEngine (Lcom/mapbox/search/record/HistoryDataProvider;Lcom/mapbox/search/record/IndexableDataProviderEngine;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public static fun update (Lcom/mapbox/search/record/HistoryDataProvider;Lcom/mapbox/search/record/HistoryRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public static fun upsert (Lcom/mapbox/search/record/HistoryDataProvider;Lcom/mapbox/search/record/HistoryRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public static fun upsertAll (Lcom/mapbox/search/record/HistoryDataProvider;Ljava/util/List;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; } public final class com/mapbox/search/record/HistoryRecord : android/os/Parcelable, com/mapbox/search/record/IndexableRecord { @@ -1214,10 +1211,6 @@ public final class com/mapbox/search/record/HistoryRecord : android/os/Parcelabl } public abstract interface class com/mapbox/search/record/IndexableDataProvider { - public abstract fun add (Lcom/mapbox/search/record/IndexableRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public abstract fun add (Lcom/mapbox/search/record/IndexableRecord;Ljava/util/concurrent/Executor;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public abstract fun addAll (Ljava/util/List;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public abstract fun addAll (Ljava/util/List;Ljava/util/concurrent/Executor;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public abstract fun clear (Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public abstract fun clear (Ljava/util/concurrent/Executor;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public abstract fun contains (Ljava/lang/String;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; @@ -1234,13 +1227,13 @@ public abstract interface class com/mapbox/search/record/IndexableDataProvider { public abstract fun remove (Ljava/lang/String;Ljava/util/concurrent/Executor;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public abstract fun unregisterIndexableDataProviderEngine (Lcom/mapbox/search/record/IndexableDataProviderEngine;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public abstract fun unregisterIndexableDataProviderEngine (Lcom/mapbox/search/record/IndexableDataProviderEngine;Ljava/util/concurrent/Executor;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public abstract fun update (Lcom/mapbox/search/record/IndexableRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public abstract fun update (Lcom/mapbox/search/record/IndexableRecord;Ljava/util/concurrent/Executor;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public abstract fun upsert (Lcom/mapbox/search/record/IndexableRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public abstract fun upsert (Lcom/mapbox/search/record/IndexableRecord;Ljava/util/concurrent/Executor;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public abstract fun upsertAll (Ljava/util/List;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public abstract fun upsertAll (Ljava/util/List;Ljava/util/concurrent/Executor;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; } public final class com/mapbox/search/record/IndexableDataProvider$DefaultImpls { - public static fun add (Lcom/mapbox/search/record/IndexableDataProvider;Lcom/mapbox/search/record/IndexableRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public static fun addAll (Lcom/mapbox/search/record/IndexableDataProvider;Ljava/util/List;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun clear (Lcom/mapbox/search/record/IndexableDataProvider;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun contains (Lcom/mapbox/search/record/IndexableDataProvider;Ljava/lang/String;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun get (Lcom/mapbox/search/record/IndexableDataProvider;Ljava/lang/String;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; @@ -1248,21 +1241,16 @@ public final class com/mapbox/search/record/IndexableDataProvider$DefaultImpls { public static fun registerIndexableDataProviderEngine (Lcom/mapbox/search/record/IndexableDataProvider;Lcom/mapbox/search/record/IndexableDataProviderEngine;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun remove (Lcom/mapbox/search/record/IndexableDataProvider;Ljava/lang/String;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun unregisterIndexableDataProviderEngine (Lcom/mapbox/search/record/IndexableDataProvider;Lcom/mapbox/search/record/IndexableDataProviderEngine;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public static fun update (Lcom/mapbox/search/record/IndexableDataProvider;Lcom/mapbox/search/record/IndexableRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public static fun upsert (Lcom/mapbox/search/record/IndexableDataProvider;Lcom/mapbox/search/record/IndexableRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public static fun upsertAll (Lcom/mapbox/search/record/IndexableDataProvider;Ljava/util/List;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; } public abstract interface class com/mapbox/search/record/IndexableDataProviderEngine { - public abstract fun add (Lcom/mapbox/search/record/IndexableRecord;)V - public abstract fun addAll (Ljava/lang/Iterable;)V public abstract fun clear ()V - public abstract fun executeBatchUpdate (Lcom/mapbox/search/record/IndexableDataProviderEngine$BatchUpdateOperation;)V public abstract fun remove (Ljava/lang/String;)V public abstract fun removeAll (Ljava/lang/Iterable;)V - public abstract fun update (Lcom/mapbox/search/record/IndexableRecord;)V -} - -public abstract interface class com/mapbox/search/record/IndexableDataProviderEngine$BatchUpdateOperation { - public abstract fun execute (Lcom/mapbox/search/record/IndexableDataProviderEngine;)V + public abstract fun upsert (Lcom/mapbox/search/record/IndexableRecord;)V + public abstract fun upsertAll (Ljava/lang/Iterable;)V } public abstract interface class com/mapbox/search/record/IndexableRecord : android/os/Parcelable { @@ -1289,8 +1277,6 @@ public abstract interface class com/mapbox/search/record/LocalDataProvider : com } public final class com/mapbox/search/record/LocalDataProvider$DefaultImpls { - public static fun add (Lcom/mapbox/search/record/LocalDataProvider;Lcom/mapbox/search/record/IndexableRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public static fun addAll (Lcom/mapbox/search/record/LocalDataProvider;Ljava/util/List;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun addOnDataChangedListener (Lcom/mapbox/search/record/LocalDataProvider;Lcom/mapbox/search/record/LocalDataProvider$OnDataChangedListener;)V public static fun addOnDataProviderEngineRegisterListener (Lcom/mapbox/search/record/LocalDataProvider;Lcom/mapbox/search/record/LocalDataProvider$OnDataProviderEngineRegisterListener;)V public static fun clear (Lcom/mapbox/search/record/LocalDataProvider;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; @@ -1300,7 +1286,8 @@ public final class com/mapbox/search/record/LocalDataProvider$DefaultImpls { public static fun registerIndexableDataProviderEngine (Lcom/mapbox/search/record/LocalDataProvider;Lcom/mapbox/search/record/IndexableDataProviderEngine;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun remove (Lcom/mapbox/search/record/LocalDataProvider;Ljava/lang/String;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; public static fun unregisterIndexableDataProviderEngine (Lcom/mapbox/search/record/LocalDataProvider;Lcom/mapbox/search/record/IndexableDataProviderEngine;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; - public static fun update (Lcom/mapbox/search/record/LocalDataProvider;Lcom/mapbox/search/record/IndexableRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public static fun upsert (Lcom/mapbox/search/record/LocalDataProvider;Lcom/mapbox/search/record/IndexableRecord;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; + public static fun upsertAll (Lcom/mapbox/search/record/LocalDataProvider;Ljava/util/List;Lcom/mapbox/search/CompletionCallback;)Lcom/mapbox/search/AsyncOperationTask; } public abstract interface class com/mapbox/search/record/LocalDataProvider$OnDataChangedListener { diff --git a/MapboxSearch/sdk/build.gradle b/MapboxSearch/sdk/build.gradle index c5b031541..7a030f59a 100644 --- a/MapboxSearch/sdk/build.gradle +++ b/MapboxSearch/sdk/build.gradle @@ -101,7 +101,6 @@ dependencies { api "com.mapbox.common:common:$common_sdk_version" api "com.mapbox.mapboxsdk:mapbox-android-core:$mapbox_android_core_version" - implementation "com.mapbox.mapboxsdk:mapbox-android-telemetry:$telemetry_version" implementation "com.mapbox.base:annotations:$mapbox_base_version" diff --git a/MapboxSearch/sdk/src/androidTest/assets/analytics/search-result-raw-feedback.json b/MapboxSearch/sdk/src/androidTest/assets/analytics/search-result-raw-feedback.json index 7ecb618a1..3cee4b98f 100644 --- a/MapboxSearch/sdk/src/androidTest/assets/analytics/search-result-raw-feedback.json +++ b/MapboxSearch/sdk/src/androidTest/assets/analytics/search-result-raw-feedback.json @@ -1,36 +1,36 @@ { - "requestParamsJSON": "{\"eta_type\":\"navigation\",\"navigation_profile\":\"driving\",\"origin\":[10.1,11.12345],\"route\":\"~fayB_gayB~fayB_gayB\",\"route_geometry\":\"polyline6\",\"sar_type\":\"isochrone\",\"time_deviation\":5.0,\"unsafe_key\":\"unsafe_value\"}", - "responseUuid": "0a197057-edf0-4447-be63-9badcf7c19be", - "resultCoordinates": [ - 27.234342, - 53.940465 - ], - "resultId": "place.11543680732831130", - "resultIndex": 0, - "selectedItemName": "Minsk", - "queryString": "Starbucks", - "bbox": [ + "bbox":[ 0.0, 0.0, 90.0, 45.0 ], - "country": [ + "country":[ "fr", "de" ], - "created": "2021-02-03T20:52:06+0300", - "endpoint": "http://localhost:8679//search/v1/suggest", - "event": "search.feedback", - "keyboardLocale": "en", - "language": [ + "created":"2021-02-03T20:52:06+0300", + "endpoint":"http://localhost:8679//search/v1/suggest", + "event":"search.feedback", + "keyboardLocale":"en", + "language":[ "en" ], - "limit": 6, - "orientation": "portrait", - "schema": "search.feedback-2.3", - "sessionIdentifier": "test-generated-uuid", - "types": [ + "limit":6, + "orientation":"portrait", + "queryString":"Starbucks", + "requestParamsJSON":"{\"eta_type\":\"navigation\",\"navigation_profile\":\"driving\",\"origin\":[10.1,11.12345],\"route\":\"~fayB_gayB~fayB_gayB\",\"route_geometry\":\"polyline6\",\"sar_type\":\"isochrone\",\"time_deviation\":5.0,\"unsafe_key\":\"unsafe_value\"}", + "responseUuid":"0a197057-edf0-4447-be63-9badcf7c19be", + "resultCoordinates":[ + 27.234342, + 53.940465 + ], + "resultId":"place.11543680732831130", + "resultIndex":0, + "schema":"search.feedback-2.3", + "selectedItemName":"Minsk", + "sessionIdentifier":"test-generated-uuid", + "types":[ "POI", "ADDRESS", "POSTCODE" diff --git a/MapboxSearch/sdk/src/androidTest/assets/analytics/search-suggestion-raw-feedback.json b/MapboxSearch/sdk/src/androidTest/assets/analytics/search-suggestion-raw-feedback.json index 69311300f..a80b17a99 100644 --- a/MapboxSearch/sdk/src/androidTest/assets/analytics/search-suggestion-raw-feedback.json +++ b/MapboxSearch/sdk/src/androidTest/assets/analytics/search-suggestion-raw-feedback.json @@ -1,33 +1,33 @@ { - "requestParamsJSON": "{\"eta_type\":\"navigation\",\"navigation_profile\":\"driving\",\"origin\":[10.1,11.12345],\"route\":\"~fayB_gayB~fayB_gayB\",\"route_geometry\":\"polyline6\",\"sar_type\":\"isochrone\",\"time_deviation\":5.0,\"unsafe_key\":\"unsafe_value\"}", - "responseUuid": "bf62f6f4-92db-11eb-a8b3-0242ac130003", - "resultId": "Y2OAgKLU9Mz8PAA\u003d.Y2OAgOTEotzUPAA\u003d.RYzBDQIxDASpJW8q4MmfGpDPtu4scjaynccJUQb9EpFI7GtntNrPaeRV0JqmH-VSrlzBW5RzcV7FtKubaDy6IIl0weyq07MC8qwWiUaTqiFUyWOQsqzbYr6Z0TBA5Bzxh7u2fWGfe9h_P-8v", - "resultIndex": 0, - "searchResultsJSON": "{\"multiStepSearch\":false,\"results\":[{\"address\":\"Belarus\",\"category\":[\"cafe\"],\"external_ids\":{\"carmen\":\"place.9038333669154200\",\"federated\":\"carmen.place.9038333669154200\"},\"id\":\"Y2OAgKLU9Mz8PAA\\u003d.Y2OAgOTEotzUPAA\\u003d.RYzBDQIxDASpJW8q4MmfGpDPtu4scjaynccJUQb9EpFI7GtntNrPaeRV0JqmH-VSrlzBW5RzcV7FtKubaDy6IIl0weyq07MC8qwWiUaTqiFUyWOQsqzbYr6Z0TBA5Bzxh7u2fWGfe9h_P-8v\",\"language\":[\"en\"],\"name\":\"Minsk\",\"result_type\":[\"REGION\"]},{\"address\":\"Minsk Region, Belarus\",\"external_ids\":{\"carmen\":\"place.11543680732831130\",\"federated\":\"carmen.place.11543680732831130\"},\"id\":\"Y2WAgIKcxORUAA\\u003d\\u003d.Y2OAgOTEotzUPAA\\u003d.RcwxDsIwDAVQjoI89wSM7CxcAKWJ1VqkNrKdoUIchpsSMFW9vf-__D7EPSFLY9cVTnDGmrQZDKA4kXCPLsR2P16DAxQyV8rem65HTRm30ddinqVgtFVyquRriJGmeRSdRUokqRRFsx03bsuI-t-n5ffn9QE\\u003d\",\"language\":[\"en\"],\"name\":\"Minsk\",\"result_type\":[\"PLACE\",\"REGION\"]},{\"address\":\"Mia\\u0027s Minks, 3249 Stevens Creek Blvd Ste 205, San Jose, California 95117, United States of America\",\"category\":[\"salon\",\"services\",\"cosmetics shop\",\"shopping\"],\"external_ids\":{\"federated\":\"poi.eMOCM3UBVg8uHqZ3mhvp\",\"poi\":\"eMOCM3UBVg8uHqZ3mhvp\",\"safegraph\":\"sg:2a639711389b4d71805e6845c2995294\"},\"id\":\"Y2aAgIL8TAA\\u003d.42eAgMSCTN2C_Ezd4tSisszkVAA\\u003d.ATUAyv8tAAAAAAAAAHNhZmVncmFwaC5zZzoyYTYzOTcxMTM4OWI0ZDcxODA1ZTY4NDVjMjk5NTI5NA\\u003d\\u003d\",\"language\":[\"en\"],\"name\":\"Mia\\u0027s Minks\",\"result_type\":[\"POI\"]},{\"address\":\"Category\",\"category\":[\"Cafe\"],\"external_ids\":{\"federated\":\"category.cafe\"},\"id\":\"42CAgOTEktT0_KJKAA\\u003d\\u003d.42eAgMSCTN2C_Ezd4tSisszkVAA\\u003d.Y2GAgOTEtFQA\",\"language\":[\"en\"],\"name\":\"Cafe\",\"result_type\":[\"CATEGORY\"]},{\"address\":\"Category\",\"category\":[\"Flower Shop\"],\"external_ids\":{\"federated\":\"category.florist\"},\"id\":\"42CAgOTEktT0_KJKAA\\u003d\\u003d.42eAgMSCTN2C_Ezd4tSisszkVAA\\u003d.Y2eAgLSc_KLM4hIA\",\"language\":[\"en\"],\"name\":\"Flower Shop\",\"result_type\":[\"CATEGORY\"]}]}", - "selectedItemName": "Minsk", - "queryString": "Starbucks", - "bbox": [ + "bbox":[ 0.0, 0.0, 90.0, 45.0 ], - "country": [ + "country":[ "fr", "de" ], - "created": "2021-02-03T20:52:06+0300", - "endpoint": "http://localhost:8679//search/v1/suggest", - "event": "search.feedback", - "keyboardLocale": "en", - "language": [ + "created":"2021-02-03T20:52:06+0300", + "endpoint":"http://localhost:8679//search/v1/suggest", + "event":"search.feedback", + "keyboardLocale":"en", + "language":[ "en" ], - "limit": 6, - "orientation": "portrait", - "schema": "search.feedback-2.3", - "sessionIdentifier": "test-generated-uuid", - "types": [ + "limit":6, + "orientation":"portrait", + "queryString":"Starbucks", + "requestParamsJSON":"{\"eta_type\":\"navigation\",\"navigation_profile\":\"driving\",\"origin\":[10.1,11.12345],\"route\":\"~fayB_gayB~fayB_gayB\",\"route_geometry\":\"polyline6\",\"sar_type\":\"isochrone\",\"time_deviation\":5.0,\"unsafe_key\":\"unsafe_value\"}", + "responseUuid":"bf62f6f4-92db-11eb-a8b3-0242ac130003", + "resultId":"Y2OAgKLU9Mz8PAA\u003d.Y2OAgOTEotzUPAA\u003d.RYzBDQIxDASpJW8q4MmfGpDPtu4scjaynccJUQb9EpFI7GtntNrPaeRV0JqmH-VSrlzBW5RzcV7FtKubaDy6IIl0weyq07MC8qwWiUaTqiFUyWOQsqzbYr6Z0TBA5Bzxh7u2fWGfe9h_P-8v", + "resultIndex":0, + "schema":"search.feedback-2.3", + "searchResultsJSON":"{\"multiStepSearch\":false,\"results\":[{\"address\":\"Belarus\",\"category\":[\"cafe\"],\"external_ids\":{\"carmen\":\"place.9038333669154200\",\"federated\":\"carmen.place.9038333669154200\"},\"id\":\"Y2OAgKLU9Mz8PAA\\u003d.Y2OAgOTEotzUPAA\\u003d.RYzBDQIxDASpJW8q4MmfGpDPtu4scjaynccJUQb9EpFI7GtntNrPaeRV0JqmH-VSrlzBW5RzcV7FtKubaDy6IIl0weyq07MC8qwWiUaTqiFUyWOQsqzbYr6Z0TBA5Bzxh7u2fWGfe9h_P-8v\",\"language\":[\"en\"],\"name\":\"Minsk\",\"result_type\":[\"REGION\"]},{\"address\":\"Minsk Region, Belarus\",\"external_ids\":{\"carmen\":\"place.11543680732831130\",\"federated\":\"carmen.place.11543680732831130\"},\"id\":\"Y2WAgIKcxORUAA\\u003d\\u003d.Y2OAgOTEotzUPAA\\u003d.RcwxDsIwDAVQjoI89wSM7CxcAKWJ1VqkNrKdoUIchpsSMFW9vf-__D7EPSFLY9cVTnDGmrQZDKA4kXCPLsR2P16DAxQyV8rem65HTRm30ddinqVgtFVyquRriJGmeRSdRUokqRRFsx03bsuI-t-n5ffn9QE\\u003d\",\"language\":[\"en\"],\"name\":\"Minsk\",\"result_type\":[\"PLACE\",\"REGION\"]},{\"address\":\"Mia\\u0027s Minks, 3249 Stevens Creek Blvd Ste 205, San Jose, California 95117, United States of America\",\"category\":[\"salon\",\"services\",\"cosmetics shop\",\"shopping\"],\"external_ids\":{\"federated\":\"poi.eMOCM3UBVg8uHqZ3mhvp\",\"poi\":\"eMOCM3UBVg8uHqZ3mhvp\",\"safegraph\":\"sg:2a639711389b4d71805e6845c2995294\"},\"id\":\"Y2aAgIL8TAA\\u003d.42eAgMSCTN2C_Ezd4tSisszkVAA\\u003d.ATUAyv8tAAAAAAAAAHNhZmVncmFwaC5zZzoyYTYzOTcxMTM4OWI0ZDcxODA1ZTY4NDVjMjk5NTI5NA\\u003d\\u003d\",\"language\":[\"en\"],\"name\":\"Mia\\u0027s Minks\",\"result_type\":[\"POI\"]},{\"address\":\"Category\",\"category\":[\"Cafe\"],\"external_ids\":{\"federated\":\"category.cafe\"},\"id\":\"42CAgOTEktT0_KJKAA\\u003d\\u003d.42eAgMSCTN2C_Ezd4tSisszkVAA\\u003d.Y2GAgOTEtFQA\",\"language\":[\"en\"],\"name\":\"Cafe\",\"result_type\":[\"CATEGORY\"]},{\"address\":\"Category\",\"category\":[\"Flower Shop\"],\"external_ids\":{\"federated\":\"category.florist\"},\"id\":\"42CAgOTEktT0_KJKAA\\u003d\\u003d.42eAgMSCTN2C_Ezd4tSisszkVAA\\u003d.Y2eAgLSc_KLM4hIA\",\"language\":[\"en\"],\"name\":\"Flower Shop\",\"result_type\":[\"CATEGORY\"]}]}", + "selectedItemName":"Minsk", + "sessionIdentifier":"test-generated-uuid", + "types":[ "POI", "ADDRESS", "POSTCODE" diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/BaseTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/BaseTest.kt index f78540e22..b7eecb214 100644 --- a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/BaseTest.kt +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/BaseTest.kt @@ -1,7 +1,6 @@ package com.mapbox.search import android.app.Application -import android.os.Build import androidx.annotation.CallSuper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry @@ -47,9 +46,11 @@ internal abstract class BaseTest { val DEFAULT_TEST_USER_LOCATION: Point = Point.fromLngLat(10.1, 11.1234567) val TESTING_USER_AGENT: String - get() = "Search SDK sample test/Unknown " + - "(com.mapbox.search.test; build:0; Android ${Build.VERSION.RELEASE}) " + - "MapboxSearchSDK-Android/${BuildConfig.VERSION_NAME}" + get() = if (BuildConfig.DEBUG) { + "search-sdk-android-internal/${BuildConfig.VERSION_NAME}" + } else { + "search-sdk-android/${BuildConfig.VERSION_NAME}" + } fun Double.format(digits: Int) = "%.${digits}f".format(Locale.ENGLISH, this) diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/CategorySearchIntegrationTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/CategorySearchIntegrationTest.kt index daac39718..44e2fbd8d 100644 --- a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/CategorySearchIntegrationTest.kt +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/CategorySearchIntegrationTest.kt @@ -20,17 +20,17 @@ import com.mapbox.search.result.ServerSearchResultImpl import com.mapbox.search.tests_support.BlockingCompletionCallback import com.mapbox.search.tests_support.BlockingSearchCallback import com.mapbox.search.tests_support.EmptySearchCallback +import com.mapbox.search.tests_support.compareSearchResultWithServerSearchResult import com.mapbox.search.tests_support.createHistoryRecord import com.mapbox.search.tests_support.createTestHistoryRecord import com.mapbox.search.tests_support.createTestOriginalSearchResult -import com.mapbox.search.tests_support.record.addAllBlocking -import com.mapbox.search.tests_support.record.addBlocking import com.mapbox.search.tests_support.record.clearBlocking import com.mapbox.search.tests_support.record.getSizeBlocking +import com.mapbox.search.tests_support.record.upsertAllBlocking +import com.mapbox.search.tests_support.record.upsertBlocking import com.mapbox.search.utils.CaptureErrorsReporter import com.mapbox.search.utils.KeyboardLocaleProvider import com.mapbox.search.utils.TimeProvider -import com.mapbox.search.utils.UUIDProvider import com.mapbox.search.utils.assertEqualsIgnoreCase import com.mapbox.search.utils.concurrent.SearchSdkMainThreadWorker import com.mapbox.search.utils.enqueueMultiple @@ -66,7 +66,6 @@ internal class CategorySearchIntegrationTest : BaseTest() { private lateinit var historyDataProvider: HistoryDataProvider private lateinit var favoritesDataProvider: FavoritesDataProvider private val timeProvider: TimeProvider = TimeProvider { TEST_LOCAL_TIME_MILLIS } - private val uuidProvider: UUIDProvider = UUIDProvider { TEST_UUID } private val keyboardLocaleProvider: KeyboardLocaleProvider = KeyboardLocaleProvider { TEST_KEYBOARD_LOCALE } private val orientationProvider: ScreenOrientationProvider = ScreenOrientationProvider { TEST_ORIENTATION } private val errorsReporter: CaptureErrorsReporter = CaptureErrorsReporter() @@ -87,7 +86,6 @@ internal class CategorySearchIntegrationTest : BaseTest() { searchEngineSettings = searchEngineSettings, allowReinitialization = true, timeProvider = timeProvider, - uuidProvider = uuidProvider, keyboardLocaleProvider = keyboardLocaleProvider, orientationProvider = orientationProvider, errorsReporter = errorsReporter, @@ -142,7 +140,7 @@ internal class CategorySearchIntegrationTest : BaseTest() { assertEquals(TEST_ROUTE_OPTIONS.timeDeviationMinutes.formatToBackendConvention(), url.queryParameter("time_deviation")) // Route encoded as polyline6 format, it's tricky to decode it manually and test. - assertEquals(TEST_UUID, request.headers["X-Request-ID"]) + assertFalse(request.headers["X-Request-ID"].isNullOrBlank()) assertEquals(TESTING_USER_AGENT, request.headers["User-Agent"]) } @@ -238,31 +236,29 @@ internal class CategorySearchIntegrationTest : BaseTest() { externalIDs = mapOf( "tripadvisor" to "4113702", "foursquare" to "4740b317f964a520724c1fe3", - ) + ), ) - assertEquals( - ServerSearchResultImpl( - listOf(SearchResultType.POI), - originalSearchResult, - RequestOptions( - query = TEST_CATEGORY, - endpoint = "category", - options = SearchOptions(proximity = TEST_USER_LOCATION, origin = TEST_USER_LOCATION), - proximityRewritten = true, - originRewritten = true, - sessionID = TEST_UUID, - requestContext = SearchRequestContext( - apiType = ApiType.SBS, - keyboardLocale = TEST_KEYBOARD_LOCALE, - screenOrientation = TEST_ORIENTATION, - responseUuid = "544304d0-2007-4354-a599-c522cb150bb0" - ) + val expected = ServerSearchResultImpl( + listOf(SearchResultType.POI), + originalSearchResult, + RequestOptions( + query = TEST_CATEGORY, + endpoint = "category", + options = SearchOptions(proximity = TEST_USER_LOCATION, origin = TEST_USER_LOCATION), + proximityRewritten = true, + originRewritten = true, + sessionID = "any", + requestContext = SearchRequestContext( + apiType = ApiType.SBS, + keyboardLocale = TEST_KEYBOARD_LOCALE, + screenOrientation = TEST_ORIENTATION, + responseUuid = "544304d0-2007-4354-a599-c522cb150bb0" ) - ), - searchResult + ) ) + assertTrue(compareSearchResultWithServerSearchResult(expected, searchResult)) assertNotNull(res.responseInfo.coreSearchResponse) } @@ -299,7 +295,7 @@ internal class CategorySearchIntegrationTest : BaseTest() { categories = listOf(TEST_CATEGORY), ) } - historyDataProvider.addAllBlocking(records, callbacksExecutor) + historyDataProvider.upsertAllBlocking(records, callbacksExecutor) val callback = BlockingSearchCallback() searchEngine.search(TEST_CATEGORY, CategorySearchOptions(), callback) @@ -322,7 +318,7 @@ internal class CategorySearchIntegrationTest : BaseTest() { assertEquals(0, historyDataProvider.getSizeBlocking(callbacksExecutor)) assertNotNull(firstRun.responseInfo.coreSearchResponse) - historyDataProvider.addBlocking( + historyDataProvider.upsertBlocking( createHistoryRecord(firstRun.results.first(), timeProvider.currentTimeMillis()), callbacksExecutor, ) @@ -356,8 +352,8 @@ internal class CategorySearchIntegrationTest : BaseTest() { assertNotNull(secondRun.responseInfo.coreSearchResponse) - assertEquals(secondRun.results[1], firstRun.results[1]) - assertEquals(secondRun.results[2], firstRun.results[2]) + assertTrue(compareSearchResultWithServerSearchResult(secondRun.results[1], firstRun.results[1])) + assertTrue(compareSearchResultWithServerSearchResult(secondRun.results[2], firstRun.results[2])) } @Test @@ -373,7 +369,7 @@ internal class CategorySearchIntegrationTest : BaseTest() { assertEquals(0, historyDataProvider.getSizeBlocking(callbacksExecutor)) assertNotNull(firstRun.responseInfo.coreSearchResponse) - historyDataProvider.addBlocking( + historyDataProvider.upsertBlocking( createHistoryRecord(firstRun.results.first(), timeProvider.currentTimeMillis()), callbacksExecutor, ) @@ -399,7 +395,7 @@ internal class CategorySearchIntegrationTest : BaseTest() { coordinate = recordCoordinate, categories = listOf(TEST_CATEGORY), ) - historyDataProvider.addBlocking(record, callbacksExecutor) + historyDataProvider.upsertBlocking(record, callbacksExecutor) val callback = BlockingSearchCallback() searchEngine.search( @@ -427,7 +423,7 @@ internal class CategorySearchIntegrationTest : BaseTest() { coordinate = recordCoordinate, categories = listOf(TEST_CATEGORY), ) - historyDataProvider.addBlocking(record, callbacksExecutor) + historyDataProvider.upsertBlocking(record, callbacksExecutor) // recordCoordinate + approximately 50 meters val userLocation = Point.fromLngLat(2.29497347098094, 48.8580726347223) @@ -458,7 +454,7 @@ internal class CategorySearchIntegrationTest : BaseTest() { coordinate = recordCoordinate, categories = listOf(TEST_CATEGORY), ) - historyDataProvider.addBlocking(record, callbacksExecutor) + historyDataProvider.upsertBlocking(record, callbacksExecutor) // recordCoordinate + approximately 50 meters val userLocation = Point.fromLngLat(2.29497347098094, 48.8580726347223) @@ -635,7 +631,6 @@ internal class CategorySearchIntegrationTest : BaseTest() { val TEST_USER_LOCATION: Point = Point.fromLngLat(10.1, 11.1234567) const val TEST_LOCAL_TIME_MILLIS = 12345L - const val TEST_UUID = "test-generated-uuid" val TEST_KEYBOARD_LOCALE: Locale = Locale.ENGLISH val TEST_ORIENTATION = ScreenOrientation.PORTRAIT diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/CustomDataProviderTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/CustomDataProviderTest.kt index 6267ce78b..e23de343f 100644 --- a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/CustomDataProviderTest.kt +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/CustomDataProviderTest.kt @@ -13,6 +13,7 @@ import com.mapbox.search.result.SearchRequestContext import com.mapbox.search.tests_support.BlockingCompletionCallback import com.mapbox.search.tests_support.BlockingSearchSelectionCallback import com.mapbox.search.tests_support.BlockingSearchSelectionCallback.SearchEngineResult +import com.mapbox.search.tests_support.compareSearchResultWithServerSearchResult import com.mapbox.search.tests_support.createTestOriginalSearchResult import com.mapbox.search.tests_support.equalsTo import com.mapbox.search.tests_support.record.CustomRecord @@ -23,7 +24,6 @@ import com.mapbox.search.tests_support.record.getBlocking import com.mapbox.search.utils.CaptureErrorsReporter import com.mapbox.search.utils.KeyboardLocaleProvider import com.mapbox.search.utils.TimeProvider -import com.mapbox.search.utils.UUIDProvider import com.mapbox.search.utils.concurrent.SearchSdkMainThreadWorker import com.mapbox.search.utils.orientation.ScreenOrientation import com.mapbox.search.utils.orientation.ScreenOrientationProvider @@ -43,7 +43,6 @@ internal class CustomDataProviderTest : BaseTest() { private lateinit var historyDataProvider: HistoryDataProvider private lateinit var favoritesDataProvider: FavoritesDataProvider private val timeProvider: TimeProvider = TimeProvider { TEST_LOCAL_TIME_MILLIS } - private val uuidProvider: UUIDProvider = UUIDProvider { TEST_UUID } private val keyboardLocaleProvider: KeyboardLocaleProvider = KeyboardLocaleProvider { TEST_KEYBOARD_LOCALE } private val orientationProvider: ScreenOrientationProvider = ScreenOrientationProvider { TEST_ORIENTATION } private val errorsReporter: CaptureErrorsReporter = CaptureErrorsReporter() @@ -64,7 +63,6 @@ internal class CustomDataProviderTest : BaseTest() { searchEngineSettings = searchEngineSettings, allowReinitialization = true, timeProvider = timeProvider, - uuidProvider = uuidProvider, keyboardLocaleProvider = keyboardLocaleProvider, orientationProvider = orientationProvider, errorsReporter = errorsReporter, @@ -106,32 +104,32 @@ internal class CustomDataProviderTest : BaseTest() { assertTrue(searchResult is SearchEngineResult.Suggestions) val suggestions = (searchResult as SearchEngineResult.Suggestions).suggestions - assertEquals( - IndexableRecordSearchSuggestion( - record = secondCustomRecord, - originalSearchResult = createTestOriginalSearchResult( - id = secondCustomRecord.id, - layerId = customDataProvider.dataProviderName, - types = listOf(OriginalResultType.USER_RECORD), - names = listOf(secondCustomRecord.name), - center = secondCustomRecord.coordinate, - addresses = listOf(SearchAddress()), - distanceMeters = 0.0, - serverIndex = null, + val expectedSuggestion = IndexableRecordSearchSuggestion( + record = secondCustomRecord, + originalSearchResult = createTestOriginalSearchResult( + id = secondCustomRecord.id, + layerId = customDataProvider.dataProviderName, + types = listOf(OriginalResultType.USER_RECORD), + names = listOf(secondCustomRecord.name), + center = secondCustomRecord.coordinate, + addresses = listOf(SearchAddress()), + distanceMeters = 0.0, + serverIndex = null, + ), + requestOptions = TEST_REQUEST_OPTIONS.copy( + query = secondCustomRecord.name, + options = options.copy( + proximity = TEST_USER_LOCATION, + origin = secondCustomRecord.coordinate ), - requestOptions = TEST_REQUEST_OPTIONS.copy( - query = secondCustomRecord.name, - options = options.copy( - proximity = TEST_USER_LOCATION, - origin = secondCustomRecord.coordinate - ), - proximityRewritten = true, - requestContext = TEST_REQUEST_OPTIONS.requestContext.copy( - responseUuid = "bf62f6f4-92db-11eb-a8b3-0242ac130003" - ) + proximityRewritten = true, + requestContext = TEST_REQUEST_OPTIONS.requestContext.copy( + responseUuid = "bf62f6f4-92db-11eb-a8b3-0242ac130003" ) - ), - suggestions[0] + ) + ) + assertTrue( + compareSearchResultWithServerSearchResult(expectedSuggestion, suggestions[0]) ) callback.reset() diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/OfflineSearchEngineIntegrationTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/OfflineSearchEngineIntegrationTest.kt index ff23851a3..334e6d795 100644 --- a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/OfflineSearchEngineIntegrationTest.kt +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/OfflineSearchEngineIntegrationTest.kt @@ -23,6 +23,7 @@ import com.mapbox.search.tests_support.BlockingSearchCallback import com.mapbox.search.tests_support.BlockingSearchCallback.SearchEngineResult import com.mapbox.search.tests_support.BlockingSearchSelectionCallback import com.mapbox.search.tests_support.EmptySearchSuggestionsCallback +import com.mapbox.search.tests_support.compareSearchResultWithServerSearchResult import com.mapbox.search.tests_support.createTestOriginalSearchResult import com.mapbox.search.tests_support.createTestSuggestion import com.mapbox.search.tests_support.getAllTileRegionsBlocking @@ -30,7 +31,9 @@ import com.mapbox.search.tests_support.loadTileRegionBlocking import com.mapbox.search.tests_support.record.clearBlocking import com.mapbox.search.tests_support.record.getAllBlocking import com.mapbox.search.tests_support.removeTileRegionBlocking +import com.mapbox.search.tests_support.reverseGeocodingBlocking import com.mapbox.search.tests_support.searchBlocking +import com.mapbox.search.tests_support.selectBlocking import com.mapbox.search.utils.TimeProvider import com.mapbox.search.utils.concurrent.SearchSdkMainThreadWorker import org.junit.After @@ -104,6 +107,54 @@ internal class OfflineSearchEngineIntegrationTest : BaseTest() { tileStore.removeObserver(debugTileStoreObserver) } + @Test + fun complexForwardReverseTest() { + val listener = BlockingOnIndexChangeListener(1) + searchEngine.addOnIndexChangeListener(listener) + + val descriptors = listOf(searchEngine.createTilesetDescriptor()) + + val dcLoadOptions = TileRegionLoadOptions.Builder() + .descriptors(descriptors) + .geometry(MAPBOX_DC_LOCATION) + .acceptExpired(true) + .build() + + val loadTilesResult = tileStore.loadTileRegionBlocking(TEST_GROUP_ID, dcLoadOptions) + assertTrue(loadTilesResult.isValue) + + val tileRegion = requireNotNull(loadTilesResult.value) + assertEquals(TEST_GROUP_ID, tileRegion.id) + assertTrue(tileRegion.completedResourceCount > 0) + assertEquals(tileRegion.requiredResourceCount, tileRegion.completedResourceCount) + + val events = listener.getResultsBlocking() + events.forEach { + val result = (it as? BlockingOnIndexChangeListener.OnIndexChangeResult.Result) + assertNotNull(result) + assertTrue( + "Event type should be ADD, but was ${result!!.event.type}", + result.event.type == OfflineIndexChangeEvent.EventType.ADD + ) + } + + // Forward geocoding 1st step + val searchResult = searchEngine.searchBlocking("1") + assertTrue(searchResult is BlockingSearchSelectionCallback.SearchEngineResult.Suggestions) + val suggestions = searchResult.requireSuggestions() + assertTrue(suggestions.isNotEmpty()) + + // Forward geocoding 2nd step + val selectionResult = searchEngine.selectBlocking(suggestions.first()) + assertTrue(selectionResult is BlockingSearchSelectionCallback.SearchEngineResult.Result) + val selectedSearchResult = selectionResult.requireResult() + + // Reverse geocoding + val reverseResult = searchEngine.reverseGeocodingBlocking(selectedSearchResult.result.coordinate!!) + assertTrue(reverseResult is SearchEngineResult.Results) + assertTrue(reverseResult.requireResults().isNotEmpty()) + } + private fun loadOfflineData() { val listener = BlockingOnIndexChangeListener(1) searchEngine.addOnIndexChangeListener(listener) @@ -287,7 +338,7 @@ internal class OfflineSearchEngineIntegrationTest : BaseTest() { val result = (callback.getResultBlocking() as BlockingSearchSelectionCallback.SearchEngineResult.Result).result val originalSearchResult = (result as BaseSearchResult).originalSearchResult val expectedSearchResult = TEST_SEARCH_RESULT_MAPBOX.copy(id = originalSearchResult.id) - assertEquals(expectedSearchResult, originalSearchResult) + assertTrue(compareSearchResultWithServerSearchResult(expectedSearchResult, originalSearchResult)) val historyRecords = historyDataProvider.getAllBlocking(callbacksExecutor) @@ -365,7 +416,7 @@ internal class OfflineSearchEngineIntegrationTest : BaseTest() { val originalSearchResult = (results.first() as BaseSearchResult).originalSearchResult val expectedSearchResult = TEST_SEARCH_RESULT_MAPBOX.copy(id = originalSearchResult.id) - assertEquals(expectedSearchResult, originalSearchResult) + assertTrue(compareSearchResultWithServerSearchResult(expectedSearchResult, originalSearchResult)) } @Test @@ -403,7 +454,12 @@ internal class OfflineSearchEngineIntegrationTest : BaseTest() { val originalSearchResult = (results.first() as BaseSearchResult).originalSearchResult val expectedResult = TEST_SEARCH_RESULT_MAPBOX.copy(id = originalSearchResult.id, distanceMeters = null) - assertEquals(expectedResult, (results.first() as BaseSearchResult).originalSearchResult) + assertTrue( + compareSearchResultWithServerSearchResult( + expectedResult, + (results.first() as BaseSearchResult).originalSearchResult + ) + ) } @Test diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/ReverseGeocodingSearchIntegrationTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/ReverseGeocodingSearchIntegrationTest.kt index 5e5e59dfa..518d2ae68 100644 --- a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/ReverseGeocodingSearchIntegrationTest.kt +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/ReverseGeocodingSearchIntegrationTest.kt @@ -19,15 +19,15 @@ import com.mapbox.search.result.SearchResultType import com.mapbox.search.result.ServerSearchResultImpl import com.mapbox.search.tests_support.BlockingSearchCallback import com.mapbox.search.tests_support.EmptySearchCallback +import com.mapbox.search.tests_support.compareSearchResultWithServerSearchResult import com.mapbox.search.tests_support.createHistoryRecord import com.mapbox.search.tests_support.createTestOriginalSearchResult -import com.mapbox.search.tests_support.record.addBlocking import com.mapbox.search.tests_support.record.clearBlocking import com.mapbox.search.tests_support.record.getSizeBlocking +import com.mapbox.search.tests_support.record.upsertBlocking import com.mapbox.search.utils.CaptureErrorsReporter import com.mapbox.search.utils.KeyboardLocaleProvider import com.mapbox.search.utils.TimeProvider -import com.mapbox.search.utils.UUIDProvider import com.mapbox.search.utils.assertEqualsIgnoreCase import com.mapbox.search.utils.concurrent.SearchSdkMainThreadWorker import com.mapbox.search.utils.enqueueMultiple @@ -60,7 +60,6 @@ internal class ReverseGeocodingSearchIntegrationTest : BaseTest() { private lateinit var historyDataProvider: HistoryDataProvider private lateinit var favoritesDataProvider: FavoritesDataProvider private val timeProvider: TimeProvider = TimeProvider { TEST_LOCAL_TIME_MILLIS } - private val uuidProvider: UUIDProvider = UUIDProvider { TEST_UUID } private val keyboardLocaleProvider: KeyboardLocaleProvider = KeyboardLocaleProvider { TEST_KEYBOARD_LOCALE } private val orientationProvider: ScreenOrientationProvider = ScreenOrientationProvider { TEST_ORIENTATION } private val errorsReporter: CaptureErrorsReporter = CaptureErrorsReporter() @@ -81,7 +80,6 @@ internal class ReverseGeocodingSearchIntegrationTest : BaseTest() { searchEngineSettings = searchEngineSettings, allowReinitialization = true, timeProvider = timeProvider, - uuidProvider = uuidProvider, keyboardLocaleProvider = keyboardLocaleProvider, orientationProvider = orientationProvider, errorsReporter = errorsReporter, @@ -126,8 +124,8 @@ internal class ReverseGeocodingSearchIntegrationTest : BaseTest() { // We don't test `reverseMode` because SBS doesn't accept it anymore. - assertEquals(TEST_UUID, request.headers.get("X-Request-ID")) - assertEquals(TESTING_USER_AGENT, request.headers.get("User-Agent")) + assertFalse(request.headers["X-Request-ID"].isNullOrEmpty()) + assertEquals(TESTING_USER_AGENT, request.headers["User-Agent"]) } @Test @@ -211,32 +209,29 @@ internal class ReverseGeocodingSearchIntegrationTest : BaseTest() { ) ) - assertEquals( - ServerSearchResultImpl( - listOf(SearchResultType.POI), - originalSearchResult, - RequestOptions( - query = formatPoints(TEST_POINT), - endpoint = "reverse", - options = SearchOptions( - languages = listOf(Language(Locale.getDefault().language)), - proximity = TEST_POINT, - origin = TEST_POINT - ), - proximityRewritten = false, - originRewritten = false, - sessionID = "", - requestContext = SearchRequestContext( - apiType = ApiType.SBS, - keyboardLocale = TEST_KEYBOARD_LOCALE, - screenOrientation = TEST_ORIENTATION, - responseUuid = "6b5d7e47-f901-48e9-ab14-9b8319fa07ed" - ) + val expectedResult = ServerSearchResultImpl( + listOf(SearchResultType.POI), + originalSearchResult, + RequestOptions( + query = formatPoints(TEST_POINT), + endpoint = "reverse", + options = SearchOptions( + languages = listOf(Language(Locale.getDefault().language)), + proximity = TEST_POINT, + origin = TEST_POINT + ), + proximityRewritten = false, + originRewritten = false, + sessionID = "", + requestContext = SearchRequestContext( + apiType = ApiType.SBS, + keyboardLocale = TEST_KEYBOARD_LOCALE, + screenOrientation = TEST_ORIENTATION, + responseUuid = "6b5d7e47-f901-48e9-ab14-9b8319fa07ed" ) - ), - searchResult + ) ) - + assertTrue(compareSearchResultWithServerSearchResult(expectedResult, searchResult)) assertNotNull(res.responseInfo.coreSearchResponse) } @@ -266,7 +261,7 @@ internal class ReverseGeocodingSearchIntegrationTest : BaseTest() { val searchResult = firstRun.results.first() - historyDataProvider.addBlocking( + historyDataProvider.upsertBlocking( createHistoryRecord(searchResult, timeProvider.currentTimeMillis()), callbacksExecutor, ) @@ -279,7 +274,12 @@ internal class ReverseGeocodingSearchIntegrationTest : BaseTest() { val secondRun = callback.getResultBlocking() as BlockingSearchCallback.SearchEngineResult.Results assertFalse(secondRun.results.any { it is IndexableRecordSearchResult }) - assertEquals(firstRun.results, secondRun.results) + assertEquals(firstRun.results.size, secondRun.results.size) + firstRun.results.indices.forEach { index -> + assertTrue( + compareSearchResultWithServerSearchResult(firstRun.results[index], secondRun.results[index]) + ) + } assertNotNull(secondRun.responseInfo.coreSearchResponse) } @@ -441,7 +441,6 @@ internal class ReverseGeocodingSearchIntegrationTest : BaseTest() { val TEST_POINT: Point = Point.fromLngLat(2.2946, 48.85836) const val TEST_LOCAL_TIME_MILLIS = 12345L - const val TEST_UUID = "test-generated-uuid" val TEST_KEYBOARD_LOCALE: Locale = Locale.ENGLISH val TEST_ORIENTATION = ScreenOrientation.PORTRAIT } diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineBatchRetrieveTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineBatchRetrieveTest.kt index ea79f86a0..503a06127 100644 --- a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineBatchRetrieveTest.kt +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineBatchRetrieveTest.kt @@ -12,8 +12,8 @@ import com.mapbox.search.tests_support.BlockingSearchSelectionCallback import com.mapbox.search.tests_support.createTestHistoryRecord import com.mapbox.search.tests_support.createTestOriginalSearchResult import com.mapbox.search.tests_support.equalsTo -import com.mapbox.search.tests_support.record.addAllBlocking import com.mapbox.search.tests_support.record.clearBlocking +import com.mapbox.search.tests_support.record.upsertAllBlocking import com.mapbox.search.utils.CaptureErrorsReporter import com.mapbox.search.utils.concurrent.SearchSdkMainThreadWorker import okhttp3.mockwebserver.MockResponse @@ -166,7 +166,7 @@ internal class SearchEngineBatchRetrieveTest : BaseTest() { createTestHistoryRecord(id = "id$it", name = "$TEST_QUERY $it") } - historyDataProvider.addAllBlocking(records, callbacksExecutor) + historyDataProvider.upsertAllBlocking(records, callbacksExecutor) val callback = BlockingSearchSelectionCallback() searchEngine.search(TEST_QUERY, SearchOptions(limit = records.size), callback) @@ -201,7 +201,7 @@ internal class SearchEngineBatchRetrieveTest : BaseTest() { createTestHistoryRecord(id = "history_item_$it", name = "$TEST_QUERY $it") } - historyDataProvider.addAllBlocking(records, callbacksExecutor) + historyDataProvider.upsertAllBlocking(records, callbacksExecutor) val callback = BlockingSearchSelectionCallback() searchEngine.search(TEST_QUERY, SearchOptions(), callback) diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineDataProvidersIntegrationTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineDataProvidersIntegrationTest.kt index c3183be65..413b78a46 100644 --- a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineDataProvidersIntegrationTest.kt +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineDataProvidersIntegrationTest.kt @@ -6,8 +6,8 @@ import com.mapbox.search.record.HistoryDataProvider import com.mapbox.search.result.SearchSuggestionType import com.mapbox.search.tests_support.createTestFavoriteRecord import com.mapbox.search.tests_support.createTestHistoryRecord -import com.mapbox.search.tests_support.record.addAllBlocking import com.mapbox.search.tests_support.record.clearBlocking +import com.mapbox.search.tests_support.record.upsertAllBlocking import com.mapbox.search.tests_support.registerDataProviderBlocking import com.mapbox.search.tests_support.searchBlocking import com.mapbox.search.tests_support.unregisterDataProviderBlocking @@ -175,11 +175,11 @@ internal class SearchEngineDataProvidersIntegrationTest : BaseTest() { } private fun addTestHistoryAndFavorites(numberOfRecords: Int = 3) { - historyDataProvider.addAllBlocking( + historyDataProvider.upsertAllBlocking( (1..numberOfRecords).map { createTestHistoryRecord(id = "id-history-$it", name = "$TEST_QUERY $it") } ) - favoritesDataProvider.addAllBlocking( + favoritesDataProvider.upsertAllBlocking( (1..numberOfRecords).map { createTestFavoriteRecord(id = "id-favorite-$it", name = "$TEST_QUERY $it") } ) } diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineIntegrationTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineIntegrationTest.kt index cb69bbcaf..d2dc2a9e9 100644 --- a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineIntegrationTest.kt +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/SearchEngineIntegrationTest.kt @@ -25,17 +25,19 @@ import com.mapbox.search.tests_support.BlockingCompletionCallback import com.mapbox.search.tests_support.BlockingSearchSelectionCallback import com.mapbox.search.tests_support.BlockingSearchSelectionCallback.SearchEngineResult import com.mapbox.search.tests_support.EmptySearchSuggestionsCallback +import com.mapbox.search.tests_support.compareSearchResultWithServerSearchResult import com.mapbox.search.tests_support.createHistoryRecord import com.mapbox.search.tests_support.createTestHistoryRecord import com.mapbox.search.tests_support.createTestOriginalSearchResult -import com.mapbox.search.tests_support.record.addAllBlocking -import com.mapbox.search.tests_support.record.addBlocking +import com.mapbox.search.tests_support.equalsTo import com.mapbox.search.tests_support.record.clearBlocking import com.mapbox.search.tests_support.record.getSizeBlocking +import com.mapbox.search.tests_support.record.upsertAllBlocking +import com.mapbox.search.tests_support.record.upsertBlocking +import com.mapbox.search.tests_support.searchBlocking import com.mapbox.search.utils.CaptureErrorsReporter import com.mapbox.search.utils.KeyboardLocaleProvider import com.mapbox.search.utils.TimeProvider -import com.mapbox.search.utils.UUIDProvider import com.mapbox.search.utils.assertEqualsIgnoreCase import com.mapbox.search.utils.concurrent.SearchSdkMainThreadWorker import com.mapbox.search.utils.enqueueMultiple @@ -72,7 +74,6 @@ internal class SearchEngineIntegrationTest : BaseTest() { private lateinit var favoritesDataProvider: FavoritesDataProvider private lateinit var searchEngineSettings: SearchEngineSettings private val timeProvider: TimeProvider = TimeProvider { TEST_LOCAL_TIME_MILLIS } - private val uuidProvider: UUIDProvider = UUIDProvider { TEST_UUID } private val keyboardLocaleProvider: KeyboardLocaleProvider = KeyboardLocaleProvider { TEST_KEYBOARD_LOCALE } private val orientationProvider: ScreenOrientationProvider = ScreenOrientationProvider { TEST_ORIENTATION } private val errorsReporter: CaptureErrorsReporter = CaptureErrorsReporter() @@ -96,7 +97,6 @@ internal class SearchEngineIntegrationTest : BaseTest() { searchEngineSettings = searchEngineSettings, allowReinitialization = true, timeProvider = timeProvider, - uuidProvider = uuidProvider, keyboardLocaleProvider = keyboardLocaleProvider, orientationProvider = orientationProvider, errorsReporter = errorsReporter, @@ -163,7 +163,7 @@ internal class SearchEngineIntegrationTest : BaseTest() { ) // Route encoded as polyline6 format, it's tricky to decode it manually and test. - assertEquals(TEST_UUID, request.headers["X-Request-ID"]) + assertFalse(request.headers["X-Request-ID"].isNullOrEmpty()) assertEquals(TESTING_USER_AGENT, request.headers["User-Agent"]) } @@ -234,19 +234,17 @@ internal class SearchEngineIntegrationTest : BaseTest() { ) ) - assertEquals( - ServerSearchSuggestion( - originalSearchResult, - TEST_REQUEST_OPTIONS.copy( - options = options.copy(proximity = TEST_USER_LOCATION), - proximityRewritten = true, - requestContext = TEST_REQUEST_OPTIONS.requestContext.copy( - responseUuid = "bf62f6f4-92db-11eb-a8b3-0242ac130003" - ) + val expectedResult = ServerSearchSuggestion( + originalSearchResult, + TEST_REQUEST_OPTIONS.copy( + options = options.copy(proximity = TEST_USER_LOCATION), + proximityRewritten = true, + requestContext = TEST_REQUEST_OPTIONS.requestContext.copy( + responseUuid = "bf62f6f4-92db-11eb-a8b3-0242ac130003" ) - ), - first + ) ) + assertTrue(compareSearchResultWithServerSearchResult(expectedResult, first)) assertEquals(SearchSuggestionType.SearchResultSuggestion(SearchResultType.PLACE, SearchResultType.REGION), suggestions[1].type) assertEquals(SearchSuggestionType.SearchResultSuggestion(SearchResultType.POI), suggestions[2].type) @@ -310,7 +308,7 @@ internal class SearchEngineIntegrationTest : BaseTest() { ) } - historyDataProvider.addAllBlocking(records, callbacksExecutor) + historyDataProvider.upsertAllBlocking(records, callbacksExecutor) val callback = BlockingSearchSelectionCallback() searchEngine.search(TEST_QUERY, SearchOptions(limit = records.size), callback) @@ -366,7 +364,7 @@ internal class SearchEngineIntegrationTest : BaseTest() { val records = (1..10).map { createTestHistoryRecord(id = "id$it", name = "$TEST_QUERY $it") } - historyDataProvider.addAllBlocking(records, callbacksExecutor) + historyDataProvider.upsertAllBlocking(records, callbacksExecutor) val callback = BlockingSearchSelectionCallback() searchEngine.search(TEST_QUERY, SearchOptions(), callback) @@ -391,7 +389,7 @@ internal class SearchEngineIntegrationTest : BaseTest() { val records = (1..3).map { createTestHistoryRecord(id = "id$it", name = "$TEST_QUERY $it") } - historyDataProvider.addAllBlocking(records, callbacksExecutor) + historyDataProvider.upsertAllBlocking(records, callbacksExecutor) val callback = BlockingSearchSelectionCallback() val searchOptions = SearchOptions(limit = records.size + 1) @@ -415,7 +413,7 @@ internal class SearchEngineIntegrationTest : BaseTest() { createTestHistoryRecord(id = "id$it", name = "$TEST_QUERY $it") } - historyDataProvider.addAllBlocking(records, callbacksExecutor) + historyDataProvider.upsertAllBlocking(records, callbacksExecutor) val callback = BlockingSearchSelectionCallback() searchEngine.search(TEST_QUERY, SearchOptions(ignoreIndexableRecords = true), callback) @@ -434,7 +432,7 @@ internal class SearchEngineIntegrationTest : BaseTest() { val recordCoordinate = Point.fromLngLat(2.295135021209717, 48.859291076660156) val record = createTestHistoryRecord(id = "id1", name = TEST_QUERY, coordinate = recordCoordinate) - historyDataProvider.addBlocking(record, callbacksExecutor) + historyDataProvider.upsertBlocking(record, callbacksExecutor) val callback = BlockingSearchSelectionCallback() searchEngine.search(TEST_QUERY, SearchOptions( @@ -453,7 +451,7 @@ internal class SearchEngineIntegrationTest : BaseTest() { val recordCoordinate = Point.fromLngLat(2.2945173400760424, 48.85832005563483) val record = createTestHistoryRecord(id = "id1", name = TEST_QUERY, coordinate = recordCoordinate) - historyDataProvider.addBlocking(record, callbacksExecutor) + historyDataProvider.upsertBlocking(record, callbacksExecutor) // recordCoordinate + approximately 50 meters val userLocation = Point.fromLngLat(2.29497347098094, 48.8580726347223) @@ -476,7 +474,7 @@ internal class SearchEngineIntegrationTest : BaseTest() { val recordCoordinate = Point.fromLngLat(2.2945173400760424, 48.85832005563483) val record = createTestHistoryRecord(id = "id1", name = TEST_QUERY, coordinate = recordCoordinate) - historyDataProvider.addBlocking(record, callbacksExecutor) + historyDataProvider.upsertBlocking(record, callbacksExecutor) // recordCoordinate + approximately 50 meters val userLocation = Point.fromLngLat(2.29497347098094, 48.8580726347223) @@ -577,22 +575,20 @@ internal class SearchEngineIntegrationTest : BaseTest() { externalIDs = mapOf("carmen" to "place.11543680732831130") ) - assertEquals( - ServerSearchResultImpl( - listOf(SearchResultType.PLACE, SearchResultType.REGION), - originalSearchResult, - TEST_REQUEST_OPTIONS.run { - copy( - options = options.copy(proximity = TEST_USER_LOCATION), - proximityRewritten = true, - requestContext = requestContext.copy( - responseUuid = "0a197057-edf0-4447-be63-9badcf7c19be" - ) + val expectedResult = ServerSearchResultImpl( + listOf(SearchResultType.PLACE, SearchResultType.REGION), + originalSearchResult, + TEST_REQUEST_OPTIONS.run { + copy( + options = options.copy(proximity = TEST_USER_LOCATION), + proximityRewritten = true, + requestContext = requestContext.copy( + responseUuid = "0a197057-edf0-4447-be63-9badcf7c19be" ) - } - ), - searchResult + ) + } ) + assertTrue(compareSearchResultWithServerSearchResult(expectedResult, searchResult)) assertEquals(1, historyDataProvider.getSizeBlocking(callbacksExecutor)) @@ -675,17 +671,16 @@ internal class SearchEngineIntegrationTest : BaseTest() { ), ) - assertEquals( - ServerSearchSuggestion( - recursionSearchResult, - TEST_REQUEST_OPTIONS.copy( - options = options.copy(proximity = TEST_USER_LOCATION), - proximityRewritten = true - ) - ), - suggestions.first() + val expectedResult = ServerSearchSuggestion( + recursionSearchResult, + TEST_REQUEST_OPTIONS.copy( + options = options.copy(proximity = TEST_USER_LOCATION), + proximityRewritten = true + ) ) + assertTrue(compareSearchResultWithServerSearchResult(expectedResult, suggestions.first())) + mockServer.enqueue(createSuccessfulResponse("sbs_responses/suggestions-successful-with-recursion-query.json")) callback.reset() @@ -694,7 +689,10 @@ internal class SearchEngineIntegrationTest : BaseTest() { val selectionResult = callback.getResultBlocking() as SearchEngineResult.Suggestions val newSuggestions = selectionResult.suggestions - assertEquals(suggestions, newSuggestions) + assertEquals(suggestions.size, newSuggestions.size) + newSuggestions.indices.forEach { index -> + assertTrue(compareSearchResultWithServerSearchResult(suggestions[index], newSuggestions[index])) + } assertNotNull(selectionResult.responseInfo.coreSearchResponse) } @@ -731,24 +729,22 @@ internal class SearchEngineIntegrationTest : BaseTest() { ), ) - assertEquals( - ServerSearchSuggestion( - originalCategorySuggestion, - TEST_REQUEST_OPTIONS.run { - copy( - options = options.copy( - proximity = TEST_USER_LOCATION, - types = listOf(QueryType.CATEGORY) - ), - proximityRewritten = true, - requestContext = requestContext.copy( - responseUuid = "be35d556-9e14-4303-be15-57497c331348" - ), - ) - } - ), - suggestions.first() + val expectedSearchSuggestion = ServerSearchSuggestion( + originalCategorySuggestion, + TEST_REQUEST_OPTIONS.run { + copy( + options = options.copy( + proximity = TEST_USER_LOCATION, + types = listOf(QueryType.CATEGORY) + ), + proximityRewritten = true, + requestContext = requestContext.copy( + responseUuid = "be35d556-9e14-4303-be15-57497c331348" + ), + ) + } ) + assertTrue(compareSearchResultWithServerSearchResult(expectedSearchSuggestion, suggestions.first())) assertEquals(SearchSuggestionType.Category("cafe"), suggestions[0].type) assertEquals(SearchSuggestionType.Category("internet_cafe"), suggestions[1].type) @@ -797,26 +793,25 @@ internal class SearchEngineIntegrationTest : BaseTest() { distanceMeters = 1.2674344310855685E7, ) - assertEquals( - ServerSearchResultImpl( - listOf(SearchResultType.POI), - originalCategorySearchResult, - TEST_REQUEST_OPTIONS.run { - copy( - options = options.copy( - proximity = TEST_USER_LOCATION, - types = listOf(QueryType.CATEGORY) - ), - proximityRewritten = true, - requestContext = requestContext.copy( - responseUuid = "730bb97b-b30f-4e54-9772-8af2d4e0edf0" - ), - ) - } - ), - categoryResults.first() + val expectedSearchResult = ServerSearchResultImpl( + listOf(SearchResultType.POI), + originalCategorySearchResult, + TEST_REQUEST_OPTIONS.run { + copy( + options = options.copy( + proximity = TEST_USER_LOCATION, + types = listOf(QueryType.CATEGORY) + ), + proximityRewritten = true, + requestContext = requestContext.copy( + responseUuid = "730bb97b-b30f-4e54-9772-8af2d4e0edf0" + ), + ) + } ) + assertTrue(compareSearchResultWithServerSearchResult(expectedSearchResult, categoryResults.first())) + assertEquals(categoryResults.size, 3) assertNotNull(selectionResult.responseInfo.coreSearchResponse) @@ -872,6 +867,21 @@ internal class SearchEngineIntegrationTest : BaseTest() { } } + @Test + fun testNetworkErrorForConsecutiveRequests() { + mockServer.enqueue(createSuccessfulResponse("sbs_responses/suggestions-successful-for-minsk.json")) + mockServer.enqueue(MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)) + + val successfulResponse = searchEngine.searchBlocking(TEST_QUERY, SearchOptions()) + assertTrue(successfulResponse.requireSuggestions().isNotEmpty()) + + val errorResponse = searchEngine.searchBlocking(TEST_QUERY, SearchOptions()) + + // TODO(https://github.com/mapbox/mapbox-search-sdk/issues/870) + // Native Search SDK should return Http Error here. See testNetworkError() test. + assertTrue(errorResponse.requireError().equalsTo(Exception("Unable to perform search request: Invalid json response"))) + } + @Test fun testBrokenResponseContent() { mockServer.enqueue(MockResponse().setResponseCode(200).setBody("I'm broken")) diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/AnalyticsEventJsonParserTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/AnalyticsEventJsonParserTest.kt index 756221cc3..fad7b2c22 100644 --- a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/AnalyticsEventJsonParserTest.kt +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/AnalyticsEventJsonParserTest.kt @@ -2,141 +2,13 @@ package com.mapbox.search.analytics import com.mapbox.search.BaseTest import com.mapbox.search.analytics.events.AppMetadata -import com.mapbox.search.analytics.events.BaseSearchEvent -import com.mapbox.search.analytics.events.BaseSearchSessionEvent -import com.mapbox.search.analytics.events.QueryChangeEvent import com.mapbox.search.analytics.events.SearchFeedbackEvent -import com.mapbox.search.analytics.events.SearchSelectEvent -import com.mapbox.search.analytics.events.SearchStartEvent import org.json.JSONException import org.junit.Assert import org.junit.Test internal class AnalyticsEventJsonParserTest : BaseTest() { - @Test - fun testSearchSelectEventParse() { - val jsonParser = AnalyticsEventJsonParser() - val textJson = readFileFromAssets("search-select-event.json") - - val event = SearchSelectEvent().apply { - event = "search.select" - resultIndex = 3 - resultPlaceName = "Minsk" - resultId = "region.16795317862473520" - fillBaseSearchSessionData() - } - - Assert.assertEquals(event, jsonParser.parse(textJson)) - } - - @Test - fun testSearchSelectEventSerialize() { - val jsonParser = AnalyticsEventJsonParser() - - val event = SearchSelectEvent().apply { - event = "search.select" - resultIndex = 3 - resultPlaceName = "Minsk" - resultId = "region.16795317862473520" - fillBaseSearchSessionData() - } - - val serializedEvent = jsonParser.serialize(event) - Assert.assertEquals(event, jsonParser.parse(serializedEvent)) - } - - @Test - fun testEmptySearchSelectEventParse() { - val jsonParser = AnalyticsEventJsonParser() - val textJson = readFileFromAssets("search-select-event-empty.json") - - val event = SearchSelectEvent().apply { - event = "search.select" - } - - Assert.assertEquals(event, jsonParser.parse(textJson)) - } - - @Test - fun testSearchStartEventParse() { - val jsonParser = AnalyticsEventJsonParser() - val textJson = readFileFromAssets("search-start-event.json") - - val event = SearchStartEvent().apply { - event = "search.start" - fillBaseSearchSessionData() - } - - Assert.assertEquals(event, jsonParser.parse(textJson)) - } - - @Test - fun testSearchStartEventSerialize() { - val jsonParser = AnalyticsEventJsonParser() - - val event = SearchStartEvent().apply { - event = "search.start" - fillBaseSearchSessionData() - } - - val serializedEvent = jsonParser.serialize(event) - Assert.assertEquals(event, jsonParser.parse(serializedEvent)) - } - - @Test - fun testEmptySearchStartEventParse() { - val jsonParser = AnalyticsEventJsonParser() - val textJson = readFileFromAssets("search-start-event-empty.json") - - val event = SearchStartEvent().apply { - event = "search.start" - } - - Assert.assertEquals(event, jsonParser.parse(textJson)) - } - - @Test - fun testQueryChangeEventParse() { - val jsonParser = AnalyticsEventJsonParser() - val textJson = readFileFromAssets("search-query-change-event.json") - - val event = QueryChangeEvent().apply { - event = "search.query_change" - oldQuery = "map" - newQuery = "maps" - changeType = "keyboard_type" - fillBaseSearchData() - } - - Assert.assertEquals(event, jsonParser.parse(textJson)) - } - - @Test - fun testQueryChangeEventSerialize() { - val jsonParser = AnalyticsEventJsonParser() - - val event = SearchStartEvent().apply { - event = "search.start" - fillBaseSearchSessionData() - } - - val serializedEvent = jsonParser.serialize(event) - Assert.assertEquals(event, jsonParser.parse(serializedEvent)) - } - - @Test - fun testEmptyQueryChangeEventParse() { - val jsonParser = AnalyticsEventJsonParser() - val textJson = readFileFromAssets("search-query-change-event-empty.json") - - val event = QueryChangeEvent().apply { - event = "search.query_change" - } - - Assert.assertEquals(event, jsonParser.parse(textJson)) - } - @Test fun testFeedbackEventParse() { val jsonParser = AnalyticsEventJsonParser() @@ -162,7 +34,9 @@ internal class AnalyticsEventJsonParserTest : BaseTest() { sessionId = "test-session-id" ) searchResultsJson = "{\"results\":[{\"address\":\"Minsk Region, Belarus, Planet Earth\",\"coordinates\":[27.234342,53.940465],\"external_ids\":{\"carmen\":\"place.11543680732831130\"},\"id\":\"place.11543680732831130\",\"language\":[\"en\"],\"name\":\"Minsk\",\"result_type\":[\"PLACE\",\"REGION\"]}],\"multiStepSearch\":true}" - fillBaseSearchSessionData() + queryString = "Minsk" + cached = false + fillBaseSearchData() } Assert.assertEquals(event, jsonParser.parse(textJson)) @@ -192,7 +66,9 @@ internal class AnalyticsEventJsonParserTest : BaseTest() { sessionId = "test-session-id" ) searchResultsJson = "{\"results\":[{\"address\":\"Minsk Region, Belarus, Planet Earth\",\"coordinates\":[27.234342,53.940465],\"external_ids\":{\"carmen\":\"place.11543680732831130\"},\"id\":\"place.11543680732831130\",\"language\":[\"en\"],\"name\":\"Minsk\",\"result_type\":[\"PLACE\",\"REGION\"]}],\"multiStepSearch\":true}" - fillBaseSearchSessionData() + queryString = "Minsk" + cached = false + fillBaseSearchData() } val serializedEvent = jsonParser.serialize(event) @@ -225,20 +101,14 @@ internal class AnalyticsEventJsonParserTest : BaseTest() { fun testIncorrectEventSerialize() { AnalyticsEventJsonParser().serialize( SearchFeedbackEvent().apply { - event = SearchSelectEvent.EVENT_NAME + event = "incorrect event" } ) } private companion object { - fun BaseSearchSessionEvent.fillBaseSearchSessionData() { - fillBaseSearchData() - queryString = "Minsk" - cached = false - } - - fun BaseSearchEvent.fillBaseSearchData() { + fun SearchFeedbackEvent.fillBaseSearchData() { created = "2020-05-16T23:06:35+0300" latitude = 53.911334999999994 limit = 10 diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/AnalyticsEventParcelizationTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/AnalyticsEventParcelizationTest.kt deleted file mode 100644 index ad5f5dead..000000000 --- a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/AnalyticsEventParcelizationTest.kt +++ /dev/null @@ -1,145 +0,0 @@ -package com.mapbox.search.analytics - -import com.mapbox.search.analytics.events.AppMetadata -import com.mapbox.search.analytics.events.BaseSearchEvent -import com.mapbox.search.analytics.events.BaseSearchSessionEvent -import com.mapbox.search.analytics.events.QueryChangeEvent -import com.mapbox.search.analytics.events.SearchFeedbackEvent -import com.mapbox.search.analytics.events.SearchSelectEvent -import com.mapbox.search.analytics.events.SearchStartEvent -import com.mapbox.search.utils.assertEqualsButNotSame -import com.mapbox.search.utils.cloneFromParcel -import org.junit.Test - -internal class AnalyticsEventParcelizationTest { - - @Test - fun searchSelectEventParcelTest() { - val event = createTestSelectEvent() - val clonedEvent = event.cloneFromParcel() - assertEqualsButNotSame(event, clonedEvent) - } - - @Test - fun emptySearchSelectEventParcelTest() { - val event = SearchSelectEvent() - val clonedEvent = event.cloneFromParcel() - assertEqualsButNotSame(event, clonedEvent) - } - - @Test - fun searchStartEventParcelTest() { - val event = SearchStartEvent().apply { - fillBaseSearchSessionData() - } - val clonedEvent = event.cloneFromParcel() - assertEqualsButNotSame(event, clonedEvent) - } - - @Test - fun emptySearchStartEventParcelTest() { - val event = SearchStartEvent() - val clonedEvent = event.cloneFromParcel() - assertEqualsButNotSame(event, clonedEvent) - } - - @Test - fun queryChangeEventParcelTest() { - val event = createTestQueryChangeEvent() - val clonedEvent = event.cloneFromParcel() - assertEqualsButNotSame(event, clonedEvent) - } - - @Test - fun emptyQueryChangeEventParcelTest() { - val event = QueryChangeEvent() - val clonedEvent = event.cloneFromParcel() - assertEqualsButNotSame(event, clonedEvent) - } - - @Test - fun feedbackEventParcelTest() { - val event = createTestFeedbackEvent() - val clonedEvent = event.cloneFromParcel() - assertEqualsButNotSame(event, clonedEvent) - } - - @Test - fun emptyFeedbackEventParcelTest() { - val event = SearchFeedbackEvent() - val clonedEvent = event.cloneFromParcel() - assertEqualsButNotSame(event, clonedEvent) - } - - private companion object { - - fun createTestQueryChangeEvent(): QueryChangeEvent { - return QueryChangeEvent().apply { - fillBaseSearchData() - oldQuery = "map" - newQuery = "maps" - changeType = "keyboard_type" - } - } - - fun createTestSelectEvent(): SearchSelectEvent { - return SearchSelectEvent().apply { - fillBaseSearchSessionData() - resultIndex = 0 - resultPlaceName = "test resultPlaceName" - resultId = "region.16795317862473520" - } - } - - fun createTestFeedbackEvent(): SearchFeedbackEvent { - return SearchFeedbackEvent().apply { - fillBaseSearchSessionData() - feedbackReason = "Reason Incorrect data" - feedbackText = "Text Incorrect data" - resultIndex = 99 - selectedItemName = "test selectedItemName" - resultId = "region.16795317862473520" - responseUuid = "e0a2b1d6-3621-11eb-adc1-0242ac120002" - requestParamsJson = "{\"navigation_profile\":\"driving\",\"eta_type\":\"navigation\",\"route\":\"iikeFfygjVixNiwhAjvNkw\",\"route_geometry\":\"polyline6\",\"time_deviation\":1,\"sar_type\":\"isochrone\",\"origin\":[1.0,1.0]}" - appMetadata = AppMetadata( - name = "App Name", - version = "v1.1", - userId = "test-user-id", - sessionId = "test-session-id" - ) - searchResultsJson = "{\"results\":[{\"address\":\"Minsk Region, Belarus, Planet Earth\",\"coordinates\":[27.234342,53.940465],\"external_ids\":{\"carmen\":\"place.11543680732831130\"},\"id\":\"place.11543680732831130\",\"language\":[\"en\"],\"name\":\"Minsk\",\"result_type\":[\"PLACE\",\"REGION\"]}],\"multiStepSearch\":true}" - } - } - - fun BaseSearchSessionEvent.fillBaseSearchSessionData() { - fillBaseSearchData() - cached = true - queryString = "test query" - } - - fun BaseSearchEvent.fillBaseSearchData() { - event = "test event" - created = "created date" - latitude = 1.0 - longitude = 1.0 - sessionIdentifier = "test sessionIdentifier" - userAgent = "test userAgent" - boundingBox = listOf(.0, .1) - autocomplete = true - routing = true - country = listOf("test country") - types = listOf("address", "poi") - endpoint = "test" - orientation = "portrait" - proximity = listOf(.0, .1) - fuzzyMatch = true - limit = 100 - language = listOf("by") - keyboardLocale = "by" - mapZoom = 11.0f - mapCenterLatitude = 10.1 - mapCenterLongitude = 11.1234567 - schema = "test-schema-v1.0" - } - } -} diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/TelemetryServiceTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/AnalyticsServiceImplTest.kt similarity index 84% rename from MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/TelemetryServiceTest.kt rename to MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/AnalyticsServiceImplTest.kt index 6ce819183..934e8c923 100644 --- a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/TelemetryServiceTest.kt +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/AnalyticsServiceImplTest.kt @@ -16,29 +16,30 @@ import com.mapbox.search.SearchNavigationOptions import com.mapbox.search.SearchNavigationProfile import com.mapbox.search.SearchOptions import com.mapbox.search.common.FixedPointLocationEngine +import com.mapbox.search.tests_support.BlockingCompletionCallback import com.mapbox.search.tests_support.BlockingSearchSelectionCallback +import com.mapbox.search.tests_support.fixNonDeterminedFields import com.mapbox.search.utils.FormattedTimeProvider import com.mapbox.search.utils.KeyboardLocaleProvider -import com.mapbox.search.utils.UUIDProvider +import com.mapbox.search.utils.concurrent.SearchSdkMainThreadWorker import com.mapbox.search.utils.orientation.ScreenOrientation import com.mapbox.search.utils.orientation.ScreenOrientationProvider import com.mapbox.search.utils.removeJsonPrettyPrinting import okhttp3.mockwebserver.MockWebServer import org.junit.After -import org.junit.Assert +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import java.util.Locale import java.util.concurrent.TimeUnit -internal class TelemetryServiceTest : BaseTest() { +internal class AnalyticsServiceImplTest : BaseTest() { private lateinit var mockServer: MockWebServer private lateinit var searchEngine: SearchEngine private lateinit var analyticsService: AnalyticsService private val formattedTimeProvider: FormattedTimeProvider = FormattedTimeProvider { TEST_LOCAL_TIME_FORMATTED } - private val uuidProvider: UUIDProvider = UUIDProvider { TEST_UUID } private val keyboardLocaleProvider: KeyboardLocaleProvider = KeyboardLocaleProvider { TEST_KEYBOARD_LOCALE } private val orientationProvider: ScreenOrientationProvider = ScreenOrientationProvider { TEST_ORIENTATION } private val noOpErrorsReporter: ErrorsReporter = ErrorsReporter {} @@ -62,7 +63,6 @@ internal class TelemetryServiceTest : BaseTest() { searchEngineSettings = searchEngineSettings, allowReinitialization = true, formattedTimeProvider = formattedTimeProvider, - uuidProvider = uuidProvider, keyboardLocaleProvider = keyboardLocaleProvider, orientationProvider = orientationProvider, errorsReporter = noOpErrorsReporter, @@ -99,15 +99,23 @@ internal class TelemetryServiceTest : BaseTest() { val res = callback.getResultBlocking() val (suggestions, responseInfo) = res as BlockingSearchSelectionCallback.SearchEngineResult.Suggestions - val suggestion = suggestions.first() - val rawEvent = analyticsService.createRawFeedbackEvent(suggestion, responseInfo) + val feedbackEventCallback = BlockingCompletionCallback() + + @Suppress("DEPRECATION") + analyticsService.createRawFeedbackEvent( + suggestions.first().fixNonDeterminedFields(-1, TEST_UUID), + responseInfo.fixNonDeterminedFields(TEST_UUID), + SearchSdkMainThreadWorker.mainExecutor, + feedbackEventCallback + ) + val rawEvent = feedbackEventCallback.getResultBlocking().requireResult() val expectedRawEvent = readBytesFromAssets("analytics/search-suggestion-raw-feedback.json") .let(::String) .removeJsonPrettyPrinting() - Assert.assertEquals(expectedRawEvent, rawEvent) + assertEquals(expectedRawEvent, rawEvent) } @Test @@ -145,13 +153,22 @@ internal class TelemetryServiceTest : BaseTest() { val selectionResult = callback.getResultBlocking() val (searchResult, responseInfo) = selectionResult as BlockingSearchSelectionCallback.SearchEngineResult.Result - val rawEvent = analyticsService.createRawFeedbackEvent(searchResult, responseInfo) + val feedbackEventCallback = BlockingCompletionCallback() + + @Suppress("DEPRECATION") + analyticsService.createRawFeedbackEvent( + searchResult.fixNonDeterminedFields(-1, TEST_UUID), + responseInfo.fixNonDeterminedFields(TEST_UUID), + SearchSdkMainThreadWorker.mainExecutor, + feedbackEventCallback + ) + val rawEvent = feedbackEventCallback.getResultBlocking().requireResult() val expectedRawEvent = readBytesFromAssets("analytics/search-result-raw-feedback.json") .let(::String) .removeJsonPrettyPrinting() - Assert.assertEquals(expectedRawEvent, rawEvent) + assertEquals(expectedRawEvent, rawEvent) } @After diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/CrashEventsFactoryTest.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/CrashEventsFactoryTest.kt new file mode 100644 index 000000000..5809074f7 --- /dev/null +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/analytics/CrashEventsFactoryTest.kt @@ -0,0 +1,144 @@ +package com.mapbox.search.analytics + +import com.mapbox.search.utils.AppInfoProvider +import com.mapbox.search.utils.TimeProvider +import org.json.JSONException +import org.json.JSONObject +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import java.text.SimpleDateFormat +import java.util.Locale + +internal class CrashEventsFactoryTest { + + private lateinit var timeProvider: TimeProvider + private lateinit var appInfoProvider: AppInfoProvider + + private lateinit var crashEventsFactory: CrashEventsFactory + + private val testException = createTestException() + + @Before + fun setUp() { + timeProvider = TestTimeProvider() + appInfoProvider = TestAppInfoProvider() + + crashEventsFactory = CrashEventsFactory(timeProvider, appInfoProvider) + } + + @Test + fun testGeneratedEvent() { + val event = crashEventsFactory.createEvent(throwable = testException, isSilent = true, customData = null) + + val json = JSONObject(event) + + assertEquals("mobile.crash", json.getString("event")) + assertEquals( + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US).format(LOCAL_TIME_MILLIS), + json.getString("created") + ) + assertEquals(appInfoProvider.searchSdkPackageName, json.getString("sdkIdentifier")) + assertEquals(appInfoProvider.searchSdkVersionName, json.getString("sdkVersion")) + assertEquals("Android-${appInfoProvider.osVersion}", json.getString("osVersion")) + assertEquals(appInfoProvider.deviceModel, json.getString("model")) + assertEquals(appInfoProvider.deviceName, json.getString("device")) + assertEquals(appInfoProvider.appPackageName, json.getString("appId")) + assertEquals(appInfoProvider.appVersion, json.getString("appVersion")) + + assertEquals("true", json.getString("isSilent")) + assertEquals(expectedStackTraceHash(testException), json.getString("stackTraceHash")) + assertEquals(expectedStackTraceElement(testException), json.getString("stackTrace")) + assertTrue( + runCatching { + assertEquals(null, json.getString("customData")) + }.exceptionOrNull() is JSONException + ) + } + + @Test + fun testFilledCustomData() { + val event = crashEventsFactory.createEvent( + throwable = testException, + isSilent = true, + customData = mapOf("sdk" to "Search SDK") + ) + + val json = JSONObject(event) + + assertEquals("[{\"name\":\"sdk\",\"value\":\"Search SDK\"}]", json.getString("customData")) + } + + @Test(expected = JSONException::class) + fun testEmptyCustomData() { + val event = crashEventsFactory.createEvent(throwable = testException, isSilent = true, customData = null) + val json = JSONObject(event) + assertEquals(null, json.getString("customData")) + } + + // This is a function that just returns an exception, + // because we need at least one method of the SDK in the Exception stack trace + private fun createTestException() = Exception("Test message") + + private class TestTimeProvider : TimeProvider { + override fun currentTimeMillis() = LOCAL_TIME_MILLIS + } + + private class TestAppInfoProvider : AppInfoProvider { + override val searchSdkPackageName: String = SEARCH_SDK_PACKAGE_NAME + override val searchSdkVersionName: String = SEARCH_SDK_VERSION_NAME + override val deviceModel: String = DEVICE_MODEL + override val deviceName: String = DEVICE_NAME + override val osVersion: String = OS_VERSION + override val appPackageName: String = APP_PACKAGE_NAME + override val appVersion: String = APP_VERSION + } + + private companion object { + + const val LOCAL_TIME_MILLIS = 12345L + const val SEARCH_SDK_PACKAGE_NAME: String = "com.mapbox.search" + const val SEARCH_SDK_VERSION_NAME: String = "test.1.2.3" + const val DEVICE_MODEL: String = "Android SDK built for x86" + const val DEVICE_NAME: String = "generic_x86" + const val OS_VERSION: String = "123" + const val APP_PACKAGE_NAME: String = "test.app.name" + const val APP_VERSION: String = "test.3.1.5" + + fun expectedStackTraceHash(throwable: Throwable): String { + val hashCode = throwable.stackTrace.joinToString("") { + it.className + it.methodName + }.hashCode() + return Integer.toHexString(hashCode) + } + + private fun expectedStackTraceElement(throwable: Throwable): String { + val stackTraceElements = throwable.stackTrace + + val prefix = if ( + stackTraceElements.isNotEmpty() && + isAllowedStacktraceElement(stackTraceElements[0]) && + throwable.message != null + ) { + throwable.message + } else { + "***" + } + + val trace = stackTraceElements.joinToString(separator = "\n", postfix = "\n") { + if (isAllowedStacktraceElement(it)) { + "${it.className}.${it.methodName}(${it.fileName}:${it.lineNumber})" + } else { + "*" + } + } + + return "$prefix\n$trace" + } + + fun isAllowedStacktraceElement(stackTraceElement: StackTraceElement): Boolean { + return stackTraceElement.className.startsWith(SEARCH_SDK_PACKAGE_NAME) + } + } +} diff --git a/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/tests_support/Utils.kt b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/tests_support/Utils.kt new file mode 100644 index 000000000..f9e38ed70 --- /dev/null +++ b/MapboxSearch/sdk/src/androidTest/java/com/mapbox/search/tests_support/Utils.kt @@ -0,0 +1,98 @@ +package com.mapbox.search.tests_support + +import com.mapbox.search.ResponseInfo +import com.mapbox.search.result.BaseSearchResult +import com.mapbox.search.result.BaseSearchSuggestion +import com.mapbox.search.result.IndexableRecordSearchResultImpl +import com.mapbox.search.result.IndexableRecordSearchSuggestion +import com.mapbox.search.result.OriginalSearchResult +import com.mapbox.search.result.SearchResult +import com.mapbox.search.result.SearchSuggestion +import com.mapbox.search.result.ServerSearchResultImpl +import com.mapbox.search.result.ServerSearchSuggestion + +internal fun ResponseInfo.fixNonDeterminedFields(fixedSessionID: String): ResponseInfo { + return ResponseInfo( + requestOptions = requestOptions.copy(sessionID = fixedSessionID), + coreSearchResponse = coreSearchResponse, + isReproducible = isReproducible, + ) +} + +internal fun SearchSuggestion.fixNonDeterminedFields(userRecordPriority: Int, sessionID: String): SearchSuggestion { + this as BaseSearchSuggestion + val fixedOriginalSearchResult = originalSearchResult.copy(userRecordPriority = userRecordPriority) + val fixedRequestOptions = requestOptions.copy(sessionID = sessionID) + return when (this) { + is ServerSearchSuggestion -> { + copy(originalSearchResult = fixedOriginalSearchResult, requestOptions = fixedRequestOptions) + } + is IndexableRecordSearchSuggestion -> { + copy(originalSearchResult = fixedOriginalSearchResult, requestOptions = fixedRequestOptions) + } + else -> throw IllegalStateException("Unknown type of $javaClass") + } +} + +internal fun SearchResult.fixNonDeterminedFields(userRecordPriority: Int, sessionID: String): SearchResult { + this as BaseSearchResult + val fixedOriginalSearchResult = originalSearchResult.copy(userRecordPriority = userRecordPriority) + val fixedRequestOptions = requestOptions.copy(sessionID = sessionID) + return when (this) { + is ServerSearchResultImpl -> { + copy(originalSearchResult = fixedOriginalSearchResult, requestOptions = fixedRequestOptions) + } + is IndexableRecordSearchResultImpl -> { + copy(originalSearchResult = fixedOriginalSearchResult, requestOptions = fixedRequestOptions) + } + else -> throw IllegalStateException("Unknown type of $javaClass") + } +} + +internal fun compareSearchResultWithServerSearchResult( + expected: SearchResult, + serverResult: SearchResult +): Boolean { + if (expected === serverResult) return true + if (expected.javaClass != serverResult.javaClass) return false + + expected as BaseSearchResult + serverResult as BaseSearchResult + + val fixedResult = expected.fixNonDeterminedFields( + serverResult.originalSearchResult.userRecordPriority, + serverResult.requestOptions.sessionID + ) + return fixedResult == serverResult +} + +internal fun compareSearchResultWithServerSearchResult( + expected: SearchSuggestion, + serverResult: SearchSuggestion +): Boolean { + if (expected === serverResult) return true + if (expected.javaClass != serverResult.javaClass) return false + + expected as BaseSearchSuggestion + serverResult as BaseSearchSuggestion + + val fixedResult = expected.fixNonDeterminedFields( + serverResult.originalSearchResult.userRecordPriority, + serverResult.requestOptions.sessionID + ) + return fixedResult == serverResult +} + +internal fun compareSearchResultWithServerSearchResult( + expected: OriginalSearchResult, + serverResult: OriginalSearchResult +): Boolean { + if (expected === serverResult) return true + if (expected.javaClass != serverResult.javaClass) return false + + val fixedOriginalSearchResult = expected.copy( + userRecordPriority = serverResult.userRecordPriority + ) + + return fixedOriginalSearchResult == serverResult +} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/IndexableDataProvidersRegistryImpl.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/IndexableDataProvidersRegistryImpl.kt index 9118f4b18..246445b38 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/IndexableDataProvidersRegistryImpl.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/IndexableDataProvidersRegistryImpl.kt @@ -7,12 +7,10 @@ import com.mapbox.search.record.DataProviderResolver import com.mapbox.search.record.IndexableDataProvider import com.mapbox.search.record.IndexableDataProviderEngineImpl import com.mapbox.search.record.IndexableRecord -import com.mapbox.search.utils.SyncLocker import com.mapbox.search.utils.extension.addValue import java.util.concurrent.Executor internal class IndexableDataProvidersRegistryImpl( - private val syncLocker: SyncLocker, private val dataProviderEngineRegistrationService: DataProviderEngineRegistrationService ) : IndexableDataProvidersRegistry, DataProviderResolver { @@ -38,10 +36,8 @@ internal class IndexableDataProvidersRegistryImpl( val dataProviderContext = registry.dataProviderContext(dataProvider.dataProviderName) if (dataProviderContext != null) { - syncLocker.executeInSync { - registry.register(dataProvider, searchEngine) - searchEngine.addUserLayer(dataProviderContext.engine.coreLayerContext.coreLayer) - } + registry.register(dataProvider, searchEngine) + searchEngine.addUserLayer(dataProviderContext.engine.coreLayer) executor.execute { callback.onComplete(Unit) } return CompletedAsyncOperationTask } @@ -51,24 +47,22 @@ internal class IndexableDataProvidersRegistryImpl( dataProvider, object : CompletionCallback { override fun onComplete(result: IndexableDataProviderEngineImpl) { - syncLocker.executeInSync { - task.runIfNotCancelled { - runSynchronized { - registry.registerDataProviderContext( - dataProvider, - DataProviderContext( - engine = result, - provider = dataProvider, - ) + task.runIfNotCancelled { + runSynchronized { + registry.registerDataProviderContext( + dataProvider, + DataProviderContext( + engine = result, + provider = dataProvider, ) - registry.register(dataProvider, searchEngine) - searchEngine.addUserLayer(result.coreLayerContext.coreLayer) - } - - executor.execute { - task.onComplete() - callback.onComplete(Unit) - } + ) + registry.register(dataProvider, searchEngine) + searchEngine.addUserLayer(result.coreLayer) + } + + executor.execute { + task.onComplete() + callback.onComplete(Unit) } } } @@ -104,17 +98,21 @@ internal class IndexableDataProvidersRegistryImpl( return CompletedAsyncOperationTask } + val context = registry.dataProviderContext(dataProvider.dataProviderName) + assertDebug(context != null) { + "Null context for registered data provider. Data provider: ${dataProvider.dataProviderName}" + } + context ?: return CompletedAsyncOperationTask + val task = AsyncOperationTaskImpl() - syncLocker.executeInSync { - task.runIfNotCancelled { - runSynchronized { - registry.unregister(dataProvider, searchEngine) - searchEngine.removeUserLayer(dataProvider.dataProviderName) - } - executor.execute { - task.onComplete() - callback.onComplete(Unit) - } + task.runIfNotCancelled { + runSynchronized { + registry.unregister(dataProvider, searchEngine) + searchEngine.removeUserLayer(context.engine.coreLayer) + } + executor.execute { + task.onComplete() + callback.onComplete(Unit) } } return task diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/MapboxSearchSdk.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/MapboxSearchSdk.kt index db53f280a..d3ed4cb81 100755 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/MapboxSearchSdk.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/MapboxSearchSdk.kt @@ -12,8 +12,12 @@ import com.mapbox.common.TileStoreOptions import com.mapbox.common.module.LibraryLoader import com.mapbox.common.module.provider.MapboxModuleProvider import com.mapbox.common.module.provider.ModuleProviderArgument +import com.mapbox.search.analytics.AnalyticsEventJsonParser +import com.mapbox.search.analytics.AnalyticsServiceImpl +import com.mapbox.search.analytics.CrashEventsFactory import com.mapbox.search.analytics.ErrorsReporter -import com.mapbox.search.analytics.TelemetryService +import com.mapbox.search.analytics.SearchEventsService +import com.mapbox.search.analytics.SearchFeedbackEventsFactory import com.mapbox.search.common.BuildConfig import com.mapbox.search.common.CommonErrorsReporter import com.mapbox.search.common.concurrent.CommonMainThreadChecker @@ -23,14 +27,6 @@ import com.mapbox.search.core.CoreEngineOptions import com.mapbox.search.core.CoreLocationProvider import com.mapbox.search.core.CoreSearchEngine import com.mapbox.search.core.CoreSearchEngineInterface -import com.mapbox.search.core.PlatformClientImpl -import com.mapbox.search.core.http.AsyncHttpCallbackDecorator -import com.mapbox.search.core.http.HttpClientImpl -import com.mapbox.search.core.http.HttpErrorsCache -import com.mapbox.search.core.http.HttpErrorsCacheImpl -import com.mapbox.search.core.http.OkHttpHelper -import com.mapbox.search.core.http.UserAgentProviderImpl -import com.mapbox.search.internal.bindgen.PlatformClient import com.mapbox.search.location.LocationEngineAdapter import com.mapbox.search.location.WrapperLocationProvider import com.mapbox.search.record.DataProviderEngineRegistrationServiceImpl @@ -40,12 +36,11 @@ import com.mapbox.search.record.IndexableDataProvider import com.mapbox.search.record.RecordsFileStorage import com.mapbox.search.result.SearchResultFactory import com.mapbox.search.utils.AndroidKeyboardLocaleProvider +import com.mapbox.search.utils.AppInfoProviderImpl import com.mapbox.search.utils.FormattedTimeProvider import com.mapbox.search.utils.FormattedTimeProviderImpl import com.mapbox.search.utils.KeyboardLocaleProvider import com.mapbox.search.utils.LocalTimeProvider -import com.mapbox.search.utils.SyncLocker -import com.mapbox.search.utils.SyncLockerImpl import com.mapbox.search.utils.TimeProvider import com.mapbox.search.utils.UUIDProvider import com.mapbox.search.utils.UUIDProviderImpl @@ -73,17 +68,14 @@ public object MapboxSearchSdk { } else { "search-sdk-android/${BuildConfig.VERSION_NAME}" } - private val globalDataProvidersLock: SyncLocker = SyncLockerImpl() private var isInitialized = false // Strong references for bindgen interfaces - private lateinit var platformClient: PlatformClient private lateinit var coreLocationProvider: CoreLocationProvider private lateinit var searchRequestContextProvider: SearchRequestContextProvider - private lateinit var httpErrorsCache: HttpErrorsCache private lateinit var searchResultFactory: SearchResultFactory private lateinit var tileStore: TileStore @@ -202,26 +194,33 @@ public object MapboxSearchSdk { load(SEARCH_SDK_NATIVE_LIBRARY_NAME) } - val analyticsService = TelemetryService( - context = application, - accessToken = accessToken, - userAgent = userAgent, - locationEngine = locationEngine, + val eventJsonParser = AnalyticsEventJsonParser() + + val searchFeedbackEventsFactory = SearchFeedbackEventsFactory( + providedUserAgent = userAgent, viewportProvider = viewportProvider, uuidProvider = uuidProvider, coreEngineProvider = ::getCoreEngineByApiType, + eventJsonParser = eventJsonParser, formattedTimeProvider = formattedTimeProvider, ) - httpErrorsCache = HttpErrorsCacheImpl() - - val debugLogsEnabled = System.getProperty("com.mapbox.mapboxsearch.enableDebugLogs")?.toBoolean() == true + val crashEventsFactory = CrashEventsFactory( + timeProvider = LocalTimeProvider(), + appInfoProvider = AppInfoProviderImpl( + context = application, + searchSdkPackageName = com.mapbox.search.BuildConfig.LIBRARY_PACKAGE_NAME, + searchSdkVersionName = BuildConfig.VERSION_NAME + ) + ) - val httpClient = HttpClientImpl( - client = OkHttpHelper(debugLogsEnabled).getClient(), - errorsCache = httpErrorsCache, - uuidProvider = uuidProvider, - userAgentProvider = UserAgentProviderImpl(application) + val analyticsService = AnalyticsServiceImpl( + context = application, + eventsService = SearchEventsService(accessToken, userAgent), + eventsJsonParser = eventJsonParser, + feedbackEventsFactory = searchFeedbackEventsFactory, + crashEventsFactory = crashEventsFactory, + locationEngine = locationEngine, ) searchEnginesExecutor = Executors.newSingleThreadExecutor { runnable -> @@ -234,19 +233,6 @@ public object MapboxSearchSdk { Thread(runnable, "Global DataProviderRegistry executor") } - platformClient = PlatformClientImpl( - httpClient = httpClient, - analyticsService = analyticsService, - uuidProvider = uuidProvider, - callbackDecorator = { - AsyncHttpCallbackDecorator( - executor = searchEnginesExecutor, - syncLocker = globalDataProvidersLock, - originalCallback = it, - ) - }, - ) - coreLocationProvider = WrapperLocationProvider(locationProvider, viewportProvider) geocodingCoreSearchEngine = createCoreEngineByApiType(ApiType.GEOCODING, searchEngineSettings) @@ -258,10 +244,8 @@ public object MapboxSearchSdk { ) indexableDataProvidersRegistry = IndexableDataProvidersRegistryImpl( - syncLocker = globalDataProvidersLock, dataProviderEngineRegistrationService = DataProviderEngineRegistrationServiceImpl( registryExecutor = globalDataProvidersRegistryExecutor, - syncLocker = globalDataProvidersLock, ) ) @@ -362,7 +346,7 @@ public object MapboxSearchSdk { return when (type) { MapboxModuleType.CommonLibraryLoader -> arrayOf() MapboxModuleType.CommonLogger -> arrayOf() - MapboxModuleType.CommonHttpClient, // TODO support common Http service + MapboxModuleType.CommonHttpClient -> arrayOf() MapboxModuleType.NavigationRouter, MapboxModuleType.NavigationTripNotification, MapboxModuleType.MapTelemetry -> throw IllegalArgumentException("not supported: $type") @@ -439,7 +423,6 @@ public object MapboxSearchSdk { return SearchEngineImpl( apiType, coreEngine, - httpErrorsCache, internalServiceProvider.historyService(), searchRequestContextProvider, searchResultFactory, @@ -463,7 +446,6 @@ public object MapboxSearchSdk { checkInitialized() return OfflineSearchEngineImpl( coreEngine = coreEngine, - httpErrorsCache = httpErrorsCache, historyService = internalServiceProvider.historyService(), requestContextProvider = searchRequestContextProvider, searchResultFactory = searchResultFactory, @@ -482,8 +464,8 @@ public object MapboxSearchSdk { } return CoreSearchEngine( - CoreEngineOptions(accessToken, endpoint, apiType.mapToCore(), userAgent), - platformClient, + // TODO allow customer to customize events url + CoreEngineOptions(accessToken, endpoint, apiType.mapToCore(), userAgent, null), coreLocationProvider, ).apply { coreSearchEngines.add(this) diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/OfflineSearchEngineImpl.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/OfflineSearchEngineImpl.kt index bdcaf73c7..87e49a9bf 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/OfflineSearchEngineImpl.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/OfflineSearchEngineImpl.kt @@ -10,7 +10,6 @@ import com.mapbox.search.common.printableName import com.mapbox.search.core.CoreOfflineIndexObserver import com.mapbox.search.core.CoreSearchEngine import com.mapbox.search.core.CoreSearchEngineInterface -import com.mapbox.search.core.http.HttpErrorsCache import com.mapbox.search.engine.BaseSearchEngine import com.mapbox.search.engine.OneStepRequestCallbackWrapper import com.mapbox.search.engine.TwoStepsRequestCallbackWrapper @@ -29,7 +28,6 @@ import java.util.concurrent.ExecutorService internal class OfflineSearchEngineImpl( private val coreEngine: CoreSearchEngineInterface, - private val httpErrorsCache: HttpErrorsCache, private val historyService: HistoryService, private val requestContextProvider: SearchRequestContextProvider, private val searchResultFactory: SearchResultFactory, @@ -50,6 +48,12 @@ internal class OfflineSearchEngineImpl( coreEngine.setTileStore(tileStore) { synchronized(initializationLock) { isEngineReady = true + engineReadyCallbacks.forEach { (callback, executor) -> + executor.execute { + callback.onEngineReady() + } + } + engineReadyCallbacks.clear() } logd("setTileStore done") } @@ -75,12 +79,11 @@ internal class OfflineSearchEngineImpl( ): SearchRequestTask { logd("search($query, $options) called") - return makeRequest(callback, engineExecutorService) { request -> + return makeRequest(callback) { request -> coreEngine.searchOffline( query, emptyList(), options.mapToCore(), TwoStepsRequestCallbackWrapper( coreEngine = coreEngine, - httpErrorsCache = httpErrorsCache, historyService = historyService, searchResultFactory = searchResultFactory, callbackExecutor = executor, @@ -106,15 +109,12 @@ internal class OfflineSearchEngineImpl( val coreRequestOptions = suggestion.requestOptions.mapToCore() return when (suggestion) { - is ServerSearchSuggestion -> makeRequest( - callback, engineExecutorService - ) { request -> + is ServerSearchSuggestion -> makeRequest(callback) { request -> coreEngine.retrieveOffline( coreRequestOptions, suggestion.originalSearchResult.mapToCore(), TwoStepsRequestCallbackWrapper( coreEngine = coreEngine, - httpErrorsCache = httpErrorsCache, historyService = historyService, searchResultFactory = searchResultFactory, callbackExecutor = executor, @@ -145,11 +145,10 @@ internal class OfflineSearchEngineImpl( executor: Executor, callback: SearchCallback ): SearchRequestTask { - return makeRequest(callback, engineExecutorService) { request: SearchRequestTaskImpl -> + return makeRequest(callback) { request: SearchRequestTaskImpl -> coreEngine.reverseGeocodingOffline( options.mapToCore(), OneStepRequestCallbackWrapper( - httpErrorsCache = httpErrorsCache, searchResultFactory = searchResultFactory, callbackExecutor = executor, workerExecutor = engineExecutorService, @@ -175,13 +174,12 @@ internal class OfflineSearchEngineImpl( return SearchRequestTaskImpl.completed() } - return makeRequest(callback, engineExecutorService) { request: SearchRequestTaskImpl -> + return makeRequest(callback) { request: SearchRequestTaskImpl -> coreEngine.getAddressesOffline( street, proximity, radiusMeters, OneStepRequestCallbackWrapper( - httpErrorsCache = httpErrorsCache, searchResultFactory = searchResultFactory, callbackExecutor = executor, workerExecutor = engineExecutorService, diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/SearchEngineImpl.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/SearchEngineImpl.kt index cc43eb54e..e5e5e2a59 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/SearchEngineImpl.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/SearchEngineImpl.kt @@ -3,7 +3,6 @@ package com.mapbox.search import com.mapbox.search.common.assertDebug import com.mapbox.search.common.logger.logd import com.mapbox.search.core.CoreSearchEngineInterface -import com.mapbox.search.core.http.HttpErrorsCache import com.mapbox.search.engine.BaseSearchEngine import com.mapbox.search.engine.OneStepRequestCallbackWrapper import com.mapbox.search.engine.TwoStepsBatchRequestCallbackWrapper @@ -27,7 +26,6 @@ import java.util.concurrent.ExecutorService internal class SearchEngineImpl( override val apiType: ApiType, private val coreEngine: CoreSearchEngineInterface, - private val httpErrorsCache: HttpErrorsCache, private val historyService: HistoryService, private val requestContextProvider: SearchRequestContextProvider, private val searchResultFactory: SearchResultFactory, @@ -43,13 +41,12 @@ internal class SearchEngineImpl( ): SearchRequestTask { logd("search($query, $options) called") - return makeRequest(callback, engineExecutorService) { request -> + return makeRequest(callback) { request -> val requestContext = requestContextProvider.provide(apiType) coreEngine.search(query, emptyList(), options.mapToCore(), TwoStepsRequestCallbackWrapper( apiType = apiType, coreEngine = coreEngine, - httpErrorsCache = httpErrorsCache, historyService = historyService, searchResultFactory = searchResultFactory, callbackExecutor = executor, @@ -137,7 +134,7 @@ internal class SearchEngineImpl( val resultingFunction: (List) -> List = { remoteResults -> assertDebug(remoteResults.size == toResolve.size) { - "Not all items has been resolved. " + + "Not all items have been resolved. " + "To resolve: ${toResolve.map { it.id to it.type }}, " + "actual: ${remoteResults.map { it.id to it.types }}" } @@ -160,7 +157,7 @@ internal class SearchEngineImpl( } } - makeRequest(callback, engineExecutorService) { searchRequestTask -> + makeRequest(callback) { searchRequestTask -> val requestOptions = toResolve.first().requestOptions val requestContext = requestOptions.requestContext coreEngine.retrieveBucket( @@ -168,7 +165,6 @@ internal class SearchEngineImpl( coreSearchResults, TwoStepsBatchRequestCallbackWrapper( suggestions = filtered, - httpErrorsCache = httpErrorsCache, searchResultFactory = searchResultFactory, callbackExecutor = executor, workerExecutor = engineExecutorService, @@ -199,24 +195,25 @@ internal class SearchEngineImpl( ): SearchRequestTask { val searchRequestTask = SearchRequestTaskImpl(callback) - searchRequestTask += engineExecutorService.submit { - coreEngine.onSelected(coreRequestOptions, suggestion.originalSearchResult.mapToCore()) + coreEngine.onSelected(coreRequestOptions, suggestion.originalSearchResult.mapToCore()) - val responseInfo = ResponseInfo( - requestOptions = suggestion.requestOptions, - coreSearchResponse = null, - isReproducible = false - ) + val responseInfo = ResponseInfo( + requestOptions = suggestion.requestOptions, + coreSearchResponse = null, + isReproducible = false + ) - if (!options.addResultToHistory) { - searchRequestTask.markExecutedAndRunOnCallback(executor) { - onResult(suggestion, resolved, responseInfo) - } - return@submit + if (!options.addResultToHistory) { + searchRequestTask.markExecutedAndRunOnCallback(executor) { + onResult(suggestion, resolved, responseInfo) } + return searchRequestTask + } - if (!searchRequestTask.isCompleted) { - searchRequestTask += historyService.addToHistoryIfNeeded(resolved, engineExecutorService, object : CompletionCallback { + if (!searchRequestTask.isCompleted) { + searchRequestTask += historyService.addToHistoryIfNeeded( + searchResult = resolved, + callback = object : CompletionCallback { override fun onComplete(result: Boolean) { searchRequestTask.markExecutedAndRunOnCallback(executor) { onResult(suggestion, resolved, responseInfo) @@ -228,10 +225,9 @@ internal class SearchEngineImpl( onError(e) } } - }) - } + } + ) } - return searchRequestTask } @@ -244,7 +240,7 @@ internal class SearchEngineImpl( ) completeSearchResultSelection(suggestion, searchResult) } - is ServerSearchSuggestion -> makeRequest(callback, engineExecutorService) { request -> + is ServerSearchSuggestion -> makeRequest(callback) { request -> val requestContext = suggestion.requestOptions.requestContext coreEngine.retrieve( coreRequestOptions, @@ -252,7 +248,6 @@ internal class SearchEngineImpl( TwoStepsRequestCallbackWrapper( apiType = apiType, coreEngine = coreEngine, - httpErrorsCache = httpErrorsCache, historyService = historyService, searchResultFactory = searchResultFactory, callbackExecutor = executor, @@ -285,14 +280,13 @@ internal class SearchEngineImpl( executor: Executor, callback: SearchCallback, ): SearchRequestTask { - return makeRequest(callback, engineExecutorService) { request -> + return makeRequest(callback) { request -> val requestContext = requestContextProvider.provide(apiType) coreEngine.search( "", listOf(categoryName), options.mapToCoreCategory(), OneStepRequestCallbackWrapper( - httpErrorsCache = httpErrorsCache, searchResultFactory = searchResultFactory, callbackExecutor = executor, workerExecutor = engineExecutorService, @@ -309,12 +303,11 @@ internal class SearchEngineImpl( executor: Executor, callback: SearchCallback ): SearchRequestTask { - return makeRequest(callback, engineExecutorService) { request -> + return makeRequest(callback) { request -> val requestContext = requestContextProvider.provide(apiType) coreEngine.reverseGeocoding( options.mapToCore(), OneStepRequestCallbackWrapper( - httpErrorsCache = httpErrorsCache, searchResultFactory = searchResultFactory, callbackExecutor = executor, workerExecutor = engineExecutorService, diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/AnalyticsEventJsonParser.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/AnalyticsEventJsonParser.kt index bb169d63b..d6906ebdf 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/AnalyticsEventJsonParser.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/AnalyticsEventJsonParser.kt @@ -1,19 +1,11 @@ package com.mapbox.search.analytics import com.google.gson.Gson -import com.mapbox.search.analytics.events.BaseSearchEvent -import com.mapbox.search.analytics.events.QueryChangeEvent import com.mapbox.search.analytics.events.SearchFeedbackEvent -import com.mapbox.search.analytics.events.SearchSelectEvent -import com.mapbox.search.analytics.events.SearchStartEvent import org.json.JSONObject internal class AnalyticsEventJsonParser { - // Telemetry SDK uses Gson to serialize event, so we have 2 options: - // 1. Annotate Event's field with Gson's @SerializedName annotation - // 2. Make sure that field name is the same as required json attribute, in this case we have to be sure that proguard won't change it - // We use the first approach. Need to change parser and get rid of Gson when Telemetry SDK removes it as well. private val gson: Gson = Gson() /** @@ -21,32 +13,22 @@ internal class AnalyticsEventJsonParser { * @throws com.google.gson.JsonSyntaxException if [jsonEvent] is not a valid representation for an object of subtype of BaseSearchEvent * @throws IllegalArgumentException if [jsonEvent] has unknown "event" type */ - fun parse(jsonEvent: String): BaseSearchEvent { + fun parse(jsonEvent: String): SearchFeedbackEvent { val jsonObject = JSONObject(jsonEvent) - return when (val event = jsonObject.getString(BaseSearchEvent.EVENT_ATTRIBUTE_NAME)) { - SearchSelectEvent.EVENT_NAME -> gson.fromJson(jsonEvent, SearchSelectEvent::class.java) - SearchStartEvent.EVENT_NAME -> gson.fromJson(jsonEvent, SearchStartEvent::class.java) - QueryChangeEvent.EVENT_NAME -> gson.fromJson(jsonEvent, QueryChangeEvent::class.java) + return when (val event = jsonObject.getString("event")) { SearchFeedbackEvent.EVENT_NAME -> gson.fromJson(jsonEvent, SearchFeedbackEvent::class.java) else -> throw IllegalArgumentException("Unknown event type $event") } } - fun serialize(event: BaseSearchEvent): String { - require(event.validateEventName()) { - "BaseSearchEvent should contain valid \"event\"`property! Event: $event" + fun serialize(event: SearchFeedbackEvent): String { + require(event.event == SearchFeedbackEvent.EVENT_NAME) { + "$event is not valid" } - return gson.toJson(event) } - private fun BaseSearchEvent.validateEventName(): Boolean { - return when (this) { - is SearchSelectEvent -> event == SearchSelectEvent.EVENT_NAME - is SearchStartEvent -> event == SearchStartEvent.EVENT_NAME - is QueryChangeEvent -> event == QueryChangeEvent.EVENT_NAME - is SearchFeedbackEvent -> event == SearchFeedbackEvent.EVENT_NAME - else -> false - } + fun serializeAny(obj: Any): String { + return gson.toJson(obj) } } diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/AnalyticsService.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/AnalyticsService.kt index ffeee0dcb..15f72d9d9 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/AnalyticsService.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/AnalyticsService.kt @@ -1,10 +1,12 @@ package com.mapbox.search.analytics +import com.mapbox.search.CompletionCallback import com.mapbox.search.ResponseInfo import com.mapbox.search.record.FavoriteRecord import com.mapbox.search.record.HistoryRecord import com.mapbox.search.result.SearchResult import com.mapbox.search.result.SearchSuggestion +import java.util.concurrent.Executor /** * Class for tracking analytics events from user side. @@ -15,15 +17,37 @@ public interface AnalyticsService { * Creates raw feedback event. This event may be cached and used later. * @param searchResult search result, for which raw feedback event will be created. * @param responseInfo search context, associated with provided *searchResult*. + * @param executor Executor used for result dispatching. By default result is dispatched on the main thread. + * @param callback The callback to retrieve result. */ - public fun createRawFeedbackEvent(searchResult: SearchResult, responseInfo: ResponseInfo): String + @Deprecated( + "This function returns raw event in a very specific format and should not be used", + replaceWith = ReplaceWith("Replace with own implementation specific for your analytics system") + ) + public fun createRawFeedbackEvent( + searchResult: SearchResult, + responseInfo: ResponseInfo, + executor: Executor, + callback: CompletionCallback + ) /** * Creates raw feedback event. This event may be cached and used later. * @param searchSuggestion search suggestion, for which raw feedback event will be created. * @param responseInfo search context, associated with provided *searchSuggestion*. + * @param executor Executor used for result dispatching. By default result is dispatched on the main thread. + * @param callback The callback to retrieve result. */ - public fun createRawFeedbackEvent(searchSuggestion: SearchSuggestion, responseInfo: ResponseInfo): String + @Deprecated( + "This function returns raw event in a very specific format and should not be used", + replaceWith = ReplaceWith("Replace with own implementation specific for your analytics system") + ) + public fun createRawFeedbackEvent( + searchSuggestion: SearchSuggestion, + responseInfo: ResponseInfo, + executor: Executor, + callback: CompletionCallback + ) /** * Sends feedback event to analytics. @@ -60,11 +84,4 @@ public interface AnalyticsService { * @param event extra information for feedback, provided by user. */ public fun sendMissingResultFeedback(event: MissingResultFeedbackEvent) - - /** - * Sends feedback event to analytics with pre-cached raw feedback event info. - * @param rawFeedbackEvent raw feedback event, which was received from [AnalyticsService.createRawFeedbackEvent]. - * @param event extra information for feedback, provided by user. - */ - public fun sendRawFeedbackEvent(rawFeedbackEvent: String, event: FeedbackEvent) } diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/TelemetryService.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/AnalyticsServiceImpl.kt similarity index 51% rename from MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/TelemetryService.kt rename to MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/AnalyticsServiceImpl.kt index 763c23b50..c7d19bbec 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/TelemetryService.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/AnalyticsServiceImpl.kt @@ -1,22 +1,16 @@ package com.mapbox.search.analytics import android.content.Context -import com.google.gson.Gson import com.mapbox.android.core.location.LocationEngine -import com.mapbox.android.telemetry.MapboxCrashReporter -import com.mapbox.android.telemetry.MapboxTelemetry import com.mapbox.geojson.Point -import com.mapbox.search.ApiType +import com.mapbox.search.CompletionCallback import com.mapbox.search.ResponseInfo -import com.mapbox.search.ViewportProvider import com.mapbox.search.analytics.events.SearchFeedbackEvent -import com.mapbox.search.common.BuildConfig import com.mapbox.search.common.assertDebug import com.mapbox.search.common.extension.lastKnownLocationOrNull import com.mapbox.search.common.logger.logd import com.mapbox.search.common.logger.loge import com.mapbox.search.common.throwDebug -import com.mapbox.search.core.CoreSearchEngineInterface import com.mapbox.search.record.FavoriteRecord import com.mapbox.search.record.HistoryRecord import com.mapbox.search.result.BaseSearchResult @@ -29,111 +23,73 @@ import com.mapbox.search.result.SearchResult import com.mapbox.search.result.SearchSuggestion import com.mapbox.search.result.ServerSearchResultImpl import com.mapbox.search.result.ServerSearchSuggestion -import com.mapbox.search.utils.FormattedTimeProvider -import com.mapbox.search.utils.UUIDProvider +import java.util.concurrent.Executor -internal class TelemetryService : InternalAnalyticsService, ErrorsReporter { - - private val context: Context - private val mapBoxTelemetry: MapboxTelemetry - private val eventsJsonParser: AnalyticsEventJsonParser - private val eventsFactory: TelemetrySearchEventsFactory - private val errorsReporter: MapboxCrashReporter +internal class AnalyticsServiceImpl( + private val context: Context, + private val eventsService: SearchEventsService, + private val eventsJsonParser: AnalyticsEventJsonParser, + private val feedbackEventsFactory: SearchFeedbackEventsFactory, + private val crashEventsFactory: CrashEventsFactory, private val locationEngine: LocationEngine - - private val gson = Gson() - - internal constructor( - context: Context, - mapBoxTelemetry: MapboxTelemetry, - locationEngine: LocationEngine, - eventsJsonParser: AnalyticsEventJsonParser, - eventsFactory: TelemetrySearchEventsFactory, - errorsReporter: MapboxCrashReporter, - ) { - this.context = context - this.mapBoxTelemetry = mapBoxTelemetry - this.locationEngine = locationEngine - this.eventsJsonParser = eventsJsonParser - this.eventsFactory = eventsFactory - this.errorsReporter = errorsReporter - } - - internal constructor( - context: Context, - accessToken: String, - userAgent: String, - locationEngine: LocationEngine, - viewportProvider: ViewportProvider?, - uuidProvider: UUIDProvider, - coreEngineProvider: (ApiType) -> CoreSearchEngineInterface, - formattedTimeProvider: FormattedTimeProvider, - eventsJsonParser: AnalyticsEventJsonParser = AnalyticsEventJsonParser(), - ) { - this.context = context.applicationContext - this.mapBoxTelemetry = MapboxTelemetry(context.applicationContext, accessToken, userAgent).apply { - enable() - updateDebugLoggingEnabled(BuildConfig.DEBUG) - } - this.eventsJsonParser = eventsJsonParser - this.eventsFactory = TelemetrySearchEventsFactory( - userAgent, viewportProvider, uuidProvider, coreEngineProvider, - eventsJsonParser, formattedTimeProvider, gson::toJson - ) - this.errorsReporter = MapboxCrashReporter( - mapBoxTelemetry, - BuildConfig.LIBRARY_PACKAGE_NAME, - BuildConfig.VERSION_NAME, - emptySet() - ) - this.locationEngine = locationEngine - - logd("Initialize TelemetryAnalyticsService with $userAgent agent", tag = LOG_TAG) - } - - override fun postJsonEvent(event: String) { - try { - logd("postJsonEvent: $event", tag = LOG_TAG) - val parsedEvent = eventsJsonParser.parse(event) - check(parsedEvent.isValid) { - "Broken telemetry event $event" - } - mapBoxTelemetry.push(parsedEvent) - logd("Parsed event: $parsedEvent", tag = LOG_TAG) - } catch (e: Exception) { - loge(e, "Unable to send event: $event", tag = LOG_TAG) - throwDebug(e) - } - } +) : InternalAnalyticsService, ErrorsReporter { override fun createRawFeedbackEvent( searchResult: SearchResult, - responseInfo: ResponseInfo - ): String { - val feedbackEvent = createFeedbackEvent( + responseInfo: ResponseInfo, + executor: Executor, + callback: CompletionCallback + ) { + createFeedbackEvent( searchResult = searchResult, responseInfo = responseInfo, // Location is null because it's not needed for template events. - // See TelemetrySearchEventsFactory.createSearchFeedbackEvent + // See SearchFeedbackEventsFactory.createSearchFeedbackEvent currentLocation = null, asTemplate = true, + callback = object : CompletionCallback { + override fun onComplete(result: SearchFeedbackEvent) { + executor.execute { + callback.onComplete(eventsJsonParser.serialize(result)) + } + } + + override fun onError(e: Exception) { + executor.execute { + callback.onError(e) + } + } + } ) - return eventsJsonParser.serialize(feedbackEvent) } override fun createRawFeedbackEvent( searchSuggestion: SearchSuggestion, - responseInfo: ResponseInfo - ): String { - val feedbackEvent = createFeedbackEvent( + responseInfo: ResponseInfo, + executor: Executor, + callback: CompletionCallback + ) { + createFeedbackEvent( searchSuggestion = searchSuggestion, responseInfo = responseInfo, // Location is null because it's not needed for template events. - // See TelemetrySearchEventsFactory.createSearchFeedbackEvent + // See SearchFeedbackEventsFactory.createSearchFeedbackEvent currentLocation = null, asTemplate = true, + callback = object : CompletionCallback { + override fun onComplete(result: SearchFeedbackEvent) { + executor.execute { + callback.onComplete(eventsJsonParser.serialize(result)) + } + } + + override fun onError(e: Exception) { + executor.execute { + callback.onError(e) + } + } + } ) - return eventsJsonParser.serialize(feedbackEvent) } override fun sendFeedback( @@ -142,13 +98,21 @@ internal class TelemetryService : InternalAnalyticsService, ErrorsReporter { event: FeedbackEvent ) { locationEngine.lastKnownLocationOrNull(context) { - val feedbackEvent = createFeedbackEvent( + createFeedbackEvent( searchResult = searchResult, responseInfo = responseInfo, currentLocation = it, - event = event + event = event, + callback = object : CompletionCallback { + override fun onComplete(result: SearchFeedbackEvent) { + sendFeedbackInternal(result) + } + + override fun onError(e: Exception) { + loge(e, "Unable to send event: $event") + } + } ) - sendFeedbackInternal(feedbackEvent) } } @@ -158,60 +122,66 @@ internal class TelemetryService : InternalAnalyticsService, ErrorsReporter { event: FeedbackEvent ) { locationEngine.lastKnownLocationOrNull(context) { - val feedbackEvent = createFeedbackEvent( + createFeedbackEvent( searchSuggestion = searchSuggestion, responseInfo = responseInfo, currentLocation = it, event = event, + callback = object : CompletionCallback { + override fun onComplete(result: SearchFeedbackEvent) { + sendFeedbackInternal(result) + } + + override fun onError(e: Exception) { + loge(e, "Unable to send event: $event") + } + } ) - sendFeedbackInternal(feedbackEvent) } } override fun sendFeedback(historyRecord: HistoryRecord, event: FeedbackEvent) { locationEngine.lastKnownLocationOrNull(context) { - val feedbackEvent = eventsFactory.createSearchFeedbackEvent(historyRecord, event, currentLocation = it) + val feedbackEvent = feedbackEventsFactory.createSearchFeedbackEvent(historyRecord, event, currentLocation = it) sendFeedbackInternal(feedbackEvent) } } override fun sendFeedback(favoriteRecord: FavoriteRecord, event: FeedbackEvent) { locationEngine.lastKnownLocationOrNull(context) { - val feedbackEvent = eventsFactory.createSearchFeedbackEvent(favoriteRecord, event, currentLocation = it) + val feedbackEvent = feedbackEventsFactory.createSearchFeedbackEvent(favoriteRecord, event, currentLocation = it) sendFeedbackInternal(feedbackEvent) } } override fun sendMissingResultFeedback(event: MissingResultFeedbackEvent) { locationEngine.lastKnownLocationOrNull(context) { - val feedbackEvent = eventsFactory.createSearchFeedbackEvent(event, currentLocation = it) - sendFeedbackInternal(feedbackEvent) - } - } - - override fun sendRawFeedbackEvent(rawFeedbackEvent: String, event: FeedbackEvent) { - val cachedFeedbackEvent = try { - eventsJsonParser.parse(rawFeedbackEvent) as SearchFeedbackEvent - } catch (e: Exception) { - loge(e, "Could not parse cached event template as SearchFeedbackEvent. Feedback won't be sent.") - return - } + feedbackEventsFactory.createSearchFeedbackEvent( + event, + currentLocation = it, + object : CompletionCallback { + override fun onComplete(result: SearchFeedbackEvent) { + sendFeedbackInternal(result) + } - locationEngine.lastKnownLocationOrNull(context) { - eventsFactory.updateCachedSearchFeedbackEvent(cachedFeedbackEvent, event, currentLocation = it) - sendFeedbackInternal(cachedFeedbackEvent) + override fun onError(e: Exception) { + loge(e, "Unable to send event: $event") + } + } + ) } } private fun sendFeedbackInternal(feedbackEvent: SearchFeedbackEvent) { try { check(feedbackEvent.isValid) { - "Broken telemetry event $feedbackEvent" + "Event is not valid $feedbackEvent" } - mapBoxTelemetry.push(feedbackEvent) - logd("Feedback event: $feedbackEvent", tag = LOG_TAG) + val jsonEvent = eventsJsonParser.serialize(feedbackEvent) + eventsService.sendEventJson(jsonEvent) + logd("Feedback event: $feedbackEvent") } catch (e: Exception) { - loge(e, "Unable to send event: $feedbackEvent", tag = LOG_TAG) + loge(e, "Unable to send event: $feedbackEvent") throwDebug(e) } } @@ -221,8 +191,9 @@ internal class TelemetryService : InternalAnalyticsService, ErrorsReporter { responseInfo: ResponseInfo?, currentLocation: Point?, event: FeedbackEvent? = null, - asTemplate: Boolean = false - ): SearchFeedbackEvent { + asTemplate: Boolean = false, + callback: CompletionCallback + ) { assertDebug(searchResult is ServerSearchResultImpl || searchResult is IndexableRecordSearchResultImpl ) { @@ -231,7 +202,7 @@ internal class TelemetryService : InternalAnalyticsService, ErrorsReporter { } require(searchResult is BaseSearchResult) { "Parameter searchResult must provide original response." } - return eventsFactory.createSearchFeedbackEvent( + return feedbackEventsFactory.createSearchFeedbackEvent( originalSearchResult = searchResult.originalSearchResult, requestOptions = searchResult.requestOptions, searchResponse = responseInfo?.coreSearchResponse, @@ -239,7 +210,8 @@ internal class TelemetryService : InternalAnalyticsService, ErrorsReporter { isReproducible = responseInfo?.isReproducible, event = event, isCached = searchResult is IndexableRecordSearchResult, - asTemplate = asTemplate + asTemplate = asTemplate, + callback = callback, ) } @@ -248,8 +220,9 @@ internal class TelemetryService : InternalAnalyticsService, ErrorsReporter { responseInfo: ResponseInfo?, currentLocation: Point?, event: FeedbackEvent? = null, - asTemplate: Boolean = false - ): SearchFeedbackEvent { + asTemplate: Boolean = false, + callback: CompletionCallback + ) { assertDebug(searchSuggestion is ServerSearchSuggestion || searchSuggestion is IndexableRecordSearchSuggestion || searchSuggestion is GeocodingCompatSearchSuggestion @@ -259,7 +232,7 @@ internal class TelemetryService : InternalAnalyticsService, ErrorsReporter { } require(searchSuggestion is BaseSearchSuggestion) { "Parameter searchSuggestion must provide original response." } - return eventsFactory.createSearchFeedbackEvent( + feedbackEventsFactory.createSearchFeedbackEvent( originalSearchResult = searchSuggestion.originalSearchResult, requestOptions = searchSuggestion.requestOptions, searchResponse = responseInfo?.coreSearchResponse, @@ -267,20 +240,22 @@ internal class TelemetryService : InternalAnalyticsService, ErrorsReporter { isReproducible = responseInfo?.isReproducible, event = event, isCached = searchSuggestion is IndexableRecordSearchSuggestion, - asTemplate = asTemplate + asTemplate = asTemplate, + callback = callback, ) } override fun setAccessToken(accessToken: String) { - mapBoxTelemetry.updateAccessToken(accessToken) + eventsService.updateAccessToken(accessToken) } override fun reportError(throwable: Throwable) { - // TODO(#692): remove workaround - errorsReporter.reportError(throwable, mapOf("source" to "Search SDK")) - } + if (!crashEventsFactory.isAllowedForAnalytics(throwable)) { + logd("$throwable is not Search SDK related error. Skip it.") + return + } - private companion object { - const val LOG_TAG = "TelemetryAnalyticsService" + val event = crashEventsFactory.createEvent(throwable, isSilent = true, customData = null) + eventsService.sendEventJson(event) } } diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/CrashEventsFactory.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/CrashEventsFactory.kt new file mode 100644 index 000000000..76040b369 --- /dev/null +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/CrashEventsFactory.kt @@ -0,0 +1,109 @@ +package com.mapbox.search.analytics + +import com.mapbox.search.common.logger.loge +import com.mapbox.search.utils.AppInfoProvider +import com.mapbox.search.utils.TimeProvider +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.text.SimpleDateFormat +import java.util.Locale + +// https://github.com/mapbox/event-schema/blob/master/lib/base-schemas/mobile.crash.js +internal class CrashEventsFactory( + private val timeProvider: TimeProvider, + private val appInfoProvider: AppInfoProvider, +) { + + fun createEvent(throwable: Throwable, isSilent: Boolean, customData: Map?): String { + return JSONObject().apply { + putSafe("event", CRASH_EVENT_NAME) + putSafe("created", DATE_FORMAT.format(timeProvider.currentTimeMillis())) + putSafe("sdkIdentifier", appInfoProvider.searchSdkPackageName) + putSafe("sdkVersion", appInfoProvider.searchSdkVersionName) + putSafe("osVersion", "Android-${appInfoProvider.osVersion}") + putSafe("model", appInfoProvider.deviceModel) + putSafe("device", appInfoProvider.deviceName) + putSafe("appId", appInfoProvider.appPackageName) + putSafe("appVersion", appInfoProvider.appVersion) + putSafe("isSilent", java.lang.Boolean.toString(isSilent)) + putSafe("stackTraceHash", createStackTraceHash(throwable)) + putSafe("stackTrace", createStackTraceElement(throwable)) + if (!customData.isNullOrEmpty()) { + putSafe("customData", createCustomDataElement(customData)) + } + }.toString() + } + + fun isAllowedForAnalytics(throwable: Throwable): Boolean = throwable.stackTrace.any { isAllowedStacktraceElement(it) } + + private fun isAllowedStacktraceElement(stackTraceElement: StackTraceElement): Boolean { + return stackTraceElement.className.startsWith(appInfoProvider.searchSdkPackageName) + } + + private fun JSONObject.putSafe(key: String, value: Any?) { + try { + if (value == null) { + put(key, "null") + } else { + put(key, value) + } + } catch (e: JSONException) { + loge(e, "Failed json encode value: $value") + } + } + + private fun createCustomDataElement(customData: Map): JSONArray? { + return try { + val jsonArray = JSONArray() + for ((key, value) in customData) { + val keyValueObject = JSONObject() + keyValueObject.put("name", key) + keyValueObject.put("value", value) + jsonArray.put(keyValueObject) + } + jsonArray + } catch (e: JSONException) { + loge(e, "Failed to create JSON array for custom data") + null + } + } + + private fun createStackTraceElement(throwable: Throwable): String { + val stackTraceElements = throwable.stackTrace + + val prefix = if ( + stackTraceElements.isNotEmpty() && + isAllowedStacktraceElement(stackTraceElements[0]) && + throwable.message != null + ) { + throwable.message + } else { + "***" + } + + val trace = stackTraceElements.joinToString(separator = "\n", postfix = "\n") { + if (isAllowedStacktraceElement(it)) { + "${it.className}.${it.methodName}(${it.fileName}:${it.lineNumber})" + } else { + "*" + } + } + + return "$prefix\n$trace" + } + + private fun createStackTraceHash(throwable: Throwable): String { + val result = StringBuilder() + for (element in throwable.stackTrace) { + result.append(element.className) + result.append(element.methodName) + } + return Integer.toHexString(result.toString().hashCode()) + } + + private companion object { + const val CRASH_EVENT_NAME = "mobile.crash" + val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US) + } +} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/InternalAnalyticsService.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/InternalAnalyticsService.kt index 341fb36c4..683d8e2e3 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/InternalAnalyticsService.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/InternalAnalyticsService.kt @@ -1,8 +1,5 @@ package com.mapbox.search.analytics internal interface InternalAnalyticsService : AnalyticsService { - - fun postJsonEvent(event: String) - fun setAccessToken(accessToken: String) } diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/SearchEventsService.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/SearchEventsService.kt new file mode 100644 index 000000000..521565555 --- /dev/null +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/SearchEventsService.kt @@ -0,0 +1,41 @@ +package com.mapbox.search.analytics + +import com.mapbox.bindgen.Value +import com.mapbox.common.Event +import com.mapbox.common.EventsService +import com.mapbox.common.EventsServiceOptions +import com.mapbox.search.common.logger.loge + +internal class SearchEventsService( + token: String, + private val userAgent: String, +) { + + private var eventsService: EventsService + + init { + val options = EventsServiceOptions(token, userAgent, null) + eventsService = EventsService(options) + } + + @Synchronized + fun sendEventJson(eventJson: String) { + val eventValue = Value.fromJson(eventJson) + if (eventValue.isValue) { + val event = Event(eventValue.value!!) + eventsService.sendEvent(event) { error -> + if (error != null) { + loge("Unable to send event: $error") + } + } + } else { + loge("Unable to create event from json event: ${eventValue.error}") + } + } + + @Synchronized + fun updateAccessToken(newToken: String) { + val options = EventsServiceOptions(newToken, userAgent, null) + eventsService = EventsService(options) + } +} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/TelemetrySearchEventsFactory.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/SearchFeedbackEventsFactory.kt similarity index 64% rename from MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/TelemetrySearchEventsFactory.kt rename to MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/SearchFeedbackEventsFactory.kt index 270d8aa81..7d0b50c11 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/TelemetrySearchEventsFactory.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/SearchFeedbackEventsFactory.kt @@ -5,13 +5,13 @@ import android.graphics.Bitmap import com.mapbox.geojson.Point import com.mapbox.search.ApiType import com.mapbox.search.BuildConfig +import com.mapbox.search.CompletionCallback import com.mapbox.search.RequestOptions import com.mapbox.search.ViewportProvider import com.mapbox.search.analytics.events.AppMetadata import com.mapbox.search.analytics.events.SearchFeedbackEvent import com.mapbox.search.analytics.events.SearchResultEntry import com.mapbox.search.analytics.events.SearchResultsInfo -import com.mapbox.search.common.throwDebug import com.mapbox.search.core.CoreSearchEngineInterface import com.mapbox.search.location.calculateMapZoom import com.mapbox.search.mapToCore @@ -27,43 +27,23 @@ import com.mapbox.search.utils.bitmap.BitmapEncodeOptions import com.mapbox.search.utils.bitmap.encodeBase64 import java.util.TreeMap -internal class TelemetrySearchEventsFactory( +internal class SearchFeedbackEventsFactory( private val providedUserAgent: String, private val viewportProvider: ViewportProvider?, private val uuidProvider: UUIDProvider, private val coreEngineProvider: (ApiType) -> CoreSearchEngineInterface, private val eventJsonParser: AnalyticsEventJsonParser, private val formattedTimeProvider: FormattedTimeProvider, - private val jsonSerializer: (Any) -> String, private val bitmapEncoder: (Bitmap) -> String = { it.encodeBase64(BITMAP_ENCODE_OPTIONS) } ) { - fun updateCachedSearchFeedbackEvent( - cachedEvent: SearchFeedbackEvent, - event: FeedbackEvent, + @SuppressLint("WrongConstant") + fun createSearchFeedbackEvent( + event: MissingResultFeedbackEvent, currentLocation: Point?, + callback: CompletionCallback ) { - cachedEvent.apply { - this.event = SearchFeedbackEvent.EVENT_NAME - - // Mandatory fields - created = formattedTimeProvider.currentTimeIso8601Formatted() - feedbackReason = event.reason - - // Optional fields - fillCommonData(currentLocation, event.feedbackId ?: uuidProvider.generateUUID()) - cached = true - feedbackText = event.text ?: feedbackText - screenshot = event.screenshot?.let(bitmapEncoder) ?: screenshot - if (event.sessionId != null) { - appMetadata = AppMetadata(sessionId = event.sessionId) - } - } - } - - @SuppressLint("WrongConstant") - fun createSearchFeedbackEvent(event: MissingResultFeedbackEvent, currentLocation: Point?): SearchFeedbackEvent { - return createSearchFeedbackEvent( + createSearchFeedbackEvent( originalSearchResult = null, requestOptions = event.responseInfo.requestOptions, searchResponse = event.responseInfo.coreSearchResponse, @@ -76,7 +56,8 @@ internal class TelemetrySearchEventsFactory( sessionId = event.sessionId, feedbackId = event.feedbackId, ), - asTemplate = false + asTemplate = false, + callback = callback ) } @@ -120,64 +101,65 @@ internal class TelemetrySearchEventsFactory( event: FeedbackEvent? = null, isCached: Boolean? = null, asTemplate: Boolean = false, - ): SearchFeedbackEvent { - // TODO remove coreEngineProvider for constructing SearchFeedbackEvent - val baseEvent = try { - requestOptions.requestContext.apiType.let(coreEngineProvider) - .makeFeedbackEvent(requestOptions.mapToCore(), originalSearchResult?.mapToCore()).let { - eventJsonParser.parse(it) as? SearchFeedbackEvent - } ?: throw IllegalStateException("Could not parse core event as SearchFeedbackEvent.") - } catch (e: Exception) { - throwDebug(e) { "Creating default SearchFeedbackEvent empty version." } - SearchFeedbackEvent() - } - - return baseEvent.apply { - this.event = SearchFeedbackEvent.EVENT_NAME - - // Mandatory fields - created = formattedTimeProvider.currentTimeIso8601Formatted() - // IndexableRecordSearchResultImpl and IndexableRecordSearchSuggestion may have - // serverIndex == null due to implementation details. Because "resultIndex" is - // mandatory, fallback to -1 for null case. - resultIndex = originalSearchResult?.serverIndex ?: -1 - sessionIdentifier = when (isCached) { - true -> INDEXABLE_RECORD_SESSION_IDENTIFIER - else -> requestOptions.sessionID + callback: CompletionCallback + ) { + val engine = coreEngineProvider(requestOptions.requestContext.apiType) + engine.makeFeedbackEvent(requestOptions.mapToCore(), originalSearchResult?.mapToCore()) { + val baseEvent = eventJsonParser.parse(it) as? SearchFeedbackEvent + if (baseEvent == null) { + callback.onError(Exception("Unable to parse event: $it")) + return@makeFeedbackEvent } - selectedItemName = originalSearchResult?.names?.firstOrNull() ?: "" - feedbackReason = event?.reason - queryString = requestOptions.query - // Optional fields - fillRequestOptionsData(requestOptions) - fillSearchResultData(searchResponse, isReproducible) - feedbackText = event?.text - screenshot = event?.screenshot?.let(bitmapEncoder) - if (event?.sessionId != null) { - appMetadata = AppMetadata(sessionId = event.sessionId) + baseEvent.apply { + this.event = SearchFeedbackEvent.EVENT_NAME + + // Mandatory fields + created = formattedTimeProvider.currentTimeIso8601Formatted() + // IndexableRecordSearchResultImpl and IndexableRecordSearchSuggestion may have + // serverIndex == null due to implementation details. Because "resultIndex" is + // mandatory, fallback to -1 for null case. + resultIndex = originalSearchResult?.serverIndex ?: -1 + sessionIdentifier = when (isCached) { + true -> INDEXABLE_RECORD_SESSION_IDENTIFIER + else -> requestOptions.sessionID + } + selectedItemName = originalSearchResult?.names?.firstOrNull() ?: "" + feedbackReason = event?.reason + queryString = requestOptions.query + + // Optional fields + fillRequestOptionsData(requestOptions) + fillSearchResultData(searchResponse, isReproducible) + feedbackText = event?.text + screenshot = event?.screenshot?.let(bitmapEncoder) + if (event?.sessionId != null) { + appMetadata = AppMetadata(sessionId = event.sessionId) + } + resultId = when (isCached) { + true -> null // ID of IndexableRecord may potentially contain PII, + // so we don't specify it + else -> originalSearchResult?.id + } + resultCoordinates = originalSearchResult?.center?.coordinates() + schema = "${SearchFeedbackEvent.EVENT_NAME}-$SEARCH_FEEDBACK_SCHEMA_VERSION" + + // For events, that will be used for template creation, + // we don't want specify common data and cached flag, because it will + // be overridden during sending anyway. + if (!asTemplate) { + fillCommonData(currentLocation, event?.feedbackId ?: uuidProvider.generateUUID()) + cached = isCached + } else { + // This properties might be populated by Core SDK, so + // clean up is needed. + latitude = null + longitude = null + userAgent = null + } } - resultId = when (isCached) { - true -> null // ID of IndexableRecord may potentially contain PII, - // so we don't specify it - else -> originalSearchResult?.id - } - resultCoordinates = originalSearchResult?.center?.coordinates() - schema = "${SearchFeedbackEvent.EVENT_NAME}-$SEARCH_FEEDBACK_SCHEMA_VERSION" - // For events, that will be used for template creation, - // we don't want specify common data and cached flag, because it will - // be overridden during sending anyway. - if (!asTemplate) { - fillCommonData(currentLocation, event?.feedbackId ?: uuidProvider.generateUUID()) - cached = isCached - } else { - // This properties might be populated by Core SDK, so - // clean up is needed. - latitude = null - longitude = null - userAgent = null - } + callback.onComplete(baseEvent) } } @@ -221,7 +203,7 @@ internal class TelemetrySearchEventsFactory( } searchResultsJson = if (resultEntries != null && isReproducible != null) { - jsonSerializer(SearchResultsInfo(resultEntries, !isReproducible)) + eventJsonParser.serializeAny(SearchResultsInfo(resultEntries, !isReproducible)) } else { null } diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/BaseSearchEvent.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/BaseSearchEvent.kt deleted file mode 100644 index cd4e01bc2..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/BaseSearchEvent.kt +++ /dev/null @@ -1,209 +0,0 @@ -package com.mapbox.search.analytics.events - -import android.os.Parcel -import com.google.gson.annotations.SerializedName -import com.mapbox.android.telemetry.Event - -// Event schemas, that shares common fields: -// https://github.com/mapbox/event-schema/blob/master/lib/base-schemas/search.start.js -// https://github.com/mapbox/event-schema/blob/master/lib/base-schemas/search.select.js -// https://github.com/mapbox/event-schema/blob/master/lib/base-schemas/search.query_change.js -// https://github.com/mapbox/event-schema/blob/master/lib/base-schemas/search.feedback.js -internal abstract class BaseSearchEvent : Event { - - public open val isValid: Boolean - get() = event != null && created != null && sessionIdentifier != null - - @SerializedName(EVENT_ATTRIBUTE_NAME) - var event: String? = null - - @SerializedName("created") - var created: String? = null - - @SerializedName("lat") - var latitude: Double? = null - - @SerializedName("lng") - var longitude: Double? = null - - @SerializedName("sessionIdentifier") - var sessionIdentifier: String? = null - - @SerializedName("userAgent") - var userAgent: String? = null - - @SerializedName("bbox") - var boundingBox: List? = null - - @SerializedName("autocomplete") - var autocomplete: Boolean? = null - - @SerializedName("routing") - var routing: Boolean? = null - - @SerializedName("country") - var country: List? = null - - @SerializedName("types") - var types: List? = null - - @SerializedName("endpoint") - var endpoint: String? = null - - @SerializedName("orientation") - var orientation: String? = null - - @SerializedName("proximity") - var proximity: List? = null - - @SerializedName("fuzzyMatch") - var fuzzyMatch: Boolean? = null - - @SerializedName("limit") - var limit: Int? = null - - @SerializedName("language") - var language: List? = null - - @SerializedName("keyboardLocale") - var keyboardLocale: String? = null - - @SerializedName("mapZoom") - var mapZoom: Float? = null - - @SerializedName("mapCenterLat") - var mapCenterLatitude: Double? = null - - @SerializedName("mapCenterLng") - var mapCenterLongitude: Double? = null - - @SerializedName("schema") - var schema: String? = null - - constructor() : super() - constructor(parcel: Parcel) : this() { - event = parcel.readString() - created = parcel.readString() - latitude = parcel.readValue(Double::class.java.classLoader) as? Double - longitude = parcel.readValue(Double::class.java.classLoader) as? Double - sessionIdentifier = parcel.readString() - userAgent = parcel.readString() - autocomplete = parcel.readValue(Boolean::class.java.classLoader) as? Boolean - routing = parcel.readValue(Boolean::class.java.classLoader) as? Boolean - country = parcel.createStringArrayList() - types = parcel.createStringArrayList() - endpoint = parcel.readString() - orientation = parcel.readString() - fuzzyMatch = parcel.readValue(Boolean::class.java.classLoader) as? Boolean - limit = parcel.readValue(Int::class.java.classLoader) as? Int - language = parcel.createStringArrayList() - keyboardLocale = parcel.readString() - mapZoom = parcel.readValue(Float::class.java.classLoader) as? Float - mapCenterLatitude = parcel.readValue(Double::class.java.classLoader) as? Double - mapCenterLongitude = parcel.readValue(Double::class.java.classLoader) as? Double - @Suppress("UNCHECKED_CAST") - proximity = parcel.readSerializable() as? List - @Suppress("UNCHECKED_CAST") - boundingBox = parcel.readSerializable() as? List - schema = parcel.readString() - } - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeString(event) - parcel.writeString(created) - parcel.writeValue(latitude) - parcel.writeValue(longitude) - parcel.writeString(sessionIdentifier) - parcel.writeString(userAgent) - parcel.writeValue(autocomplete) - parcel.writeValue(routing) - parcel.writeStringList(country) - parcel.writeStringList(types) - parcel.writeString(endpoint) - parcel.writeString(orientation) - parcel.writeValue(fuzzyMatch) - parcel.writeValue(limit) - parcel.writeStringList(language) - parcel.writeString(keyboardLocale) - parcel.writeValue(mapZoom) - parcel.writeValue(mapCenterLatitude) - parcel.writeValue(mapCenterLongitude) - parcel.writeSerializable(proximity?.let { ArrayList(it) }) - parcel.writeSerializable(boundingBox?.let { ArrayList(it) }) - parcel.writeString(schema) - } - - override fun describeContents(): Int { - return 0 - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as BaseSearchEvent - - if (event != other.event) return false - if (created != other.created) return false - if (latitude != other.latitude) return false - if (longitude != other.longitude) return false - if (sessionIdentifier != other.sessionIdentifier) return false - if (userAgent != other.userAgent) return false - if (boundingBox != other.boundingBox) return false - if (autocomplete != other.autocomplete) return false - if (routing != other.routing) return false - if (country != other.country) return false - if (types != other.types) return false - if (endpoint != other.endpoint) return false - if (orientation != other.orientation) return false - if (proximity != other.proximity) return false - if (fuzzyMatch != other.fuzzyMatch) return false - if (limit != other.limit) return false - if (language != other.language) return false - if (keyboardLocale != other.keyboardLocale) return false - if (mapZoom != other.mapZoom) return false - if (mapCenterLatitude != other.mapCenterLatitude) return false - if (mapCenterLongitude != other.mapCenterLongitude) return false - if (schema != other.schema) return false - - return true - } - - override fun hashCode(): Int { - var result = event?.hashCode() ?: 0 - result = 31 * result + (created?.hashCode() ?: 0) - result = 31 * result + (latitude?.hashCode() ?: 0) - result = 31 * result + (longitude?.hashCode() ?: 0) - result = 31 * result + (sessionIdentifier?.hashCode() ?: 0) - result = 31 * result + (userAgent?.hashCode() ?: 0) - result = 31 * result + (boundingBox?.hashCode() ?: 0) - result = 31 * result + (autocomplete?.hashCode() ?: 0) - result = 31 * result + (routing?.hashCode() ?: 0) - result = 31 * result + (country?.hashCode() ?: 0) - result = 31 * result + (types?.hashCode() ?: 0) - result = 31 * result + (endpoint?.hashCode() ?: 0) - result = 31 * result + (orientation?.hashCode() ?: 0) - result = 31 * result + (proximity?.hashCode() ?: 0) - result = 31 * result + (fuzzyMatch?.hashCode() ?: 0) - result = 31 * result + (limit ?: 0) - result = 31 * result + (language?.hashCode() ?: 0) - result = 31 * result + (keyboardLocale?.hashCode() ?: 0) - result = 31 * result + (mapZoom?.hashCode() ?: 0) - result = 31 * result + (mapCenterLatitude?.hashCode() ?: 0) - result = 31 * result + (mapCenterLongitude?.hashCode() ?: 0) - result = 31 * result + (schema?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "event=$event, created=$created, latitude=$latitude, longitude=$longitude, sessionIdentifier=$sessionIdentifier, " + - "userAgent=$userAgent, boundingBox=$boundingBox, autocomplete=$autocomplete, routing=$routing, " + - "country=$country, types=$types, endpoint=$endpoint, orientation=$orientation, proximity=$proximity, " + - "fuzzyMatch=$fuzzyMatch, limit=$limit, language=$language, keyboardLocale=$keyboardLocale, " + - "mapZoom=$mapZoom, mapCenterLatitude=$mapCenterLatitude, mapCenterLongitude=$mapCenterLongitude, schema=$schema" - } - - internal companion object { - const val EVENT_ATTRIBUTE_NAME = "event" - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/BaseSearchSessionEvent.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/BaseSearchSessionEvent.kt deleted file mode 100644 index 31ba37229..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/BaseSearchSessionEvent.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.mapbox.search.analytics.events - -import android.os.Parcel -import com.google.gson.annotations.SerializedName - -internal abstract class BaseSearchSessionEvent : BaseSearchEvent { - - override val isValid: Boolean - get() = super.isValid && queryString != null - - @SerializedName("cached") - var cached: Boolean? = null - - @SerializedName("queryString") - var queryString: String? = null - - constructor() : super() - constructor(parcel: Parcel) : super(parcel) { - cached = parcel.readValue(Boolean::class.java.classLoader) as? Boolean - queryString = parcel.readString() - } - - override fun writeToParcel(parcel: Parcel, flags: Int) { - super.writeToParcel(parcel, flags) - parcel.writeValue(cached) - parcel.writeString(queryString) - } - - override fun describeContents(): Int { - return 0 - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as BaseSearchSessionEvent - - if (cached != other.cached) return false - if (queryString != other.queryString) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + (cached?.hashCode() ?: 0) - result = 31 * result + (queryString?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "${super.toString()}, cached=$cached, queryString=$queryString" - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/QueryChangeEvent.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/QueryChangeEvent.kt deleted file mode 100644 index 3c05add20..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/QueryChangeEvent.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.mapbox.search.analytics.events - -import android.os.Parcel -import android.os.Parcelable -import com.google.gson.annotations.SerializedName - -// Event schema available by link below -// https://github.com/mapbox/event-schema/blob/master/lib/base-schemas/search.query_change.js -internal class QueryChangeEvent : BaseSearchEvent { - - override val isValid: Boolean - get() = super.isValid && oldQuery != null && newQuery != null && event == EVENT_NAME - - @SerializedName("oldQuery") - var oldQuery: String? = null - - @SerializedName("newQuery") - var newQuery: String? = null - - @SerializedName("changeType") - var changeType: String? = null - - constructor() : super() - constructor(parcel: Parcel) : super(parcel) { - oldQuery = parcel.readString() - newQuery = parcel.readString() - changeType = parcel.readString() - } - - override fun writeToParcel(parcel: Parcel, flags: Int) { - super.writeToParcel(parcel, flags) - parcel.writeString(oldQuery) - parcel.writeString(newQuery) - parcel.writeString(changeType) - } - - override fun describeContents(): Int { - return 0 - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as QueryChangeEvent - - if (oldQuery != other.oldQuery) return false - if (newQuery != other.newQuery) return false - if (changeType != other.changeType) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + (oldQuery?.hashCode() ?: 0) - result = 31 * result + (newQuery?.hashCode() ?: 0) - result = 31 * result + (changeType?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "QueryChangeEvent(${super.toString()}, oldQuery=$oldQuery, newQuery=$newQuery, changeType=$changeType)" - } - - companion object { - - const val EVENT_NAME = "search.query_change" - - @JvmField - val CREATOR = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): QueryChangeEvent { - return QueryChangeEvent(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/SearchFeedbackEvent.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/SearchFeedbackEvent.kt index 1dcddcaa1..b9278266a 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/SearchFeedbackEvent.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/SearchFeedbackEvent.kt @@ -1,161 +1,127 @@ package com.mapbox.search.analytics.events -import android.os.Parcel -import android.os.Parcelable import com.google.gson.annotations.SerializedName -// Event schema available by link below // https://github.com/mapbox/event-schema/blob/master/lib/base-schemas/search.feedback.js -internal class SearchFeedbackEvent : BaseSearchSessionEvent { +internal data class SearchFeedbackEvent( + @SerializedName("event") + var event: String? = null, - override val isValid: Boolean - get() = super.isValid && feedbackReason?.isNotEmpty() == true && resultIndex != null && - selectedItemName != null && event == EVENT_NAME && appMetadata?.isValid != false + @SerializedName("created") + var created: String? = null, + + @SerializedName("lat") + var latitude: Double? = null, + + @SerializedName("lng") + var longitude: Double? = null, + + @SerializedName("sessionIdentifier") + var sessionIdentifier: String? = null, + + @SerializedName("userAgent") + var userAgent: String? = null, + + @SerializedName("bbox") + var boundingBox: List? = null, + + @SerializedName("autocomplete") + var autocomplete: Boolean? = null, + + @SerializedName("routing") + var routing: Boolean? = null, + + @SerializedName("country") + var country: List? = null, + + @SerializedName("types") + var types: List? = null, + + @SerializedName("endpoint") + var endpoint: String? = null, + + @SerializedName("orientation") + var orientation: String? = null, + + @SerializedName("proximity") + var proximity: List? = null, + + @SerializedName("fuzzyMatch") + var fuzzyMatch: Boolean? = null, + + @SerializedName("limit") + var limit: Int? = null, + + @SerializedName("language") + var language: List? = null, + + @SerializedName("keyboardLocale") + var keyboardLocale: String? = null, + + @SerializedName("mapZoom") + var mapZoom: Float? = null, + + @SerializedName("mapCenterLat") + var mapCenterLatitude: Double? = null, + + @SerializedName("mapCenterLng") + var mapCenterLongitude: Double? = null, + + @SerializedName("schema") + var schema: String? = null, @SerializedName("feedbackReason") - var feedbackReason: String? = null + var feedbackReason: String? = null, @SerializedName("feedbackText") - var feedbackText: String? = null + var feedbackText: String? = null, @SerializedName("resultIndex") - var resultIndex: Int? = null + var resultIndex: Int? = null, @SerializedName("selectedItemName") - var selectedItemName: String? = null + var selectedItemName: String? = null, @SerializedName("resultId") - var resultId: String? = null + var resultId: String? = null, @SerializedName("responseUuid") - var responseUuid: String? = null + var responseUuid: String? = null, @SerializedName("feedbackId") - var feedbackId: String? = null + var feedbackId: String? = null, @SerializedName("isTest") - var isTest: Boolean? = null + var isTest: Boolean? = null, @SerializedName("screenshot") - var screenshot: String? = null + var screenshot: String? = null, @SerializedName("resultCoordinates") - var resultCoordinates: List? = null + var resultCoordinates: List? = null, @SerializedName("requestParamsJSON") - var requestParamsJson: String? = null + var requestParamsJson: String? = null, @SerializedName("appMetadata") - var appMetadata: AppMetadata? = null + var appMetadata: AppMetadata? = null, @SerializedName("searchResultsJSON") - var searchResultsJson: String? = null - - constructor() : super() - constructor(parcel: Parcel) : super(parcel) { - feedbackReason = parcel.readString() - feedbackText = parcel.readString() - resultIndex = parcel.readValue(Int::class.java.classLoader) as? Int - selectedItemName = parcel.readString() - resultId = parcel.readString() - responseUuid = parcel.readString() - feedbackId = parcel.readString() - isTest = parcel.readValue(Boolean::class.java.classLoader) as? Boolean - screenshot = parcel.readString() - @Suppress("UNCHECKED_CAST") - resultCoordinates = parcel.readSerializable() as? List - requestParamsJson = parcel.readString() - appMetadata = parcel.readParcelable(AppMetadata::class.java.classLoader) - searchResultsJson = parcel.readString() - } + var searchResultsJson: String? = null, - override fun writeToParcel(parcel: Parcel, flags: Int) { - super.writeToParcel(parcel, flags) - parcel.writeString(feedbackReason) - parcel.writeString(feedbackText) - parcel.writeValue(resultIndex) - parcel.writeString(selectedItemName) - parcel.writeString(resultId) - parcel.writeString(responseUuid) - parcel.writeString(feedbackId) - parcel.writeValue(isTest) - parcel.writeString(screenshot) - parcel.writeSerializable(resultCoordinates?.let { ArrayList(it) }) - parcel.writeString(requestParamsJson) - parcel.writeParcelable(appMetadata, flags) - parcel.writeString(searchResultsJson) - } + @SerializedName("cached") + var cached: Boolean? = null, - override fun describeContents(): Int { - return 0 - } + @SerializedName("queryString") + var queryString: String? = null, +) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as SearchFeedbackEvent - - if (feedbackReason != other.feedbackReason) return false - if (feedbackText != other.feedbackText) return false - if (resultIndex != other.resultIndex) return false - if (selectedItemName != other.selectedItemName) return false - if (resultId != other.resultId) return false - if (responseUuid != other.responseUuid) return false - if (feedbackId != other.feedbackId) return false - if (isTest != other.isTest) return false - if (screenshot != other.screenshot) return false - if (resultCoordinates != other.resultCoordinates) return false - if (requestParamsJson != other.requestParamsJson) return false - if (appMetadata != other.appMetadata) return false - if (searchResultsJson != other.searchResultsJson) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + (feedbackReason?.hashCode() ?: 0) - result = 31 * result + (feedbackText?.hashCode() ?: 0) - result = 31 * result + (resultIndex ?: 0) - result = 31 * result + (selectedItemName?.hashCode() ?: 0) - result = 31 * result + (resultId?.hashCode() ?: 0) - result = 31 * result + (responseUuid?.hashCode() ?: 0) - result = 31 * result + (feedbackId?.hashCode() ?: 0) - result = 31 * result + (isTest?.hashCode() ?: 0) - result = 31 * result + (screenshot?.hashCode() ?: 0) - result = 31 * result + (resultCoordinates?.hashCode() ?: 0) - result = 31 * result + (requestParamsJson?.hashCode() ?: 0) - result = 31 * result + (appMetadata?.hashCode() ?: 0) - result = 31 * result + (searchResultsJson?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "SearchFeedbackEvent(" + - "${super.toString()}, feedbackReason=$feedbackReason, feedbackText=$feedbackText, " + - "resultIndex=$resultIndex, selectedItemName=$selectedItemName, resultId=$resultId, " + - "responseUuid=$responseUuid, feedbackId=$feedbackId, isTest=$isTest, " + - "screenshot=${screenshot?.run { "$length byte(s)" }}, resultCoordinates=$resultCoordinates, " + - "requestParamsJson=$requestParamsJson, appMetadata=$appMetadata, " + - "searchResultsJson=$searchResultsJson" + - ")" - } + val isValid: Boolean + get() = created != null && sessionIdentifier != null && feedbackReason?.isNotEmpty() == true && + resultIndex != null && selectedItemName != null && event == EVENT_NAME && + appMetadata?.isValid != false && queryString != null companion object { - const val EVENT_NAME = "search.feedback" - - @JvmField - val CREATOR = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): SearchFeedbackEvent { - return SearchFeedbackEvent(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } } } diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/SearchSelectEvent.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/SearchSelectEvent.kt deleted file mode 100644 index 3b057c9f3..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/SearchSelectEvent.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.mapbox.search.analytics.events - -import android.os.Parcel -import android.os.Parcelable -import com.google.gson.annotations.SerializedName - -// Event schema available by link below -// https://github.com/mapbox/event-schema/blob/master/lib/base-schemas/search.select.js -internal class SearchSelectEvent : BaseSearchSessionEvent { - - override val isValid: Boolean - get() = super.isValid && resultIndex != null && event == EVENT_NAME - - @SerializedName("resultIndex") - var resultIndex: Int? = null - - @SerializedName("resultPlaceName") - var resultPlaceName: String? = null - - @SerializedName("resultId") - var resultId: String? = null - - constructor() : super() - constructor(parcel: Parcel) : super(parcel) { - resultPlaceName = parcel.readString() - resultIndex = parcel.readValue(Int::class.java.classLoader) as? Int - resultId = parcel.readString() - } - - override fun writeToParcel(parcel: Parcel, flags: Int) { - super.writeToParcel(parcel, flags) - parcel.writeString(resultPlaceName) - parcel.writeValue(resultIndex) - parcel.writeString(resultId) - } - - override fun describeContents(): Int { - return 0 - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as SearchSelectEvent - - if (resultIndex != other.resultIndex) return false - if (resultPlaceName != other.resultPlaceName) return false - if (resultId != other.resultId) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + (resultIndex ?: 0) - result = 31 * result + (resultPlaceName?.hashCode() ?: 0) - result = 31 * result + (resultId?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "SearchSelectEvent(${super.toString()}, resultIndex=$resultIndex, resultPlaceName=$resultPlaceName, resultId=$resultId)" - } - - companion object { - - const val EVENT_NAME = "search.select" - - @JvmField - val CREATOR = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): SearchSelectEvent { - return SearchSelectEvent(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/SearchStartEvent.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/SearchStartEvent.kt deleted file mode 100644 index 946b9c929..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/analytics/events/SearchStartEvent.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.mapbox.search.analytics.events - -import android.os.Parcel -import android.os.Parcelable - -// Event schema available by link below -// https://github.com/mapbox/event-schema/blob/master/lib/base-schemas/search.start.js -internal class SearchStartEvent : BaseSearchSessionEvent { - - override val isValid: Boolean - get() = super.isValid && event == EVENT_NAME - - constructor() : super() - constructor(parcel: Parcel) : super(parcel) - - override fun toString(): String { - return "SearchStartEvent(${super.toString()})" - } - - internal companion object { - - const val EVENT_NAME = "search.start" - - @JvmField - val CREATOR = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): SearchStartEvent { - return SearchStartEvent(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/CoreAliases.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/CoreAliases.kt index 214fc5c12..8be59dda0 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/CoreAliases.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/CoreAliases.kt @@ -1,10 +1,7 @@ package com.mapbox.search.core -internal typealias CoreHttpCallback = com.mapbox.search.internal.bindgen.HttpCallback internal typealias CoreLocationProvider = com.mapbox.search.internal.bindgen.LocationProvider -internal typealias CoreLogLevel = com.mapbox.search.internal.bindgen.LogLevel internal typealias CoreBoundingBox = com.mapbox.search.internal.bindgen.LonLatBBox -internal typealias CorePlatformClient = com.mapbox.search.internal.bindgen.PlatformClient internal typealias CoreQueryType = com.mapbox.search.internal.bindgen.QueryType internal typealias CoreRequestOptions = com.mapbox.search.internal.bindgen.RequestOptions internal typealias CoreResultType = com.mapbox.search.internal.bindgen.ResultType @@ -16,7 +13,6 @@ internal typealias CoreSearchOptions = com.mapbox.search.internal.bindgen.Search internal typealias CoreSearchResponse = com.mapbox.search.internal.bindgen.SearchResponse internal typealias CoreSearchResult = com.mapbox.search.internal.bindgen.SearchResult internal typealias CoreSuggestAction = com.mapbox.search.internal.bindgen.SuggestAction -internal typealias CoreTaskFunction = com.mapbox.search.internal.bindgen.TaskFunction internal typealias CoreSearchAddress = com.mapbox.search.internal.bindgen.SearchAddress internal typealias CoreReverseMode = com.mapbox.search.internal.bindgen.ReverseMode internal typealias CoreReverseGeoOptions = com.mapbox.search.internal.bindgen.ReverseGeoOptions diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/PlatformClientImpl.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/PlatformClientImpl.kt deleted file mode 100644 index f226b24cf..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/PlatformClientImpl.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.mapbox.search.core - -import com.mapbox.search.analytics.InternalAnalyticsService -import com.mapbox.search.common.logger.DEFAULT_SEARCH_SDK_LOG_TAG -import com.mapbox.search.common.logger.logd -import com.mapbox.search.common.logger.loge -import com.mapbox.search.common.logger.logi -import com.mapbox.search.common.logger.logw -import com.mapbox.search.core.http.HttpClient -import com.mapbox.search.utils.UUIDProvider -import com.mapbox.search.utils.concurrent.MainThreadWorker -import com.mapbox.search.utils.concurrent.SearchSdkMainThreadWorker - -internal class PlatformClientImpl( - private val httpClient: HttpClient, - private val analyticsService: InternalAnalyticsService, - private val uuidProvider: UUIDProvider, - private val mainThreadWorker: MainThreadWorker = SearchSdkMainThreadWorker, - private val callbackDecorator: (CoreHttpCallback) -> CoreHttpCallback = { it }, -) : CorePlatformClient { - - override fun httpRequest(url: String, body: ByteArray?, requestID: Int, sessionID: String, callback: CoreHttpCallback) { - val decoratedCallback = callbackDecorator(callback) - - if (body == null) { - httpClient.httpGet(url, requestID, sessionID, decoratedCallback) - } else { - httpClient.httpPost(url, body, requestID, sessionID, decoratedCallback) - } - } - - override fun log(level: CoreLogLevel, message: String) { - when (level) { - CoreLogLevel.DEBUG -> logd(tag = DEFAULT_SEARCH_SDK_LOG_TAG, message = message) - CoreLogLevel.INFO -> logi(tag = DEFAULT_SEARCH_SDK_LOG_TAG, message = message) - CoreLogLevel.WARNING -> logw(tag = DEFAULT_SEARCH_SDK_LOG_TAG, message = message) - CoreLogLevel.ERROR -> loge(tag = DEFAULT_SEARCH_SDK_LOG_TAG, message = message) - } - } - - override fun generateUUID(): String { - return uuidProvider.generateUUID() - } - - override fun postEvent(json: String) { - analyticsService.postJsonEvent(json) - } - - override fun scheduleTask(function: CoreTaskFunction, delayMS: Int) { - mainThreadWorker.postDelayed(delayMS.toLong()) { - function.run() - } - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/AsyncHttpCallbackDecorator.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/AsyncHttpCallbackDecorator.kt deleted file mode 100644 index e89559719..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/AsyncHttpCallbackDecorator.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.mapbox.search.core.http - -import com.mapbox.search.core.CoreHttpCallback -import com.mapbox.search.utils.SyncLocker -import java.util.concurrent.Executor - -internal data class AsyncHttpCallbackDecorator( - private val executor: Executor, - private val syncLocker: SyncLocker, - private val originalCallback: CoreHttpCallback, -) : CoreHttpCallback { - - override fun run(httpBody: String, responseCode: Int) { - executor.execute { - syncLocker.executeInSync { - originalCallback.run(httpBody, responseCode) - } - } - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/HttpClient.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/HttpClient.kt deleted file mode 100644 index 0ab4cb097..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/HttpClient.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.mapbox.search.core.http - -import com.mapbox.search.core.CoreHttpCallback - -internal interface HttpClient { - fun httpGet(url: String, requestID: Int, sessionID: String, callback: CoreHttpCallback) - - fun httpPost(url: String, body: ByteArray, requestID: Int, sessionID: String, callback: CoreHttpCallback) -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/HttpClientImpl.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/HttpClientImpl.kt deleted file mode 100755 index 7599e40e6..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/HttpClientImpl.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.mapbox.search.core.http - -import com.mapbox.search.core.CoreHttpCallback -import com.mapbox.search.utils.UUIDProvider -import okhttp3.Call -import okhttp3.Callback -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response -import java.io.IOException - -internal class HttpClientImpl( - private val client: OkHttpClient, - private val errorsCache: HttpErrorsCache, - private val uuidProvider: UUIDProvider, - private val userAgentProvider: UserAgentProvider -) : HttpClient { - - override fun httpGet(url: String, requestID: Int, sessionID: String, callback: CoreHttpCallback) { - makeRequest(url, null, requestID, sessionID, callback) - } - - override fun httpPost(url: String, body: ByteArray, requestID: Int, sessionID: String, callback: CoreHttpCallback) { - makeRequest(url, body, requestID, sessionID, callback) - } - - private fun makeRequest(url: String, body: ByteArray?, requestID: Int, sessionID: String, callback: CoreHttpCallback) { - try { - val request = with(Request.Builder()) { - url(url) - addHeader(HEADER_SESSION_ID, sessionID) - addHeader(HEADER_REQUEST_ID, uuidProvider.generateUUID()) - addHeader(HEADER_USER_AGENT, userAgentProvider.userAgent()) - if (body != null) { - post(body.toRequestBody(MEDIA_TYPE_JSON)) - } - build() - } - - client.newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - errorsCache.put(requestID, e) - callback.run(e.message ?: HTTP_ERROR_MESSAGE, Int.MIN_VALUE) - } - - override fun onResponse(call: Call, response: Response) { - val bodyText = response.body?.string() ?: "" - callback.run(bodyText, response.code) - } - }) - } catch (e: Exception) { - errorsCache.put(requestID, e) - callback.run(e.message ?: HTTP_ERROR_MESSAGE, Int.MIN_VALUE) - } - } - - private companion object { - - const val HTTP_ERROR_MESSAGE = "http error" - const val HEADER_SESSION_ID = "X-MBX-SEARCH-SID" - const val HEADER_REQUEST_ID = "X-Request-ID" - const val HEADER_USER_AGENT = "User-Agent" - - val MEDIA_TYPE_JSON = "application/json".toMediaType() - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/HttpErrorsCache.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/HttpErrorsCache.kt deleted file mode 100644 index df96830a1..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/HttpErrorsCache.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.mapbox.search.core.http - -import androidx.annotation.AnyThread -import java.util.concurrent.ConcurrentHashMap - -@AnyThread -internal interface HttpErrorsCache { - fun put(requestId: Int, e: Exception) - fun getAndRemove(requestId: Int): Exception? -} - -internal class HttpErrorsCacheImpl : HttpErrorsCache { - - private val map = ConcurrentHashMap() - - override fun put(requestId: Int, e: Exception) { - map[requestId] = e - } - - override fun getAndRemove(requestId: Int): Exception? { - return map.remove(requestId) - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/OkHttpHelper.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/OkHttpHelper.kt deleted file mode 100644 index f8a31a179..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/OkHttpHelper.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.mapbox.search.core.http - -import androidx.annotation.VisibleForTesting -import com.mapbox.search.BuildConfig -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import java.util.concurrent.TimeUnit - -internal class OkHttpHelper(debugLogsEnabled: Boolean) { - - private val httpLogsLevel = if (BuildConfig.DEBUG || debugLogsEnabled) { - HttpLoggingInterceptor.Level.BODY - } else { - HttpLoggingInterceptor.Level.NONE - } - - fun getClient(): OkHttpClient { - return getOkHttpBuilder().build() - } - - @VisibleForTesting - fun getMockedClient(mockInterceptor: Interceptor, timeout: Long = 10_000L): OkHttpClient { - return getOkHttpBuilder() - .readTimeout(timeout, TimeUnit.MILLISECONDS) - .writeTimeout(timeout, TimeUnit.MILLISECONDS) - .connectTimeout(timeout, TimeUnit.MILLISECONDS) - .addInterceptor(mockInterceptor) - .build() - } - - private fun getOkHttpBuilder(): OkHttpClient.Builder { - return OkHttpClient.Builder() - .addInterceptor(HttpLoggingInterceptor().setLevel(httpLogsLevel)) - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/UserAgentProviderImpl.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/UserAgentProviderImpl.kt deleted file mode 100644 index 8951f359b..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/core/http/UserAgentProviderImpl.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.mapbox.search.core.http - -import android.content.Context -import android.content.res.Configuration -import android.os.Build -import com.mapbox.search.common.BuildConfig -import com.mapbox.search.utils.extension.packageInfoOrNull -import com.mapbox.search.utils.extension.versionCodeCompat -import java.util.Locale - -internal fun interface UserAgentProvider { - fun userAgent(): String -} - -internal class UserAgentProviderImpl(private val context: Context) : UserAgentProvider { - - private val userAgent: String by lazy { - val hostAppName = when { - // If application label is initialized from string resource, - // then try to get string for english locale - context.applicationInfo.labelRes != 0 -> { - val config = Configuration(context.resources.configuration).apply { - setLocale(Locale.ENGLISH) - } - context.createConfigurationContext(config).getText(context.applicationInfo.labelRes) - } - // Otherwise, request default application label - else -> context.packageManager.getApplicationLabel(context.applicationInfo) - } - - val hostAppVersionName = context.packageInfoOrNull?.versionName ?: "Unknown" - val hostAppPackage = context.packageName - val hostAppVersionCode = context.packageInfoOrNull?.versionCodeCompat ?: "Unknown" - - val androidVersion = Build.VERSION.RELEASE - - val searchSdkVersionName = BuildConfig.VERSION_NAME - - val userAgent = "$hostAppName/$hostAppVersionName " + - "($hostAppPackage; build:$hostAppVersionCode; Android $androidVersion) " + - "MapboxSearchSDK-Android/$searchSdkVersionName" - - userAgent.encodeToValidHeaderValue() - } - - override fun userAgent(): String = userAgent - - /** - * OkHttp has internal check for HTTP Header values: - * https://github.com/square/okhttp/blob/okhttp_4.9.x/okhttp/src/main/kotlin/okhttp3/Headers.kt#L450-L453 - */ - private fun String.encodeToValidHeaderValue(): String { - return asSequence() - .map { c -> - if (c == '\t' || c in '\u0020'..'\u007e') { - c - } else { - '_' - } - } - .joinToString(separator = "") - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/BaseSearchEngine.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/BaseSearchEngine.kt index 4ea4f1aa6..3a89185c4 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/BaseSearchEngine.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/BaseSearchEngine.kt @@ -4,7 +4,6 @@ import com.mapbox.search.SearchRequestTask import com.mapbox.search.SearchRequestTaskImpl import com.mapbox.search.plusAssign import java.lang.ref.WeakReference -import java.util.concurrent.ExecutorService internal abstract class BaseSearchEngine( /** @@ -17,19 +16,16 @@ internal abstract class BaseSearchEngine( protected fun makeRequest( callback: T, - engineExecutor: ExecutorService, searchCall: (SearchRequestTaskImpl) -> Unit ): SearchRequestTask { val task = SearchRequestTaskImpl().apply { callbackDelegate = callback } - task += engineExecutor.submit { - searchCall(task) - if (autoCancelPreviousRequest) { - previousRequestTask?.get()?.cancel() - previousRequestTask = WeakReference(task) - } + searchCall(task) + if (autoCancelPreviousRequest) { + previousRequestTask?.get()?.cancel() + previousRequestTask = WeakReference(task) } return task diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/OneStepRequestCallbackWrapper.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/OneStepRequestCallbackWrapper.kt index b0fc2b1a0..e5b86611f 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/OneStepRequestCallbackWrapper.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/OneStepRequestCallbackWrapper.kt @@ -4,13 +4,11 @@ import androidx.collection.SparseArrayCompat import com.mapbox.search.AsyncOperationTask import com.mapbox.search.ResponseInfo import com.mapbox.search.SearchCallback -import com.mapbox.search.SearchRequestException import com.mapbox.search.SearchRequestTaskImpl import com.mapbox.search.common.reportRelease import com.mapbox.search.common.throwDebug import com.mapbox.search.core.CoreSearchCallback import com.mapbox.search.core.CoreSearchResponse -import com.mapbox.search.core.http.HttpErrorsCache import com.mapbox.search.internal.bindgen.SearchResponseError import com.mapbox.search.mapToPlatform import com.mapbox.search.markExecutedAndRunOnCallback @@ -19,10 +17,10 @@ import com.mapbox.search.result.SearchRequestContext import com.mapbox.search.result.SearchResult import com.mapbox.search.result.SearchResultFactory import com.mapbox.search.result.mapToPlatform +import com.mapbox.search.utils.extension.toPlatformHttpException import java.util.concurrent.Executor internal class OneStepRequestCallbackWrapper( - private val httpErrorsCache: HttpErrorsCache, private val searchResultFactory: SearchResultFactory, private val callbackExecutor: Executor, private val workerExecutor: Executor, @@ -52,10 +50,7 @@ internal class OneStepRequestCallbackWrapper( when (coreError.typeInfo) { SearchResponseError.Type.HTTP_ERROR -> { - val error = httpErrorsCache.getAndRemove(response.requestID) ?: SearchRequestException( - message = coreError.httpError.message, - code = coreError.httpError.httpCode - ) + val error = coreError.toPlatformHttpException() reportRelease(error) searchRequestTask.markExecutedAndRunOnCallback(callbackExecutor) { diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/TwoStepsBatchRequestCallbackWrapper.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/TwoStepsBatchRequestCallbackWrapper.kt index 0ad05e196..92cbc30f5 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/TwoStepsBatchRequestCallbackWrapper.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/TwoStepsBatchRequestCallbackWrapper.kt @@ -2,13 +2,11 @@ package com.mapbox.search.engine import com.mapbox.search.ResponseInfo import com.mapbox.search.SearchMultipleSelectionCallback -import com.mapbox.search.SearchRequestException import com.mapbox.search.SearchRequestTaskImpl import com.mapbox.search.common.assertDebug import com.mapbox.search.common.reportRelease import com.mapbox.search.core.CoreSearchCallback import com.mapbox.search.core.CoreSearchResponse -import com.mapbox.search.core.http.HttpErrorsCache import com.mapbox.search.internal.bindgen.SearchResponseError import com.mapbox.search.mapToPlatform import com.mapbox.search.markExecutedAndRunOnCallback @@ -17,11 +15,11 @@ import com.mapbox.search.result.SearchResult import com.mapbox.search.result.SearchResultFactory import com.mapbox.search.result.SearchSuggestion import com.mapbox.search.result.mapToPlatform +import com.mapbox.search.utils.extension.toPlatformHttpException import java.util.concurrent.Executor internal class TwoStepsBatchRequestCallbackWrapper( private val suggestions: List, - private val httpErrorsCache: HttpErrorsCache, private val searchResultFactory: SearchResultFactory, private val callbackExecutor: Executor, private val workerExecutor: Executor, @@ -49,10 +47,7 @@ internal class TwoStepsBatchRequestCallbackWrapper( when (coreError.typeInfo) { SearchResponseError.Type.HTTP_ERROR -> { - val error = httpErrorsCache.getAndRemove(response.requestID) ?: SearchRequestException( - message = coreError.httpError.message, - code = coreError.httpError.httpCode - ) + val error = coreError.toPlatformHttpException() reportRelease(error) searchRequestTask.markExecutedAndRunOnCallback(callbackExecutor) { diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/TwoStepsRequestCallbackWrapper.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/TwoStepsRequestCallbackWrapper.kt index 732025f13..43e1b04b6 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/TwoStepsRequestCallbackWrapper.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/engine/TwoStepsRequestCallbackWrapper.kt @@ -6,7 +6,6 @@ import com.mapbox.search.AsyncOperationTask import com.mapbox.search.CompletionCallback import com.mapbox.search.RequestOptions import com.mapbox.search.ResponseInfo -import com.mapbox.search.SearchRequestException import com.mapbox.search.SearchRequestTaskImpl import com.mapbox.search.SearchSelectionCallback import com.mapbox.search.SearchSuggestionsCallback @@ -17,7 +16,6 @@ import com.mapbox.search.common.throwDebug import com.mapbox.search.core.CoreSearchCallback import com.mapbox.search.core.CoreSearchEngineInterface import com.mapbox.search.core.CoreSearchResponse -import com.mapbox.search.core.http.HttpErrorsCache import com.mapbox.search.internal.bindgen.SearchResponseError import com.mapbox.search.mapToPlatform import com.mapbox.search.markExecutedAndRunOnCallback @@ -28,12 +26,12 @@ import com.mapbox.search.result.SearchResultFactory import com.mapbox.search.result.SearchSuggestion import com.mapbox.search.result.SearchSuggestionType import com.mapbox.search.result.mapToPlatform +import com.mapbox.search.utils.extension.toPlatformHttpException import java.util.concurrent.Executor internal class TwoStepsRequestCallbackWrapper( private val apiType: ApiType = ApiType.SBS, private val coreEngine: CoreSearchEngineInterface, - private val httpErrorsCache: HttpErrorsCache, private val historyService: HistoryService, private val searchResultFactory: SearchResultFactory, private val callbackExecutor: Executor, @@ -67,10 +65,7 @@ internal class TwoStepsRequestCallbackWrapper( when (coreError.typeInfo) { SearchResponseError.Type.HTTP_ERROR -> { - val error = httpErrorsCache.getAndRemove(response.requestID) ?: SearchRequestException( - message = coreError.httpError.message, - code = coreError.httpError.httpCode - ) + val error = coreError.toPlatformHttpException() reportRelease(error) searchRequestTask.markExecutedAndRunOnCallback(callbackExecutor) { diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/DataProviderEngineRegistrationService.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/DataProviderEngineRegistrationService.kt index 02a2a8f0b..215963c1d 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/DataProviderEngineRegistrationService.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/DataProviderEngineRegistrationService.kt @@ -7,7 +7,6 @@ import com.mapbox.search.CompletionCallback import com.mapbox.search.common.failDebug import com.mapbox.search.core.CoreSearchEngine import com.mapbox.search.core.CoreUserRecordsLayer -import com.mapbox.search.utils.SyncLocker import java.util.concurrent.Executor internal interface DataProviderEngineRegistrationService { @@ -20,8 +19,7 @@ internal interface DataProviderEngineRegistrationService { internal class DataProviderEngineRegistrationServiceImpl( private val registryExecutor: Executor, - private val syncLocker: SyncLocker, - private val coreLayerProvider: (String, Int) -> CoreUserRecordsLayer = ::createCoreLayer, + private val coreLayerProvider: (String, Int) -> CoreUserRecordsLayer = Companion::createCoreLayer, ) : DataProviderEngineRegistrationService { private val processingProviders = mutableMapOf() @@ -54,11 +52,9 @@ internal class DataProviderEngineRegistrationServiceImpl( } val engine = IndexableDataProviderEngineImpl( - coreLayerContext = IndexableDataProviderEngineImpl.CoreLayerContext( - coreLayerProvider( - dataProvider.dataProviderName, - dataProvider.priority - ), syncLocker + coreLayer = coreLayerProvider( + dataProvider.dataProviderName, + dataProvider.priority ) ) diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/HistoryDataProvider.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/HistoryDataProvider.kt index 114d04527..ad3b4c688 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/HistoryDataProvider.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/HistoryDataProvider.kt @@ -10,6 +10,7 @@ import com.mapbox.search.result.SearchResult import com.mapbox.search.result.SearchSuggestion import com.mapbox.search.utils.LocalTimeProvider import com.mapbox.search.utils.TimeProvider +import com.mapbox.search.utils.concurrent.SearchSdkMainThreadWorker import java.util.PriorityQueue import java.util.concurrent.Executor import java.util.concurrent.ExecutorService @@ -47,7 +48,7 @@ public interface HistoryDataProvider : LocalDataProvider { internal interface HistoryService : HistoryDataProvider { fun addToHistoryIfNeeded( searchResult: SearchResult, - executor: Executor, + executor: Executor = SearchSdkMainThreadWorker.mainExecutor, callback: CompletionCallback ): AsyncOperationTask } @@ -125,7 +126,7 @@ internal class HistoryDataProviderImpl( callback: CompletionCallback ): AsyncOperationTask { return if (!searchResult.isHistory) { - add( + upsert( HistoryRecord( id = searchResult.id, name = searchResult.name, diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/IndexableDataProvider.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/IndexableDataProvider.kt index e7b661846..35a5e77af 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/IndexableDataProvider.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/IndexableDataProvider.kt @@ -182,74 +182,51 @@ public interface IndexableDataProvider { ) /** - * Adds a [record] to this data provider. + * Insert or update a [record] in this data provider. * * @param record Record to be added. * @param executor Executor used for events dispatching. By default events are dispatched on the main thread. * @param callback Callback to handle result. * @return an object representing pending completion of the task. */ - public fun add(record: R, executor: Executor, callback: CompletionCallback): AsyncOperationTask + public fun upsert(record: R, executor: Executor, callback: CompletionCallback): AsyncOperationTask /** - * Adds a [record] to this data provider. + * Insert or update a [record] to this data provider. * * @param record Record to be added. * @param callback Callback to handle result, triggered on the main thread. * @return an object representing pending completion of the task. */ - public fun add(record: R, callback: CompletionCallback): AsyncOperationTask = add( + public fun upsert(record: R, callback: CompletionCallback): AsyncOperationTask = upsert( record = record, executor = SearchSdkMainThreadWorker.mainExecutor, callback = callback, ) /** - * Add multiple records to this data provider. + * Upsert (insert or update) multiple records to this data provider. * * @param records Records to be added. * @param executor Executor used for events dispatching. By default events are dispatched on the main thread. * @param callback Callback to handle result. * @return an object representing pending completion of the task. */ - public fun addAll(records: List, executor: Executor, callback: CompletionCallback): AsyncOperationTask + public fun upsertAll(records: List, executor: Executor, callback: CompletionCallback): AsyncOperationTask /** - * Add multiple records to this data provider. + * Upsert (insert or update) multiple records to this data provider. * * @param records Records to be added. * @param callback Callback to handle result, triggered on the main thread. * @return an object representing pending completion of the task. */ - public fun addAll(records: List, callback: CompletionCallback): AsyncOperationTask = addAll( + public fun upsertAll(records: List, callback: CompletionCallback): AsyncOperationTask = upsertAll( records = records, executor = SearchSdkMainThreadWorker.mainExecutor, callback = callback, ) - /** - * Updates [record] in this data provider. - * - * @param record Record to update. - * @param executor Executor used for events dispatching. By default events are dispatched on the main thread. - * @param callback Callback to handle result. - * @return an object representing pending completion of the task. - */ - public fun update(record: R, executor: Executor, callback: CompletionCallback): AsyncOperationTask - - /** - * Updates [record] in this data provider. - * - * @param record Record to update. - * @param callback Callback to handle result, triggered on the main thread. - * @return an object representing pending completion of the task. - */ - public fun update(record: R, callback: CompletionCallback): AsyncOperationTask = update( - record = record, - executor = SearchSdkMainThreadWorker.mainExecutor, - callback = callback, - ) - /** * Removes the record with specified [id]. * Passed to [callback] true if the record was removed and `false` if there was no record with the specified [id]. diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/IndexableDataProviderEngine.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/IndexableDataProviderEngine.kt index ebfe612e7..3e1a4a3af 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/IndexableDataProviderEngine.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/IndexableDataProviderEngine.kt @@ -1,8 +1,6 @@ package com.mapbox.search.record import com.mapbox.search.core.CoreUserRecordsLayer -import com.mapbox.search.record.IndexableDataProviderEngine.BatchUpdateOperation -import com.mapbox.search.utils.SyncLocker /** * Provides a mechanism for search index changes in core layers. @@ -16,25 +14,18 @@ import com.mapbox.search.utils.SyncLocker public interface IndexableDataProviderEngine { /** - * Adds [IndexableRecord] to search index. + * Insert or update existing [IndexableRecord] to search index. * * @param record record to be added. */ - public fun add(record: IndexableRecord) + public fun upsert(record: IndexableRecord) /** * Adds a bunch of [IndexableRecord] to search index. * * @param records records to be added. */ - public fun addAll(records: Iterable) - - /** - * Updates [IndexableRecord] in search index. - * - * @param record record to be updated. - */ - public fun update(record: IndexableRecord) + public fun upsertAll(records: Iterable) /** * Removes [IndexableRecord] with specified [id] from search index. @@ -54,86 +45,31 @@ public interface IndexableDataProviderEngine { * Clears the whole search index. */ public fun clear() - - /** - * Atomically executes all operations on engine layer. - * - * @param batchUpdateOperation lambda, that will be executed atomically. - */ - public fun executeBatchUpdate(batchUpdateOperation: BatchUpdateOperation) - - /** - * Interface definition for a function to be executed atomically during [IndexableDataProviderEngine] update. - */ - public fun interface BatchUpdateOperation { - - /** - * Applies all modifications to [engine], that should be executed atomically. - * - * @param engine engine layer, that will be updated by this operation. - */ - public fun execute(engine: IndexableDataProviderEngine) - } } internal class IndexableDataProviderEngineImpl internal constructor( - internal val coreLayerContext: CoreLayerContext + val coreLayer: CoreUserRecordsLayer ) : IndexableDataProviderEngine { - override fun add(record: IndexableRecord) { + override fun upsert(record: IndexableRecord) { val coreRecord = record.mapToCore() - coreLayerContext.executeInSync { - add(coreRecord) - } + coreLayer.upsert(coreRecord) } - override fun addAll(records: Iterable) { + override fun upsertAll(records: Iterable) { val coreRecords = records.map { it.mapToCore() } - coreLayerContext.executeInSync { - addMulti(coreRecords) - } - } - - override fun update(record: IndexableRecord) { - val coreRecord = record.mapToCore() - coreLayerContext.executeInSync { - update(coreRecord) - } + coreLayer.upsertMulti(coreRecords) } override fun remove(id: String) { - coreLayerContext.executeInSync { - remove(id) - } + coreLayer.remove(id) } override fun removeAll(ids: Iterable) { - coreLayerContext.executeInSync { - removeMulti(ids.toList()) - } + coreLayer.removeMulti(ids.toList()) } override fun clear() { - coreLayerContext.executeInSync { - clear() - } - } - - override fun executeBatchUpdate(batchUpdateOperation: BatchUpdateOperation) { - coreLayerContext.executeInSync { - batchUpdateOperation.execute(this@IndexableDataProviderEngineImpl) - } - } - - internal data class CoreLayerContext( - val coreLayer: CoreUserRecordsLayer, - val syncLocker: SyncLocker, - ) { - - fun executeInSync(action: CoreUserRecordsLayer.() -> Unit) { - syncLocker.executeInSync { - coreLayer.action() - } - } + coreLayer.clear() } } diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/LocalDataProvider.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/LocalDataProvider.kt index a0d8cfe99..6816e4f15 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/LocalDataProvider.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/record/LocalDataProvider.kt @@ -160,7 +160,7 @@ internal abstract class LocalDataProviderImpl( val recordsList = records.values.toList() dataProviderEngines.forEach { dataProviderEngine -> - dataProviderEngine.addAll(recordsList) + dataProviderEngine.upsertAll(recordsList) } } catch (e: Exception) { dataState = DataState.Error(e) @@ -234,7 +234,7 @@ internal abstract class LocalDataProviderImpl( task += backgroundTaskExecutorService.submit { when (val dataState = getLocalData()) { is DataState.Data -> { - dataProviderEngine.addAll(dataState.records.values) + dataProviderEngine.upsertAll(dataState.records.values) synchronized(dataProviderEngineLock) { dataProviderEngines.add(dataProviderEngine) @@ -348,12 +348,11 @@ internal abstract class LocalDataProviderImpl( return task } - // TODO should we replace old item when we add a new with the same id? - override fun add(record: R, executor: Executor, callback: CompletionCallback): AsyncOperationTask { - return addAll(listOf(record), executor, callback) + override fun upsert(record: R, executor: Executor, callback: CompletionCallback): AsyncOperationTask { + return upsertAll(listOf(record), executor, callback) } - override fun addAll( + override fun upsertAll( records: List, executor: Executor, callback: CompletionCallback @@ -372,52 +371,8 @@ internal abstract class LocalDataProviderImpl( this.dataState = DataState.Data(data) dataProviderEngines.forEach { dataProviderEngine -> - dataProviderEngine.executeBatchUpdate { engine -> - engine.addAll(records) - engine.removeAll(removeList) - } - } - - postOnExecutorIfNeeded(task, executor) { - callback.onComplete(Unit) - } - - notifyListeners(recordsList) - } catch (e: Exception) { - postOnExecutorIfNeeded(task, executor) { - callback.onError(e) - } - } - } - is DataState.Error -> { - postOnExecutorIfNeeded(task, executor) { - callback.onError(dataState.error) - } - } - } - } - return task - } - - override fun update(record: R, executor: Executor, callback: CompletionCallback): AsyncOperationTask { - val task = AsyncOperationTaskImpl() - task += backgroundTaskExecutorService.submit { - when (val dataState = getLocalData()) { - is DataState.Data -> { - try { - val data = dataState.recordsCopy() - val removeList = data.addAndTrimRecords(listOf(record)).map { it.id } - - val recordsList = data.values.toList() - persistData(recordsList) - - this.dataState = DataState.Data(data) - - dataProviderEngines.forEach { dataProviderEngine -> - dataProviderEngine.executeBatchUpdate { engine -> - engine.update(record) - engine.removeAll(removeList) - } + dataProviderEngine.upsertAll(records) + dataProviderEngine.removeAll(removeList) } postOnExecutorIfNeeded(task, executor) { diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/result/OriginalSearchResult.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/result/OriginalSearchResult.kt index cd9f9a297..3e4db5000 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/result/OriginalSearchResult.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/result/OriginalSearchResult.kt @@ -27,6 +27,7 @@ internal data class OriginalSearchResult( val externalIDs: Map?, val layerId: String?, val userRecordId: String?, + val userRecordPriority: Int, val action: SearchResultSuggestAction?, val serverIndex: Int?, val etaMinutes: Double? @@ -74,6 +75,7 @@ internal fun CoreSearchResult.mapToPlatform() = OriginalSearchResult( externalIDs = externalIDs, layerId = layer, userRecordId = userRecordID, + userRecordPriority = userRecordPriority, action = action?.mapToPlatform(), serverIndex = serverIndex, etaMinutes = eta, @@ -97,6 +99,7 @@ internal fun OriginalSearchResult.mapToCore() = CoreSearchResult( externalIDs?.let { (it as? HashMap) ?: HashMap(it) }, layerId, userRecordId, + userRecordPriority, action?.mapToCore(), serverIndex, ) diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/AppInfoProvider.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/AppInfoProvider.kt new file mode 100644 index 000000000..5f2fa1c93 --- /dev/null +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/AppInfoProvider.kt @@ -0,0 +1,42 @@ +package com.mapbox.search.utils + +import android.content.Context +import android.os.Build + +internal interface AppInfoProvider { + val searchSdkPackageName: String + val searchSdkVersionName: String + val deviceModel: String + val deviceName: String + val osVersion: String + val appPackageName: String + val appVersion: String +} + +internal class AppInfoProviderImpl( + private val context: Context, + override val searchSdkPackageName: String, + override val searchSdkVersionName: String, +) : AppInfoProvider { + + override val deviceModel: String + get() = Build.MODEL + + override val deviceName: String + get() = Build.DEVICE + + override val osVersion: String + get() = Build.VERSION.RELEASE + + override val appPackageName: String + get() = context.packageName + + override val appVersion: String by lazy { + try { + val packageName = context.packageName + context.packageManager.getPackageInfo(packageName, 0).versionName + } catch (exception: Exception) { + "unknown" + } + } +} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/SyncLocker.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/SyncLocker.kt deleted file mode 100644 index 67944d189..000000000 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/SyncLocker.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.mapbox.search.utils - -internal interface SyncLocker { - fun executeInSync(action: () -> T): T -} - -internal class SyncLockerImpl( - private val lock: Any = Any(), -) : SyncLocker { - - override fun executeInSync(action: () -> T): T { - return synchronized(lock) { - action() - } - } -} diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/bitmap/Bitmaps.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/bitmap/Bitmaps.kt index bffb71d11..cc078a405 100644 --- a/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/bitmap/Bitmaps.kt +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/bitmap/Bitmaps.kt @@ -8,7 +8,7 @@ import kotlin.math.roundToInt /** * Helper function to encode Bitmap in Base64 String. Useful for sending screenshots - * over Telemetry SDK, which doesn't support Bitmap encoding out of the box. + * over Events SDK, which doesn't support Bitmap encoding out of the box. */ internal fun Bitmap.encodeBase64(encodeOptions: BitmapEncodeOptions): String { val (scaledWidth, scaledHeight) = if (width <= height) { diff --git a/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/extension/SearchResponseError.kt b/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/extension/SearchResponseError.kt new file mode 100644 index 000000000..ead3ee750 --- /dev/null +++ b/MapboxSearch/sdk/src/main/java/com/mapbox/search/utils/extension/SearchResponseError.kt @@ -0,0 +1,18 @@ +package com.mapbox.search.utils.extension + +import com.mapbox.search.SearchRequestException +import com.mapbox.search.internal.bindgen.SearchResponseError +import java.io.IOException + +// Temporary solution for https://github.com/mapbox/mapbox-search-sdk/issues/857 +internal fun SearchResponseError.toPlatformHttpException(): Exception { + check(typeInfo == SearchResponseError.Type.HTTP_ERROR) + return if (httpError.httpCode >= 200) { + SearchRequestException( + message = httpError.message, + code = httpError.httpCode + ) + } else { + IOException(httpError.message) + } +} diff --git a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/BlockingSearchSelectionCallback.kt b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/BlockingSearchSelectionCallback.kt index 6b9778d9f..9f2b795d9 100644 --- a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/BlockingSearchSelectionCallback.kt +++ b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/BlockingSearchSelectionCallback.kt @@ -43,6 +43,10 @@ internal class BlockingSearchSelectionCallback : fun requireSuggestions() = (this as Suggestions).suggestions + fun requireResult() = (this as Result) + + fun requireError() = (this as Error).e + data class Suggestions(val suggestions: List, val responseInfo: ResponseInfo) : SearchEngineResult() data class Result(val result: SearchResult, val responseInfo: ResponseInfo) : SearchEngineResult() data class CategoryResult(val results: List, val responseInfo: ResponseInfo) : SearchEngineResult() diff --git a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/OfflineSearchEngine.kt b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/OfflineSearchEngine.kt index 63299bd5f..7cf083c35 100644 --- a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/OfflineSearchEngine.kt +++ b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/OfflineSearchEngine.kt @@ -1,7 +1,10 @@ package com.mapbox.search.tests_support +import com.mapbox.geojson.Point +import com.mapbox.search.OfflineReverseGeoOptions import com.mapbox.search.OfflineSearchEngine import com.mapbox.search.OfflineSearchOptions +import com.mapbox.search.result.SearchSuggestion internal fun OfflineSearchEngine.searchBlocking( query: String, @@ -11,3 +14,22 @@ internal fun OfflineSearchEngine.searchBlocking( search(query, options, callback) return callback.getResultBlocking() } + +internal fun OfflineSearchEngine.selectBlocking( + suggestion: SearchSuggestion, +): BlockingSearchSelectionCallback.SearchEngineResult { + val callback = BlockingSearchSelectionCallback() + select(suggestion, callback) + return callback.getResultBlocking() +} + +internal fun OfflineSearchEngine.reverseGeocodingBlocking( + options: OfflineReverseGeoOptions, +): BlockingSearchCallback.SearchEngineResult { + val callback = BlockingSearchCallback() + reverseGeocoding(options, callback) + return callback.getResultBlocking() +} + +internal fun OfflineSearchEngine.reverseGeocodingBlocking(point: Point): BlockingSearchCallback.SearchEngineResult = + reverseGeocodingBlocking(OfflineReverseGeoOptions(point)) diff --git a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/SearchEngine.kt b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/SearchEngine.kt index 0f9dd8ba3..7f15a3acd 100644 --- a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/SearchEngine.kt +++ b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/SearchEngine.kt @@ -2,8 +2,10 @@ package com.mapbox.search.tests_support import com.mapbox.search.SearchEngine import com.mapbox.search.SearchOptions +import com.mapbox.search.SelectOptions import com.mapbox.search.record.IndexableDataProvider import com.mapbox.search.record.IndexableRecord +import com.mapbox.search.result.SearchSuggestion import com.mapbox.search.utils.concurrent.SearchSdkMainThreadWorker import java.util.concurrent.Executor @@ -17,6 +19,16 @@ internal fun SearchEngine.searchBlocking( return callback.getResultBlocking() } +internal fun SearchEngine.selectBlocking( + suggestion: SearchSuggestion, + options: SelectOptions = SelectOptions(), + executor: Executor = SearchSdkMainThreadWorker.mainExecutor, +): BlockingSearchSelectionCallback.SearchEngineResult { + val callback = BlockingSearchSelectionCallback() + select(suggestion, options, executor, callback) + return callback.getResultBlocking() +} + internal fun SearchEngine.registerDataProviderBlocking( dataProvider: IndexableDataProvider, executor: Executor = SearchSdkMainThreadWorker.mainExecutor diff --git a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/TestDataFactory.kt b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/TestDataFactory.kt index 4acbc6b13..d9263e55a 100644 --- a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/TestDataFactory.kt +++ b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/TestDataFactory.kt @@ -56,6 +56,7 @@ internal fun createTestOriginalSearchResult( externalIDs: Map? = null, layerId: String? = null, userRecordId: String? = null, + userRecordPriority: Int = -1, action: SearchResultSuggestAction? = null, serverIndex: Int? = 0, etaMinutes: Double? = null, @@ -76,6 +77,7 @@ internal fun createTestOriginalSearchResult( externalIDs = externalIDs, layerId = layerId, userRecordId = userRecordId, + userRecordPriority = userRecordPriority, action = action, serverIndex = serverIndex, etaMinutes = etaMinutes @@ -279,6 +281,7 @@ internal fun createTestCoreSearchResult( externalIDs: Map? = null, layerId: String? = null, userRecordId: String? = null, + userRecordPriority: Int = 0, action: CoreSuggestAction? = null, serverIndex: Int? = 0, ) = CoreSearchResult( @@ -299,6 +302,7 @@ internal fun createTestCoreSearchResult( externalIDs?.let { (it as? HashMap) ?: HashMap(it) }, layerId, userRecordId, + userRecordPriority, action, serverIndex ) diff --git a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/TestDataProviderEngine.kt b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/TestDataProviderEngine.kt index 51ca72405..34815f5e5 100644 --- a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/TestDataProviderEngine.kt +++ b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/TestDataProviderEngine.kt @@ -1,7 +1,6 @@ package com.mapbox.search.tests_support import com.mapbox.search.record.IndexableDataProviderEngine -import com.mapbox.search.record.IndexableDataProviderEngine.BatchUpdateOperation import com.mapbox.search.record.IndexableRecord internal class TestDataProviderEngine : IndexableDataProviderEngine { @@ -12,22 +11,14 @@ internal class TestDataProviderEngine : IndexableDataProvid val records: List get() = _records.toList() as List - override fun add(record: IndexableRecord) { + override fun upsert(record: IndexableRecord) { + _records.remove(record) _records.add(record) } - override fun addAll(records: Iterable) { - this._records.addAll(records) - } - - override fun update(record: IndexableRecord) { - _records.replaceAll { - if (record.id == it.id) { - record - } else { - it - } - } + override fun upsertAll(records: Iterable) { + _records.removeAll(records) + _records.addAll(records) } override fun remove(id: String) { @@ -42,8 +33,4 @@ internal class TestDataProviderEngine : IndexableDataProvid override fun clear() { _records.clear() } - - override fun executeBatchUpdate(batchUpdateOperation: BatchUpdateOperation) { - batchUpdateOperation.execute(this) - } } diff --git a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/TestSyncLocker.kt b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/TestSyncLocker.kt deleted file mode 100644 index 02d3b6810..000000000 --- a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/TestSyncLocker.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.mapbox.search.tests_support - -import com.mapbox.search.utils.SyncLocker - -internal class TestSyncLocker : SyncLocker { - override fun executeInSync(action: () -> T): T { - return action() - } -} diff --git a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/record/IndexableDataProvider.kt b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/record/IndexableDataProvider.kt index f80e1b130..e39fa6332 100644 --- a/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/record/IndexableDataProvider.kt +++ b/MapboxSearch/sdk/src/sharedTest/java/com/mapbox/search/tests_support/record/IndexableDataProvider.kt @@ -53,30 +53,21 @@ internal fun IndexableDataProvider.containsBlocking( return (callback.getResultBlocking() as BlockingCompletionCallback.CompletionCallbackResult.Result).result } -internal fun IndexableDataProvider.addBlocking( +internal fun IndexableDataProvider.upsertBlocking( record: T, executor: Executor = SearchSdkMainThreadWorker.mainExecutor ) { val callback = BlockingCompletionCallback() - add(record, executor, callback) + upsert(record, executor, callback) callback.getResultBlocking() } -internal fun IndexableDataProvider.addAllBlocking( +internal fun IndexableDataProvider.upsertAllBlocking( records: List, executor: Executor = SearchSdkMainThreadWorker.mainExecutor ) { val callback = BlockingCompletionCallback() - addAll(records, executor, callback) - callback.getResultBlocking() -} - -internal fun IndexableDataProvider.updateBlocking( - record: T, - executor: Executor = SearchSdkMainThreadWorker.mainExecutor -) { - val callback = BlockingCompletionCallback() - update(record, executor, callback) + upsertAll(records, executor, callback) callback.getResultBlocking() } diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/CategorySearchTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/CategorySearchTest.kt index ffa652845..751cb755c 100644 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/CategorySearchTest.kt +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/CategorySearchTest.kt @@ -6,7 +6,6 @@ import com.mapbox.search.common.reportError import com.mapbox.search.core.CoreSearchCallback import com.mapbox.search.core.CoreSearchEngineInterface import com.mapbox.search.core.CoreSearchOptions -import com.mapbox.search.core.http.HttpErrorsCache import com.mapbox.search.internal.bindgen.ResultType import com.mapbox.search.internal.bindgen.SearchAddress import com.mapbox.search.record.DataProviderResolver @@ -35,9 +34,7 @@ import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestFactory -import java.io.IOException import java.util.concurrent.Executor -import java.util.concurrent.ExecutorService /** * Contains only forward-geocoding related functionality tests. @@ -47,10 +44,8 @@ internal class CategorySearchTest { private lateinit var coreEngine: CoreSearchEngineInterface private lateinit var dataProviderResolver: DataProviderResolver - private lateinit var httpErrorsCache: HttpErrorsCache private lateinit var searchResultFactory: SearchResultFactory private lateinit var executor: Executor - private lateinit var engineExecutorService: ExecutorService private lateinit var requestContextProvider: SearchRequestContextProvider private lateinit var searchEngine: SearchEngine @@ -59,10 +54,8 @@ internal class CategorySearchTest { fun setUp() { coreEngine = mockk(relaxed = true) dataProviderResolver = mockk() - httpErrorsCache = mockk() searchResultFactory = spyk(SearchResultFactory(dataProviderResolver)) executor = spyk(TestExecutor()) - engineExecutorService = spyk(TestThreadExecutorService()) requestContextProvider = mockk() every { requestContextProvider.provide(ApiType.GEOCODING) } returns TEST_SEARCH_REQUEST_CONTEXT @@ -70,11 +63,10 @@ internal class CategorySearchTest { searchEngine = SearchEngineImpl( apiType = ApiType.GEOCODING, coreEngine = coreEngine, - httpErrorsCache = httpErrorsCache, historyService = mockk(), requestContextProvider = requestContextProvider, searchResultFactory = searchResultFactory, - engineExecutorService = engineExecutorService, + engineExecutorService = TestThreadExecutorService(), indexableDataProvidersRegistry = mockk(), ) } @@ -108,10 +100,6 @@ internal class CategorySearchTest { Then("SearchRequestTask is executed", true, searchRequestTask.isDone) Then("SearchRequestTask is not cancelled", false, searchRequestTask.isCancelled) - Verify("Operation is scheduled on engine thread") { - engineExecutorService.submit(any()) - } - Verify("Callbacks called inside executor") { executor.execute(any()) } @@ -149,9 +137,6 @@ internal class CategorySearchTest { slotSearchCallback.captured.run(TEST_ERROR_CORE_RESPONSE) } - val errorCause = IOException() - every { httpErrorsCache.getAndRemove(TEST_REQUEST_ID) } returns errorCause - When("Initial search called") { val callback = mockk(relaxed = true) @@ -166,10 +151,6 @@ internal class CategorySearchTest { Then("SearchRequestTask is executed", true, searchRequestTask.isDone) Then("SearchRequestTask is not cancelled", false, searchRequestTask.isCancelled) - Verify("Operation is scheduled on engine thread") { - engineExecutorService.submit(any()) - } - Verify("Callbacks called inside executor") { executor.execute(any()) } @@ -183,12 +164,13 @@ internal class CategorySearchTest { ) } - Verify("Error cause retrieved from errors cache") { - httpErrorsCache.getAndRemove(TEST_REQUEST_ID) - } - Verify("Error passed to callback") { - callback.onError(errorCause) + callback.onError( + SearchRequestException( + message = TEST_ERROR_CORE_RESPONSE_MESSAGE, + code = TEST_ERROR_CORE_RESPONSE_HTTP_CODE + ) + ) } } } @@ -362,9 +344,13 @@ internal class CategorySearchTest { TEST_RESPONSE_UUID ) + val TEST_ERROR_CORE_RESPONSE_HTTP_CODE = 401 + + val TEST_ERROR_CORE_RESPONSE_MESSAGE = "Auth failed" + val TEST_ERROR_CORE_RESPONSE = createTestCoreSearchResponseError( - 401, - "Auth failed", + TEST_ERROR_CORE_RESPONSE_HTTP_CODE, + TEST_ERROR_CORE_RESPONSE_MESSAGE, TEST_REQUEST_ID, TEST_REQUEST_OPTIONS.mapToCore(), TEST_RESPONSE_UUID diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/IndexableDataProvidersRegistryTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/IndexableDataProvidersRegistryTest.kt index 77ac3aada..c5e38754a 100644 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/IndexableDataProvidersRegistryTest.kt +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/IndexableDataProvidersRegistryTest.kt @@ -6,11 +6,9 @@ import com.mapbox.search.record.HistoryRecord import com.mapbox.search.record.IndexableDataProvider import com.mapbox.search.record.IndexableDataProviderEngineImpl import com.mapbox.search.tests_support.TestExecutor -import com.mapbox.search.tests_support.TestSyncLocker import com.mapbox.search.tests_support.TestThreadExecutorService import com.mapbox.search.tests_support.equalsTo import com.mapbox.search.tests_support.record.TestDataProvider -import com.mapbox.search.utils.SyncLocker import com.mapbox.test.dsl.TestCase import io.mockk.every import io.mockk.mockk @@ -24,7 +22,6 @@ import java.util.concurrent.ExecutorService @Suppress("LargeClass") internal class IndexableDataProvidersRegistryTest { - private lateinit var syncLocker: SyncLocker private lateinit var registrationService: DataProviderEngineRegistrationService private lateinit var executorService: ExecutorService @@ -41,14 +38,11 @@ internal class IndexableDataProvidersRegistryTest { @BeforeEach fun setUp() { - syncLocker = spyk(TestSyncLocker()) registrationService = mockk(relaxed = true) executorService = spyk(TestThreadExecutorService()) dataProviderEngine1 = IndexableDataProviderEngineImpl( - IndexableDataProviderEngineImpl.CoreLayerContext( - mockk(relaxed = true), syncLocker - ) + mockk(relaxed = true) ) dataProvider1 = spyk( TestDataProvider( @@ -58,9 +52,7 @@ internal class IndexableDataProvidersRegistryTest { ) dataProviderEngine2 = IndexableDataProviderEngineImpl( - IndexableDataProviderEngineImpl.CoreLayerContext( - mockk(relaxed = true), syncLocker - ) + mockk(relaxed = true) ) dataProvider2 = spyk( TestDataProvider( @@ -72,7 +64,7 @@ internal class IndexableDataProvidersRegistryTest { searchEngine1 = mockk(relaxed = true) searchEngine2 = mockk(relaxed = true) - registry = IndexableDataProvidersRegistryImpl(syncLocker, registrationService) + registry = IndexableDataProvidersRegistryImpl(registrationService) } private fun mockDataProviderRegistration( @@ -103,12 +95,8 @@ internal class IndexableDataProvidersRegistryTest { registrationService.register(dataProvider1, any()) } - VerifyOnce("SyncLocker triggered") { - syncLocker.executeInSync(any()) - } - VerifyOnce("CoreUserRecordsLayer returned from the service added to the SearchEngine") { - searchEngine1.addUserLayer(dataProviderEngine1.coreLayerContext.coreLayer) + searchEngine1.addUserLayer(dataProviderEngine1.coreLayer) } VerifyOnce("Callback executor triggered") { @@ -142,12 +130,8 @@ internal class IndexableDataProvidersRegistryTest { registrationService.register(dataProvider2, any()) } - Verify("SyncLocker triggered for each CoreSearchEngine.addUserLayer()", exactly = 2) { - syncLocker.executeInSync(any()) - } - VerifyOnce("New CoreUserRecordsLayer added to the SearchEngine") { - searchEngine1.addUserLayer(dataProviderEngine2.coreLayerContext.coreLayer) + searchEngine1.addUserLayer(dataProviderEngine2.coreLayer) } VerifyOnce("Callback executor triggered") { @@ -182,12 +166,8 @@ internal class IndexableDataProvidersRegistryTest { registrationService.register(dataProvider1, any()) } - VerifyOnce("SyncLocker is not triggered again") { - syncLocker.executeInSync(any()) - } - VerifyOnce("CoreUserRecordsLayer is not added to the SearchEngine again") { - searchEngine1.addUserLayer(dataProviderEngine1.coreLayerContext.coreLayer) + searchEngine1.addUserLayer(dataProviderEngine1.coreLayer) } VerifyOnce("Callback executor triggered") { @@ -236,12 +216,8 @@ internal class IndexableDataProvidersRegistryTest { registrationService.register(dataProvider1, any()) } - Verify("SyncLocker triggered for each CoreSearchEngine.addUserLayer()", exactly = 2) { - syncLocker.executeInSync(any()) - } - VerifyOnce("CoreUserRecordsLayer returned from the service added to the SearchEngine") { - searchEngine2.addUserLayer(dataProviderEngine1.coreLayerContext.coreLayer) + searchEngine2.addUserLayer(dataProviderEngine1.coreLayer) } VerifyOnce("Callback executor triggered") { @@ -280,10 +256,6 @@ internal class IndexableDataProvidersRegistryTest { registrationService.register(dataProvider1, any()) } - VerifyNo("SyncLocker is not triggered") { - syncLocker.executeInSync(any()) - } - VerifyNo("No CoreUserRecordsLayer added to the SearchEngine") { searchEngine1.addUserLayer(any()) } @@ -320,10 +292,6 @@ internal class IndexableDataProvidersRegistryTest { registrationService.register(dataProvider1, any()) } - VerifyNo("SyncLocker is not triggered") { - syncLocker.executeInSync(any()) - } - VerifyNo("CoreUserRecordsLayer is not added to the SearchEngine") { searchEngine1.addUserLayer(any()) } @@ -360,12 +328,8 @@ internal class IndexableDataProvidersRegistryTest { registrationService.register(dataProvider1, any()) } - Verify("SyncLocker triggered for core user layer add and remove", exactly = 2) { - syncLocker.executeInSync(any()) - } - VerifyOnce("Data provider removed from the SearchEngine") { - searchEngine1.removeUserLayer(eq("Test data provider 1")) + searchEngine1.removeUserLayer(dataProviderEngine1.coreLayer) } VerifyOnce("Callback executor triggered") { @@ -398,10 +362,6 @@ internal class IndexableDataProvidersRegistryTest { registrationService.register(dataProvider1, any()) } - VerifyNo("SyncLocker is not triggered") { - syncLocker.executeInSync(any()) - } - VerifyNo("SearchEngine is not accessed") { searchEngine1.addUserLayer(any()) searchEngine1.removeUserLayer(any()) diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/OfflineSearchEngineTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/OfflineSearchEngineTest.kt index 126c6c876..46d03d84a 100644 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/OfflineSearchEngineTest.kt +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/OfflineSearchEngineTest.kt @@ -5,7 +5,6 @@ import com.mapbox.search.TestConstants.ASSERTIONS_KT_CLASS_NAME import com.mapbox.search.common.reportError import com.mapbox.search.core.CoreSearchCallback import com.mapbox.search.core.CoreSearchEngineInterface -import com.mapbox.search.core.http.HttpErrorsCache import com.mapbox.search.internal.bindgen.ResultType import com.mapbox.search.record.DataProviderResolver import com.mapbox.search.record.FavoritesDataProvider @@ -53,7 +52,6 @@ internal class OfflineSearchEngineTest { private lateinit var coreEngine: CoreSearchEngineInterface private lateinit var dataProviderResolver: DataProviderResolver - private lateinit var httpErrorsCache: HttpErrorsCache private lateinit var historyService: HistoryService private lateinit var searchResultFactory: SearchResultFactory private lateinit var executorService: ExecutorService @@ -67,7 +65,6 @@ internal class OfflineSearchEngineTest { @BeforeEach fun setUp() { - httpErrorsCache = mockk() historyService = mockk(relaxed = true) executorService = spyk(TestThreadExecutorService()) mainThreadWorker = spyk(TestMainThreadWorker()) @@ -101,7 +98,6 @@ internal class OfflineSearchEngineTest { private fun createSearchEngine() { searchEngine = OfflineSearchEngineImpl( coreEngine = coreEngine, - httpErrorsCache = httpErrorsCache, historyService = historyService, requestContextProvider = requestContextProvider, searchResultFactory = searchResultFactory, diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/ReverseGeocodingSearchTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/ReverseGeocodingSearchTest.kt index 7999063ae..6e73e2fa9 100644 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/ReverseGeocodingSearchTest.kt +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/ReverseGeocodingSearchTest.kt @@ -6,7 +6,6 @@ import com.mapbox.search.common.reportError import com.mapbox.search.core.CoreReverseGeoOptions import com.mapbox.search.core.CoreSearchCallback import com.mapbox.search.core.CoreSearchEngineInterface -import com.mapbox.search.core.http.HttpErrorsCache import com.mapbox.search.internal.bindgen.ResultType import com.mapbox.search.internal.bindgen.SearchAddress import com.mapbox.search.record.DataProviderResolver @@ -35,9 +34,7 @@ import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestFactory -import java.io.IOException import java.util.concurrent.Executor -import java.util.concurrent.ExecutorService /** * Contains only forward-geocoding related functionality tests. @@ -47,10 +44,8 @@ internal class ReverseGeocodingSearchTest { private lateinit var coreEngine: CoreSearchEngineInterface private lateinit var dataProviderResolver: DataProviderResolver - private lateinit var httpErrorsCache: HttpErrorsCache private lateinit var searchResultFactory: SearchResultFactory private lateinit var executor: Executor - private lateinit var engineExecutorService: ExecutorService private lateinit var requestContextProvider: SearchRequestContextProvider private lateinit var searchEngine: SearchEngine @@ -59,10 +54,8 @@ internal class ReverseGeocodingSearchTest { fun setUp() { coreEngine = mockk(relaxed = true) dataProviderResolver = mockk() - httpErrorsCache = mockk() searchResultFactory = spyk(SearchResultFactory(dataProviderResolver)) executor = spyk(TestExecutor()) - engineExecutorService = spyk(TestThreadExecutorService()) requestContextProvider = mockk() every { requestContextProvider.provide(ApiType.GEOCODING) } returns TEST_SEARCH_REQUEST_CONTEXT @@ -70,11 +63,10 @@ internal class ReverseGeocodingSearchTest { searchEngine = SearchEngineImpl( apiType = ApiType.GEOCODING, coreEngine = coreEngine, - httpErrorsCache = httpErrorsCache, historyService = mockk(), requestContextProvider = requestContextProvider, searchResultFactory = searchResultFactory, - engineExecutorService = engineExecutorService, + engineExecutorService = TestThreadExecutorService(), indexableDataProvidersRegistry = mockk(), ) } @@ -108,10 +100,6 @@ internal class ReverseGeocodingSearchTest { Then("SearchRequestTask is not cancelled", false, searchRequestTask.isCancelled) Then("SearchRequestTask is executed", true, searchRequestTask.isDone) - Verify("Operation is scheduled on engine thread") { - engineExecutorService.submit(any()) - } - Verify("Callbacks called inside executor") { executor.execute(any()) } @@ -144,9 +132,6 @@ internal class ReverseGeocodingSearchTest { slotSearchCallback.captured.run(TEST_ERROR_CORE_RESPONSE) } - val errorCause = IOException() - every { httpErrorsCache.getAndRemove(TEST_REQUEST_ID) } returns errorCause - When("Initial search called") { val callback = mockk(relaxed = true) @@ -160,10 +145,6 @@ internal class ReverseGeocodingSearchTest { Then("SearchRequestTask is executed", true, searchRequestTask.isDone) Then("SearchRequestTask is not cancelled", false, searchRequestTask.isCancelled) - Verify("Operation is scheduled on engine thread") { - engineExecutorService.submit(any()) - } - Verify("Callbacks called inside executor") { executor.execute(any()) } @@ -172,12 +153,13 @@ internal class ReverseGeocodingSearchTest { coreEngine.reverseGeocoding(slotSearchOptions.captured, slotSearchCallback.captured) } - Verify("Error cause retrieved from errors cache") { - httpErrorsCache.getAndRemove(TEST_REQUEST_ID) - } - Verify("Error passed to callback") { - callback.onError(errorCause) + callback.onError( + SearchRequestException( + message = TEST_ERROR_CORE_RESPONSE_MESSAGE, + code = TEST_ERROR_CORE_RESPONSE_HTTP_COE, + ) + ) } } } @@ -340,9 +322,13 @@ internal class ReverseGeocodingSearchTest { TEST_RESPONSE_UUID ) + val TEST_ERROR_CORE_RESPONSE_MESSAGE = "Auth failed" + + val TEST_ERROR_CORE_RESPONSE_HTTP_COE = 401 + val TEST_ERROR_CORE_RESPONSE = createTestCoreSearchResponseError( - 401, - "Auth failed", + TEST_ERROR_CORE_RESPONSE_HTTP_COE, + TEST_ERROR_CORE_RESPONSE_MESSAGE, TEST_REQUEST_ID, TEST_REQUEST_OPTIONS.mapToCore(), TEST_RESPONSE_UUID diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/SearchEngineTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/SearchEngineTest.kt index 1b300ae16..03d786981 100644 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/SearchEngineTest.kt +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/SearchEngineTest.kt @@ -8,7 +8,6 @@ import com.mapbox.search.core.CoreSearchCallback import com.mapbox.search.core.CoreSearchEngineInterface import com.mapbox.search.core.CoreSearchOptions import com.mapbox.search.core.CoreSearchResult -import com.mapbox.search.core.http.HttpErrorsCache import com.mapbox.search.internal.bindgen.ResultType import com.mapbox.search.internal.bindgen.SearchAddress import com.mapbox.search.record.DataProviderResolver @@ -48,9 +47,7 @@ import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestFactory -import java.io.IOException import java.util.concurrent.Executor -import java.util.concurrent.ExecutorService /** * Contains only forward-geocoding related functionality tests. @@ -61,11 +58,9 @@ internal class SearchEngineTest { private lateinit var coreEngine: CoreSearchEngineInterface private lateinit var dataProviderResolver: DataProviderResolver - private lateinit var httpErrorsCache: HttpErrorsCache private lateinit var historyService: HistoryService private lateinit var searchResultFactory: SearchResultFactory private lateinit var executor: Executor - private lateinit var engineExecutorService: ExecutorService private lateinit var requestContextProvider: SearchRequestContextProvider private lateinit var indexableDataProvidersRegistry: IndexableDataProvidersRegistry @@ -74,7 +69,6 @@ internal class SearchEngineTest { @BeforeEach fun setUp() { coreEngine = mockk(relaxed = true) - httpErrorsCache = mockk() historyService = mockk(relaxed = true) dataProviderResolver = mockk() @@ -89,7 +83,6 @@ internal class SearchEngineTest { } executor = spyk(TestExecutor()) - engineExecutorService = spyk(TestThreadExecutorService()) requestContextProvider = mockk() every { requestContextProvider.provide(ApiType.SBS) } returns TEST_SEARCH_REQUEST_CONTEXT @@ -99,11 +92,10 @@ internal class SearchEngineTest { searchEngine = SearchEngineImpl( apiType = ApiType.SBS, coreEngine = coreEngine, - httpErrorsCache = httpErrorsCache, historyService = historyService, requestContextProvider = requestContextProvider, searchResultFactory = searchResultFactory, - engineExecutorService = engineExecutorService, + engineExecutorService = TestThreadExecutorService(), indexableDataProvidersRegistry = indexableDataProvidersRegistry, ) } @@ -131,10 +123,6 @@ internal class SearchEngineTest { Then("SearchRequestTask is executed", true, searchRequestTask.isDone) Then("SearchRequestTask is not cancelled", false, searchRequestTask.isCancelled) - Verify("Operation is scheduled on engine thread") { - engineExecutorService.submit(any()) - } - Verify("Callbacks called inside executor") { executor.execute(any()) } @@ -174,9 +162,6 @@ internal class SearchEngineTest { slotSearchCallback.captured.run(TEST_ERROR_CORE_RESPONSE) } - val errorCause = IOException() - every { httpErrorsCache.getAndRemove(TEST_REQUEST_ID) } returns errorCause - When("Initial search called") { val callback = mockk(relaxed = true) @@ -191,10 +176,6 @@ internal class SearchEngineTest { Then("SearchRequestTask is executed", true, searchRequestTask.isDone) Then("SearchRequestTask is not cancelled", false, searchRequestTask.isCancelled) - Verify("Operation is scheduled on engine thread") { - engineExecutorService.submit(any()) - } - Verify("CoreSearchEngine.search() called") { coreEngine.search( eq(TEST_QUERY), @@ -208,12 +189,13 @@ internal class SearchEngineTest { executor.execute(any()) } - Verify("Error cause retrieved from errors cache") { - httpErrorsCache.getAndRemove(TEST_REQUEST_ID) - } - Verify("Error passed to callback") { - callback.onError(errorCause) + callback.onError( + SearchRequestException( + message = TEST_ERROR_CORE_RESPONSE_MESSAGE, + code = TEST_ERROR_CORE_RESPONSE_HTTP_CODE, + ) + ) } } } @@ -374,10 +356,6 @@ internal class SearchEngineTest { Then("SearchRequestTask is executed", true, searchRequestTask.isDone) Then("SearchRequestTask is not cancelled", false, searchRequestTask.isCancelled) - Verify("Operation is scheduled on engine thread") { - engineExecutorService.submit(any()) - } - Verify("Callbacks called inside executor") { executor.execute(any()) } @@ -446,10 +424,6 @@ internal class SearchEngineTest { Then("SearchRequestTask is executed", true, searchRequestTask.isDone) Then("SearchRequestTask is not cancelled", false, searchRequestTask.isCancelled) - Verify("Operation is scheduled on engine thread") { - engineExecutorService.submit(any()) - } - Verify("Callbacks called inside executor") { executor.execute(any()) } @@ -533,10 +507,6 @@ internal class SearchEngineTest { Then("SearchRequestTask is executed", true, searchRequestTask.isDone) Then("SearchRequestTask is not cancelled", false, searchRequestTask.isCancelled) - Verify("Operation is scheduled on engine thread") { - engineExecutorService.submit(any()) - } - Verify("Callbacks called inside executor") { executor.execute(any()) } @@ -650,9 +620,13 @@ internal class SearchEngineTest { TEST_RESPONSE_UUID ) + val TEST_ERROR_CORE_RESPONSE_MESSAGE = "Auth failed" + + val TEST_ERROR_CORE_RESPONSE_HTTP_CODE = 401 + val TEST_ERROR_CORE_RESPONSE = createTestCoreSearchResponseError( - 401, - "Auth failed", + TEST_ERROR_CORE_RESPONSE_HTTP_CODE, + TEST_ERROR_CORE_RESPONSE_MESSAGE, TEST_REQUEST_ID, TEST_REQUEST_OPTIONS, TEST_RESPONSE_UUID diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/AnalyticsEventsTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/AnalyticsEventsTest.kt index fea478fb2..ba0c4b6f9 100644 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/AnalyticsEventsTest.kt +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/AnalyticsEventsTest.kt @@ -5,42 +5,6 @@ import org.junit.jupiter.api.TestFactory internal class AnalyticsEventsTest { - @TestFactory - fun `Check test query changed events for validity`() = TestCase { - TEST_QUERY_CHANGE_EVENTS.forEach { (inputValue, expectedValue) -> - Given("QueryChangedEvent = $inputValue") { - When("QueryChangedEvent = $inputValue") { - val actualValue = inputValue.isValid - Then("Event is valid should be <$expectedValue>", expectedValue, actualValue) - } - } - } - } - - @TestFactory - fun `Check test search start events for validity`() = TestCase { - TEST_SEARCH_START_EVENTS.forEach { (inputValue, expectedValue) -> - Given("SearchStartEvent = $inputValue") { - When("SearchStartEvent = $inputValue") { - val actualValue = inputValue.isValid - Then("Event is valid should be <$expectedValue>", expectedValue, actualValue) - } - } - } - } - - @TestFactory - fun `Check test search select events for validity`() = TestCase { - TEST_SEARCH_SELECT_EVENTS.forEach { (inputValue, expectedValue) -> - Given("SearchSelectEvent = $inputValue") { - When("SearchSelectEvent = $inputValue") { - val actualValue = inputValue.isValid - Then("Event is valid should be <$expectedValue>", expectedValue, actualValue) - } - } - } - } - @TestFactory fun `Check test search feedback events for validity`() = TestCase { TEST_SEARCH_FEEDBACK_EVENTS.forEach { (inputValue, expectedValue) -> @@ -72,43 +36,8 @@ internal class AnalyticsEventsTest { const val TEST_FEEDBACK_REASON = "Other reason" const val TEST_FEEDBACK_TEXT = "Incorrect coordinates" const val TEST_RESPONSE_UUID = "e0a2b1d6-3621-11eb-adc1-0242ac120002" - const val SEARCH_QUERY_CHANGED_EVENT_NAME = "search.query_change" - const val SEARCH_START_EVENT_NAME = "search.start" - const val SEARCH_SELECT_EVENT_NAME = "search.select" const val SEARCH_FEEDBACK_EVENT_NAME = "search.feedback" - val TEST_QUERY_CHANGE_EVENTS: Map - get() = mapOf( - QueryChangeEvent().apply { event = null; newQuery = "aa"; oldQuery = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT } to false, - QueryChangeEvent().apply { event = ""; newQuery = "aa"; oldQuery = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT } to false, - QueryChangeEvent().apply { event = SEARCH_QUERY_CHANGED_EVENT_NAME; newQuery = null; oldQuery = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT } to false, - QueryChangeEvent().apply { event = SEARCH_QUERY_CHANGED_EVENT_NAME; newQuery = "aa"; oldQuery = null; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT } to false, - QueryChangeEvent().apply { event = SEARCH_QUERY_CHANGED_EVENT_NAME; newQuery = "aa"; oldQuery = "a"; sessionIdentifier = null; created = TEST_TIME_IN_CORRECT_FORMAT } to false, - QueryChangeEvent().apply { event = SEARCH_QUERY_CHANGED_EVENT_NAME; newQuery = "aa"; oldQuery = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = null } to false, - QueryChangeEvent().apply { event = SEARCH_QUERY_CHANGED_EVENT_NAME; newQuery = "aa"; oldQuery = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT } to true - ) - - val TEST_SEARCH_START_EVENTS: Map - get() = mapOf( - SearchStartEvent().apply { event = null; queryString = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT } to false, - SearchStartEvent().apply { event = ""; queryString = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT } to false, - SearchStartEvent().apply { event = SEARCH_START_EVENT_NAME; queryString = null; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT } to false, - SearchStartEvent().apply { event = SEARCH_START_EVENT_NAME; queryString = "a"; sessionIdentifier = null; created = TEST_TIME_IN_CORRECT_FORMAT } to false, - SearchStartEvent().apply { event = SEARCH_START_EVENT_NAME; queryString = null; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = null } to false, - SearchStartEvent().apply { event = SEARCH_START_EVENT_NAME; queryString = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = null } to false - ) - - val TEST_SEARCH_SELECT_EVENTS: Map - get() = mapOf( - SearchSelectEvent().apply { event = null; queryString = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT; resultIndex = TEST_RESULT_INDEX } to false, - SearchSelectEvent().apply { event = ""; queryString = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT; resultIndex = TEST_RESULT_INDEX } to false, - SearchSelectEvent().apply { event = SEARCH_SELECT_EVENT_NAME; queryString = null; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT; resultIndex = TEST_RESULT_INDEX } to false, - SearchSelectEvent().apply { event = SEARCH_SELECT_EVENT_NAME; queryString = "a"; sessionIdentifier = null; created = TEST_TIME_IN_CORRECT_FORMAT; resultIndex = TEST_RESULT_INDEX } to false, - SearchSelectEvent().apply { event = SEARCH_SELECT_EVENT_NAME; queryString = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = null; resultIndex = TEST_RESULT_INDEX } to false, - SearchSelectEvent().apply { event = SEARCH_SELECT_EVENT_NAME; queryString = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT; resultIndex = null } to false, - SearchSelectEvent().apply { event = SEARCH_SELECT_EVENT_NAME; queryString = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT; resultIndex = TEST_RESULT_INDEX } to true, - ) - val TEST_SEARCH_FEEDBACK_EVENTS: Map get() = mapOf( SearchFeedbackEvent().apply { event = null; queryString = "a"; sessionIdentifier = TEST_SESSION_IDENTIFIER; created = TEST_TIME_IN_CORRECT_FORMAT; resultIndex = TEST_RESULT_INDEX; feedbackReason = TEST_FEEDBACK_REASON; feedbackText = TEST_FEEDBACK_TEXT; selectedItemName = "A"; responseUuid = TEST_RESPONSE_UUID } to false, diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/AnalyticsServiceImplTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/AnalyticsServiceImplTest.kt new file mode 100644 index 000000000..c4f831a38 --- /dev/null +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/AnalyticsServiceImplTest.kt @@ -0,0 +1,469 @@ +package com.mapbox.search.analytics.events + +import android.content.Context +import android.location.Location +import com.mapbox.android.core.location.LocationEngine +import com.mapbox.android.core.permissions.PermissionsManager +import com.mapbox.geojson.Point +import com.mapbox.search.BuildConfig +import com.mapbox.search.CompletionCallback +import com.mapbox.search.ResponseInfo +import com.mapbox.search.analytics.AnalyticsEventJsonParser +import com.mapbox.search.analytics.AnalyticsServiceImpl +import com.mapbox.search.analytics.CrashEventsFactory +import com.mapbox.search.analytics.FeedbackEvent +import com.mapbox.search.analytics.MissingResultFeedbackEvent +import com.mapbox.search.analytics.SearchEventsService +import com.mapbox.search.analytics.SearchFeedbackEventsFactory +import com.mapbox.search.common.FixedPointLocationEngine +import com.mapbox.search.result.mapToPlatform +import com.mapbox.search.tests_support.BlockingCompletionCallback +import com.mapbox.search.tests_support.TestExecutor +import com.mapbox.search.tests_support.catchThrowable +import com.mapbox.search.tests_support.createTestCoreSearchResponseSuccess +import com.mapbox.search.tests_support.createTestFavoriteRecord +import com.mapbox.search.tests_support.createTestRequestOptions +import com.mapbox.search.tests_support.createTestSearchResult +import com.mapbox.search.tests_support.createTestSuggestion +import com.mapbox.test.dsl.TestCase +import io.mockk.Called +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.spyk +import io.mockk.unmockkStatic +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.TestFactory +import java.lang.IllegalStateException +import java.util.concurrent.Executor + +@Suppress("LargeClass") +internal class AnalyticsServiceImplTest { + + private lateinit var context: Context + private lateinit var locationEngine: LocationEngine + private lateinit var eventsService: SearchEventsService + private lateinit var eventsJsonParser: AnalyticsEventJsonParser + private lateinit var feedbackEventsFactory: SearchFeedbackEventsFactory + private lateinit var crashEventsFactory: CrashEventsFactory + private lateinit var analyticsServiceImpl: AnalyticsServiceImpl + private lateinit var callbackExecutor: Executor + + private lateinit var validMockedFeedbackEvent: SearchFeedbackEvent + private lateinit var invalidMockedFeedbackEvent: SearchFeedbackEvent + + @BeforeEach + fun setUp() { + val mockedLocation = mockk(relaxed = true) + every { mockedLocation.latitude } returns TEST_LOCATION.latitude() + every { mockedLocation.longitude } returns TEST_LOCATION.longitude() + + context = mockk(relaxed = true) + locationEngine = FixedPointLocationEngine(mockedLocation) + eventsService = mockk(relaxed = true) + eventsJsonParser = mockk() + feedbackEventsFactory = mockk(relaxed = true) + crashEventsFactory = mockk(relaxed = true) + callbackExecutor = spyk(TestExecutor()) + + validMockedFeedbackEvent = mockk() + every { validMockedFeedbackEvent.isValid } returns true + + invalidMockedFeedbackEvent = mockk() + every { invalidMockedFeedbackEvent.isValid } returns false + + analyticsServiceImpl = AnalyticsServiceImpl( + context, eventsService, eventsJsonParser, feedbackEventsFactory, crashEventsFactory, locationEngine + ) + + every { + eventsJsonParser.serialize(validMockedFeedbackEvent) + } returns TEST_SERIALIZED_FEEDBACK_EVENT + + mockkStatic(PermissionsManager::class) + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns true + } + + @AfterEach + fun tearDown() { + unmockkStatic(PermissionsManager::class) + } + + @TestFactory + fun `Send feedback for SearchSuggestion with valid data`() = TestCase { + Given("AnalyticsService with mocked dependencies") { + val searchSuggestion = createTestSuggestion() + + val callbackSlot = slot>() + every { + feedbackEventsFactory.createSearchFeedbackEvent( + originalSearchResult = searchSuggestion.originalSearchResult, + requestOptions = searchSuggestion.requestOptions, + searchResponse = TEST_RESPONSE_INFO.coreSearchResponse, + currentLocation = TEST_LOCATION, + isReproducible = true, + event = TEST_FEEDBACK_EVENT, + isCached = any(), + callback = capture(callbackSlot), + ) + } answers { + callbackSlot.captured.onComplete(validMockedFeedbackEvent) + } + + When("Sending feedback for search suggestion with valid data") { + analyticsServiceImpl.sendFeedback(searchSuggestion, TEST_RESPONSE_INFO, TEST_FEEDBACK_EVENT) + + VerifyOnce("Event serialized") { + eventsJsonParser.serialize(validMockedFeedbackEvent) + } + + VerifyOnce("Event sent") { + eventsService.sendEventJson(TEST_SERIALIZED_FEEDBACK_EVENT) + } + } + } + } + + @TestFactory + fun `Send feedback for SearchSuggestion with invalid data`() = TestCase { + Given("AnalyticsService with mocked dependencies") { + val searchSuggestion = createTestSuggestion() + val mockedFeedbackEvent = mockk() + + val callbackSlot = slot>() + + every { + feedbackEventsFactory.createSearchFeedbackEvent( + originalSearchResult = searchSuggestion.originalSearchResult, + requestOptions = searchSuggestion.requestOptions, + searchResponse = TEST_RESPONSE_INFO.coreSearchResponse, + currentLocation = TEST_LOCATION, + isReproducible = true, + event = TEST_FEEDBACK_EVENT, + isCached = any(), + callback = capture(callbackSlot), + ) + } answers { + callbackSlot.captured.onComplete(mockedFeedbackEvent) + } + + When("Sending feedback for search suggestion with invalid data") { + every { mockedFeedbackEvent.isValid } returns false + val caughtException = catchThrowable { + analyticsServiceImpl.sendFeedback(searchSuggestion, TEST_RESPONSE_INFO, TEST_FEEDBACK_EVENT) + } + + Verify("Events service wasn't called") { eventsService wasNot Called } + + if (BuildConfig.DEBUG) { + Then("IllegalStateException was thrown") { + Assertions.assertTrue(caughtException is IllegalStateException) + } + } + } + } + } + + @TestFactory + fun `Send feedback for SearchResult with valid data`() = TestCase { + Given("AnalyticsService with mocked dependencies") { + val searchResult = createTestSearchResult() + + val callbackSlot = slot>() + every { + feedbackEventsFactory.createSearchFeedbackEvent( + originalSearchResult = searchResult.originalSearchResult, + requestOptions = searchResult.requestOptions, + searchResponse = TEST_RESPONSE_INFO.coreSearchResponse, + currentLocation = TEST_LOCATION, + isReproducible = true, + event = TEST_FEEDBACK_EVENT, + isCached = any(), + callback = capture(callbackSlot), + ) + } answers { + callbackSlot.captured.onComplete(validMockedFeedbackEvent) + } + + When("Sending feedback for search result with valid data") { + analyticsServiceImpl.sendFeedback(searchResult, TEST_RESPONSE_INFO, TEST_FEEDBACK_EVENT) + + VerifyOnce("Event serialized") { + eventsJsonParser.serialize(validMockedFeedbackEvent) + } + + VerifyOnce("Event sent") { + eventsService.sendEventJson(TEST_SERIALIZED_FEEDBACK_EVENT) + } + } + } + } + + @TestFactory + fun `Send feedback for SearchResult with invalid data`() = TestCase { + Given("AnalyticsService with mocked dependencies") { + val searchResult = createTestSearchResult() + + val callbackSlot = slot>() + + every { + feedbackEventsFactory.createSearchFeedbackEvent( + originalSearchResult = searchResult.originalSearchResult, + requestOptions = searchResult.requestOptions, + searchResponse = TEST_RESPONSE_INFO.coreSearchResponse, + currentLocation = TEST_LOCATION, + isReproducible = true, + event = TEST_FEEDBACK_EVENT, + isCached = any(), + callback = capture(callbackSlot), + ) + } answers { + callbackSlot.captured.onComplete(invalidMockedFeedbackEvent) + } + + When("Sending feedback for search result with invalid data") { + val caughtException = catchThrowable { + analyticsServiceImpl.sendFeedback(searchResult, TEST_RESPONSE_INFO, TEST_FEEDBACK_EVENT) + } + + Verify("Events service wasn't called") { eventsService wasNot Called } + + if (BuildConfig.DEBUG) { + Then("IllegalStateException was thrown") { + Assertions.assertTrue(caughtException is IllegalStateException) + } + } + } + } + } + + @TestFactory + fun `Send feedback for IndexableRecord with valid data`() = TestCase { + Given("AnalyticsService with mocked dependencies") { + val favoriteRecord = createTestFavoriteRecord() + every { + feedbackEventsFactory.createSearchFeedbackEvent(favoriteRecord, TEST_FEEDBACK_EVENT, TEST_LOCATION) + } returns validMockedFeedbackEvent + + When("Sending feedback for indexable record with valid data") { + analyticsServiceImpl.sendFeedback(favoriteRecord, TEST_FEEDBACK_EVENT) + + VerifyOnce("Event serialized") { + eventsJsonParser.serialize(validMockedFeedbackEvent) + } + + VerifyOnce("Event sent") { + eventsService.sendEventJson(TEST_SERIALIZED_FEEDBACK_EVENT) + } + } + } + } + + @TestFactory + fun `Send feedback for IndexableRecord with invalid data`() = TestCase { + Given("AnalyticsService with mocked dependencies") { + val favoriteRecord = createTestFavoriteRecord() + every { + feedbackEventsFactory.createSearchFeedbackEvent(favoriteRecord, TEST_FEEDBACK_EVENT, TEST_LOCATION) + } returns invalidMockedFeedbackEvent + + When("Sending feedback for indexable record with invalid data") { + val caughtException = catchThrowable { + analyticsServiceImpl.sendFeedback(favoriteRecord, TEST_FEEDBACK_EVENT) + } + + Verify("Events service wasn't called") { eventsService wasNot Called } + + if (BuildConfig.DEBUG) { + Then("IllegalStateException was thrown") { + Assertions.assertTrue(caughtException is IllegalStateException) + } + } + } + } + } + + @TestFactory + fun `Send missing result feedback for ResponseInfo with valid data`() = TestCase { + Given("AnalyticsService with mocked dependencies") { + val callbackSlot = slot>() + every { + feedbackEventsFactory.createSearchFeedbackEvent(TEST_MISSING_RESULT_FEEDBACK_EVENT, TEST_LOCATION, capture(callbackSlot)) + } answers { + callbackSlot.captured.onComplete(validMockedFeedbackEvent) + } + + When("Sending feedback for response info with valid data") { + analyticsServiceImpl.sendMissingResultFeedback(TEST_MISSING_RESULT_FEEDBACK_EVENT) + + VerifyOnce("Event serialized") { + eventsJsonParser.serialize(validMockedFeedbackEvent) + } + + VerifyOnce("Event sent") { + eventsService.sendEventJson(TEST_SERIALIZED_FEEDBACK_EVENT) + } + } + } + } + + @TestFactory + fun `Send missing result feedback for ResponseInfo with invalid data`() = TestCase { + Given("AnalyticsService with mocked dependencies") { + val callbackSlot = slot>() + every { + feedbackEventsFactory.createSearchFeedbackEvent(TEST_MISSING_RESULT_FEEDBACK_EVENT, TEST_LOCATION, capture(callbackSlot)) + } answers { + callbackSlot.captured.onComplete(invalidMockedFeedbackEvent) + } + + When("Sending feedback for indexable record with invalid data") { + val caughtException = catchThrowable { + analyticsServiceImpl.sendMissingResultFeedback(TEST_MISSING_RESULT_FEEDBACK_EVENT) + } + + Verify("Events service wasn't called") { eventsService wasNot Called } + + if (BuildConfig.DEBUG) { + Then("IllegalStateException was thrown") { + Assertions.assertTrue(caughtException is IllegalStateException) + } + } + } + } + } + + @TestFactory + fun `Create event raw feedback event`() = TestCase { + Given("AnalyticsService with mocked dependencies") { + val searchResult = createTestSearchResult() + val mockedFeedbackEvent = mockk() + + val callbackSlot = slot>() + + every { + feedbackEventsFactory.createSearchFeedbackEvent( + originalSearchResult = any(), + requestOptions = any(), + searchResponse = any(), + currentLocation = null, + isReproducible = true, + event = null, + isCached = any(), + asTemplate = true, + callback = capture(callbackSlot) + ) + } answers { + callbackSlot.captured.onComplete(mockedFeedbackEvent) + } + + every { eventsJsonParser.serialize(mockedFeedbackEvent) } returns TEST_RAW_EVENT + + When("Creating raw event for SearchResult") { + val callback = BlockingCompletionCallback() + + @Suppress("DEPRECATION") + analyticsServiceImpl.createRawFeedbackEvent(searchResult, TEST_RESPONSE_INFO, callbackExecutor, callback) + + Then( + "Raw event was successfully created", + callback.getResultBlocking().requireResult(), + TEST_RAW_EVENT + ) + + Verify("Callback called inside executor", exactly = 2) { + callbackExecutor.execute(any()) + } + } + + When("Creating raw event for SearchSuggestion") { + val callback = BlockingCompletionCallback() + + val searchSuggestion = createTestSuggestion() + + @Suppress("DEPRECATION") + analyticsServiceImpl.createRawFeedbackEvent(searchSuggestion, TEST_RESPONSE_INFO, callbackExecutor, callback) + + Then("Raw event was successfully created", callback.getResultBlocking().requireResult(), TEST_RAW_EVENT) + + Verify("Callback called inside executor", exactly = 2) { + callbackExecutor.execute(any()) + } + } + } + } + + @TestFactory + fun `reportError called with allowed exception`() = TestCase { + Given("AnalyticsService with mocked dependencies") { + val throwable = mockk() + val errorJson = "error json" + + every { crashEventsFactory.isAllowedForAnalytics(throwable) } returns true + every { crashEventsFactory.createEvent(eq(throwable), any(), any()) } returns errorJson + + When("reportError() called") { + analyticsServiceImpl.reportError(throwable) + + VerifyOnce("Checked if error is allowed to be sent") { + crashEventsFactory.isAllowedForAnalytics(throwable) + } + + VerifyOnce("Json event created") { + crashEventsFactory.createEvent(eq(throwable), eq(true), null) + } + + VerifyOnce("Event sent") { + eventsService.sendEventJson(errorJson) + } + } + } + } + + @TestFactory + fun `reportError called with not allowed exception`() = TestCase { + Given("AnalyticsService with mocked dependencies") { + val throwable = mockk() + + every { crashEventsFactory.isAllowedForAnalytics(throwable) } returns false + + When("reportError() called") { + analyticsServiceImpl.reportError(throwable) + + VerifyOnce("Checked if error is allowed to be sent") { + crashEventsFactory.isAllowedForAnalytics(throwable) + } + + VerifyNo("Json event is not created") { + crashEventsFactory.createEvent(any(), any(), any()) + } + + VerifyNo("Event not sent") { + eventsService.sendEventJson(any()) + } + } + } + } + + private companion object { + + const val TEST_RAW_EVENT = "{\"event\":\"search.feedback\"}" + + const val TEST_SERIALIZED_FEEDBACK_EVENT = "{\"event\":\"search.feedback\"}" + + val TEST_LOCATION: Point = Point.fromLngLat(10.0, 20.0) + val TEST_FEEDBACK_EVENT = FeedbackEvent("Missing routable point", "Fix, please!") + val TEST_RESPONSE_INFO = ResponseInfo( + requestOptions = createTestRequestOptions(), + coreSearchResponse = createTestCoreSearchResponseSuccess().mapToPlatform(), + isReproducible = true, + ) + val TEST_MISSING_RESULT_FEEDBACK_EVENT = MissingResultFeedbackEvent( + TEST_RESPONSE_INFO, + "Fix, please!" + ) + } +} diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/TelemetrySearchEventsFactoryTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/SearchFeedbackEventsFactoryTest.kt similarity index 84% rename from MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/TelemetrySearchEventsFactoryTest.kt rename to MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/SearchFeedbackEventsFactoryTest.kt index 5161bb25a..acff56c2e 100644 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/TelemetrySearchEventsFactoryTest.kt +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/SearchFeedbackEventsFactoryTest.kt @@ -17,8 +17,9 @@ import com.mapbox.search.ViewportProvider import com.mapbox.search.analytics.AnalyticsEventJsonParser import com.mapbox.search.analytics.FeedbackEvent import com.mapbox.search.analytics.MissingResultFeedbackEvent -import com.mapbox.search.analytics.TelemetrySearchEventsFactory +import com.mapbox.search.analytics.SearchFeedbackEventsFactory import com.mapbox.search.core.CoreSearchEngineInterface +import com.mapbox.search.internal.bindgen.FeedbackEventCallback import com.mapbox.search.location.calculateMapZoom import com.mapbox.search.record.FavoriteRecord import com.mapbox.search.record.HistoryRecord @@ -35,6 +36,7 @@ import com.mapbox.search.result.ServerSearchResultImpl import com.mapbox.search.result.ServerSearchSuggestion import com.mapbox.search.result.mapToCore import com.mapbox.search.result.mapToPlatform +import com.mapbox.search.tests_support.BlockingCompletionCallback import com.mapbox.search.tests_support.StubIndexableRecord import com.mapbox.search.tests_support.assertEqualsJsonify import com.mapbox.search.tests_support.createTestCoreSearchResponseSuccess @@ -46,12 +48,13 @@ import com.mapbox.search.utils.orientation.ScreenOrientation import com.mapbox.test.dsl.TestCase import io.mockk.every import io.mockk.mockk +import io.mockk.slot import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestFactory import java.util.Locale @Suppress("LargeClass") -internal class TelemetrySearchEventsFactoryTest { +internal class SearchFeedbackEventsFactoryTest { private val viewportProvider = ViewportProvider { TEST_VIEWPORT } private val formattedDateProvider = FormattedTimeProvider { TEST_EVENT_CREATION_DATE } @@ -60,33 +63,40 @@ internal class TelemetrySearchEventsFactoryTest { private lateinit var eventJsonParser: AnalyticsEventJsonParser private lateinit var coreEngine: CoreSearchEngineInterface private lateinit var mockBitmap: Bitmap - private lateinit var jsonSerializer: (Any) -> String - private lateinit var eventsFactory: TelemetrySearchEventsFactory + private lateinit var feedbackEventsFactory: SearchFeedbackEventsFactory @BeforeEach fun setUp() { coreEngine = mockk() eventJsonParser = mockk() mockBitmap = mockk() - jsonSerializer = mockk() - every { coreEngine.makeFeedbackEvent(any(), any()) } returns TEST_CORE_RAW_EVENT - every { jsonSerializer.invoke(ofType()) } returns TEST_SEARCH_RESULTS_INFO_JSON + val feedbackEventCallbackSlot = slot() + every { coreEngine.makeFeedbackEvent(any(), any(), capture(feedbackEventCallbackSlot)) } answers { + feedbackEventCallbackSlot.captured.run(TEST_CORE_RAW_EVENT) + } + + every { eventJsonParser.serializeAny(any()) } returns TEST_SEARCH_RESULTS_INFO_JSON - eventsFactory = createEventsFactory() + feedbackEventsFactory = createEventsFactory() } - private fun createEventsFactory(): TelemetrySearchEventsFactory { - return TelemetrySearchEventsFactory( - TEST_USER_AGENT, viewportProvider, uuidProvider, { coreEngine }, - eventJsonParser, formattedDateProvider, jsonSerializer, bitmapEncoder + private fun createEventsFactory(): SearchFeedbackEventsFactory { + return SearchFeedbackEventsFactory( + TEST_USER_AGENT, + viewportProvider, + uuidProvider, + { coreEngine }, + eventJsonParser, + formattedDateProvider, + bitmapEncoder ) } @TestFactory fun `Check SearchResults converts to SearchFeedbackEvent`() = TestCase { - Given("TelemetrySearchEventsFactory with mocked dependencies") { + Given("SearchFeedbackEventsFactory with mocked dependencies") { every { eventJsonParser.parse(TEST_CORE_RAW_EVENT) } answers { SearchFeedbackEvent().apply { event = SearchFeedbackEvent.EVENT_NAME @@ -95,12 +105,15 @@ internal class TelemetrySearchEventsFactoryTest { } } - eventsFactory = createEventsFactory() + feedbackEventsFactory = createEventsFactory() When("Converting search results with default feedback id") { listOf(TEST_SERVER_SEARCH_RESULT, TEST_LOCAL_SEARCH_RESULT).forEach { searchResult -> + val callback = BlockingCompletionCallback() + val isCached = searchResult is IndexableRecordSearchResult - val feedbackEvent = eventsFactory.createSearchFeedbackEvent( + + feedbackEventsFactory.createSearchFeedbackEvent( searchResult.originalSearchResult, searchResult.requestOptions, createTestCoreSearchResponseSuccess( @@ -114,7 +127,8 @@ internal class TelemetrySearchEventsFactoryTest { screenshot = mockBitmap, sessionId = TEST_SESSION_ID, ), - isCached = isCached + isCached = isCached, + callback = callback ) Then("Feedback event for ${searchResult.javaClass.simpleName} contains all values") { @@ -169,16 +183,18 @@ internal class TelemetrySearchEventsFactoryTest { searchResultsJson = TEST_SEARCH_RESULTS_INFO_JSON schema = SEARCH_FEEDBACK_SCHEMA_VERSION }, - actualValue = feedbackEvent + actualValue = callback.getResultBlocking().requireResult() ) } } } When("Converting search results with overridden feedback id") { + val callback = BlockingCompletionCallback() + val overriddenFeedbackId = "overridden-feedback-id" - val feedbackEvent = eventsFactory.createSearchFeedbackEvent( + feedbackEventsFactory.createSearchFeedbackEvent( TEST_SERVER_SEARCH_RESULT.originalSearchResult, TEST_SERVER_SEARCH_RESULT.requestOptions, createTestCoreSearchResponseSuccess( @@ -193,17 +209,22 @@ internal class TelemetrySearchEventsFactoryTest { sessionId = TEST_SESSION_ID, feedbackId = overriddenFeedbackId, ), - isCached = false + isCached = false, + callback = callback, ) - Then("Feedback id should be $overriddenFeedbackId", overriddenFeedbackId, feedbackEvent.feedbackId) + Then( + "Feedback id should be $overriddenFeedbackId", + overriddenFeedbackId, + callback.getResultBlocking().requireResult().feedbackId + ) } } } @TestFactory fun `Check SearchSuggestion converts to SearchFeedbackEvent`() = TestCase { - Given("TelemetrySearchEventsFactory with mocked dependencies") { + Given("SearchFeedbackEventsFactory with mocked dependencies") { every { eventJsonParser.parse(TEST_CORE_RAW_EVENT) } answers { SearchFeedbackEvent().apply { event = SearchFeedbackEvent.EVENT_NAME @@ -212,7 +233,7 @@ internal class TelemetrySearchEventsFactoryTest { } } - eventsFactory = createEventsFactory() + feedbackEventsFactory = createEventsFactory() When("Converting search suggestions with default feedback id") { listOf( @@ -220,8 +241,11 @@ internal class TelemetrySearchEventsFactoryTest { TEST_LOCAL_SEARCH_SUGGESTION, TEST_GEOCODING_COMPAT_SEARCH_SUGGESTION ).forEach { searchSuggestion -> + val callback = BlockingCompletionCallback() + val isCached = searchSuggestion is IndexableRecordSearchSuggestion - val feedbackEvent = eventsFactory.createSearchFeedbackEvent( + + feedbackEventsFactory.createSearchFeedbackEvent( searchSuggestion.originalSearchResult, searchSuggestion.requestOptions, null, @@ -232,7 +256,8 @@ internal class TelemetrySearchEventsFactoryTest { text = "Fix, please!", screenshot = mockBitmap, ), - isCached = isCached + isCached = isCached, + callback = callback, ) Then("Feedback event for ${searchSuggestion.javaClass.simpleName} contains all values") { @@ -282,16 +307,18 @@ internal class TelemetrySearchEventsFactoryTest { searchResultsJson = null schema = SEARCH_FEEDBACK_SCHEMA_VERSION }, - actualValue = feedbackEvent + actualValue = callback.getResultBlocking().requireResult() ) } } } When("Converting search suggestion with overridden feedback id") { + val callback = BlockingCompletionCallback() + val overriddenFeedbackId = "overridden-feedback-id" - val feedbackEvent = eventsFactory.createSearchFeedbackEvent( + feedbackEventsFactory.createSearchFeedbackEvent( TEST_SERVER_SEARCH_SUGGESTION.originalSearchResult, TEST_SERVER_SEARCH_SUGGESTION.requestOptions, null, @@ -303,17 +330,22 @@ internal class TelemetrySearchEventsFactoryTest { screenshot = mockBitmap, feedbackId = overriddenFeedbackId, ), - isCached = false + isCached = false, + callback = callback ) - Then("Feedback id should be", overriddenFeedbackId, feedbackEvent.feedbackId) + Then( + "Feedback id should be", + overriddenFeedbackId, + callback.getResultBlocking().requireResult().feedbackId + ) } } } @TestFactory fun `Check IndexableRecord converts to SearchFeedbackEvent`() = TestCase { - Given("TelemetrySearchEventsFactory with mocked dependencies") { + Given("SearchFeedbackEventsFactory with mocked dependencies") { every { eventJsonParser.parse(TEST_CORE_RAW_EVENT) } answers { SearchFeedbackEvent().apply { event = SearchFeedbackEvent.EVENT_NAME @@ -321,10 +353,10 @@ internal class TelemetrySearchEventsFactoryTest { } } - eventsFactory = createEventsFactory() + feedbackEventsFactory = createEventsFactory() When("Converting FavoriteRecord with default feedback id") { - val feedbackEvent = eventsFactory.createSearchFeedbackEvent( + val feedbackEvent = feedbackEventsFactory.createSearchFeedbackEvent( TEST_FAVORITE_RECORD, FeedbackEvent( reason = "Missing routable point", @@ -370,7 +402,7 @@ internal class TelemetrySearchEventsFactoryTest { When("Converting FavoriteRecord with overridden feedback id") { val overriddenFeedbackId = "overridden-feedback-id" - val feedbackEvent = eventsFactory.createSearchFeedbackEvent( + val feedbackEvent = feedbackEventsFactory.createSearchFeedbackEvent( TEST_FAVORITE_RECORD, FeedbackEvent( reason = "Missing routable point", @@ -384,7 +416,7 @@ internal class TelemetrySearchEventsFactoryTest { } When("Converting HistoryRecord with default feedback id") { - val feedbackEvent = eventsFactory.createSearchFeedbackEvent( + val feedbackEvent = feedbackEventsFactory.createSearchFeedbackEvent( TEST_HISTORY_RECORD, FeedbackEvent( reason = "Missing routable point", @@ -429,7 +461,7 @@ internal class TelemetrySearchEventsFactoryTest { When("Converting HistoryRecord with overridden feedback id") { val overriddenFeedbackId = "overridden-feedback-id" - val feedbackEvent = eventsFactory.createSearchFeedbackEvent( + val feedbackEvent = feedbackEventsFactory.createSearchFeedbackEvent( TEST_HISTORY_RECORD, FeedbackEvent( reason = "Missing routable point", @@ -446,7 +478,7 @@ internal class TelemetrySearchEventsFactoryTest { @TestFactory fun `Check ResponseInfo converts to SearchFeedbackEvent`() = TestCase { - Given("TelemetrySearchEventsFactory with mocked dependencies") { + Given("SearchFeedbackEventsFactory with mocked dependencies") { every { eventJsonParser.parse(TEST_CORE_RAW_EVENT) } answers { SearchFeedbackEvent().apply { event = SearchFeedbackEvent.EVENT_NAME @@ -455,11 +487,13 @@ internal class TelemetrySearchEventsFactoryTest { } } - eventsFactory = createEventsFactory() + feedbackEventsFactory = createEventsFactory() When("Converting ResponseInfo with default feedback id") { - val feedbackEvent = eventsFactory.createSearchFeedbackEvent( - MissingResultFeedbackEvent( + val callback = BlockingCompletionCallback() + + feedbackEventsFactory.createSearchFeedbackEvent( + event = MissingResultFeedbackEvent( ResponseInfo( requestOptions = TEST_REQUEST_OPTIONS, coreSearchResponse = createTestCoreSearchResponseSuccess( @@ -471,7 +505,8 @@ internal class TelemetrySearchEventsFactoryTest { sessionId = TEST_SESSION_ID, screenshot = mockBitmap ), - TEST_USER_LOCATION + currentLocation = TEST_USER_LOCATION, + callback = callback, ) Then("Feedback event contains all values") { @@ -517,16 +552,18 @@ internal class TelemetrySearchEventsFactoryTest { searchResultsJson = TEST_SEARCH_RESULTS_INFO_JSON schema = SEARCH_FEEDBACK_SCHEMA_VERSION }, - actualValue = feedbackEvent + actualValue = callback.getResultBlocking().requireResult() ) } } When("Converting ResponseInfo with overridden feedback id") { + val callback = BlockingCompletionCallback() + val overriddenFeedbackId = "overridden-feedback-id" - val feedbackEvent = eventsFactory.createSearchFeedbackEvent( - MissingResultFeedbackEvent( + feedbackEventsFactory.createSearchFeedbackEvent( + event = MissingResultFeedbackEvent( ResponseInfo( requestOptions = TEST_REQUEST_OPTIONS, coreSearchResponse = createTestCoreSearchResponseSuccess( @@ -539,74 +576,15 @@ internal class TelemetrySearchEventsFactoryTest { screenshot = mockBitmap, feedbackId = overriddenFeedbackId, ), - TEST_USER_LOCATION + currentLocation = TEST_USER_LOCATION, + callback = callback, ) - Then("Feedback id should be $overriddenFeedbackId", overriddenFeedbackId, feedbackEvent.feedbackId) - } - } - } - - @TestFactory - fun `Check cached SearchFeedbackEvent`() = TestCase { - Given("TelemetrySearchEventsFactory with mocked dependencies") { - val eventsFactory = createEventsFactory() - val cachedSearchEvent = SearchFeedbackEvent().apply { - event = SearchFeedbackEvent.EVENT_NAME - selectedItemName = "cached-item" - sessionIdentifier = "cached-session-id" - resultIndex = -1 - queryString = "cached query" - schema = "search.feedback-2.0" - } - - When("Updating cached search feedback event: $cachedSearchEvent") { - eventsFactory.updateCachedSearchFeedbackEvent( - cachedSearchEvent, - FeedbackEvent( - reason = "Missing routable point", - text = "Fix, please!", - screenshot = mockBitmap, - sessionId = TEST_SESSION_ID - ), - TEST_USER_LOCATION + Then( + "Feedback id should be $overriddenFeedbackId", + overriddenFeedbackId, + callback.getResultBlocking().requireResult().feedbackId ) - - Then("Feedback event was properly created from raw event") { - assertEqualsJsonify( - expectedValue = SearchFeedbackEvent().apply { - event = SearchFeedbackEvent.EVENT_NAME - cached = true - created = TEST_EVENT_CREATION_DATE - latitude = TEST_USER_LOCATION.latitude() - longitude = TEST_USER_LOCATION.longitude() - resultIndex = -1 - userAgent = TEST_USER_AGENT - queryString = "cached query" - feedbackReason = "Missing routable point" - feedbackText = "Fix, please!" - selectedItemName = "cached-item" - mapZoom = calculateMapZoom(TEST_VIEWPORT) - mapCenterLatitude = TEST_VIEWPORT.centerLatitude() - mapCenterLongitude = TEST_VIEWPORT.centerLongitude() - sessionIdentifier = "cached-session-id" - feedbackId = TEST_UUID - if (BuildConfig.DEBUG) { - isTest = true - } - screenshot = TEST_ENCODED_BITMAP - appMetadata = AppMetadata( - name = null, - version = null, - userId = null, - sessionId = TEST_SESSION_ID - ) - searchResultsJson = null - schema = "search.feedback-2.0" - }, - actualValue = cachedSearchEvent - ) - } } } } diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/TelemetryServiceTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/TelemetryServiceTest.kt deleted file mode 100644 index dfd7b2f34..000000000 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/TelemetryServiceTest.kt +++ /dev/null @@ -1,424 +0,0 @@ -package com.mapbox.search.analytics.events - -import android.content.Context -import android.location.Location -import com.mapbox.android.core.location.LocationEngine -import com.mapbox.android.core.permissions.PermissionsManager -import com.mapbox.android.telemetry.MapboxCrashReporter -import com.mapbox.android.telemetry.MapboxTelemetry -import com.mapbox.geojson.Point -import com.mapbox.search.BuildConfig -import com.mapbox.search.ResponseInfo -import com.mapbox.search.analytics.AnalyticsEventJsonParser -import com.mapbox.search.analytics.FeedbackEvent -import com.mapbox.search.analytics.MissingResultFeedbackEvent -import com.mapbox.search.analytics.TelemetrySearchEventsFactory -import com.mapbox.search.analytics.TelemetryService -import com.mapbox.search.common.FixedPointLocationEngine -import com.mapbox.search.result.mapToPlatform -import com.mapbox.search.tests_support.catchThrowable -import com.mapbox.search.tests_support.createTestCoreSearchResponseSuccess -import com.mapbox.search.tests_support.createTestFavoriteRecord -import com.mapbox.search.tests_support.createTestRequestOptions -import com.mapbox.search.tests_support.createTestSearchResult -import com.mapbox.search.tests_support.createTestSuggestion -import com.mapbox.test.dsl.TestCase -import io.mockk.Called -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkStatic -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.TestFactory -import java.lang.IllegalStateException - -@Suppress("LargeClass") -internal class TelemetryServiceTest { - - private lateinit var context: Context - private lateinit var locationEngine: LocationEngine - private lateinit var mapBoxTelemetry: MapboxTelemetry - private lateinit var eventsJsonParser: AnalyticsEventJsonParser - private lateinit var eventsFactory: TelemetrySearchEventsFactory - private lateinit var crashReporter: MapboxCrashReporter - private lateinit var telemetryService: TelemetryService - - @BeforeEach - fun setUp() { - val mockedLocation = mockk(relaxed = true) - every { mockedLocation.latitude } returns TEST_LOCATION.latitude() - every { mockedLocation.longitude } returns TEST_LOCATION.longitude() - - context = mockk(relaxed = true) - locationEngine = FixedPointLocationEngine(mockedLocation) - mapBoxTelemetry = mockk(relaxed = true) - eventsJsonParser = mockk() - eventsFactory = mockk(relaxed = true) - crashReporter = mockk() - - mockkStatic(PermissionsManager::class) - every { PermissionsManager.areLocationPermissionsGranted(any()) } returns true } - - @AfterEach - fun tearDown() { - unmockkStatic(PermissionsManager::class) - } - - @TestFactory - fun `Send feedback for SearchSuggestion with valid data`() = TestCase { - Given("TelemetryAnalyticsService with mocked dependencies") { - val searchSuggestion = createTestSuggestion() - val mockedFeedbackEvent = mockk() - every { - eventsFactory.createSearchFeedbackEvent( - originalSearchResult = searchSuggestion.originalSearchResult, - requestOptions = searchSuggestion.requestOptions, - searchResponse = TEST_RESPONSE_INFO.coreSearchResponse, - currentLocation = TEST_LOCATION, - isReproducible = true, - event = TEST_FEEDBACK_EVENT, - isCached = any(), - ) - } returns mockedFeedbackEvent - telemetryService = TelemetryService( - context, mapBoxTelemetry, locationEngine, eventsJsonParser, eventsFactory, crashReporter - ) - - When("Sending feedback for search suggestion with valid data") { - every { mockedFeedbackEvent.isValid } returns true - telemetryService.sendFeedback(searchSuggestion, TEST_RESPONSE_INFO, TEST_FEEDBACK_EVENT) - - VerifyOnce("Telemetry called once") { mapBoxTelemetry.push(mockedFeedbackEvent) } - } - } - } - - @TestFactory - fun `Send feedback for SearchSuggestion with invalid data`() = TestCase { - Given("TelemetryAnalyticsService with mocked dependencies") { - val searchSuggestion = createTestSuggestion() - val mockedFeedbackEvent = mockk() - every { - eventsFactory.createSearchFeedbackEvent( - originalSearchResult = searchSuggestion.originalSearchResult, - requestOptions = searchSuggestion.requestOptions, - searchResponse = TEST_RESPONSE_INFO.coreSearchResponse, - currentLocation = TEST_LOCATION, - isReproducible = true, - event = TEST_FEEDBACK_EVENT, - isCached = any(), - ) - } returns mockedFeedbackEvent - telemetryService = TelemetryService( - context, mapBoxTelemetry, locationEngine, eventsJsonParser, eventsFactory, crashReporter - ) - - When("Sending feedback for search suggestion with invalid data") { - every { mockedFeedbackEvent.isValid } returns false - val caughtException = catchThrowable { - telemetryService.sendFeedback(searchSuggestion, TEST_RESPONSE_INFO, TEST_FEEDBACK_EVENT) - } - - Verify("Telemetry isn't called") { mapBoxTelemetry wasNot Called } - - if (BuildConfig.DEBUG) { - Then("IllegalStateException was thrown") { - Assertions.assertTrue(caughtException is IllegalStateException) - } - } - } - } - } - - @TestFactory - fun `Send feedback for SearchResult with valid data`() = TestCase { - Given("TelemetryAnalyticsService with mocked dependencies") { - val searchResult = createTestSearchResult() - val mockedFeedbackEvent = mockk() - every { - eventsFactory.createSearchFeedbackEvent( - originalSearchResult = searchResult.originalSearchResult, - requestOptions = searchResult.requestOptions, - searchResponse = TEST_RESPONSE_INFO.coreSearchResponse, - currentLocation = TEST_LOCATION, - isReproducible = true, - event = TEST_FEEDBACK_EVENT, - isCached = any(), - ) - } returns mockedFeedbackEvent - - telemetryService = TelemetryService( - context, mapBoxTelemetry, locationEngine, eventsJsonParser, eventsFactory, crashReporter - ) - - When("Sending feedback for search result with valid data") { - every { mockedFeedbackEvent.isValid } returns true - telemetryService.sendFeedback(searchResult, TEST_RESPONSE_INFO, TEST_FEEDBACK_EVENT) - - VerifyOnce("Telemetry called once") { mapBoxTelemetry.push(mockedFeedbackEvent) } - } - } - } - - @TestFactory - fun `Send feedback for SearchResult with invalid data`() = TestCase { - Given("TelemetryAnalyticsService with mocked dependencies") { - val searchResult = createTestSearchResult() - val mockedFeedbackEvent = mockk() - every { - eventsFactory.createSearchFeedbackEvent( - originalSearchResult = searchResult.originalSearchResult, - requestOptions = searchResult.requestOptions, - searchResponse = TEST_RESPONSE_INFO.coreSearchResponse, - currentLocation = TEST_LOCATION, - isReproducible = true, - event = TEST_FEEDBACK_EVENT, - isCached = any(), - ) - } returns mockedFeedbackEvent - telemetryService = TelemetryService( - context, mapBoxTelemetry, locationEngine, eventsJsonParser, eventsFactory, crashReporter - ) - - When("Sending feedback for search result with invalid data") { - every { mockedFeedbackEvent.isValid } returns false - val caughtException = catchThrowable { - telemetryService.sendFeedback(searchResult, TEST_RESPONSE_INFO, TEST_FEEDBACK_EVENT) - } - - Verify("Telemetry isn't called") { mapBoxTelemetry wasNot Called } - - if (BuildConfig.DEBUG) { - Then("IllegalStateException was thrown") { - Assertions.assertTrue(caughtException is IllegalStateException) - } - } - } - } - } - - @TestFactory - fun `Send feedback for IndexableRecord with valid data`() = TestCase { - Given("TelemetryAnalyticsService with mocked dependencies") { - val favoriteRecord = createTestFavoriteRecord() - val mockedFeedbackEvent = mockk() - every { - eventsFactory.createSearchFeedbackEvent(favoriteRecord, TEST_FEEDBACK_EVENT, TEST_LOCATION) - } returns mockedFeedbackEvent - telemetryService = TelemetryService( - context, mapBoxTelemetry, locationEngine, eventsJsonParser, eventsFactory, crashReporter - ) - - When("Sending feedback for indexable record with valid data") { - every { mockedFeedbackEvent.isValid } returns true - telemetryService.sendFeedback(favoriteRecord, TEST_FEEDBACK_EVENT) - - VerifyOnce("Telemetry called once") { mapBoxTelemetry.push(mockedFeedbackEvent) } - } - } - } - - @TestFactory - fun `Send feedback for IndexableRecord with invalid data`() = TestCase { - Given("TelemetryAnalyticsService with mocked dependencies") { - val favoriteRecord = createTestFavoriteRecord() - val mockedFeedbackEvent = mockk() - every { - eventsFactory.createSearchFeedbackEvent(favoriteRecord, TEST_FEEDBACK_EVENT, TEST_LOCATION) - } returns mockedFeedbackEvent - telemetryService = TelemetryService( - context, mapBoxTelemetry, locationEngine, eventsJsonParser, eventsFactory, crashReporter - ) - - When("Sending feedback for indexable record with invalid data") { - every { mockedFeedbackEvent.isValid } returns false - val caughtException = catchThrowable { - telemetryService.sendFeedback(favoriteRecord, TEST_FEEDBACK_EVENT) - } - - Verify("Telemetry isn't called") { mapBoxTelemetry wasNot Called } - - if (BuildConfig.DEBUG) { - Then("IllegalStateException was thrown") { - Assertions.assertTrue(caughtException is IllegalStateException) - } - } - } - } - } - - @TestFactory - fun `Send missing result feedback for ResponseInfo with valid data`() = TestCase { - Given("TelemetryAnalyticsService with mocked dependencies") { - val mockedFeedbackEvent = mockk() - every { - eventsFactory.createSearchFeedbackEvent(TEST_MISSING_RESULT_FEEDBACK_EVENT, TEST_LOCATION) - } returns mockedFeedbackEvent - telemetryService = TelemetryService( - context, mapBoxTelemetry, locationEngine, eventsJsonParser, eventsFactory, crashReporter - ) - - When("Sending feedback for response info with valid data") { - every { mockedFeedbackEvent.isValid } returns true - telemetryService.sendMissingResultFeedback(TEST_MISSING_RESULT_FEEDBACK_EVENT) - - VerifyOnce("Telemetry called once") { mapBoxTelemetry.push(mockedFeedbackEvent) } - } - } - } - - @TestFactory - fun `Send missing result feedback for ResponseInfo with invalid data`() = TestCase { - Given("TelemetryAnalyticsService with mocked dependencies") { - val mockedFeedbackEvent = mockk() - every { - eventsFactory.createSearchFeedbackEvent(TEST_MISSING_RESULT_FEEDBACK_EVENT, TEST_LOCATION) - } returns mockedFeedbackEvent - telemetryService = TelemetryService( - context, mapBoxTelemetry, locationEngine, eventsJsonParser, eventsFactory, crashReporter - ) - - When("Sending feedback for indexable record with invalid data") { - every { mockedFeedbackEvent.isValid } returns false - val caughtException = catchThrowable { - telemetryService.sendMissingResultFeedback(TEST_MISSING_RESULT_FEEDBACK_EVENT) - } - - Verify("Telemetry isn't called") { mapBoxTelemetry wasNot Called } - - if (BuildConfig.DEBUG) { - Then("IllegalStateException was thrown") { - Assertions.assertTrue(caughtException is IllegalStateException) - } - } - } - } - } - - @TestFactory - fun `Create event raw feedback event`() = TestCase { - Given("TelemetryAnalyticsService with mocked dependencies") { - val searchResult = createTestSearchResult() - val mockedFeedbackEvent = mockk() - every { - eventsFactory.createSearchFeedbackEvent( - originalSearchResult = any(), - requestOptions = any(), - searchResponse = any(), - currentLocation = null, - isReproducible = true, - event = null, - isCached = any(), - asTemplate = true, - ) - } returns mockedFeedbackEvent - every { eventsJsonParser.serialize(mockedFeedbackEvent) } returns TEST_RAW_EVENT - telemetryService = TelemetryService( - context, mapBoxTelemetry, locationEngine, eventsJsonParser, eventsFactory, crashReporter - ) - - When("Creating raw event for SearchResult") { - val rawEvent = telemetryService.createRawFeedbackEvent(searchResult, TEST_RESPONSE_INFO) - - Then("Raw event was successfully created", rawEvent, TEST_RAW_EVENT) - } - - When("Creating raw event for SearchSuggestion") { - val searchSuggestion = createTestSuggestion() - val rawEvent = telemetryService.createRawFeedbackEvent(searchSuggestion, TEST_RESPONSE_INFO) - - Then("Raw event was successfully created", rawEvent, TEST_RAW_EVENT) - } - } - } - - @TestFactory - fun `Send feedback for valid raw feedback event`() = TestCase { - Given("TelemetryAnalyticsService with mocked dependencies") { - val searchResult = createTestSearchResult() - val mockedFeedbackEvent = mockk() - every { - eventsFactory.createSearchFeedbackEvent( - originalSearchResult = any(), - requestOptions = any(), - searchResponse = any(), - currentLocation = null, - isReproducible = true, - event = null, - isCached = any(), - asTemplate = true, - ) - } returns mockedFeedbackEvent - every { eventsJsonParser.serialize(mockedFeedbackEvent) } returns TEST_RAW_EVENT - telemetryService = TelemetryService( - context, mapBoxTelemetry, locationEngine, eventsJsonParser, eventsFactory, crashReporter - ) - - When("Sending feedback for valid raw feedback event") { - every { eventsJsonParser.parse(TEST_RAW_EVENT) } returns mockedFeedbackEvent - every { mockedFeedbackEvent.isValid } returns true - val rawEvent = telemetryService.createRawFeedbackEvent(searchResult, TEST_RESPONSE_INFO) - telemetryService.sendRawFeedbackEvent(rawEvent, TEST_FEEDBACK_EVENT) - - VerifyOnce("Telemetry called once") { mapBoxTelemetry.push(mockedFeedbackEvent) } - } - } - } - - @TestFactory - fun `Send feedback for invalid raw feedback event`() = TestCase { - Given("TelemetryAnalyticsService with mocked dependencies") { - val searchResult = createTestSearchResult() - val mockedFeedbackEvent = mockk() - every { - eventsFactory.createSearchFeedbackEvent( - originalSearchResult = any(), - requestOptions = any(), - searchResponse = any(), - currentLocation = null, - isReproducible = true, - event = null, - isCached = any(), - asTemplate = true, - ) - } returns mockedFeedbackEvent - every { eventsJsonParser.serialize(mockedFeedbackEvent) } returns TEST_RAW_EVENT - telemetryService = TelemetryService( - context, mapBoxTelemetry, locationEngine, eventsJsonParser, eventsFactory, crashReporter - ) - - When("Sending feedback for invalid raw feedback event") { - every { eventsJsonParser.parse(TEST_RAW_EVENT) } throws IllegalArgumentException() - every { mockedFeedbackEvent.isValid } returns false - val rawEvent = telemetryService.createRawFeedbackEvent(searchResult, TEST_RESPONSE_INFO) - telemetryService.sendRawFeedbackEvent(rawEvent, TEST_FEEDBACK_EVENT) - - VerifyNo("Event factory isn't called") { - eventsFactory.updateCachedSearchFeedbackEvent(any(), any(), TEST_LOCATION) - } - - VerifyNo("Telemetry isn't RequestOptionscalled") { - mapBoxTelemetry.push(any()) - } - } - } - } - - private companion object { - - const val TEST_RAW_EVENT = "{\"event\":\"search.feedback\"}" - val TEST_LOCATION: Point = Point.fromLngLat(10.0, 20.0) - val TEST_FEEDBACK_EVENT = FeedbackEvent("Missing routable point", "Fix, please!") - val TEST_RESPONSE_INFO = ResponseInfo( - requestOptions = createTestRequestOptions(), - coreSearchResponse = createTestCoreSearchResponseSuccess().mapToPlatform(), - isReproducible = true, - ) - val TEST_MISSING_RESULT_FEEDBACK_EVENT = MissingResultFeedbackEvent( - TEST_RESPONSE_INFO, - "Fix, please!" - ) - } -} diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/PlatformClientImplTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/PlatformClientImplTest.kt deleted file mode 100644 index c48c3a8ae..000000000 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/PlatformClientImplTest.kt +++ /dev/null @@ -1,196 +0,0 @@ -package com.mapbox.search.core - -import com.mapbox.search.TestConstants -import com.mapbox.search.analytics.InternalAnalyticsService -import com.mapbox.search.common.logger.DEFAULT_SEARCH_SDK_LOG_TAG -import com.mapbox.search.common.logger.logd -import com.mapbox.search.common.logger.loge -import com.mapbox.search.common.logger.logi -import com.mapbox.search.common.logger.logw -import com.mapbox.search.core.http.HttpClient -import com.mapbox.search.tests_support.TestMainThreadWorker -import com.mapbox.search.tests_support.TestThreadExecutorService -import com.mapbox.search.utils.UUIDProvider -import com.mapbox.search.utils.concurrent.MainThreadWorker -import com.mapbox.test.dsl.TestCase -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.spyk -import io.mockk.unmockkStatic -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.TestFactory -import java.util.concurrent.ExecutorService -import java.util.concurrent.TimeUnit - -internal class PlatformClientImplTest { - - private lateinit var platformClient: PlatformClientImpl - private lateinit var httpClient: HttpClient - private lateinit var analyticsService: InternalAnalyticsService - private lateinit var uuidProvider: UUIDProvider - private lateinit var callbackExecutor: ExecutorService - private lateinit var callbackDecorator: (CoreHttpCallback) -> CoreHttpCallback - private lateinit var mainThreadWorker: MainThreadWorker - - @BeforeEach - fun setUp() { - httpClient = mockk() - analyticsService = mockk() - uuidProvider = mockk() - callbackExecutor = spyk(TestThreadExecutorService()) - mainThreadWorker = spyk(TestMainThreadWorker()) - callbackDecorator = mockk() - - every { callbackDecorator(TEST_HTTP_CALLBACK) } returns TEST_HTTP_CALLBACK_2 - - platformClient = PlatformClientImpl( - httpClient, analyticsService, uuidProvider, mainThreadWorker, callbackDecorator - ) - } - - @TestFactory - fun `Check httpGet function`() = TestCase { - every { httpClient.httpGet(any(), any(), any(), any()) }.returns(Unit) - - Given("PlatformClientImpl with mocked http client") { - When("Call httpGet()") { - platformClient.httpRequest(TEST_URL, null, TEST_REQUEST_ID, TEST_SESSION_ID, TEST_HTTP_CALLBACK) - - Verify("Call dispatched to dependency") { - httpClient.httpGet( - TEST_URL, - TEST_REQUEST_ID, - TEST_SESSION_ID, - TEST_HTTP_CALLBACK_2, - ) - } - - Verify("Callback decorator was called") { - callbackDecorator(TEST_HTTP_CALLBACK) - } - } - } - } - - @TestFactory - fun `Check log function`() = TestCase { - Given("PlatformClientImpl with mocked search logger") { - When("Call log(DEBUG)") { - platformClient.log(CoreLogLevel.DEBUG, TEST_LOG_MSG) - - Verify("Call dispatched to Log") { - logd(message = TEST_LOG_MSG, tag = DEFAULT_SEARCH_SDK_LOG_TAG) - } - } - - When("Call log(INFO)") { - platformClient.log(CoreLogLevel.INFO, TEST_LOG_MSG) - - Verify("Call dispatched to Log") { - logi(message = TEST_LOG_MSG, tag = DEFAULT_SEARCH_SDK_LOG_TAG) - } - } - - When("Call log(WARNING)") { - platformClient.log(CoreLogLevel.WARNING, TEST_LOG_MSG) - - Verify("Call dispatched to Log") { - logw(message = TEST_LOG_MSG, tag = DEFAULT_SEARCH_SDK_LOG_TAG) - } - } - - When("Call log(ERROR)") { - platformClient.log(CoreLogLevel.ERROR, TEST_LOG_MSG) - - Verify("Call dispatched to Log") { - loge(message = TEST_LOG_MSG, tag = DEFAULT_SEARCH_SDK_LOG_TAG) - } - } - } - } - - @TestFactory - fun `Check postEvent function`() = TestCase { - every { analyticsService.postJsonEvent(any()) }.returns(Unit) - - Given("PlatformClientImpl with mocked analytics service") { - When("Call postEvent") { - platformClient.postEvent(TEST_JSON_EVENT) - - Verify("Call dispatched to dependency") { - analyticsService.postJsonEvent(TEST_JSON_EVENT) - } - } - } - } - - @TestFactory - fun `Check scheduleTask function`() = TestCase { - val task: CoreTaskFunction = mockk() - every { task.run() }.returns(Unit) - - Given("PlatformClientImpl with mocked executor") { - When("Call scheduleTask()") { - platformClient.scheduleTask(task, TEST_SCHEDULE_DELAY_MILLIS) - - Verify("Call dispatched to dependency") { - mainThreadWorker.postDelayed( - TEST_SCHEDULE_DELAY_MILLIS.toLong(), - TimeUnit.MILLISECONDS, - any() - ) - } - - Verify("Task function called") { - task.run() - } - } - } - } - - @TestFactory - fun `Check uuid function`() = TestCase { - every { uuidProvider.generateUUID() }.returns(TEST_UUID) - - Given("PlatformClientImpl with mocked UUID provider") { - When("generateUUID() called") { - val generatedUUID = platformClient.generateUUID() - - Then("Returned UUID is $TEST_UUID", TEST_UUID, generatedUUID) - Verify("Call dispatched to dependency") { - uuidProvider.generateUUID() - } - } - } - } - - private companion object { - - const val TEST_URL = "https://mapbox.com" - const val TEST_SESSION_ID = "session.id.test" - const val TEST_REQUEST_ID = 0 - const val TEST_UUID = "test-generated-uuid" - val TEST_HTTP_CALLBACK = CoreHttpCallback { _, _ -> } - val TEST_HTTP_CALLBACK_2 = CoreHttpCallback { _, _ -> } - const val TEST_LOG_MSG = "TestMsg" - const val TEST_SCHEDULE_DELAY_MILLIS = 0 - const val TEST_JSON_EVENT = "{}" - - @Suppress("DEPRECATION", "JVM_STATIC_IN_PRIVATE_COMPANION") - @BeforeAll - @JvmStatic - fun setUpAll() { - mockkStatic(TestConstants.LOG_KT_CLASS_NAME) - } - - @Suppress("JVM_STATIC_IN_PRIVATE_COMPANION") - @AfterAll - @JvmStatic - fun tearDownAll() { - unmockkStatic(TestConstants.LOG_KT_CLASS_NAME) - } - } -} diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/http/HttpClientImplTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/http/HttpClientImplTest.kt deleted file mode 100644 index 850fe39bc..000000000 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/http/HttpClientImplTest.kt +++ /dev/null @@ -1,188 +0,0 @@ -package com.mapbox.search.core.http - -import com.mapbox.search.core.CoreHttpCallback -import com.mapbox.search.utils.UUIDProvider -import com.mapbox.test.dsl.TestCase -import io.mockk.every -import io.mockk.mockk -import io.mockk.slot -import io.mockk.spyk -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.mock.MediaTypes.MEDIATYPE_JSON -import okhttp3.mock.MockInterceptor -import okhttp3.mock.body -import okhttp3.mock.respond -import okhttp3.mock.rule -import okhttp3.mock.startWith -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.TestFactory -import java.io.IOException - -internal class HttpClientImplTest { - - private lateinit var mockedHttpErrorsCache: HttpErrorsCache - private lateinit var mockedOkHttp: OkHttpClient - private lateinit var mockedUUIDProvider: UUIDProvider - private lateinit var mockedUserAgentProvider: UserAgentProvider - - @BeforeEach - fun setUp() { - mockedHttpErrorsCache = mockk(relaxed = true) - mockedOkHttp = mockk() - - mockedUUIDProvider = mockk(relaxed = true) - every { mockedUUIDProvider.generateUUID() } returns TEST_UUID - - mockedUserAgentProvider = mockk() - every { mockedUserAgentProvider.userAgent() } returns TEST_USER_AGENT - } - - @TestFactory - fun `Check url and headers`() = TestCase { - val testUrl = "https://api.mapbox.com/" - val testSessionId = "Test session id" - - Given("HttpClientImpl with mocked OkHttpClient") { - val coreCallback = spyk() - val slot = slot() - every { mockedOkHttp.newCall(capture(slot)) } returns mockk() - - val httpClient = HttpClientImpl(mockedOkHttp, mockedHttpErrorsCache, mockedUUIDProvider, mockedUserAgentProvider) - - When("Send request $testUrl with sessionId=\"$testSessionId\"") { - httpClient.httpGet(testUrl, 0, testSessionId, coreCallback) - - Then( - "Url should be $testUrl", - testUrl, - slot.captured.url.toString() - ) - - Then( - "Session id should be \"$testSessionId\"", - testSessionId, - slot.captured.header("X-MBX-SEARCH-SID") - ) - - Then( - "'X-Request-ID' header should be '$TEST_UUID'", - TEST_UUID, - slot.captured.header("X-Request-ID") - ) - - Verify("UUID received from provider") { - mockedUUIDProvider.generateUUID() - } - - Then( - "'User-Agent' header should be '$TEST_USER_AGENT'", - TEST_USER_AGENT, - slot.captured.header("User-Agent") - ) - - Verify("User agent received from provider") { - mockedUserAgentProvider.userAgent() - } - } - } - } - - @TestFactory - fun `Check http client implementation`() = TestCase { - Given("HttpClientImpl with mocked client") { - val coreCallback = spyk() - testData.forEach { (input, expected) -> - val httpClient = HttpClientImpl( - OkHttpHelper(debugLogsEnabled = false).getMockedClient(input.getInterceptor()), - mockedHttpErrorsCache, - mockedUUIDProvider, - mockedUserAgentProvider - ) - When("Send request ${input.url}") { - httpClient.httpGet(input.url, TEST_REQUEST_ID, "", coreCallback) - Verify( - "Callback with body = ${expected.body}, code = ${expected.code}", - timeoutMs = 500L - ) { - coreCallback.run(expected.body, expected.code) - } - - if (expected.isError) { - Verify("Error saved to errors cache", timeoutMs = 500L) { - mockedHttpErrorsCache.put(TEST_REQUEST_ID, any()) - } - } - } - } - } - } - - private companion object { - - const val TEST_UUID = "test-generated-uuid" - const val TEST_USER_AGENT = "test-user-agent" - const val TEST_REQUEST_ID = 0 - - val testData = mapOf( - NormalInput("http://request1", 200, "") to ExpectedCall("", 200, isError = false), - NormalInput("http://request2", 500, "error") to ExpectedCall("error", 500, isError = true), - NormalInput( - "htt://request3", - 500, - "error" - ) to ExpectedCall( - "Expected URL scheme 'http' or 'https' but was 'htt'", - Int.MIN_VALUE, - isError = true - ), - ExceptionInput("http://request4", 500, IOException()) to ExpectedCall( - "http error", - Int.MIN_VALUE, - isError = true - ), - ExceptionInput("http://request5", 500, IOException("error")) to ExpectedCall( - "error", - Int.MIN_VALUE, - isError = true - ) - ) - } -} - -internal sealed class InputParams(open val url: String, open val httpCode: Int) { - abstract fun getInterceptor(): MockInterceptor -} - -internal data class ExceptionInput( - override val url: String, - override val httpCode: Int, - val exception: Exception -) : - InputParams(url, httpCode) { - - override fun getInterceptor() = MockInterceptor().apply { - rule(okhttp3.mock.url startWith url) { - respond(code = httpCode) { - throw exception - } - } - } -} - -internal data class NormalInput( - override val url: String, - override val httpCode: Int, - val body: String -) : InputParams(url, httpCode) { - - override fun getInterceptor() = MockInterceptor().apply { - rule(okhttp3.mock.url startWith url) { - respond(code = httpCode) { - body(body, MEDIATYPE_JSON) - } - } - } -} - -internal data class ExpectedCall(val body: String, val code: Int, val isError: Boolean) diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/http/HttpErrorsCacheTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/http/HttpErrorsCacheTest.kt deleted file mode 100644 index 9e0b05df5..000000000 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/http/HttpErrorsCacheTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.mapbox.search.core.http - -import com.mapbox.test.dsl.TestCase -import org.junit.jupiter.api.TestFactory - -internal class HttpErrorsCacheTest { - - @TestFactory - fun `Test HttpErrorsCache`() = TestCase { - Given("HttpErrorsCache") { - val httpErrorsCache = HttpErrorsCacheImpl() - - When("Save error to cache and retrieve error with existing request id") { - val requestId = 0 - val exception = Exception() - httpErrorsCache.put(requestId, exception) - Then("Saved error should be returned", exception, httpErrorsCache.getAndRemove(requestId)) - } - - When("Save error to cache, retrieve error with existing request id and try to retrieve it again") { - val requestId = 0 - val exception = Exception() - httpErrorsCache.put(requestId, exception) - Then("First retrieve should return correct saved error", exception, httpErrorsCache.getAndRemove(requestId)) - Then("Next retrieves should return null", null, httpErrorsCache.getAndRemove(requestId)) - } - - When("Save error to cache and retrieve error with unknown request id") { - val requestId = 0 - httpErrorsCache.put(requestId, Exception()) - Then("Returned error should be null", null, httpErrorsCache.getAndRemove(requestId + 1)) - } - } - } -} diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/http/UserAgentProviderTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/http/UserAgentProviderTest.kt deleted file mode 100644 index 2a693be68..000000000 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/core/http/UserAgentProviderTest.kt +++ /dev/null @@ -1,174 +0,0 @@ -package com.mapbox.search.core.http - -import android.content.Context -import android.content.pm.ApplicationInfo -import android.content.pm.PackageInfo -import android.content.pm.PackageManager -import android.content.res.Configuration -import android.os.Build -import com.mapbox.search.common.BuildConfig -import com.mapbox.test.dsl.TestCase -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.TestFactory - -internal class UserAgentProviderTest { - - private lateinit var packageInfo: PackageInfo - private lateinit var packageManager: PackageManager - private lateinit var context: Context - private lateinit var userAgentProvider: UserAgentProvider - - @BeforeEach - fun setUp() { - packageInfo = mockk() - packageInfo.versionName = TEST_VERSION_NAME - @Suppress("DEPRECATION") - packageInfo.versionCode = TEST_VERSION_CODE - if (Build.VERSION.SDK_INT >= 28) { - packageInfo.longVersionCode = TEST_VERSION_CODE.toLong() - } - - packageManager = mockk(relaxed = true) - every { packageManager.getPackageInfo(TEST_APP_PACKAGE, any()) } returns packageInfo - - context = mockk(relaxed = true) - every { context.packageName } returns TEST_APP_PACKAGE - every { context.packageManager } returns packageManager - - userAgentProvider = UserAgentProviderImpl(context) - } - - @TestFactory - fun `Test user agent`() = TestCase { - Given("UserAgentProvider with mocked dependencies and known PackageInfo") { - every { packageManager.getApplicationLabel(any()) } returns TEST_APP_LABEL - - When("Access user agent") { - val userAgent = "$TEST_APP_LABEL/$TEST_VERSION_NAME " + - "($TEST_APP_PACKAGE; build:$TEST_VERSION_CODE; Android ${Build.VERSION.RELEASE}) " + - "MapboxSearchSDK-Android/${BuildConfig.VERSION_NAME}" - - Then("Returned value should be", userAgent, userAgentProvider.userAgent()) - - Verify("context.packageName was accessed") { - context.packageName - } - - Verify("packageManager.getApplicationLabel() was accessed") { - packageManager.getApplicationLabel(any()) - } - } - } - } - - @TestFactory - fun `Test user agent with unknown PackageInfo`() = TestCase { - Given("UserAgentProvider with mocked dependencies and unknown PackageInfo") { - every { packageManager.getApplicationLabel(any()) } returns TEST_APP_LABEL - every { packageManager.getPackageInfo(TEST_APP_PACKAGE, any()) } returns null - - When("Access user agent") { - val userAgent = "$TEST_APP_LABEL/Unknown " + - "($TEST_APP_PACKAGE; build:Unknown; Android ${Build.VERSION.RELEASE}) " + - "MapboxSearchSDK-Android/${BuildConfig.VERSION_NAME}" - - Then("Returned value should be", userAgent, userAgentProvider.userAgent()) - - Verify("context.packageName was accessed") { - context.packageName - } - - Verify("packageManager.getApplicationLabel() was accessed") { - packageManager.getApplicationLabel(any()) - } - } - } - } - - @TestFactory - fun `Test user agent with app name initialized from string resource with ASCII symbols`() = TestCase { - Given("UserAgentProvider with mocked dependencies") { - every { packageManager.getApplicationLabel(any()) } returns TEST_LOCALIZED_APP_LABEL - - val appInfo = ApplicationInfo().apply { - labelRes = TEST_LOCALIZED_APP_NAME_RESOURCE_ID - } - every { context.applicationInfo } returns appInfo - every { context.resources.configuration } returns Configuration() - val overriddenContext = mockk { - every { getText(TEST_LOCALIZED_APP_NAME_RESOURCE_ID) } returns TEST_APP_LABEL - } - every { context.createConfigurationContext(any()) } returns overriddenContext - - When("Access user agent") { - val userAgent = "$TEST_APP_LABEL/$TEST_VERSION_NAME " + - "($TEST_APP_PACKAGE; build:$TEST_VERSION_CODE; Android ${Build.VERSION.RELEASE}) " + - "MapboxSearchSDK-Android/${BuildConfig.VERSION_NAME}" - - Then("Returned value should be", userAgent, userAgentProvider.userAgent()) - - Verify("context.packageName was called") { - context.packageName - } - - Verify("overriddenContext was called") { - overriddenContext.getText(TEST_LOCALIZED_APP_NAME_RESOURCE_ID) - } - - VerifyNo("packageManager.getApplicationLabel() wasn't called") { - packageManager.getApplicationLabel(any()) - } - } - } - } - - @TestFactory - fun `Test user agent with app name initialized from string resource with non-ASCII symbols`() = TestCase { - Given("UserAgentProvider with mocked dependencies") { - every { packageManager.getApplicationLabel(any()) } returns TEST_LOCALIZED_APP_LABEL - - val appInfo = ApplicationInfo().apply { - labelRes = TEST_LOCALIZED_APP_NAME_RESOURCE_ID - } - every { context.applicationInfo } returns appInfo - every { context.resources.configuration } returns Configuration() - val overriddenContext = mockk { - every { getText(TEST_LOCALIZED_APP_NAME_RESOURCE_ID) } returns TEST_LOCALIZED_APP_LABEL - } - every { context.createConfigurationContext(any()) } returns overriddenContext - - When("Access user agent") { - val userAgent = "$TEST_ENCODED_LOCALIZED_APP_LABEL/$TEST_VERSION_NAME " + - "($TEST_APP_PACKAGE; build:$TEST_VERSION_CODE; Android ${Build.VERSION.RELEASE}) " + - "MapboxSearchSDK-Android/${BuildConfig.VERSION_NAME}" - - Then("Returned value should be", userAgent, userAgentProvider.userAgent()) - - Verify("context.packageName was called") { - context.packageName - } - - Verify("overriddenContext was called") { - overriddenContext.getText(TEST_LOCALIZED_APP_NAME_RESOURCE_ID) - } - - VerifyNo("packageManager.getApplicationLabel() wasn't called") { - packageManager.getApplicationLabel(any()) - } - } - } - } - - companion object { - - const val TEST_VERSION_NAME = "test-version-name" - const val TEST_APP_LABEL = "test-app-label" - const val TEST_LOCALIZED_APP_LABEL = "test-app-label-\uD83D\uDE0F" - const val TEST_ENCODED_LOCALIZED_APP_LABEL = "test-app-label-__" - const val TEST_APP_PACKAGE = "test-app-package" - const val TEST_VERSION_CODE = 123 - const val TEST_LOCALIZED_APP_NAME_RESOURCE_ID = 243 - } -} diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/record/HistoryDataProviderTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/record/HistoryDataProviderTest.kt index 213cce9ac..7791c0c9b 100644 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/record/HistoryDataProviderTest.kt +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/record/HistoryDataProviderTest.kt @@ -18,13 +18,13 @@ import com.mapbox.search.tests_support.assertEqualsJsonify import com.mapbox.search.tests_support.createTestCoreSearchResult import com.mapbox.search.tests_support.createTestHistoryRecord import com.mapbox.search.tests_support.createTestRequestOptions -import com.mapbox.search.tests_support.record.addAllBlocking -import com.mapbox.search.tests_support.record.addBlocking import com.mapbox.search.tests_support.record.addToHistoryIfNeededBlocking import com.mapbox.search.tests_support.record.getAllBlocking import com.mapbox.search.tests_support.record.getBlocking import com.mapbox.search.tests_support.record.getSizeBlocking import com.mapbox.search.tests_support.record.registerIndexableDataProviderEngineBlocking +import com.mapbox.search.tests_support.record.upsertAllBlocking +import com.mapbox.search.tests_support.record.upsertBlocking import com.mapbox.search.utils.TimeProvider import com.mapbox.search.utils.concurrent.MainThreadWorker import com.mapbox.test.dsl.TestCase @@ -210,7 +210,7 @@ internal class HistoryDataProviderTest { createTestHistoryRecord(id = "test-id-8", timestamp = 400L), createTestHistoryRecord(id = "test-id-9", timestamp = 400L), ) - historyDataProvider.addAllBlocking(anotherTestRecords, executor) + historyDataProvider.upsertAllBlocking(anotherTestRecords, executor) val allRecords = historyDataProvider.getAllBlocking(executor) val allEngineRecords = testEngine.records @@ -240,7 +240,7 @@ internal class HistoryDataProviderTest { When("One record added") { val singleRecord = createTestHistoryRecord(id = "test-id-10", timestamp = 1000L) - historyDataProvider.addBlocking(singleRecord, executor) + historyDataProvider.upsertBlocking(singleRecord, executor) val allRecords = historyDataProvider.getAllBlocking(executor) val allEngineRecords = testEngine.records @@ -291,7 +291,7 @@ internal class HistoryDataProviderTest { timestamp = 100L + (index - 5) * 150L // [250, | 400, 550, 700, 850] ) } - historyDataProvider.addAllBlocking(anotherTestRecords, executor) + historyDataProvider.upsertAllBlocking(anotherTestRecords, executor) val allRecords = historyDataProvider.getAllBlocking(executor) val allEngineRecords = testEngine.records @@ -347,7 +347,7 @@ internal class HistoryDataProviderTest { timestamp = index ) }.shuffled() - historyDataProvider.addAllBlocking(anotherTestRecords, executor) + historyDataProvider.upsertAllBlocking(anotherTestRecords, executor) val allRecords = historyDataProvider.getAllBlocking(executor) val allEngineRecords = testEngine.records diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/record/LocalDataProviderTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/record/LocalDataProviderTest.kt index e2183d522..757df6a9a 100644 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/record/LocalDataProviderTest.kt +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/record/LocalDataProviderTest.kt @@ -89,17 +89,17 @@ internal class LocalDataProviderTest { }, "add()" to { dataProvider -> val callback = BlockingCompletionCallback() - val task = dataProvider.add(mockk(), executor, callback) + val task = dataProvider.upsert(mockk(), executor, callback) callback.getResultBlocking() to task }, "addAll()" to { dataProvider -> val callback = BlockingCompletionCallback() - val task = dataProvider.addAll(mockk(), executor, callback) + val task = dataProvider.upsertAll(mockk(), executor, callback) callback.getResultBlocking() to task }, - "update" to { dataProvider -> + "upsert" to { dataProvider -> val callback = BlockingCompletionCallback() - val task = dataProvider.update(mockk(), executor, callback) + val task = dataProvider.upsert(mockk(), executor, callback) callback.getResultBlocking() to task }, "remove()" to { dataProvider -> @@ -179,17 +179,17 @@ internal class LocalDataProviderTest { val testCases = mapOf Pair, AsyncOperationTask>>( "add()" to { dataProvider -> val callback = BlockingCompletionCallback() - val task = dataProvider.add(createTestHistoryRecord("new record id-1"), executor, callback) + val task = dataProvider.upsert(createTestHistoryRecord("new record id-1"), executor, callback) callback.getResultBlocking() to task }, "addAll()" to { dataProvider -> val callback = BlockingCompletionCallback() - val task = dataProvider.addAll(listOf(createTestHistoryRecord("new record id-1")), executor, callback) + val task = dataProvider.upsertAll(listOf(createTestHistoryRecord("new record id-1")), executor, callback) callback.getResultBlocking() to task }, "update" to { dataProvider -> val callback = BlockingCompletionCallback() - val task = dataProvider.update(createTestHistoryRecord("new record id-1"), executor, callback) + val task = dataProvider.upsert(createTestHistoryRecord("new record id-1"), executor, callback) callback.getResultBlocking() to task }, "remove()" to { dataProvider -> diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/result/OriginalSearchResultTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/result/OriginalSearchResultTest.kt index 3f2da2e35..14e1783df 100644 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/result/OriginalSearchResultTest.kt +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/result/OriginalSearchResultTest.kt @@ -91,7 +91,7 @@ internal class OriginalSearchResultTest { listOf(OriginalResultType.REGION, OriginalResultType.ADDRESS, OriginalResultType.POI) to false, emptyList() to false, listOf(OriginalResultType.PLACE, OriginalResultType.CATEGORY) to false, - ) + OriginalResultType.values().map { listOf(it) to true }.toMap() + ) + OriginalResultType.values().associate { listOf(it) to true } val CORE_EMPTY_SEARCH_RESULT = CoreSearchResult( "Empty result id", @@ -111,6 +111,7 @@ internal class OriginalSearchResultTest { null, null, null, + -1, null, null ) @@ -132,6 +133,7 @@ internal class OriginalSearchResultTest { externalIDs = CORE_EMPTY_SEARCH_RESULT.externalIDs, layerId = CORE_EMPTY_SEARCH_RESULT.layer, userRecordId = CORE_EMPTY_SEARCH_RESULT.userRecordID, + userRecordPriority = CORE_EMPTY_SEARCH_RESULT.userRecordPriority, action = CORE_EMPTY_SEARCH_RESULT.action?.mapToPlatform(), serverIndex = CORE_EMPTY_SEARCH_RESULT.serverIndex, etaMinutes = CORE_EMPTY_SEARCH_RESULT.eta @@ -156,6 +158,7 @@ internal class OriginalSearchResultTest { externalIDs = CORE_FILLED_SEARCH_RESULT.externalIDs, layerId = CORE_FILLED_SEARCH_RESULT.layer, userRecordId = CORE_FILLED_SEARCH_RESULT.userRecordID, + userRecordPriority = CORE_FILLED_SEARCH_RESULT.userRecordPriority, action = CORE_FILLED_SEARCH_RESULT.action?.mapToPlatform(), serverIndex = CORE_FILLED_SEARCH_RESULT.serverIndex, etaMinutes = CORE_FILLED_SEARCH_RESULT.eta @@ -200,6 +203,7 @@ internal class OriginalSearchResultTest { hashMapOf("external id 1" to "123", "external id 2" to "456", "external id 3" to "789"), "test layer id", "test user record id", + -1, CoreSuggestAction("test endpoint", "test path", "test query", byteArrayOf(1, 2, 3), true), 123 ) diff --git a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/favorite/rename/EditFavoriteView.kt b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/favorite/rename/EditFavoriteView.kt index b9809d369..580550867 100644 --- a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/favorite/rename/EditFavoriteView.kt +++ b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/favorite/rename/EditFavoriteView.kt @@ -132,7 +132,7 @@ internal class EditFavoriteView : LinearLayout { setAddressText(favorite.address?.formattedAddress(SearchAddress.FormatStyle.Full)) doneButton.setOnClickListener { - updateFavoriteTask = MapboxSearchSdk.serviceProvider.favoritesDataProvider().update( + updateFavoriteTask = MapboxSearchSdk.serviceProvider.favoritesDataProvider().upsert( favorite.copy(name = nameEditText.text.trim().toString()), object : CompletionCallback { override fun onComplete(result: Unit) { diff --git a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/place/SearchPlaceBottomSheetView.kt b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/place/SearchPlaceBottomSheetView.kt index 2bf83492d..bc03b6c42 100644 --- a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/place/SearchPlaceBottomSheetView.kt +++ b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/place/SearchPlaceBottomSheetView.kt @@ -283,7 +283,7 @@ public class SearchPlaceBottomSheetView @JvmOverloads constructor( val listener: (View) -> Unit = { if (addFavoriteTask == null || addFavoriteTask?.isCancelled == true) { val newFavorite = searchPlace.toUserFavorite() - addFavoriteTask = favoritesDataProvider.add( + addFavoriteTask = favoritesDataProvider.upsert( newFavorite, object : CompletionCallback { override fun onComplete(result: Unit) { diff --git a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/search/address/AddressSearchViewController.kt b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/search/address/AddressSearchViewController.kt index fc2bbee21..2b4a4fc1a 100644 --- a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/search/address/AddressSearchViewController.kt +++ b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/view/search/address/AddressSearchViewController.kt @@ -105,7 +105,7 @@ internal class AddressSearchViewController : BaseSearchController { } } - updateFavoriteTask = MapboxSearchSdk.serviceProvider.favoritesDataProvider().update( + updateFavoriteTask = MapboxSearchSdk.serviceProvider.favoritesDataProvider().upsert( newFavorite, object : CompletionCallback { override fun onComplete(result: Unit) {