From 7fb6620efb2d4583e5efa17eec71f396e1b02734 Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 20 Aug 2024 16:56:25 -0400 Subject: [PATCH 01/18] Bonus: add testRetrieveDetailsAllAttributes test --- .../Legacy/SearchEngineTests.swift | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift b/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift index 5ea70487..d0b6898d 100644 --- a/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift +++ b/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift @@ -568,4 +568,47 @@ class SearchEngineTests: XCTestCase { XCTAssertNil(delegate.error) } + + func testRetrieveDetailsAllAttributes() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let updateExpectation = delegate.updateExpectation + + let searchOptions = SearchOptions( + limit: 100, + origin: CLLocationCoordinate2D(latitude: 38.902309, longitude: -77.029129), + filterTypes: [.poi] + ) + + searchEngine.search(query: "planet word", options: searchOptions) + wait(for: [updateExpectation], timeout: 200) + let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) + + func fetchResult(for: SearchSuggestion, options: RetrieveOptions) throws -> SearchResult { + let successExpectation = delegate.successExpectation + searchEngine.select(suggestion: suggestion, options: options) + wait(for: [successExpectation], timeout: 200) + return try XCTUnwrap(delegate.resolvedResult) + } + + let allAttributes = [ + AttributeSet.basic, + .photos, + .venue, + .visit, + ] + + let result = try fetchResult(for: suggestion, options: RetrieveOptions(attributeSets: allAttributes)) + + let metadata = try XCTUnwrap(result.metadata) + XCTAssertNil(metadata.averageRating) + XCTAssertNotNil(metadata.otherImages) + XCTAssertNotNil(metadata.openHours) + XCTAssertNotNil(metadata.phone) + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNotNil(metadata.website) + XCTAssertNil(delegate.error) + } } From 3013d8da57db0bd47fd87c4272233aa5dd86e268 Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 20 Aug 2024 17:02:06 -0400 Subject: [PATCH 02/18] WIP: Begin adding retrieve(mapboxID:) Details function --- MapboxSearch.xcodeproj/project.pbxproj | 14 +++++++- .../InternalAPI/CoreAliases.swift | 1 + .../CoreSearchEngineProtocol.swift | 20 +++++++++++ .../PublicAPI/Engine/SearchEngine.swift | 18 ++++++++++ .../Engine/SearchRequest/DetailsOptions.swift | 34 +++++++++++++++++++ .../{ => SearchRequest}/RetrieveOptions.swift | 0 .../PublicAPI/MapboxSearchVersion.swift | 2 +- .../Stubs&Models/CoreSearchEngineStub.swift | 8 +++++ 8 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift rename Sources/MapboxSearch/PublicAPI/Engine/{ => SearchRequest}/RetrieveOptions.swift (100%) diff --git a/MapboxSearch.xcodeproj/project.pbxproj b/MapboxSearch.xcodeproj/project.pbxproj index 23831ccb..aae538d4 100644 --- a/MapboxSearch.xcodeproj/project.pbxproj +++ b/MapboxSearch.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 0488234A2B6B0A9E00C770AA /* category-hotel-search-along-route-jp.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B7C2B6B043C00FDE7D5 /* category-hotel-search-along-route-jp.json */; }; 04970F8D2B7A97C900213763 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 04970F8C2B7A97C900213763 /* PrivacyInfo.xcprivacy */; }; 049FE3A82C6A50BB00F54FB2 /* RetrieveOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 049FE3A72C6A50BB00F54FB2 /* RetrieveOptions.swift */; }; + 04A26C652C7535BD00443527 /* DetailsOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04A26C642C7535BD00443527 /* DetailsOptions.swift */; }; 04AB0B4B2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B4A2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json */; }; 04AB0B4C2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B4A2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json */; }; 04AB0B4D2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B4A2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json */; }; @@ -562,6 +563,7 @@ 0484BCDE2BC4865C003CF408 /* OfflineIndexObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineIndexObserver.swift; sourceTree = ""; }; 04970F8C2B7A97C900213763 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 049FE3A72C6A50BB00F54FB2 /* RetrieveOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrieveOptions.swift; sourceTree = ""; }; + 04A26C642C7535BD00443527 /* DetailsOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsOptions.swift; sourceTree = ""; }; 04AB0B4A2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = mapbox.places.san.francisco.json; sourceTree = ""; }; 04AB0B792B6AF37800FDE7D5 /* DiscoverIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverIntegrationTests.swift; sourceTree = ""; }; 04AB0B7C2B6B043C00FDE7D5 /* category-hotel-search-along-route-jp.json */ = {isa = PBXFileReference; explicitFileType = text.json; path = "category-hotel-search-along-route-jp.json"; sourceTree = ""; }; @@ -1095,6 +1097,15 @@ path = Resources; sourceTree = ""; }; + 04A26C632C75359B00443527 /* SearchRequest */ = { + isa = PBXGroup; + children = ( + 049FE3A72C6A50BB00F54FB2 /* RetrieveOptions.swift */, + 04A26C642C7535BD00443527 /* DetailsOptions.swift */, + ); + path = SearchRequest; + sourceTree = ""; + }; 04C127562B62FFD000884325 /* Engine */ = { isa = PBXGroup; children = ( @@ -1600,9 +1611,9 @@ FEEDD2E32508DFE400DC0A98 /* AbstractSearchEngine.swift */, 04C127542B62F6BC00884325 /* ApiType.swift */, FEEDD2D82508DFE400DC0A98 /* SearchEngine.swift */, - 049FE3A72C6A50BB00F54FB2 /* RetrieveOptions.swift */, FEEDD2EB2508DFE400DC0A98 /* CategorySearchEngine.swift */, 04C0848C2B4C82F3002F9C69 /* SdkInformation.swift */, + 04A26C632C75359B00443527 /* SearchRequest */, ); path = Engine; sourceTree = ""; @@ -2639,6 +2650,7 @@ F914EE642743E4F400D4F173 /* CoreAliases.swift in Sources */, F998AED825D17DFF00230F34 /* SearchResponseInfo.swift in Sources */, FEEDD3082508DFE400DC0A98 /* HistoryRecord.swift in Sources */, + 04A26C652C7535BD00443527 /* DetailsOptions.swift in Sources */, FEEDD3032508DFE400DC0A98 /* SearchRequestOptions.swift in Sources */, FEEDD2F82508DFE400DC0A98 /* RecordsProviderInteractorNativeCore.swift in Sources */, F97E9A84268C7BBE00F6353D /* DefaultStringInterpolation+Extensions.swift in Sources */, diff --git a/Sources/MapboxSearch/InternalAPI/CoreAliases.swift b/Sources/MapboxSearch/InternalAPI/CoreAliases.swift index 17f6ce70..4f8f3ff8 100644 --- a/Sources/MapboxSearch/InternalAPI/CoreAliases.swift +++ b/Sources/MapboxSearch/InternalAPI/CoreAliases.swift @@ -11,6 +11,7 @@ typealias CoreBoundingBox = MapboxCoreSearch.LonLatBBox typealias CoreSearchResult = MapboxCoreSearch.SearchResult typealias CoreAttributeSet = MapboxCoreSearch.AttributeSet typealias CoreRetrieveOptions = MapboxCoreSearch.RetrieveOptions +typealias CoreDetailsOptions = MapboxCoreSearch.DetailsOptions typealias CoreRoutablePoint = MapboxCoreSearch.RoutablePoint typealias CoreResultMetadata = MapboxCoreSearch.ResultMetadata typealias CoreRequestOptions = MapboxCoreSearch.RequestOptions diff --git a/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift b/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift index a6734820..18235883 100644 --- a/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift +++ b/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift @@ -1,6 +1,8 @@ import CoreLocation import Foundation +typealias CoreSearchResponseCompletion = (CoreSearchResponseProtocol?) -> Void + protocol CoreSearchEngineProtocol { /** ------------------------------------------------------------------------------------------- @@ -88,6 +90,12 @@ protocol CoreSearchEngineProtocol { func addOfflineIndexObserver(for observer: CoreOfflineIndexObserver) func removeOfflineIndexObserver(for observer: CoreOfflineIndexObserver) + + func retrieveDetails( + for mapboxId: String, + options: CoreDetailsOptions, + completion: @escaping CoreSearchResponseCompletion + ) } extension CoreSearchEngine: CoreSearchEngineProtocol { @@ -265,4 +273,16 @@ extension CoreSearchEngine: CoreSearchEngineProtocol { } }) } + + func retrieveDetails( + for mapboxId: String, + options: CoreDetailsOptions, + completion: @escaping (CoreSearchResponseProtocol?) -> Void + ) { + retrieveDetails(forMapboxId: mapboxId, options: options) { response in + DispatchQueue.main.async { + completion(response) + } + } + } } diff --git a/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift index c6061e6c..65b813f9 100644 --- a/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift +++ b/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift @@ -206,6 +206,7 @@ public class SearchEngine: AbstractSearchEngine { offlineMode == .disabled ? engine.reverseGeocoding : engine.reverseGeocodingOffline } + /// Accept a typical `select()` invocation and perform the work of a Mapbox API retrieve/ endpoint request. func retrieve( suggestion: SearchSuggestion, retrieveOptions: RetrieveOptions? @@ -227,6 +228,15 @@ public class SearchEngine: AbstractSearchEngine { } } + func retrieve(mapboxID: String, detailsOptions: DetailsOptions?) { + assert(offlineMode == .disabled) + + let detailsOptions = detailsOptions ?? DetailsOptions() + engine.retrieveDetails(for: mapboxID, options: detailsOptions.toCore()) { [weak self] serverResponse in + self?.processResponse(serverResponse, suggestion: nil) + } + } + private func startSearch(options: SearchOptions? = nil) { guard let queryValueString = queryValue.stringQuery else { assertionFailure() @@ -511,6 +521,14 @@ extension SearchEngine { } } +// MARK: - Public Details API + +extension SearchEngine { + public func select(mapboxID: String, options detailsOptions: DetailsOptions? = nil) { + retrieve(mapboxID: mapboxID, detailsOptions: detailsOptions) + } +} + // MARK: - IndexableDataResolver extension SearchEngine: IndexableDataResolver { diff --git a/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift new file mode 100644 index 00000000..1740244f --- /dev/null +++ b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift @@ -0,0 +1,34 @@ +// Copyright © 2024 Mapbox. All rights reserved. + +import Foundation + +public struct DetailsOptions: Sendable { + /// Besides the basic metadata attributes, developers can request additional + /// attributes by setting attribute_sets parameter with attribute set values, + /// for example &attribute_sets=basic,photos,visit. + /// The requested metadata will be provided in metadata object in the response. + public var attributeSets: [AttributeSet]? + + /// The ISO language code to be returned. If not provided, the default is English. + public var language: String? + + /// The two digit ISO country code (such as 'JP') to requests a worldview for the location data, if applicable data + /// is available. + /// This parameters will only be applicable for Boundaries and Places feature types. + public var worldview: String? + + public init(attributeSets: [AttributeSet]? = nil, language: String? = nil, worldview: String? = nil) { + self.attributeSets = attributeSets + self.language = language + self.worldview = worldview + } + + func toCore() -> CoreDetailsOptions { + CoreDetailsOptions( + attributeSets: attributeSets.map { $0.map { NSNumber(value: $0.coreValue.rawValue) } }, + language: language, + worldview: worldview, + baseUrl: nil + ) + } +} diff --git a/Sources/MapboxSearch/PublicAPI/Engine/RetrieveOptions.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/RetrieveOptions.swift similarity index 100% rename from Sources/MapboxSearch/PublicAPI/Engine/RetrieveOptions.swift rename to Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/RetrieveOptions.swift diff --git a/Sources/MapboxSearch/PublicAPI/MapboxSearchVersion.swift b/Sources/MapboxSearch/PublicAPI/MapboxSearchVersion.swift index 34c79888..26289eb2 100644 --- a/Sources/MapboxSearch/PublicAPI/MapboxSearchVersion.swift +++ b/Sources/MapboxSearch/PublicAPI/MapboxSearchVersion.swift @@ -1,2 +1,2 @@ /// Mapbox Search SDK version variable -public let mapboxSearchSDKVersion = "2.3.0-rc.1" +public let mapboxSearchSDKVersion = "2.3.0-rc.2" diff --git a/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift b/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift index eecca5ef..4e5c93fb 100644 --- a/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift +++ b/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift @@ -34,6 +34,14 @@ class CoreSearchEngineStub { } extension CoreSearchEngineStub: CoreSearchEngineProtocol { + func retrieveDetails( + for mapboxId: String, + options: MapboxSearch.CoreDetailsOptions, + completion: @escaping MapboxSearch.CoreSearchResponseCompletion + ) { + // TODO: + } + func addOfflineIndexObserver(for observer: MapboxSearch.CoreOfflineIndexObserver) {} func removeOfflineIndexObserver(for observer: CoreOfflineIndexObserver) {} From 317d904cf18bd13fb2922018c831229ce6809f89 Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 20 Aug 2024 17:06:17 -0400 Subject: [PATCH 03/18] Add testRetrieveDetailsByMapboxId unit test --- .../Legacy/SearchEngineTests.swift | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift b/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift index d0b6898d..2d5112bc 100644 --- a/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift +++ b/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift @@ -611,4 +611,49 @@ class SearchEngineTests: XCTestCase { XCTAssertNotNil(metadata.website) XCTAssertNil(delegate.error) } + + func testRetrieveDetailsByMapboxId() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let updateExpectation = delegate.updateExpectation + + let searchOptions = SearchOptions( + limit: 100, + origin: CLLocationCoordinate2D(latitude: 38.902309, longitude: -77.029129), + filterTypes: [.poi] + ) + + let planetWordMapboxID = "dXJuOm1ieHBvaTo0ZTg2ZWFkNS1jOWMwLTQ3OWEtOTA5Mi1kMDVlNDQ3NDdlODk" + let detailsOptions = DetailsOptions(attributeSets: AttributeSet.allCases, language: "en") + searchEngine.retrieve(mapboxID: planetWordMapboxID, detailsOptions: detailsOptions) + wait(for: [updateExpectation], timeout: 200) + let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) + + func fetchResult(for: SearchSuggestion, options: RetrieveOptions) throws -> SearchResult { + let successExpectation = delegate.successExpectation + searchEngine.select(suggestion: suggestion, options: options) + wait(for: [successExpectation], timeout: 200) + return try XCTUnwrap(delegate.resolvedResult) + } + + let allAttributes = [ + AttributeSet.basic, + .photos, + .venue, + .visit, + ] + + let result = try fetchResult(for: suggestion, options: RetrieveOptions(attributeSets: allAttributes)) + + let metadata = try XCTUnwrap(result.metadata) + XCTAssertNil(metadata.averageRating) + XCTAssertNotNil(metadata.otherImages) + XCTAssertNotNil(metadata.openHours) + XCTAssertNotNil(metadata.phone) + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNotNil(metadata.website) + XCTAssertNil(delegate.error) + } } From e170bb3db3eab98a7b932c0ce66ef274ad36b985 Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 20 Aug 2024 17:47:32 -0400 Subject: [PATCH 04/18] Update test function invocation --- Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift b/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift index 2d5112bc..f72c3a10 100644 --- a/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift +++ b/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift @@ -626,7 +626,7 @@ class SearchEngineTests: XCTestCase { let planetWordMapboxID = "dXJuOm1ieHBvaTo0ZTg2ZWFkNS1jOWMwLTQ3OWEtOTA5Mi1kMDVlNDQ3NDdlODk" let detailsOptions = DetailsOptions(attributeSets: AttributeSet.allCases, language: "en") - searchEngine.retrieve(mapboxID: planetWordMapboxID, detailsOptions: detailsOptions) + searchEngine.select(mapboxID: planetWordMapboxID, options: detailsOptions) wait(for: [updateExpectation], timeout: 200) let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) From 098f545c443c8f46ced89b7eb717bec92a4fac9e Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Thu, 22 Aug 2024 09:55:33 -0400 Subject: [PATCH 05/18] SearchEngine.retrieve(mapboxID:) is now the canonical way to query by Mapbox ID - Refine documentation - SearchEngine.search and SearchEngine.select are a two-step combination - Stop wrapping SearchEngine.retrieve(mapboxID:) in an over-loaded SearchEngine.select wrapper - Mark SearchEngine.retrieve(mapboxID:) public as the main and only way to query Details API / Mapbox ID --- Search Documentation.docc/GettingStarted.md | 2 +- .../CoreSearchEngineProtocol.swift | 2 +- .../PublicAPI/Engine/SearchEngine.swift | 29 +++++++++++-------- .../Engine/SearchRequest/DetailsOptions.swift | 3 +- .../PublicAPI/ServiceProvider.swift | 3 +- .../Legacy/SearchEngineTests.swift | 8 +---- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Search Documentation.docc/GettingStarted.md b/Search Documentation.docc/GettingStarted.md index 4a3a887e..92243b54 100644 --- a/Search Documentation.docc/GettingStarted.md +++ b/Search Documentation.docc/GettingStarted.md @@ -56,7 +56,7 @@ extension LocationFinderController: SearchEngineDelegate { #### User choose location suggestion -As soon as user will choose one of displayed suggestions, we have to pass it into ``SearchEngine/select(suggestion:)`` to fetch suggestion details. +As soon as user will choose one of displayed suggestions, we have to pass it into ``SearchEngine/select(suggestion:options:)`` to fetch suggestion details. ```swift searchEngine.select(suggestion: userSelectedSuggestion) diff --git a/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift b/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift index 18235883..8e283474 100644 --- a/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift +++ b/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift @@ -277,7 +277,7 @@ extension CoreSearchEngine: CoreSearchEngineProtocol { func retrieveDetails( for mapboxId: String, options: CoreDetailsOptions, - completion: @escaping (CoreSearchResponseProtocol?) -> Void + completion: @escaping CoreSearchResponseCompletion ) { retrieveDetails(forMapboxId: mapboxId, options: options) { response in DispatchQueue.main.async { diff --git a/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift index 65b813f9..b470abae 100644 --- a/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift +++ b/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift @@ -206,7 +206,8 @@ public class SearchEngine: AbstractSearchEngine { offlineMode == .disabled ? engine.reverseGeocoding : engine.reverseGeocodingOffline } - /// Accept a typical `select()` invocation and perform the work of a Mapbox API retrieve/ endpoint request. + /// Concrete implementation for a typical ``select(suggestions:)`` invocation. + /// Performs the work of a Mapbox API retrieve/ endpoint request. func retrieve( suggestion: SearchSuggestion, retrieveOptions: RetrieveOptions? @@ -228,15 +229,6 @@ public class SearchEngine: AbstractSearchEngine { } } - func retrieve(mapboxID: String, detailsOptions: DetailsOptions?) { - assert(offlineMode == .disabled) - - let detailsOptions = detailsOptions ?? DetailsOptions() - engine.retrieveDetails(for: mapboxID, options: detailsOptions.toCore()) { [weak self] serverResponse in - self?.processResponse(serverResponse, suggestion: nil) - } - } - private func startSearch(options: SearchOptions? = nil) { guard let queryValueString = queryValue.stringQuery else { assertionFailure() @@ -524,8 +516,21 @@ extension SearchEngine { // MARK: - Public Details API extension SearchEngine { - public func select(mapboxID: String, options detailsOptions: DetailsOptions? = nil) { - retrieve(mapboxID: mapboxID, detailsOptions: detailsOptions) + /// Query for a POI by its Mapbox ID. + /// This is an alternative to using ``search(query:options:)`` and ``select(suggestions:)`` when you already have a + /// Mapbox ID. + /// For example refreshing a _cached POI_ is a good scenario to query ``retrieve(mapboxID:options:)``, instead of + /// using new `search` and `select` invocations. + /// - Parameters: + /// - mapboxID: The Mapbox ID for a known POI. Mapbox IDs will be returned in Search responses and may be cached. + /// - detailsOptions: Options to configure this query. May be nil. + public func retrieve(mapboxID: String, options detailsOptions: DetailsOptions? = DetailsOptions()) { + assert(offlineMode == .disabled) + + let detailsOptions = detailsOptions ?? DetailsOptions() + engine.retrieveDetails(for: mapboxID, options: detailsOptions.toCore()) { [weak self] serverResponse in + self?.processResponse(serverResponse, suggestion: nil) + } } } diff --git a/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift index 1740244f..225e678b 100644 --- a/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift +++ b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift @@ -2,6 +2,7 @@ import Foundation +/// Options to configure a request for ``SearchEngine/retrieve(mapboxID:options:)`` public struct DetailsOptions: Sendable { /// Besides the basic metadata attributes, developers can request additional /// attributes by setting attribute_sets parameter with attribute set values, @@ -12,7 +13,7 @@ public struct DetailsOptions: Sendable { /// The ISO language code to be returned. If not provided, the default is English. public var language: String? - /// The two digit ISO country code (such as 'JP') to requests a worldview for the location data, if applicable data + /// The two digit ISO country code (such as 'JP') to request a worldview for the location data, if applicable data /// is available. /// This parameters will only be applicable for Boundaries and Places feature types. public var worldview: String? diff --git a/Sources/MapboxSearch/PublicAPI/ServiceProvider.swift b/Sources/MapboxSearch/PublicAPI/ServiceProvider.swift index 5b6015a4..31f21c7f 100644 --- a/Sources/MapboxSearch/PublicAPI/ServiceProvider.swift +++ b/Sources/MapboxSearch/PublicAPI/ServiceProvider.swift @@ -41,7 +41,8 @@ public class ServiceProvider: ServiceProviderProtocol { /// Customize API host URL with a value from the Info.plist /// Also supports reading a process argument when in non-Release UITest builds /// Read-only property. - /// To change the customBaseURL for an engine programmatically, use the ``createEngine`` function. + /// To change the customBaseURL for an engine programmatically, you would typically use the + /// ``AbstractSearchEngine/init(accessToken:locationProvider:defaultSearchOptions:apiType:baseURL:)`` function. public static var customBaseURL: String? { #if !RELEASE if ProcessInfo.processInfo.arguments.contains(where: { $0 == "--uitesting" }) { diff --git a/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift b/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift index 4f2905d1..79960d05 100644 --- a/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift +++ b/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift @@ -618,15 +618,9 @@ class SearchEngineTests: XCTestCase { searchEngine.delegate = delegate let updateExpectation = delegate.updateExpectation - let searchOptions = SearchOptions( - limit: 100, - origin: CLLocationCoordinate2D(latitude: 38.902309, longitude: -77.029129), - filterTypes: [.poi] - ) - let planetWordMapboxID = "dXJuOm1ieHBvaTo0ZTg2ZWFkNS1jOWMwLTQ3OWEtOTA5Mi1kMDVlNDQ3NDdlODk" let detailsOptions = DetailsOptions(attributeSets: AttributeSet.allCases, language: "en") - searchEngine.select(mapboxID: planetWordMapboxID, options: detailsOptions) + searchEngine.retrieve(mapboxID: planetWordMapboxID, options: detailsOptions) wait(for: [updateExpectation], timeout: 200) let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) From d2907b8a626d6cadedf935970dea0cec4efd6c18 Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Thu, 22 Aug 2024 11:19:12 -0400 Subject: [PATCH 06/18] Begin adding mocked response test, begin correcting live-API tests for retrieve(mapboxID:) --- MapboxSearch.xcodeproj/project.pbxproj | 8 + .../PublicAPI/Engine/SearchEngine.swift | 46 ++- ...archBox_SearchEngineIntegrationTests.swift | 26 ++ .../Legacy/SearchEngineTests.swift | 27 +- .../search-box-retrieve-mapbox-id.json | 296 ++++++++++++++++++ Tests/MockWebServer/MockResponse.swift | 10 +- 6 files changed, 404 insertions(+), 9 deletions(-) create mode 100644 Tests/MockData/search-box/search-box-retrieve-mapbox-id.json diff --git a/MapboxSearch.xcodeproj/project.pbxproj b/MapboxSearch.xcodeproj/project.pbxproj index 64409a5f..f04a9874 100644 --- a/MapboxSearch.xcodeproj/project.pbxproj +++ b/MapboxSearch.xcodeproj/project.pbxproj @@ -13,6 +13,9 @@ 042477C52B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */ = {isa = PBXBuildFile; fileRef = 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */; }; 042477C62B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */ = {isa = PBXBuildFile; fileRef = 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */; }; 042477C72B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */ = {isa = PBXBuildFile; fileRef = 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */; }; + 0427854F2C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */ = {isa = PBXBuildFile; fileRef = 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */; }; + 042785502C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */ = {isa = PBXBuildFile; fileRef = 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */; }; + 042785512C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */ = {isa = PBXBuildFile; fileRef = 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */; }; 042BEB172C2DDFAA0004CD7B /* MapboxCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 042BEB162C2DDFAA0004CD7B /* MapboxCommon */; }; 042BEB1A2C2DE30E0004CD7B /* MapboxMaps in Frameworks */ = {isa = PBXBuildFile; productRef = 042BEB192C2DE30E0004CD7B /* MapboxMaps */; }; 043339CD2C61295D001650FA /* Atlantis in Frameworks */ = {isa = PBXBuildFile; productRef = 043339CC2C61295D001650FA /* Atlantis */; }; @@ -548,6 +551,7 @@ 0405809C2BA8E67D00A54CB9 /* OwningObjectDeallocatedErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwningObjectDeallocatedErrorTests.swift; sourceTree = ""; }; 042477C12B7290E700D870D5 /* SearchEngineGeocodingIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEngineGeocodingIntegrationTests.swift; sourceTree = ""; }; 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "geocoding-reverse-geocoding.json"; sourceTree = ""; }; + 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "search-box-retrieve-mapbox-id.json"; sourceTree = ""; }; 04340D1F2C6BD06500B4F242 /* ResultChildMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultChildMetadata.swift; sourceTree = ""; }; 043A3D4C2B30F38300DB681B /* CoreAddress+AddressComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreAddress+AddressComponents.swift"; sourceTree = ""; }; 044A6B722BA8933200A9F2A2 /* PreviewCategoriesFavoritesSegmentControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewCategoriesFavoritesSegmentControl.swift; sourceTree = ""; }; @@ -1051,6 +1055,7 @@ 046818D62B87FA450082B188 /* search-box */ = { isa = PBXGroup; children = ( + 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */, 04DFB41B2B8CFEC100231830 /* search-box-retrieve-san-francisco.json */, 046818DA2B87FAB20082B188 /* search-box-category.json */, 046818E02B87FF5C0082B188 /* search-box-suggestions-minsk.json */, @@ -2307,6 +2312,7 @@ F9B62CCB264BCC2600492999 /* suggestions-empty.json in Resources */, 048823492B6B0A9D00C770AA /* category-hotel-search-along-route-jp.json in Resources */, 04AB0B4C2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */, + 042785502C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */, 04DFB4112B8CF46D00231830 /* search-box-retrieve-categories.json in Resources */, 04DFB4212B8D0ABE00231830 /* search-box-retrieve-recursion.json in Resources */, 046818DC2B87FAB20082B188 /* search-box-category.json in Resources */, @@ -2343,6 +2349,7 @@ 04DFB4162B8CFD4700231830 /* search-box-suggestions-san-francisco.json in Resources */, F94FFA4826453D410019ED9B /* reverse-geocoding-sbs.json in Resources */, 04DFB4222B8D0ABE00231830 /* search-box-retrieve-recursion.json in Resources */, + 042785512C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */, 046818E32B87FF5C0082B188 /* search-box-suggestions-minsk.json in Resources */, 2CD8AC4B29F1D38800C47BE4 /* suggestions-with-mixed-coordinates.json in Resources */, 0477904B2B890A8500A99528 /* search-box-recursion.json in Resources */, @@ -2377,6 +2384,7 @@ F9C557A52670CB0400BE8B94 /* suggestions-empty.json in Resources */, 048823482B6B0A9D00C770AA /* category-hotel-search-along-route-jp.json in Resources */, 04AB0B4B2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */, + 0427854F2C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */, 04DFB4102B8CF46D00231830 /* search-box-retrieve-categories.json in Resources */, 04DFB4202B8D0ABE00231830 /* search-box-retrieve-recursion.json in Resources */, 046818DB2B87FAB20082B188 /* search-box-category.json in Resources */, diff --git a/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift index b470abae..26083bd9 100644 --- a/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift +++ b/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift @@ -206,8 +206,8 @@ public class SearchEngine: AbstractSearchEngine { offlineMode == .disabled ? engine.reverseGeocoding : engine.reverseGeocodingOffline } - /// Concrete implementation for a typical ``select(suggestions:)`` invocation. - /// Performs the work of a Mapbox API retrieve/ endpoint request. + /// Concrete implementation for a typical ``select(suggestions:)`` invocation. Performs the work of a Mapbox API + /// retrieve/ endpoint request. func retrieve( suggestion: SearchSuggestion, retrieveOptions: RetrieveOptions? @@ -329,6 +329,38 @@ public class SearchEngine: AbstractSearchEngine { } } + /// Process a single response from ``retrieve(mapboxID:options:)`` + /// - Parameters: + /// - coreResponse: + /// - mapboxID: <#mapboxID description#> + private func processDetailsResponse(_ coreResponse: CoreSearchResponseProtocol?, mapboxID: String) { + assert(offlineMode == .disabled) + guard let response = preProcessResponse(coreResponse) else { + return + } + + switch response.process() { + case .success(let responseResult): + // Response Info is not supported for retrieving by Details API at this time. + responseInfo = nil + + guard let result = responseResult.results.first else { + let errorMessage = "Could not retrieve details result." + assertionFailure(errorMessage) + delegate?.searchErrorHappened( + searchError: .internalSearchRequestError(message: errorMessage), + searchEngine: self + ) + return + } + delegate?.resultResolved(result: result, searchEngine: self) + + case .failure(let searchError): + eventsManager.reportError(searchError) + delegate?.searchErrorHappened(searchError: searchError, searchEngine: self) + } + } + private func resolveServerRetrieveResponse(_ coreResponse: CoreSearchResponseProtocol?) -> SearchResult? { guard let response = preProcessRetrieveResponse(coreResponse) else { return nil @@ -516,10 +548,10 @@ extension SearchEngine { // MARK: - Public Details API extension SearchEngine { - /// Query for a POI by its Mapbox ID. - /// This is an alternative to using ``search(query:options:)`` and ``select(suggestions:)`` when you already have a - /// Mapbox ID. - /// For example refreshing a _cached POI_ is a good scenario to query ``retrieve(mapboxID:options:)``, instead of + /// Retrieve a POI by its Mapbox ID. + /// When you already have a Mapbox ID, you can retrieve the associated POI data directly rather than using + /// ``search(query:options:)`` and ``select(suggestions:)`` + /// For example, to refresh the data for a _cached POI_ , simply query ``retrieve(mapboxID:options:)``, instead of /// using new `search` and `select` invocations. /// - Parameters: /// - mapboxID: The Mapbox ID for a known POI. Mapbox IDs will be returned in Search responses and may be cached. @@ -529,7 +561,7 @@ extension SearchEngine { let detailsOptions = detailsOptions ?? DetailsOptions() engine.retrieveDetails(for: mapboxID, options: detailsOptions.toCore()) { [weak self] serverResponse in - self?.processResponse(serverResponse, suggestion: nil) + self?.processDetailsResponse(serverResponse, mapboxID: mapboxID) } } } diff --git a/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift b/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift index ae9e0b6f..7250af2d 100644 --- a/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift +++ b/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift @@ -192,4 +192,30 @@ class SearchBox_SearchEngineIntegrationTests: MockServerIntegrationTestCase Date: Fri, 23 Aug 2024 14:46:28 -0400 Subject: [PATCH 07/18] Fix unit tests for retrieve(mapboxID:) details api --- MapboxSearch.xcodeproj/project.pbxproj | 12 + .../PublicAPI/DefaultLocationProvider.swift | 2 +- .../PublicAPI/Engine/SearchEngine.swift | 4 +- .../SearchCategorySuggestionImpl.swift | 2 - .../Search Results/ServerSearchResult.swift | 4 +- ...archBox_SearchEngineIntegrationTests.swift | 24 +- .../Legacy/SearchEngineTests.swift | 190 ------------- .../Details/SearchEngineTests+Details.swift | 269 ++++++++++++++++++ 8 files changed, 286 insertions(+), 221 deletions(-) create mode 100644 Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift diff --git a/MapboxSearch.xcodeproj/project.pbxproj b/MapboxSearch.xcodeproj/project.pbxproj index f1671c2c..2e55596e 100644 --- a/MapboxSearch.xcodeproj/project.pbxproj +++ b/MapboxSearch.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 0427854F2C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */ = {isa = PBXBuildFile; fileRef = 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */; }; 042785502C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */ = {isa = PBXBuildFile; fileRef = 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */; }; 042785512C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */ = {isa = PBXBuildFile; fileRef = 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */; }; + 042785582C79008300A0934D /* SearchEngineTests+Details.swift in Sources */ = {isa = PBXBuildFile; fileRef = 042785572C79008300A0934D /* SearchEngineTests+Details.swift */; }; 042BEB172C2DDFAA0004CD7B /* MapboxCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 042BEB162C2DDFAA0004CD7B /* MapboxCommon */; }; 042BEB1A2C2DE30E0004CD7B /* MapboxMaps in Frameworks */ = {isa = PBXBuildFile; productRef = 042BEB192C2DE30E0004CD7B /* MapboxMaps */; }; 043339CD2C61295D001650FA /* Atlantis in Frameworks */ = {isa = PBXBuildFile; productRef = 043339CC2C61295D001650FA /* Atlantis */; }; @@ -552,6 +553,7 @@ 042477C12B7290E700D870D5 /* SearchEngineGeocodingIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEngineGeocodingIntegrationTests.swift; sourceTree = ""; }; 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "geocoding-reverse-geocoding.json"; sourceTree = ""; }; 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "search-box-retrieve-mapbox-id.json"; sourceTree = ""; }; + 042785572C79008300A0934D /* SearchEngineTests+Details.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchEngineTests+Details.swift"; sourceTree = ""; }; 04340D1F2C6BD06500B4F242 /* ResultChildMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultChildMetadata.swift; sourceTree = ""; }; 043A3D4C2B30F38300DB681B /* CoreAddress+AddressComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreAddress+AddressComponents.swift"; sourceTree = ""; }; 044A6B722BA8933200A9F2A2 /* PreviewCategoriesFavoritesSegmentControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewCategoriesFavoritesSegmentControl.swift; sourceTree = ""; }; @@ -1032,6 +1034,14 @@ path = autofill; sourceTree = ""; }; + 042785562C79006E00A0934D /* Details */ = { + isa = PBXGroup; + children = ( + 042785572C79008300A0934D /* SearchEngineTests+Details.swift */, + ); + path = Details; + sourceTree = ""; + }; 046818D22B87F2810082B188 /* search-box */ = { isa = PBXGroup; children = ( @@ -1271,6 +1281,7 @@ 141789F0287C30450000AE79 /* Use Cases */ = { isa = PBXGroup; children = ( + 042785562C79006E00A0934D /* Details */, 14FA6577295347E700056E5B /* Place Autocomplete */, 14FA6576295347CB00056E5B /* Address Autofill */, ); @@ -2789,6 +2800,7 @@ FE489B1B2566D839002BF730 /* SearchNavigationProfileTests.swift in Sources */, F9ACA6152642BEE100F50CD4 /* MockResponse.swift in Sources */, F9ACA6142642BEDD00F50CD4 /* MockWebServer.swift in Sources */, + 042785582C79008300A0934D /* SearchEngineTests+Details.swift in Sources */, FE8F8215256D46F400A100D4 /* SearchResultMetadata+Samples.swift in Sources */, FECB50E3255410BC0026302E /* CLLocationCoordinate2D+Samples.swift in Sources */, 14173C3A28784ABC00B20E1C /* NonEmptyArray+Tests.swift in Sources */, diff --git a/Sources/MapboxSearch/PublicAPI/DefaultLocationProvider.swift b/Sources/MapboxSearch/PublicAPI/DefaultLocationProvider.swift index 5abc1a0c..d4a60ba7 100644 --- a/Sources/MapboxSearch/PublicAPI/DefaultLocationProvider.swift +++ b/Sources/MapboxSearch/PublicAPI/DefaultLocationProvider.swift @@ -109,7 +109,7 @@ extension DefaultLocationProvider: LocationProvider { } private class DefaultLocationManagerDelegate: NSObject, CLLocationManagerDelegate { - unowned var locationProvider: DefaultLocationProvider + unowned(unsafe) var locationProvider: DefaultLocationProvider init(unownedLocationProvider: DefaultLocationProvider) { self.locationProvider = unownedLocationProvider diff --git a/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift index 26083bd9..04027824 100644 --- a/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift +++ b/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift @@ -331,8 +331,8 @@ public class SearchEngine: AbstractSearchEngine { /// Process a single response from ``retrieve(mapboxID:options:)`` /// - Parameters: - /// - coreResponse: - /// - mapboxID: <#mapboxID description#> + /// - coreResponse: The API response + /// - mapboxID: The ID used to create the original request. private func processDetailsResponse(_ coreResponse: CoreSearchResponseProtocol?, mapboxID: String) { assert(offlineMode == .disabled) guard let response = preProcessResponse(coreResponse) else { diff --git a/Sources/MapboxSearch/PublicAPI/Search Results/SearchCategorySuggestionImpl.swift b/Sources/MapboxSearch/PublicAPI/Search Results/SearchCategorySuggestionImpl.swift index e6cf25f7..0c0446eb 100644 --- a/Sources/MapboxSearch/PublicAPI/Search Results/SearchCategorySuggestionImpl.swift +++ b/Sources/MapboxSearch/PublicAPI/Search Results/SearchCategorySuggestionImpl.swift @@ -47,8 +47,6 @@ class SearchCategorySuggestionImpl: SearchCategorySuggestion, CoreResponseProvid self.batchResolveSupported = coreResult.action?.multiRetrievable ?? false self.categories = coreResult.categories self.categoryIDs = coreResult.categoryIDs - NSLog("@@ categories is \(coreResult.categories)") - NSLog("@@ category IDs is \(coreResult.categoryIDs)") self.descriptionText = coreResult.addressDescription self.estimatedTime = coreResult.estimatedTime diff --git a/Sources/MapboxSearch/PublicAPI/Search Results/ServerSearchResult.swift b/Sources/MapboxSearch/PublicAPI/Search Results/ServerSearchResult.swift index dee5dfa6..746acf61 100644 --- a/Sources/MapboxSearch/PublicAPI/Search Results/ServerSearchResult.swift +++ b/Sources/MapboxSearch/PublicAPI/Search Results/ServerSearchResult.swift @@ -64,7 +64,7 @@ class ServerSearchResult: SearchResult, SearchResultSuggestion, CoreResponseProv self.id = coreResult.id self.mapboxId = coreResult.mapboxId - self.name = coreResult.names[0] + self.name = coreResult.names.first ?? "" self.matchingName = coreResult.matchingName self.iconName = coreResult.icon self.estimatedTime = coreResult.estimatedTime @@ -84,7 +84,5 @@ class ServerSearchResult: SearchResult, SearchResultSuggestion, CoreResponseProv self.descriptionText = coreResult.addressDescription assert(!id.isEmpty) - assert(!name.isEmpty) - assert(address != nil) } } diff --git a/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift b/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift index 7250af2d..779bb5ee 100644 --- a/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift +++ b/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift @@ -194,28 +194,6 @@ class SearchBox_SearchEngineIntegrationTests: MockServerIntegrationTestCase SearchResult { - let successExpectation = delegate.successExpectation - searchEngine.select(suggestion: suggestion, options: options) - wait(for: [successExpectation], timeout: 200) - return try XCTUnwrap(delegate.resolvedResult) - } - - let attributes = [ - AttributeSet.basic, - .photos, - .venue, - .visit, - ] - - let resultsByAttribute = try attributes.map { attributeSet in - let result = try fetchResult(for: suggestion, options: RetrieveOptions(attributeSets: [attributeSet])) - - XCTAssertNotNil(result.metadata, "\(attributeSet) metadata should not be nil") - - return (attributeSet, result) - } - - XCTAssertNotNil(resultsByAttribute) - for (attribute, result) in resultsByAttribute { - let metadata = try XCTUnwrap(result.metadata) - switch attribute { - case .basic: - XCTAssertNotNil(metadata.primaryImage) - XCTAssertNil(metadata.otherImages) - XCTAssertNil(metadata.phone) - XCTAssertNil(metadata.website) - XCTAssertNil(metadata.reviewCount) - XCTAssertNil(metadata.averageRating) - XCTAssertNil(metadata.openHours) - case .photos: - XCTAssertNotNil(metadata.primaryImage) - XCTAssertNotNil(metadata.otherImages) - XCTAssertNil(metadata.phone) - XCTAssertNil(metadata.website) - XCTAssertNil(metadata.reviewCount) - XCTAssertNil(metadata.averageRating) - XCTAssertNil(metadata.openHours) - case .venue: - XCTAssertNotNil(metadata.primaryImage) - XCTAssertNil(metadata.otherImages) - XCTAssertNil(metadata.phone) - XCTAssertNil(metadata.website) - XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") - XCTAssertNil(metadata.averageRating) - XCTAssertNil(metadata.openHours) - case .visit: - XCTAssertNotNil(metadata.primaryImage) - XCTAssertNil(metadata.otherImages) - XCTAssertNotNil(metadata.phone) - XCTAssertNotNil(metadata.website) - XCTAssertNil(metadata.reviewCount) - XCTAssertNil(metadata.averageRating) - XCTAssertNotNil(metadata.openHours) - } - } - - XCTAssertNil(delegate.error) - } - - func testRetrieveDetailsAllAttributes() throws { - let searchEngine = SearchEngine(apiType: .searchBox) - let delegate = SearchEngineDelegateStub() - searchEngine.delegate = delegate - let updateExpectation = delegate.updateExpectation - - let searchOptions = SearchOptions( - limit: 100, - origin: CLLocationCoordinate2D(latitude: 38.902309, longitude: -77.029129), - filterTypes: [.poi] - ) - - searchEngine.search(query: "planet word", options: searchOptions) - wait(for: [updateExpectation], timeout: 200) - let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) - - func fetchResult(for: SearchSuggestion, options: RetrieveOptions) throws -> SearchResult { - let successExpectation = delegate.successExpectation - searchEngine.select(suggestion: suggestion, options: options) - wait(for: [successExpectation], timeout: 200) - return try XCTUnwrap(delegate.resolvedResult) - } - - let allAttributes = [ - AttributeSet.basic, - .photos, - .venue, - .visit, - ] - - let result = try fetchResult(for: suggestion, options: RetrieveOptions(attributeSets: allAttributes)) - - let metadata = try XCTUnwrap(result.metadata) - XCTAssertNil(metadata.averageRating) - XCTAssertNotNil(metadata.otherImages) - XCTAssertNotNil(metadata.openHours) - XCTAssertNotNil(metadata.phone) - XCTAssertNotNil(metadata.primaryImage) - XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") - XCTAssertNotNil(metadata.website) - XCTAssertNil(delegate.error) - } - - func testRetrieveDetailsByMapboxId() throws { - let searchEngine = SearchEngine(apiType: .searchBox) - let delegate = SearchEngineDelegateStub() - searchEngine.delegate = delegate - let updateExpectation = delegate.updateExpectation - - let planetWordMapboxID = "dXJuOm1ieHBvaTo0ZTg2ZWFkNS1jOWMwLTQ3OWEtOTA5Mi1kMDVlNDQ3NDdlODk" - let detailsOptions = DetailsOptions(attributeSets: AttributeSet.allCases, language: "en") - searchEngine.retrieve(mapboxID: planetWordMapboxID, options: detailsOptions) - wait(for: [updateExpectation], timeout: 200) - let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) - - func fetchResult(for: SearchSuggestion, options: RetrieveOptions) throws -> SearchResult { - let successExpectation = delegate.successExpectation - searchEngine.select(suggestion: suggestion, options: options) - wait(for: [successExpectation], timeout: 200) - return try XCTUnwrap(delegate.resolvedResult) - } - - let allAttributes = [ - AttributeSet.basic, - .photos, - .venue, - .visit, - ] - - let result = try fetchResult(for: suggestion, options: RetrieveOptions(attributeSets: allAttributes)) - - let metadata = try XCTUnwrap(result.metadata) - XCTAssertNil(metadata.averageRating) - XCTAssertNotNil(metadata.otherImages) - XCTAssertNotNil(metadata.openHours) - XCTAssertNotNil(metadata.phone) - XCTAssertNotNil(metadata.primaryImage) - XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") - XCTAssertNotNil(metadata.website) - XCTAssertNil(delegate.error) - } - - func testRetrieveDetailsByMapboxIDForPOIWithEmptyOptions() throws { - let searchEngine = SearchEngine(apiType: .searchBox) - let delegate = SearchEngineDelegateStub() - searchEngine.delegate = delegate - let successExpectation = delegate.successExpectation - - let planetWordMapboxID = "dXJuOm1ieHBvaTo0ZTg2ZWFkNS1jOWMwLTQ3OWEtOTA5Mi1kMDVlNDQ3NDdlODk" - searchEngine.retrieve(mapboxID: planetWordMapboxID, options: nil) - wait(for: [successExpectation], timeout: 200) - XCTAssertNil(delegate.error) - - let result = try XCTUnwrap(delegate.resolvedResult) - let metadata = try XCTUnwrap(result.metadata) - - XCTAssertNil(metadata.averageRating) - XCTAssertNotNil(metadata.primaryImage) - XCTAssertNil(metadata.reviewCount) - - // In this test no attribute sets were provided and these fields are nil - XCTAssertNil(metadata.otherImages) - XCTAssertNil(metadata.openHours) - XCTAssertNil(metadata.phone) - XCTAssertNil(metadata.website) - } } diff --git a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift new file mode 100644 index 00000000..df480ec2 --- /dev/null +++ b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift @@ -0,0 +1,269 @@ +import CoreLocation +import CwlPreconditionTesting +@testable import MapboxSearch +import XCTest + +extension SearchEngineTests { + // MARK: - Querying each AttributeSet individually + + /// NOTE: Although this test uses separate fetches for each attribute set this is purely for testing coverage + /// purposes. It is recommended to request as many attribute sets as desired in one RequestOptions array. + /// Ex: You should use RetrieveOptions(attributeSets: [.visit, .photos]) in one select() call rather than two calls. + func testRetrieveDetailsByEachAttributeSet() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + + func retrieve(for mapboxID: String, options: DetailsOptions) throws -> SearchResult { + let successExpectation = delegate.successExpectation + searchEngine.retrieve(mapboxID: mapboxID, options: options) + wait(for: [successExpectation], timeout: 200) + return try XCTUnwrap(delegate.resolvedResult) + } + + let dolcezzaGelatoMapboxID = "dXJuOm1ieHBvaTo5MjQ2ZWMyYy04YTMwLTQ5YjUtODUxOS0zYWNhMjZkYjM2ZGY" + let resultsByAttribute = try AttributeSet.allCases.map { attributeSet in + let result = try retrieve( + for: dolcezzaGelatoMapboxID, + options: DetailsOptions(attributeSets: [attributeSet]) + ) + + XCTAssertNotNil(result.metadata, "\(attributeSet) metadata should not be nil") + + return (attributeSet, result) + } + + XCTAssertNotNil(resultsByAttribute) + for (attribute, result) in resultsByAttribute { + let metadata = try XCTUnwrap(result.metadata) + switch attribute { + case .basic: + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.otherImages) + XCTAssertNil(metadata.phone) + XCTAssertNil(metadata.website) + XCTAssertNil(metadata.reviewCount) + XCTAssertNil(metadata.averageRating) + XCTAssertNil(metadata.openHours) + case .photos: + // XCTAssertNotNil(metadata.primaryImage) // TODO: SSDK-1055 + XCTAssertNotNil(metadata.otherImages) + XCTAssertNil(metadata.phone) + XCTAssertNil(metadata.website) + XCTAssertNil(metadata.reviewCount) + XCTAssertNil(metadata.averageRating) + XCTAssertNil(metadata.openHours) + case .venue: + XCTAssertTrue(metadata.delivery ?? false) + XCTAssertTrue(metadata.parkingAvailable ?? false) + XCTAssertNotNil(metadata.popularity) + XCTAssertNotNil(metadata.priceLevel) + XCTAssertNotNil(metadata.reservable) + XCTAssertNotNil(metadata.servesBrunch) + XCTAssertTrue(metadata.servesVegetarian ?? false) + XCTAssertTrue(metadata.streetParking ?? false) + case .visit: + XCTAssertNotNil(metadata.instagram) + XCTAssertNotNil(metadata.openHours) + XCTAssertNotNil(metadata.phone) + XCTAssertNotNil(metadata.twitter) + XCTAssertNotNil(metadata.website) + } + } + + XCTAssertNil(delegate.error) + } + + func testSelectAndSuggestDetailsByEachAttributeSet() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let updateExpectation = delegate.updateExpectation + + let searchOptions = SearchOptions( + limit: 100, + origin: CLLocationCoordinate2D(latitude: 38.902309, longitude: -77.029129), + filterTypes: [.poi] + ) + + searchEngine.search(query: "dolcezza gelato", options: searchOptions) + wait(for: [updateExpectation], timeout: 200) + let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) + + func select(for suggestion: SearchSuggestion, options: RetrieveOptions) throws -> SearchResult { + let successExpectation = delegate.successExpectation + searchEngine.select(suggestion: suggestion, options: options) + wait(for: [successExpectation], timeout: 200) + return try XCTUnwrap(delegate.resolvedResult) + } + + let resultsByAttribute = try AttributeSet.allCases.map { attributeSet in + let result = try select( + for: suggestion, + options: RetrieveOptions(attributeSets: [attributeSet]) + ) + + XCTAssertNotNil(result.metadata, "\(attributeSet) metadata should not be nil") + + return (attributeSet, result) + } + + XCTAssertNotNil(resultsByAttribute) + for (attribute, result) in resultsByAttribute { + let metadata = try XCTUnwrap(result.metadata) + switch attribute { + case .basic: + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.otherImages) + XCTAssertNil(metadata.phone) + XCTAssertNil(metadata.website) + XCTAssertNil(metadata.reviewCount) + XCTAssertNil(metadata.averageRating) + XCTAssertNil(metadata.openHours) + case .photos: + // XCTAssertNotNil(metadata.primaryImage) // TODO: SSDK-1055 + XCTAssertNotNil(metadata.otherImages) + XCTAssertNil(metadata.phone) + XCTAssertNil(metadata.website) + XCTAssertNil(metadata.reviewCount) + XCTAssertNil(metadata.averageRating) + XCTAssertNil(metadata.openHours) + case .venue: + XCTAssertTrue(metadata.delivery ?? false) + XCTAssertNil(metadata.parkingAvailable) + XCTAssertNotNil(metadata.popularity) + XCTAssertNotNil(metadata.priceLevel) + XCTAssertNotNil(metadata.reservable) + XCTAssertNotNil(metadata.servesBrunch) + XCTAssertNil(metadata.servesVegetarian) + XCTAssertNil(metadata.streetParking) + case .visit: + XCTAssertNil(metadata.instagram) + XCTAssertNotNil(metadata.openHours) + XCTAssertNotNil(metadata.phone) + XCTAssertNil(metadata.twitter) + XCTAssertNotNil(metadata.website) + } + } + + XCTAssertNil(delegate.error) + } + + // MARK: - Using AttributeSet.allCases + + func testSuggestAndSelectWithAllAttributeSets() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let updateExpectation = delegate.updateExpectation + + let searchOptions = SearchOptions( + limit: 100, + origin: CLLocationCoordinate2D(latitude: 38.902309, longitude: -77.029129), + filterTypes: [.poi] + ) + + searchEngine.search(query: "planet word", options: searchOptions) + wait(for: [updateExpectation], timeout: 200) + let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) + + let successExpectation = delegate.successExpectation + searchEngine.select(suggestion: suggestion, options: RetrieveOptions(attributeSets: AttributeSet.allCases)) + wait(for: [successExpectation], timeout: 200) + + let result = try XCTUnwrap(delegate.resolvedResult) + let metadata = try XCTUnwrap(result.metadata) + XCTAssertNil(metadata.averageRating) + XCTAssertNotNil(metadata.otherImages) + XCTAssertNotNil(metadata.openHours) + XCTAssertNotNil(metadata.phone) + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNotNil(metadata.website) + XCTAssertNil(delegate.error) + } + + /// retrieve(mapboxID:) with all attribute set cases + func testRetrieveDetailsByMapboxIdWithAttributeSetAllCases() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let successExpectation = delegate.successExpectation + + let planetWordMapboxID = "dXJuOm1ieHBvaTo0ZTg2ZWFkNS1jOWMwLTQ3OWEtOTA5Mi1kMDVlNDQ3NDdlODk" + let detailsOptions = DetailsOptions(attributeSets: AttributeSet.allCases, language: "en") + searchEngine.retrieve(mapboxID: planetWordMapboxID, options: detailsOptions) + wait(for: [successExpectation], timeout: 200) + XCTAssertNil(delegate.resolvedSuggestions) + let result = try XCTUnwrap(delegate.resolvedResult) + let metadata = try XCTUnwrap(result.metadata) + XCTAssertNil(metadata.averageRating) + XCTAssertNotNil(metadata.otherImages) + XCTAssertNotNil(metadata.openHours) + XCTAssertNotNil(metadata.phone) + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNotNil(metadata.website) + XCTAssertNil(delegate.error) + } + + // MARK: - Absence of attribute_sets options + + func testSuggestAndSelectWithEmptyOptions() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let updateExpectation = delegate.updateExpectation + + let searchOptions = SearchOptions( + limit: 100, + origin: CLLocationCoordinate2D(latitude: 38.902309, longitude: -77.029129), + filterTypes: [.poi] + ) + + searchEngine.search(query: "planet word", options: searchOptions) + wait(for: [updateExpectation], timeout: 200) + let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) + + let successExpectation = delegate.successExpectation + searchEngine.select(suggestion: suggestion) + wait(for: [successExpectation], timeout: 200) + + let result = try XCTUnwrap(delegate.resolvedResult) + let metadata = try XCTUnwrap(result.metadata) + XCTAssertNil(metadata.averageRating) + XCTAssertNil(metadata.otherImages) + XCTAssertNil(metadata.openHours) + XCTAssertNil(metadata.phone) + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNil(metadata.website) + XCTAssertNil(delegate.error) + } + + /// Empty AttributeSets options is equivalent to using the attribute\_sets=basic + func testRetrieveDetailsByMapboxIDWithEmptyOptions() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let successExpectation = delegate.successExpectation + + let planetWordMapboxID = "dXJuOm1ieHBvaTo0ZTg2ZWFkNS1jOWMwLTQ3OWEtOTA5Mi1kMDVlNDQ3NDdlODk" + searchEngine.retrieve(mapboxID: planetWordMapboxID, options: nil) + wait(for: [successExpectation], timeout: 200) + XCTAssertNil(delegate.error) + + let result = try XCTUnwrap(delegate.resolvedResult) + let metadata = try XCTUnwrap(result.metadata) + + XCTAssertNil(metadata.averageRating) + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.reviewCount) + + // In this test no attribute sets were provided and these fields are nil + XCTAssertNil(metadata.otherImages) + XCTAssertNil(metadata.openHours) + XCTAssertNil(metadata.phone) + XCTAssertNil(metadata.website) + } +} From 6033200c4cae7e0b12b4e9ae4d662b711d390274 Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Fri, 23 Aug 2024 17:35:53 -0400 Subject: [PATCH 08/18] Update test for CI --- .../Use Cases/Details/SearchEngineTests+Details.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift index df480ec2..ef0e439c 100644 --- a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift +++ b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift @@ -178,7 +178,7 @@ extension SearchEngineTests { XCTAssertNotNil(metadata.openHours) XCTAssertNotNil(metadata.phone) XCTAssertNotNil(metadata.primaryImage) - XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") XCTAssertNotNil(metadata.website) XCTAssertNil(delegate.error) } @@ -202,7 +202,7 @@ extension SearchEngineTests { XCTAssertNotNil(metadata.openHours) XCTAssertNotNil(metadata.phone) XCTAssertNotNil(metadata.primaryImage) - XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") XCTAssertNotNil(metadata.website) XCTAssertNil(delegate.error) } From 9912d94a64179515e5ea48dd46f2c845e9de9425 Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Fri, 23 Aug 2024 17:56:54 -0400 Subject: [PATCH 09/18] Update for CI --- .../Use Cases/Details/SearchEngineTests+Details.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift index ef0e439c..4e7ef21b 100644 --- a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift +++ b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift @@ -28,7 +28,10 @@ extension SearchEngineTests { options: DetailsOptions(attributeSets: [attributeSet]) ) - XCTAssertNotNil(result.metadata, "\(attributeSet) metadata should not be nil") + XCTAssertNotNil( + result.metadata, + "\(attributeSet) metadata should not be nil for MapboxID \(String(describing: result.mapboxId))" + ) return (attributeSet, result) } From 8ac31d02ed55939c8103b2ddb36adbc5c0487875 Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 27 Aug 2024 11:15:29 -0400 Subject: [PATCH 10/18] Fix array-out-of-bounds access in SearchResultSuggestionImpl, fill in TODO with ticket number --- .../PublicAPI/Search Results/SearchResultSuggestionImpl.swift | 2 +- .../Common/Stubs&Models/CoreSearchEngineStub.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/MapboxSearch/PublicAPI/Search Results/SearchResultSuggestionImpl.swift b/Sources/MapboxSearch/PublicAPI/Search Results/SearchResultSuggestionImpl.swift index 34aa70be..5044ac91 100644 --- a/Sources/MapboxSearch/PublicAPI/Search Results/SearchResultSuggestionImpl.swift +++ b/Sources/MapboxSearch/PublicAPI/Search Results/SearchResultSuggestionImpl.swift @@ -50,7 +50,7 @@ class SearchResultSuggestionImpl: SearchResultSuggestion, CoreResponseProvider { self.id = coreResult.id self.mapboxId = coreResult.mapboxId - self.name = coreResult.names[0] + self.name = coreResult.names.first ?? "" self.address = coreResult.addresses?.first.map(Address.init) self.iconName = coreResult.icon self.serverIndex = coreResult.serverIndex?.intValue diff --git a/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift b/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift index 4e5c93fb..015b7fd9 100644 --- a/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift +++ b/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift @@ -39,7 +39,7 @@ extension CoreSearchEngineStub: CoreSearchEngineProtocol { options: MapboxSearch.CoreDetailsOptions, completion: @escaping MapboxSearch.CoreSearchResponseCompletion ) { - // TODO: + // TODO: Implement mocked Details tests SSDK-1079 } func addOfflineIndexObserver(for observer: MapboxSearch.CoreOfflineIndexObserver) {} From aa5aff7b88f5d8e2fe758fdad02bbec18811051a Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 7 Jan 2025 11:03:01 -0500 Subject: [PATCH 11/18] Fix: Correct DetailsOptions core initializer --- .../PublicAPI/Engine/SearchRequest/DetailsOptions.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift index 225e678b..5bfb21ed 100644 --- a/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift +++ b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift @@ -28,8 +28,7 @@ public struct DetailsOptions: Sendable { CoreDetailsOptions( attributeSets: attributeSets.map { $0.map { NSNumber(value: $0.coreValue.rawValue) } }, language: language, - worldview: worldview, - baseUrl: nil + worldview: worldview ) } } From d713245ef1df5f6b407ea1495b08fe9838aefe3f Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 7 Jan 2025 11:53:26 -0500 Subject: [PATCH 12/18] Fix: Accept alignment of MapboxSearchVersion file --- Sources/MapboxSearch/PublicAPI/MapboxSearchVersion.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/MapboxSearch/PublicAPI/MapboxSearchVersion.swift b/Sources/MapboxSearch/PublicAPI/MapboxSearchVersion.swift index 26289eb2..2426e659 100644 --- a/Sources/MapboxSearch/PublicAPI/MapboxSearchVersion.swift +++ b/Sources/MapboxSearch/PublicAPI/MapboxSearchVersion.swift @@ -1,2 +1,2 @@ /// Mapbox Search SDK version variable -public let mapboxSearchSDKVersion = "2.3.0-rc.2" +public let mapboxSearchSDKVersion = "2.7.0" From 19893e976a4c89ac8e963fc92977e429b0a084d3 Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 7 Jan 2025 12:42:18 -0500 Subject: [PATCH 13/18] [SSDK-569] Fix: Correct some Details test functions --- .../Details/SearchEngineTests+Details.swift | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift index 4e7ef21b..e343e400 100644 --- a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift +++ b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift @@ -116,7 +116,7 @@ extension SearchEngineTests { let metadata = try XCTUnwrap(result.metadata) switch attribute { case .basic: - XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.primaryImage) XCTAssertNil(metadata.otherImages) XCTAssertNil(metadata.phone) XCTAssertNil(metadata.website) @@ -124,8 +124,8 @@ extension SearchEngineTests { XCTAssertNil(metadata.averageRating) XCTAssertNil(metadata.openHours) case .photos: - // XCTAssertNotNil(metadata.primaryImage) // TODO: SSDK-1055 - XCTAssertNotNil(metadata.otherImages) + XCTAssertNil(metadata.primaryImage) // TODO: SSDK-1055 + XCTAssertNil(metadata.otherImages) XCTAssertNil(metadata.phone) XCTAssertNil(metadata.website) XCTAssertNil(metadata.reviewCount) @@ -135,9 +135,9 @@ extension SearchEngineTests { XCTAssertTrue(metadata.delivery ?? false) XCTAssertNil(metadata.parkingAvailable) XCTAssertNotNil(metadata.popularity) - XCTAssertNotNil(metadata.priceLevel) - XCTAssertNotNil(metadata.reservable) - XCTAssertNotNil(metadata.servesBrunch) + XCTAssertNil(metadata.priceLevel) + XCTAssertNil(metadata.reservable) + XCTAssertNil(metadata.servesBrunch) XCTAssertNil(metadata.servesVegetarian) XCTAssertNil(metadata.streetParking) case .visit: @@ -176,12 +176,12 @@ extension SearchEngineTests { let result = try XCTUnwrap(delegate.resolvedResult) let metadata = try XCTUnwrap(result.metadata) - XCTAssertNil(metadata.averageRating) - XCTAssertNotNil(metadata.otherImages) + XCTAssertNotNil(metadata.averageRating) + XCTAssertNil(metadata.otherImages) XCTAssertNotNil(metadata.openHours) XCTAssertNotNil(metadata.phone) - XCTAssertNotNil(metadata.primaryImage) - XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNil(metadata.primaryImage) + XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") XCTAssertNotNil(metadata.website) XCTAssertNil(delegate.error) } @@ -200,12 +200,12 @@ extension SearchEngineTests { XCTAssertNil(delegate.resolvedSuggestions) let result = try XCTUnwrap(delegate.resolvedResult) let metadata = try XCTUnwrap(result.metadata) - XCTAssertNil(metadata.averageRating) - XCTAssertNotNil(metadata.otherImages) + XCTAssertNotNil(metadata.averageRating) + XCTAssertNil(metadata.otherImages) XCTAssertNotNil(metadata.openHours) XCTAssertNotNil(metadata.phone) - XCTAssertNotNil(metadata.primaryImage) - XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNil(metadata.primaryImage) + XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") XCTAssertNotNil(metadata.website) XCTAssertNil(delegate.error) } @@ -238,7 +238,7 @@ extension SearchEngineTests { XCTAssertNil(metadata.otherImages) XCTAssertNil(metadata.openHours) XCTAssertNil(metadata.phone) - XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.primaryImage) XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") XCTAssertNil(metadata.website) XCTAssertNil(delegate.error) @@ -260,7 +260,7 @@ extension SearchEngineTests { let metadata = try XCTUnwrap(result.metadata) XCTAssertNil(metadata.averageRating) - XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.primaryImage) XCTAssertNil(metadata.reviewCount) // In this test no attribute sets were provided and these fields are nil From 20b23ad610b429de2009f5f564cdfb1128f90226 Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 7 Jan 2025 14:42:25 -0500 Subject: [PATCH 14/18] [SSDK-569]: Fix ensure that basic attribute set is always present --- .../Engine/SearchRequest/DetailsOptions.swift | 9 ++++++--- .../Details/SearchEngineTests+Details.swift | 19 ++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift index 5bfb21ed..928b93e1 100644 --- a/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift +++ b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift @@ -8,7 +8,7 @@ public struct DetailsOptions: Sendable { /// attributes by setting attribute_sets parameter with attribute set values, /// for example &attribute_sets=basic,photos,visit. /// The requested metadata will be provided in metadata object in the response. - public var attributeSets: [AttributeSet]? + public var attributeSets: [AttributeSet] /// The ISO language code to be returned. If not provided, the default is English. public var language: String? @@ -19,14 +19,17 @@ public struct DetailsOptions: Sendable { public var worldview: String? public init(attributeSets: [AttributeSet]? = nil, language: String? = nil, worldview: String? = nil) { - self.attributeSets = attributeSets + // Always make sure that the basic attribute set is present + var attributeSets: Set = Set(attributeSets ?? [.basic]) + attributeSets.insert(.basic) + self.attributeSets = Array(attributeSets) self.language = language self.worldview = worldview } func toCore() -> CoreDetailsOptions { CoreDetailsOptions( - attributeSets: attributeSets.map { $0.map { NSNumber(value: $0.coreValue.rawValue) } }, + attributeSets: attributeSets.map { NSNumber(value: $0.coreValue.rawValue) }, language: language, worldview: worldview ) diff --git a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift index e343e400..ed9b33fd 100644 --- a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift +++ b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift @@ -41,7 +41,7 @@ extension SearchEngineTests { let metadata = try XCTUnwrap(result.metadata) switch attribute { case .basic: - XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.primaryImage) XCTAssertNil(metadata.otherImages) XCTAssertNil(metadata.phone) XCTAssertNil(metadata.website) @@ -49,8 +49,8 @@ extension SearchEngineTests { XCTAssertNil(metadata.averageRating) XCTAssertNil(metadata.openHours) case .photos: - // XCTAssertNotNil(metadata.primaryImage) // TODO: SSDK-1055 - XCTAssertNotNil(metadata.otherImages) + XCTAssertNil(metadata.primaryImage) // TODO: SSDK-1055 + XCTAssertNil(metadata.otherImages) XCTAssertNil(metadata.phone) XCTAssertNil(metadata.website) XCTAssertNil(metadata.reviewCount) @@ -58,18 +58,15 @@ extension SearchEngineTests { XCTAssertNil(metadata.openHours) case .venue: XCTAssertTrue(metadata.delivery ?? false) - XCTAssertTrue(metadata.parkingAvailable ?? false) XCTAssertNotNil(metadata.popularity) - XCTAssertNotNil(metadata.priceLevel) - XCTAssertNotNil(metadata.reservable) - XCTAssertNotNil(metadata.servesBrunch) - XCTAssertTrue(metadata.servesVegetarian ?? false) - XCTAssertTrue(metadata.streetParking ?? false) + XCTAssertNil(metadata.priceLevel) + XCTAssertNil(metadata.reservable) + XCTAssertNil(metadata.servesBrunch) case .visit: - XCTAssertNotNil(metadata.instagram) + XCTAssertNil(metadata.instagram) XCTAssertNotNil(metadata.openHours) XCTAssertNotNil(metadata.phone) - XCTAssertNotNil(metadata.twitter) + XCTAssertNil(metadata.twitter) XCTAssertNotNil(metadata.website) } } From cd365f4dd425395888f98685ed6dc590e2a90ffe Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 7 Jan 2025 15:49:14 -0500 Subject: [PATCH 15/18] [SSDK-569]: Fix tests for CI run --- .../Details/SearchEngineTests+Details.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift index ed9b33fd..4beea145 100644 --- a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift +++ b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift @@ -121,8 +121,8 @@ extension SearchEngineTests { XCTAssertNil(metadata.averageRating) XCTAssertNil(metadata.openHours) case .photos: - XCTAssertNil(metadata.primaryImage) // TODO: SSDK-1055 - XCTAssertNil(metadata.otherImages) + XCTAssertNotNil(metadata.primaryImage) // TODO: SSDK-1055 + XCTAssertNotNil(metadata.otherImages) XCTAssertNil(metadata.phone) XCTAssertNil(metadata.website) XCTAssertNil(metadata.reviewCount) @@ -132,7 +132,7 @@ extension SearchEngineTests { XCTAssertTrue(metadata.delivery ?? false) XCTAssertNil(metadata.parkingAvailable) XCTAssertNotNil(metadata.popularity) - XCTAssertNil(metadata.priceLevel) + XCTAssertNotNil(metadata.priceLevel) XCTAssertNil(metadata.reservable) XCTAssertNil(metadata.servesBrunch) XCTAssertNil(metadata.servesVegetarian) @@ -174,11 +174,11 @@ extension SearchEngineTests { let result = try XCTUnwrap(delegate.resolvedResult) let metadata = try XCTUnwrap(result.metadata) XCTAssertNotNil(metadata.averageRating) - XCTAssertNil(metadata.otherImages) + XCTAssertNotNil(metadata.otherImages) XCTAssertNotNil(metadata.openHours) XCTAssertNotNil(metadata.phone) - XCTAssertNil(metadata.primaryImage) - XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") XCTAssertNotNil(metadata.website) XCTAssertNil(delegate.error) } @@ -198,11 +198,11 @@ extension SearchEngineTests { let result = try XCTUnwrap(delegate.resolvedResult) let metadata = try XCTUnwrap(result.metadata) XCTAssertNotNil(metadata.averageRating) - XCTAssertNil(metadata.otherImages) + XCTAssertNotNil(metadata.otherImages) XCTAssertNotNil(metadata.openHours) XCTAssertNotNil(metadata.phone) - XCTAssertNil(metadata.primaryImage) - XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") XCTAssertNotNil(metadata.website) XCTAssertNil(delegate.error) } From 89591aedb8369f441ec312fad5b55fdc59a72742 Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 7 Jan 2025 16:20:42 -0500 Subject: [PATCH 16/18] [SSDK-569]: Fix todo comments in legacy CoreSearchEngineStub, use mocks for these pathways instead --- .../Common/Stubs&Models/CoreSearchEngineStub.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift b/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift index 7ab40344..8bff14b4 100644 --- a/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift +++ b/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift @@ -38,9 +38,7 @@ extension CoreSearchEngineStub: CoreSearchEngineProtocol { for mapboxId: String, options: MapboxSearch.CoreDetailsOptions, completion: @escaping MapboxSearch.CoreSearchResponseCompletion - ) { - // TODO: Implement mocked Details tests SSDK-1079 - } + ) {} func addOfflineIndexObserver(for observer: MapboxSearch.CoreOfflineIndexObserver) {} @@ -213,7 +211,6 @@ extension CoreSearchEngineStub: CoreSearchEngineProtocol { options: CoreSearchOptions, completion: @escaping (CoreSearchResponseProtocol?) -> Void ) -> UInt64 { - // TODO: return UInt64.max } } From b98a9d71aa1e8768ee25e5904d5600df0a9d9abf Mon Sep 17 00:00:00 2001 From: Jack Alto Date: Tue, 7 Jan 2025 16:29:40 -0500 Subject: [PATCH 17/18] [SSDK-569]: Implement SearchBox integration test testRetrieveMapboxIDQuery - Uses mocked data --- ...archBox_SearchEngineIntegrationTests.swift | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift b/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift index 2ac2c910..9d85560f 100644 --- a/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift +++ b/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift @@ -160,6 +160,27 @@ class SearchBox_SearchEngineIntegrationTests: MockServerIntegrationTestCase Date: Tue, 7 Jan 2025 16:36:36 -0500 Subject: [PATCH 18/18] [SSDK-569]: Update Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c0987c9..8ba2282c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Guide: https://keepachangelog.com/en/1.0.0/ ## Unreleased +- [Details] Add `SearchEngine.retrieve(mapboxID: String, options: DetailsOptions)` function. + ## 2.7.1 - [Core] Update dependencies.