From c06f6fda6cb0d9717bb2afbed35899e126a14f40 Mon Sep 17 00:00:00 2001 From: Pedro Paulo de Amorim Date: Sun, 4 Oct 2020 20:40:11 +0100 Subject: [PATCH 1/5] Add missing instrumented tests for settings, update and search routes --- Sources/MeiliSearch/Client.swift | 10 +- Sources/MeiliSearch/Constants.swift | 1 - Sources/MeiliSearch/Documents.swift | 9 +- Sources/MeiliSearch/Indexes.swift | 13 + Sources/MeiliSearch/Model/Key.swift | 15 +- .../MeiliSearch/Model/SearchParameters.swift | 82 +- Sources/MeiliSearch/Model/SearchResult.swift | 81 +- Sources/MeiliSearch/Model/Setting.swift | 10 +- Sources/MeiliSearch/Model/Stat.swift | 12 + Sources/MeiliSearch/Model/Update.swift | 18 +- Sources/MeiliSearch/Model/Version.swift | 6 + Sources/MeiliSearch/Search.swift | 30 +- Sources/MeiliSearch/Settings.swift | 80 +- Sources/MeiliSearch/Stats.swift | 2 +- Sources/MeiliSearch/System.swift | 2 +- Sources/MeiliSearch/Updates.swift | 2 +- .../DocumentsTests.swift | 72 +- .../IndexesTests.swift | 2 +- .../MeiliSearchIntegrationTests/Pooling.swift | 1 - .../SearchTests.swift | 626 +++++++++ .../SettingsTests.swift | 1246 +++++++++++++++++ .../UpdatesTests.swift | 148 ++ .../XCTestManifests.swift | 4 + Tests/MeiliSearchUnitTests/SearchTests.swift | 2 + .../MeiliSearchUnitTests/SettingsTests.swift | 6 +- 25 files changed, 2349 insertions(+), 131 deletions(-) create mode 100644 Tests/MeiliSearchIntegrationTests/SearchTests.swift create mode 100644 Tests/MeiliSearchIntegrationTests/SettingsTests.swift create mode 100644 Tests/MeiliSearchIntegrationTests/UpdatesTests.swift diff --git a/Sources/MeiliSearch/Client.swift b/Sources/MeiliSearch/Client.swift index bca53c24..4e95dca8 100755 --- a/Sources/MeiliSearch/Client.swift +++ b/Sources/MeiliSearch/Client.swift @@ -62,6 +62,14 @@ public struct MeiliSearch { self.indexes.create(UID, completion) } + /** + Get or create a new Index for the given `uid`. + + - parameter UID: The unique identifier for the `Index` to be created. + - parameter completion: The completion closure used to notify when the server + completes the write request, it returns a `Result` object that contains `Index` + value. If the request was sucessful or `Error` if a failure occured. + */ public func getOrCreateIndex( UID: String, _ completion: @escaping (Result) -> Void) { @@ -526,7 +534,7 @@ public struct MeiliSearch { */ public func getDistinctAttribute( UID: String, - _ completion: @escaping (Result) -> Void) { + _ completion: @escaping (Result) -> Void) { self.settings.getDistinctAttribute(UID, completion) } diff --git a/Sources/MeiliSearch/Constants.swift b/Sources/MeiliSearch/Constants.swift index 38621d22..481ff2f9 100644 --- a/Sources/MeiliSearch/Constants.swift +++ b/Sources/MeiliSearch/Constants.swift @@ -5,7 +5,6 @@ struct Constants { static let customJSONDecoder: JSONDecoder = { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(Formatter.iso8601) - return decoder }() } diff --git a/Sources/MeiliSearch/Documents.swift b/Sources/MeiliSearch/Documents.swift index cc853ec4..b141b767 100755 --- a/Sources/MeiliSearch/Documents.swift +++ b/Sources/MeiliSearch/Documents.swift @@ -209,14 +209,7 @@ struct Documents { _ customDecoder: JSONDecoder? = nil, completion: (Result) -> Void) { do { - - let decoder: JSONDecoder - if let customDecoder: JSONDecoder = customDecoder { - decoder = customDecoder - } else { - decoder = Constants.customJSONDecoder - } - + let decoder: JSONDecoder = customDecoder ?? Constants.customJSONDecoder let value: T = try decoder.decode(T.self, from: data) completion(.success(value)) } catch { diff --git a/Sources/MeiliSearch/Indexes.swift b/Sources/MeiliSearch/Indexes.swift index cea23824..a0621eeb 100755 --- a/Sources/MeiliSearch/Indexes.swift +++ b/Sources/MeiliSearch/Indexes.swift @@ -12,6 +12,8 @@ struct Indexes { self.request = request } + // MARK: Functions + func get( _ UID: String, _ completion: @escaping (Result) -> Void) { @@ -162,6 +164,8 @@ struct Indexes { } + // MARK: Codable + private static func decodeJSON( _ data: Data, _ completion: (Result) -> Void) { @@ -196,9 +200,18 @@ struct UpdateIndexPayload: Codable { let primaryKey: String } +/** +Error type for all functions included in the `Indexes` struct. +*/ public enum CreateError: Swift.Error, Equatable { + + // MARK: Values + + /// Case the `Index` already exists in the Meilisearch instance, this error will return. case indexAlreadyExists + // MARK: Codable + static func decode(_ error: MSError) -> Swift.Error { let underlyingError: NSError = error.underlying as NSError diff --git a/Sources/MeiliSearch/Model/Key.swift b/Sources/MeiliSearch/Model/Key.swift index bdc2a6eb..fe851b43 100644 --- a/Sources/MeiliSearch/Model/Key.swift +++ b/Sources/MeiliSearch/Model/Key.swift @@ -6,12 +6,17 @@ import Foundation */ public struct Key: Codable, Equatable { - // MARK: Properties + // MARK: Properties - ///Private key used to access a determined set of API routes. - public let `private`: String + ///Private key used to access a determined set of API routes. + public let `private`: String - ///Public key used to access a determined set of API routes. - public let `public`: String + ///Public key used to access a determined set of API routes. + public let `public`: String + + enum CodingKeys: String, CodingKey { + case `private` + case `public` + } } diff --git a/Sources/MeiliSearch/Model/SearchParameters.swift b/Sources/MeiliSearch/Model/SearchParameters.swift index 72c9008f..aa2b8199 100644 --- a/Sources/MeiliSearch/Model/SearchParameters.swift +++ b/Sources/MeiliSearch/Model/SearchParameters.swift @@ -12,12 +12,12 @@ public struct SearchParameters: Codable, Equatable { /// Query string (mandatory). public let query: String - /// Number of documents to skip. - public let offset: Int - /// Number of documents to take. public let limit: Int + /// Number of documents to skip. + public let offset: Int + /// Document attributes to show. public let attributesToRetrieve: [String]? @@ -34,7 +34,6 @@ public struct SearchParameters: Codable, Equatable { public let filters: String? /// Select which attribute has to be filtered, useful when you need to narrow down the results of the filter. - //TODO: Migrate to FacetFilter object public let facetFilters: [[String]]? /// Retrieve the count of matching terms for each facets. @@ -47,11 +46,11 @@ public struct SearchParameters: Codable, Equatable { init( query: String, - offset: Int = 0, - limit: Int = 20, + offset: Int = Default.offset.rawValue, + limit: Int = Default.limit.rawValue, attributesToRetrieve: [String]? = nil, attributesToCrop: [String] = [], - cropLength: Int = 200, + cropLength: Int = Default.cropLength.rawValue, attributesToHighlight: [String] = [], filters: String? = nil, facetFilters: [[String]]? = nil, @@ -82,15 +81,70 @@ public struct SearchParameters: Codable, Equatable { SearchParameters(query: value) } - private func commaRepresentation(_ array: [String]) -> String { - array.joined(separator: ",") + // MARK: Codable Keys + + enum CodingKeys: String, CodingKey { + case query = "q" + case offset + case limit + case attributesToRetrieve + case attributesToCrop + case cropLength + case attributesToHighlight + case filters + case facetFilters + case facetsDistribution + case matches } - private func commaRepresentationEscaped(_ array: [String]) -> String { - var value: String = "[" - value += array.map({ string in "\"\(string)\"" }).joined(separator: ",") - value += "]" - return value + // MARK: Default value for keys + + fileprivate enum Default: Int { + case offset = 0 + case limit = 20 + case cropLength = 200 + } + +} + +extension SearchParameters { + + // MARK: Codable + + /// Encodes the `SearchParameters` to a JSON payload, removing any non necessary implicit parameter. + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(query, forKey: .query) + if limit != Default.limit.rawValue { + try container.encode(limit, forKey: .limit) + } + if offset != Default.offset.rawValue { + try container.encode(offset, forKey: .offset) + } + if let attributesToRetrieve: [String] = self.attributesToRetrieve, !attributesToRetrieve.isEmpty { + try container.encode(attributesToRetrieve, forKey: .attributesToRetrieve) + } + if !attributesToCrop.isEmpty { + try container.encode(attributesToCrop, forKey: .attributesToCrop) + } + if cropLength != Default.cropLength.rawValue { + try container.encode(cropLength, forKey: .cropLength) + } + if !attributesToHighlight.isEmpty { + try container.encode(attributesToHighlight, forKey: .attributesToHighlight) + } + if let filters: String = self.filters, !filters.isEmpty { + try container.encode(filters, forKey: .filters) + } + if let facetFilters: [[String]] = self.facetFilters { + try container.encode(facetFilters, forKey: .facetFilters) + } + if let facetsDistribution = self.facetsDistribution, !facetsDistribution.isEmpty { + try container.encode(facetsDistribution, forKey: .facetsDistribution) + } + if matches { + try container.encode(matches, forKey: .matches) + } } } diff --git a/Sources/MeiliSearch/Model/SearchResult.swift b/Sources/MeiliSearch/Model/SearchResult.swift index 7e6e6a55..a0165d6a 100644 --- a/Sources/MeiliSearch/Model/SearchResult.swift +++ b/Sources/MeiliSearch/Model/SearchResult.swift @@ -1,7 +1,8 @@ import Foundation /** - `SearchResult` instances represent result of a search. + `SearchResult` instances represent the result of a search. + Requires that the value `T` conforms to the `Codable` and `Equatable` protocols. */ public struct SearchResult: Codable, Equatable where T: Codable, T: Equatable { @@ -16,10 +17,88 @@ public struct SearchResult: Codable, Equatable where T: Codable, T: Equatable /// Number of documents taken. public let limit: Int + /// Total number of matches, + public let nbHits: Int + + /// Whether `nbHits` is exhaustive. + public let exhaustiveNbHits: Bool? + + /// Distribution of the given facets. + public let facetsDistribution: [String: [String: Int]]? + + /// Whether facetDistribution is exhaustive. + public let exhaustiveFacetsCount: Bool? + /// Time, in milliseconds, to process the query. public let processingTimeMs: Int? /// Query string from the search. public let query: String + // MARK: Codable Keys + + /** + Codable key mapping + */ + enum CodingKeys: String, CodingKey { + case hits + case offset + case limit + case nbHits + case exhaustiveNbHits + case facetsDistribution + case exhaustiveFacetsCount + case processingTimeMs + case query + } + + // MARK: Dynamic Codable + + /** + `StringKey` internally used to decode the dynamic JSON used in the `facetsDistribution` value. + */ + fileprivate struct StringKey: CodingKey { + var stringValue: String + init?(stringValue: String) { + self.stringValue = stringValue + } + var intValue: Int? { nil } + init?(intValue: Int) { nil } + } + +} + +extension SearchResult { + + /// Decodes the JSON to a `SearchParameters` object, sets the default value if required. + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + hits = (try values.decodeIfPresent([T].self, forKey: .hits)) ?? [] + offset = try values.decode(Int.self, forKey: .offset) + limit = try values.decode(Int.self, forKey: .limit) + nbHits = try values.decode(Int.self, forKey: .nbHits) + exhaustiveNbHits = try values.decodeIfPresent(Bool.self, forKey: .exhaustiveNbHits) + exhaustiveFacetsCount = try values.decodeIfPresent(Bool.self, forKey: .exhaustiveFacetsCount) + processingTimeMs = try values.decodeIfPresent(Int.self, forKey: .processingTimeMs) + query = try values.decode(String.self, forKey: .query) + + //Behemoth ancient code below needed to dynamically decode the JSON + if values.contains(.facetsDistribution) { + let nested: KeyedDecodingContainer = try values.nestedContainer(keyedBy: StringKey.self, forKey: .facetsDistribution) + var dic: [String: [String: Int]] = Dictionary(minimumCapacity: nested.allKeys.count) + try nested.allKeys.forEach { (key: KeyedDecodingContainer.Key) in + let facet: KeyedDecodingContainer = try nested.nestedContainer(keyedBy: StringKey.self, forKey: key) + var inner: [String: Int] = Dictionary(minimumCapacity: facet.allKeys.count) + try facet.allKeys.forEach { (innerKey: KeyedDecodingContainer.Key) in + inner[innerKey.stringValue] = try facet.decode(Int.self, forKey: innerKey) + } + dic[key.stringValue] = inner + } + facetsDistribution = dic + } else { + facetsDistribution = nil + } + + } + } diff --git a/Sources/MeiliSearch/Model/Setting.swift b/Sources/MeiliSearch/Model/Setting.swift index a8feff85..92e7f3fa 100644 --- a/Sources/MeiliSearch/Model/Setting.swift +++ b/Sources/MeiliSearch/Model/Setting.swift @@ -23,14 +23,22 @@ public struct Setting: Codable, Equatable { /// List of synonyms and its values for a given `Index`. public let synonyms: [String: [String]] + /// Optional distinct attribute set for a given `Index`. + public let distinctAttribute: String? + +} + +extension Setting { + /// Tries to decode the JSON object to Setting object. public init(from decoder: Decoder) throws { let values = try? decoder.container(keyedBy: CodingKeys.self) - rankingRules = (try? values?.decodeIfPresent([String].self, forKey: .rankingRules)) ?? [] searchableAttributes = (try? values?.decodeIfPresent([String].self, forKey: .searchableAttributes)) ?? ["*"] displayedAttributes = (try? values?.decodeIfPresent([String].self, forKey: .displayedAttributes)) ?? ["*"] stopWords = (try? values?.decodeIfPresent([String].self, forKey: .stopWords)) ?? [] synonyms = (try? values?.decodeIfPresent([String: [String]].self, forKey: .synonyms)) ?? [:] + distinctAttribute = try? values?.decodeIfPresent(String.self, forKey: .distinctAttribute) } + } diff --git a/Sources/MeiliSearch/Model/Stat.swift b/Sources/MeiliSearch/Model/Stat.swift index 70fb58b8..ca4ef769 100644 --- a/Sources/MeiliSearch/Model/Stat.swift +++ b/Sources/MeiliSearch/Model/Stat.swift @@ -16,6 +16,12 @@ public struct AllStats: Codable, Equatable { /// Dictionary of all Indexes containing the stat for each Index. public let indexes: [String: Stat] + enum CodingKeys: String, CodingKey { + case databaseSize + case lastUpdate + case indexes + } + } /** @@ -34,4 +40,10 @@ public struct Stat: Codable, Equatable { /// Usage frequency for each Index field. public let fieldsFrequency: [String: Int] + enum CodingKeys: String, CodingKey { + case numberOfDocuments + case isIndexing + case fieldsFrequency + } + } diff --git a/Sources/MeiliSearch/Model/Update.swift b/Sources/MeiliSearch/Model/Update.swift index 086caaf7..a2ebb73c 100644 --- a/Sources/MeiliSearch/Model/Update.swift +++ b/Sources/MeiliSearch/Model/Update.swift @@ -26,13 +26,22 @@ public struct Update: Codable, Equatable { public let type: Type ///Duration of the update process. - public let duration: TimeInterval + public let duration: TimeInterval? ///Date when the update has been enqueued. public let enqueuedAt: Date ///Date when the update has been processed. - public let processedAt: Date + public let processedAt: Date? + + enum CodingKeys: String, CodingKey { + case status + case updateId + case type + case duration + case enqueuedAt + case processedAt + } ///Typr of `Update`. public struct `Type`: Codable, Equatable { @@ -45,6 +54,11 @@ public struct Update: Codable, Equatable { /// ID of update type. public let number: Int + enum CodingKeys: String, CodingKey { + case name + case number + } + } } diff --git a/Sources/MeiliSearch/Model/Version.swift b/Sources/MeiliSearch/Model/Version.swift index 785da687..cc5b7658 100644 --- a/Sources/MeiliSearch/Model/Version.swift +++ b/Sources/MeiliSearch/Model/Version.swift @@ -16,4 +16,10 @@ public struct Version: Codable, Equatable { /// Package version, human readable, overly documented. public let pkgVersion: String + enum CodingKeys: String, CodingKey { + case commitSha + case buildDate + case pkgVersion + } + } diff --git a/Sources/MeiliSearch/Search.swift b/Sources/MeiliSearch/Search.swift index 96a6839b..35a83080 100644 --- a/Sources/MeiliSearch/Search.swift +++ b/Sources/MeiliSearch/Search.swift @@ -17,28 +17,27 @@ struct Search { _ searchParameters: SearchParameters, _ completion: @escaping (Result, Swift.Error>) -> Void) where T: Codable, T: Equatable { - - let searchParamBody: Data + + let data: Data do { - searchParamBody = try JSONEncoder().encode(searchParameters) - } catch { - completion(.failure(MeiliSearch.Error.invalidJSON)) - return - } - let api: String = "/indexes/\(UID)/search" + data = try JSONEncoder().encode(searchParameters) + } catch { + completion(.failure(MeiliSearch.Error.invalidJSON)) + return + } - self.request.post(api: api, searchParamBody) { result in + self.request.post(api: "/indexes/\(UID)/search", data) { result in switch result { case .success(let data): - + Search.decodeJSON(data, completion: completion) case .failure(let error): completion(.failure(error)) } - } + } } @@ -47,14 +46,7 @@ struct Search { _ customDecoder: JSONDecoder? = nil, completion: (Result) -> Void) { do { - - let decoder: JSONDecoder - if let customDecoder: JSONDecoder = customDecoder { - decoder = customDecoder - } else { - decoder = Constants.customJSONDecoder - } - + let decoder: JSONDecoder = customDecoder ?? Constants.customJSONDecoder let value: T = try decoder.decode(T.self, from: data) completion(.success(value)) } catch { diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index 95dc6e85..2619c949 100644 --- a/Sources/MeiliSearch/Settings.swift +++ b/Sources/MeiliSearch/Settings.swift @@ -32,8 +32,7 @@ struct Settings { } do { - let decoder: JSONDecoder = JSONDecoder() - let settings: Setting = try decoder.decode(Setting.self, from: data) + let settings: Setting = try Constants.customJSONDecoder.decode(Setting.self, from: data) completion(.success(settings)) } catch { completion(.failure(error)) @@ -66,8 +65,7 @@ struct Settings { case .success(let data): do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -96,8 +94,7 @@ struct Settings { } do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -162,8 +159,7 @@ struct Settings { case .success(let data): do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -192,8 +188,7 @@ struct Settings { } do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -258,8 +253,7 @@ struct Settings { case .success(let data): do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -288,8 +282,7 @@ struct Settings { } do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -342,7 +335,7 @@ struct Settings { let data: Data do { - data = try JSONSerialization.data(withJSONObject: rankingRules, options: []) + data = try JSONSerialization.data(withJSONObject: rankingRules, options: []) } catch { completion(.failure(error)) return @@ -354,8 +347,7 @@ struct Settings { case .success(let data): do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -384,8 +376,7 @@ struct Settings { } do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -401,9 +392,20 @@ struct Settings { // MARK: Distinct attribute + struct DistinctAttributePayload: Codable, Equatable { + + /// Distinct attribute key + public let distinctAttribute: String + + enum CodingKeys: String, CodingKey { + case distinctAttribute + } + + } + func getDistinctAttribute( _ UID: String, - _ completion: @escaping (Result) -> Void) { + _ completion: @escaping (Result) -> Void) { self.request.get(api: "/indexes/\(UID)/settings/distinct-attribute") { result in @@ -415,8 +417,8 @@ struct Settings { return } - let distinctAttribute: String = String( - decoding: data, as: UTF8.self) + let distinctAttribute: String? = + try? Constants.customJSONDecoder.decode(String.self, from: data) completion(.success(distinctAttribute)) @@ -433,19 +435,22 @@ struct Settings { _ distinctAttribute: String, _ completion: @escaping (Result) -> Void) { - guard let data: Data = distinctAttribute.data(using: .ascii) else { - completion(.failure(MeiliSearch.Error.invalidJSON)) + let encoder = JSONEncoder() + let data: Data + do { + data = try encoder.encode(DistinctAttributePayload(distinctAttribute: distinctAttribute)) + } catch { + completion(.failure(error)) return } - self.request.post(api: "/indexes/\(UID)/settings/distinct-attribute", data) { result in + self.request.post(api: "/indexes/\(UID)/settings", data) { result in switch result { case .success(let data): do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -474,8 +479,7 @@ struct Settings { } do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -540,8 +544,7 @@ struct Settings { case .success(let data): do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -570,8 +573,7 @@ struct Settings { } do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -636,8 +638,7 @@ struct Settings { case .success(let data): do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -666,8 +667,7 @@ struct Settings { } do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -732,8 +732,7 @@ struct Settings { case .success(let data): do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) @@ -762,8 +761,7 @@ struct Settings { } do { - let decoder: JSONDecoder = JSONDecoder() - let update: Update = try decoder.decode(Update.self, from: data) + let update: Update = try Constants.customJSONDecoder.decode(Update.self, from: data) completion(.success(update)) } catch { completion(.failure(error)) diff --git a/Sources/MeiliSearch/Stats.swift b/Sources/MeiliSearch/Stats.swift index 2f662c00..7079e0a5 100644 --- a/Sources/MeiliSearch/Stats.swift +++ b/Sources/MeiliSearch/Stats.swift @@ -56,7 +56,7 @@ struct Stats { do { let allStats: AllStats = try Constants.customJSONDecoder.decode(AllStats.self, from: data) - + completion(.success(allStats)) } catch { completion(.failure(error)) diff --git a/Sources/MeiliSearch/System.swift b/Sources/MeiliSearch/System.swift index 5b3a29b7..f2dc4cbb 100755 --- a/Sources/MeiliSearch/System.swift +++ b/Sources/MeiliSearch/System.swift @@ -58,7 +58,7 @@ struct System { do { let vesion: Version = try Constants.customJSONDecoder.decode(Version.self, from: data) - + completion(.success(vesion)) } catch { completion(.failure(error)) diff --git a/Sources/MeiliSearch/Updates.swift b/Sources/MeiliSearch/Updates.swift index f6846180..8ae49723 100644 --- a/Sources/MeiliSearch/Updates.swift +++ b/Sources/MeiliSearch/Updates.swift @@ -62,7 +62,7 @@ struct Updates { do { let result: [Update.Result] = try Constants.customJSONDecoder.decode([Update.Result].self, from: data) - + completion(.success(result)) } catch { completion(.failure(error)) diff --git a/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift b/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift index c44f5eac..1a8ae2d9 100755 --- a/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift @@ -33,11 +33,10 @@ private let movies: [Movie] = [ Movie(id: 1844, title: "A Moreninha", comment: "A Book from Joaquim Manuel de Macedo") ] -private let uid: String = "books_test" - class DocumentsTests: XCTestCase { private var client: MeiliSearch! + private let uid: String = "books_test" override func setUp() { super.setUp() @@ -52,9 +51,9 @@ class DocumentsTests: XCTestCase { let expectation = XCTestExpectation(description: "Create index if it does not exist") self.client.deleteIndex(UID: uid) { _ in - self.client.getOrCreateIndex(UID: uid) { result in + self.client.getOrCreateIndex(UID: self.uid) { result in switch result { - case .success(_): + case .success: break case .failure(let error): print(error) @@ -72,7 +71,7 @@ class DocumentsTests: XCTestCase { let expectation = XCTestExpectation(description: "Add or replace Movies document") self.client.addDocuments( - UID: uid, + UID: self.uid, documents: documents, primaryKey: nil ) { result in @@ -85,7 +84,7 @@ class DocumentsTests: XCTestCase { Thread.sleep(forTimeInterval: 1.0) self.client.getDocuments( - UID: uid, + UID: self.uid, limit: 20 ) { (result: Result<[Movie], Swift.Error>) in @@ -97,14 +96,16 @@ class DocumentsTests: XCTestCase { } case .failure(let error): - XCTFail(error.localizedDescription) + print(error) + XCTFail() } expectation.fulfill() } case .failure(let error): - XCTFail(error.localizedDescription) + print(error) + XCTFail() } } self.wait(for: [expectation], timeout: 5.0) @@ -115,7 +116,7 @@ class DocumentsTests: XCTestCase { let getExpectation = XCTestExpectation(description: "Get one document and fail") self.client.getDocument( - UID: uid, + UID: self.uid, identifier: "123456" ) { (result: Result) in switch result { @@ -136,7 +137,7 @@ class DocumentsTests: XCTestCase { let expectation = XCTestExpectation(description: "Add or replace Movies document") self.client.addDocuments( - UID: uid, + UID: self.uid, documents: documents, primaryKey: nil ) { result in @@ -150,7 +151,7 @@ class DocumentsTests: XCTestCase { Thread.sleep(forTimeInterval: 1.0) self.client.getDocument( - UID: uid, + UID: self.uid, identifier: "10" ) { (result: Result) in @@ -158,14 +159,16 @@ class DocumentsTests: XCTestCase { case .success(let returnedMovie): XCTAssertEqual(movie, returnedMovie) case .failure(let error): - XCTFail(error.localizedDescription) + print(error) + XCTFail() } expectation.fulfill() } case .failure(let error): - XCTFail(error.localizedDescription) + print(error) + XCTFail() expectation.fulfill() } @@ -184,7 +187,7 @@ class DocumentsTests: XCTestCase { let expectation = XCTestExpectation(description: "Add or update Movies document") self.client.updateDocuments( - UID: uid, + UID: self.uid, documents: documents, primaryKey: nil ) { result in @@ -198,22 +201,24 @@ class DocumentsTests: XCTestCase { Thread.sleep(forTimeInterval: 1.0) self.client.getDocument( - UID: uid, + UID: self.uid, identifier: "\(identifier)" ) { (result: Result) in switch result { case .success(let returnedMovie): - XCTAssertEqual(movie, returnedMovie) + XCTAssertEqual(movie, returnedMovie) case .failure(let error): - XCTFail(error.localizedDescription) + print(error) + XCTFail() } expectation.fulfill() } case .failure(let error): - XCTFail(error.localizedDescription) + print(error) + XCTFail() expectation.fulfill() } @@ -228,7 +233,7 @@ class DocumentsTests: XCTestCase { let expectation = XCTestExpectation(description: "Delete one Movie") self.client.addDocuments( - UID: uid, + UID: self.uid, documents: documents, primaryKey: nil ) { result in @@ -243,20 +248,21 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: 1.0) let deleteExpectation = XCTestExpectation(description: "Delete one Movie") - self.client.deleteDocument(UID: uid, identifier: "42") { (result: Result) in + self.client.deleteDocument(UID: self.uid, identifier: "42") { (result: Result) in switch result { case .success(let update): XCTAssertEqual(Update(updateId: 1), update) deleteExpectation.fulfill() case .failure(let error): - XCTFail(error.localizedDescription) + print(error) + XCTFail() } } self.wait(for: [deleteExpectation], timeout: 3.0) let getExpectation = XCTestExpectation(description: "Add or update Movies document") self.client.getDocument( - UID: uid, + UID: self.uid, identifier: "10" ) { (result: Result) in switch result { @@ -275,7 +281,7 @@ class DocumentsTests: XCTestCase { let expectation = XCTestExpectation(description: "Delete one Movie") self.client.addDocuments( - UID: uid, + UID: self.uid, documents: documents, primaryKey: nil ) { result in @@ -296,14 +302,15 @@ class DocumentsTests: XCTestCase { XCTAssertEqual(Update(updateId: 1), update) deleteExpectation.fulfill() case .failure(let error): - XCTFail(error.localizedDescription) + print(error) + XCTFail() } } self.wait(for: [deleteExpectation], timeout: 3.0) let getExpectation = XCTestExpectation(description: "Add or update Movies document") self.client.getDocuments( - UID: uid, + UID: self.uid, limit: 20 ) { (result: Result<[Movie], Swift.Error>) in switch result { @@ -311,7 +318,8 @@ class DocumentsTests: XCTestCase { XCTAssertEqual([], results) getExpectation.fulfill() case .failure(let error): - XCTFail(error.localizedDescription) + print(error) + XCTFail() } } @@ -325,7 +333,7 @@ class DocumentsTests: XCTestCase { let expectation = XCTestExpectation(description: "Delete batch movies") self.client.addDocuments( - UID: uid, + UID: self.uid, documents: documents, primaryKey: nil ) { result in @@ -340,7 +348,7 @@ class DocumentsTests: XCTestCase { let idsToDelete: [Int] = [2, 1, 4] - self.client.deleteBatchDocuments(UID: uid, documentsUID: idsToDelete) { (result: Result) in + self.client.deleteBatchDocuments(UID: self.uid, documentsUID: idsToDelete) { (result: Result) in switch result { case .success(let update): @@ -350,7 +358,7 @@ class DocumentsTests: XCTestCase { Thread.sleep(forTimeInterval: 1.0) self.client.getDocuments( - UID: uid, + UID: self.uid, limit: 20 ) { (result: Result<[Movie], Swift.Error>) in switch result { @@ -359,12 +367,14 @@ class DocumentsTests: XCTestCase { XCTAssertEqual(filteredMovies, results) expectation.fulfill() case .failure(let error): - XCTFail(error.localizedDescription) + print(error) + XCTFail() } } case .failure(let error): - XCTFail(error.localizedDescription) + print(error) + XCTFail() expectation.fulfill() } } diff --git a/Tests/MeiliSearchIntegrationTests/IndexesTests.swift b/Tests/MeiliSearchIntegrationTests/IndexesTests.swift index 5d8fc513..727c6422 100644 --- a/Tests/MeiliSearchIntegrationTests/IndexesTests.swift +++ b/Tests/MeiliSearchIntegrationTests/IndexesTests.swift @@ -16,7 +16,7 @@ class IndexesTests: XCTestCase { pool(client) let expectation = XCTestExpectation(description: "Try to delete index between tests") - self.client.deleteIndex(UID: self.uid) { result in + self.client.deleteIndex(UID: self.uid) { _ in expectation.fulfill() } self.wait(for: [expectation], timeout: 1.0) diff --git a/Tests/MeiliSearchIntegrationTests/Pooling.swift b/Tests/MeiliSearchIntegrationTests/Pooling.swift index 3a83cd58..77da7eef 100644 --- a/Tests/MeiliSearchIntegrationTests/Pooling.swift +++ b/Tests/MeiliSearchIntegrationTests/Pooling.swift @@ -8,7 +8,6 @@ import Foundation @testable import MeiliSearch - public func pool(_ client: MeiliSearch) { let semaphore = DispatchSemaphore(value: 1) diff --git a/Tests/MeiliSearchIntegrationTests/SearchTests.swift b/Tests/MeiliSearchIntegrationTests/SearchTests.swift new file mode 100644 index 00000000..d8ff5b23 --- /dev/null +++ b/Tests/MeiliSearchIntegrationTests/SearchTests.swift @@ -0,0 +1,626 @@ +@testable import MeiliSearch +import XCTest +import Foundation + +private struct Book: Codable, Equatable { + + let id: Int + let title: String + let comment: String? + let genres: [String]? + let formatted: FormattedBook? + let matchesInfo: MatchesInfoBook? + + enum CodingKeys: String, CodingKey { + case id + case title + case comment + case genres + case formatted = "_formatted" + case matchesInfo = "_matchesInfo" + } + + init(id: Int, title: String, comment: String? = nil, genres: [String] = [], + formatted: FormattedBook? = nil, matchesInfo: MatchesInfoBook? = nil) { + self.id = id + self.title = title + self.comment = comment + self.genres = genres + self.formatted = formatted + self.matchesInfo = matchesInfo + } + +} + +private struct FormattedBook: Codable, Equatable { + + let id: Int + let title: String + let comment: String? + + enum CodingKeys: String, CodingKey { + case id + case title + case comment + } + + init(id: Int, title: String, comment: String? = nil) { + self.id = id + self.title = title + self.comment = comment + } + +} + +private struct MatchesInfoBook: Codable, Equatable { + let comment: [Info] + enum CodingKeys: String, CodingKey { + case comment + } +} + +private struct Info: Codable, Equatable { + let start: Int + let length: Int + enum CodingKeys: String, CodingKey { + case start + case length + } +} + +private let books: [Book] = [ + Book(id: 123, title: "Pride and Prejudice", comment: "A great book", genres: ["Classic Regency nove"]), + Book(id: 456, title: "Le Petit Prince", comment: "A french book", genres: ["Novel"]), + Book(id: 2, title: "Le Rouge et le Noir", comment: "Another french book", genres: ["Bildungsroman"]), + Book(id: 1, title: "Alice In Wonderland", comment: "A weird book", genres: ["Fantasy"]), + Book(id: 1344, title: "The Hobbit", comment: "An awesome book", genres: ["High fantasy‎"]), + Book(id: 4, title: "Harry Potter and the Half-Blood Prince", comment: "The best book", genres: ["Fantasy"]), + Book(id: 42, title: "The Hitchhiker's Guide to the Galaxy", genres: ["Novel"]), + Book(id: 1844, title: "A Moreninha", comment: "A Book from Joaquim Manuel de Macedo", genres: ["Novel"]) +] + +class SearchTests: XCTestCase { + + private var client: MeiliSearch! + private let uid: String = "books_test" + + // MARK: Setup + + override func setUp() { + super.setUp() + + if client == nil { + client = try! MeiliSearch( + Config.default(apiKey: "masterKey")) + } + + pool(client) + + let documents: Data = try! JSONEncoder().encode(books) + + let expectation = XCTestExpectation(description: "Create index if it does not exist") + + self.client.deleteIndex(UID: uid) { _ in + Thread.sleep(forTimeInterval: TimeInterval(0.1)) + self.client.getOrCreateIndex(UID: self.uid) { result in + switch result { + case .success: + + self.client.addDocuments( + UID: self.uid, + documents: documents, + primaryKey: nil + ) { result in + + switch result { + case .success: + Thread.sleep(forTimeInterval: 0.5) + break + case .failure(let error): + print(error) + XCTFail() + } + expectation.fulfill() + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + } + + self.wait(for: [expectation], timeout: 10.0) + } + + // MARK: Limit + + func testSearchLimit() { + + let expectation = XCTestExpectation(description: "Search for Books using limit") + + typealias MeiliResult = Result, Swift.Error> + let limit = 5 + let query = "A Moreninha" + + self.client.search(UID: self.uid, SearchParameters(query: query, limit: limit)) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.count == 1) + XCTAssertEqual(query, documents.hits[0].title) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testSearchZeroLimit() { + + let expectation = XCTestExpectation(description: "Search for Books using zero limit") + + typealias MeiliResult = Result, Swift.Error> + let limit = 0 + let query = "A Moreninha" + + self.client.search(UID: self.uid, SearchParameters(query: query, limit: limit)) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.isEmpty) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testSearchLimitBiggerThanNumberOfBooks() { + + let expectation = XCTestExpectation(description: "Search for Books using limit bigger than the number of books stored") + + typealias MeiliResult = Result, Swift.Error> + let limit = 5 + let query = "A" + + self.client.search(UID: self.uid, SearchParameters(query: query, limit: limit)) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.count == limit) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testSearchLimitEmptySearch() { + + let expectation = XCTestExpectation(description: "Search for Books using limit but nothing in the query") + + typealias MeiliResult = Result, Swift.Error> + let limit = 5 + let query = "" + + self.client.search(UID: self.uid, SearchParameters(query: query, limit: limit)) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.count == 5) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Offset + + func testSearchOffset() { + + let expectation = XCTestExpectation(description: "Search for Books using offset") + + typealias MeiliResult = Result, Swift.Error> + let limit = 2 + let offset = 2 + let query = "A" + + self.client.search(UID: self.uid, SearchParameters(query: query, offset: offset, limit: limit)) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.offset == offset) + XCTAssertTrue(documents.hits.count == 2) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testSearchOffsetZero() { + + let expectation = XCTestExpectation(description: "Search for Books using zero offset") + + typealias MeiliResult = Result, Swift.Error> + let limit = 2 + let offset = 0 + let query = "A" + + self.client.search(UID: self.uid, SearchParameters(query: query, offset: offset, limit: limit)) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.offset == offset) + XCTAssertTrue(documents.hits.count == 2) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testSearchOffsetLastPage() { + + let expectation = XCTestExpectation(description: "Search for Books using a offset at the last page") + + typealias MeiliResult = Result, Swift.Error> + let limit = 2 + let offset = 6 + let query = "A" + + self.client.search(UID: self.uid, SearchParameters(query: query, offset: offset, limit: limit)) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.offset == offset) + XCTAssertTrue(documents.hits.count == 1) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Attributes to crop + + func testSearchAttributesToCrop() { + + let expectation = XCTestExpectation(description: "Search for Books using attributes to crop") + + typealias MeiliResult = Result, Swift.Error> + let limit = 2 + let query = "de Macedo" + let attributesToCrop = ["comment"] + let cropLength = 10 + let searchParameters = SearchParameters(query: query, limit: limit, attributesToCrop: attributesToCrop, cropLength: cropLength) + + self.client.search(UID: self.uid, searchParameters) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.count == 1) + let book: Book = documents.hits[0] + XCTAssertEqual("Manuel de Macedo", book.formatted!.comment!) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Crop length + + func testSearchCropLength() { + + let expectation = XCTestExpectation(description: "Search for Books using default crop length") + + typealias MeiliResult = Result, Swift.Error> + let limit = 2 + let query = "book" + let attributesToCrop = ["comment"] + let cropLength = 10 + let searchParameters = SearchParameters(query: query, limit: limit, attributesToCrop: attributesToCrop, cropLength: cropLength) + + self.client.search(UID: self.uid, searchParameters) { (result: MeiliResult) in + switch result { + case .success(let documents): + + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.count == 2) + + let moreninhaBook: Book = documents.hits.first(where: { book in book.id == 1844 })! + let prideBook: Book = documents.hits.first(where: { book in book.id == 123 })! + + XCTAssertEqual("A Book from", moreninhaBook.formatted!.comment!) + XCTAssertEqual("A great book", prideBook.formatted!.comment!) + + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Matches tests + + func testSearchMatches() { + + let expectation = XCTestExpectation(description: "Search for Books using matches") + + typealias MeiliResult = Result, Swift.Error> + let limit = 5 + let query = "A Moreninha" + let parameters = SearchParameters(query: query, limit: limit, matches: true) + + self.client.search(UID: self.uid, parameters) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.count == 1) + let book = documents.hits[0] + XCTAssertEqual(query, book.title) + let matchesInfo = book.matchesInfo! + XCTAssertFalse(matchesInfo.comment.isEmpty) + let info = matchesInfo.comment[0] + XCTAssertEqual(0, info.start) + XCTAssertEqual(1, info.length) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Attributes to highlight + + func testSearchAttributesToHighlight() { + + let expectation = XCTestExpectation(description: "Search for Books using attributes to highlight") + + typealias MeiliResult = Result, Swift.Error> + let limit = 5 + let query = "Joaquim Manuel de Macedo" + let attributesToHighlight = ["comment"] + let parameters = SearchParameters(query: query, limit: limit, attributesToHighlight: attributesToHighlight) + + self.client.search(UID: self.uid, parameters) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.count == 1) + let book = documents.hits[0] + XCTAssertEqual("A Moreninha", book.title) + XCTAssertTrue(book.formatted!.comment!.contains("Joaquim Manuel de Macedo")) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Attributes to retrieve + + func testSearchAttributesToRetrieve() { + + let expectation = XCTestExpectation(description: "Search for Books using attributes to retrieve") + + typealias MeiliResult = Result, Swift.Error> + let limit = 5 + let query = "Joaquim Manuel de Macedo" + let attributesToRetrieve = ["id", "title"] + let parameters = SearchParameters(query: query, limit: limit, attributesToRetrieve: attributesToRetrieve) + + self.client.search(UID: self.uid, parameters) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.count == 1) + let book = documents.hits[0] + XCTAssertEqual(1844, book.id) + XCTAssertEqual("A Moreninha", book.title) + XCTAssertNil(book.comment) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Filters + + func testSearchFilters() { + + let expectation = XCTestExpectation(description: "Search for Books using filters") + + typealias MeiliResult = Result, Swift.Error> + let limit = 5 + let query = "french book" + let filters = "id = 456" + let parameters = SearchParameters(query: query, limit: limit, filters: filters) + + self.client.search(UID: self.uid, parameters) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.count == 1) + let book = documents.hits[0] + XCTAssertEqual(456, book.id) + XCTAssertEqual("Le Petit Prince", book.title) + XCTAssertEqual("A french book", book.comment) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testSearchFiltersNotMatching() { + + let expectation = XCTestExpectation(description: "Search for Books using filters but the query and filters are not matching") + + typealias MeiliResult = Result, Swift.Error> + let limit = 5 + let query = "Joaquim Manuel de Macedo" + let filters = "id = 456" + let parameters = SearchParameters(query: query, limit: limit, filters: filters) + + self.client.search(UID: self.uid, parameters) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.isEmpty) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Facets filters + + private func configureFacets(_ completion: @escaping () -> Void) { + + let expectation = XCTestExpectation(description: "Configure attributes for faceting") + let attributesForFaceting = ["genres", "author"] + + self.client.updateAttributesForFaceting(UID: self.uid, attributesForFaceting) { result in + + switch result { + case .success: + expectation.fulfill() + Thread.sleep(forTimeInterval: 0.5) + completion() + case .failure: + XCTFail("Failed to get Books documents") + } + + } + + self.wait(for: [expectation], timeout: 1.0) + + } + + func testSearchFacetsFilters() { + + let expectation = XCTestExpectation(description: "Search for Books using facets filters") + + configureFacets { + + typealias MeiliResult = Result, Swift.Error> + let limit = 5 + let query = "A" + let facetsFilters = [["genres:Novel"]] + let parameters = SearchParameters(query: query, limit: limit, facetFilters: facetsFilters) + + self.client.search(UID: self.uid, parameters) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.count == 2) + let moreninhaBook: Book = documents.hits.first { book in book.id == 1844 }! + XCTAssertEqual("A Moreninha", moreninhaBook.title) + let petitBook: Book = documents.hits.first { book in book.id == 456 }! + XCTAssertEqual("Le Petit Prince", petitBook.title) + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + } + + self.wait(for: [expectation], timeout: 2.0) + } + + // MARK: Facets distribution + + func testSearchFacetsDistribution() { + + let expectation = XCTestExpectation(description: "Search for Books using facets distribution") + + configureFacets { + + typealias MeiliResult = Result, Swift.Error> + let limit = 5 + let query = "A" + let facetsDistribution = ["genres"] + let parameters = SearchParameters(query: query, limit: limit, facetsDistribution: facetsDistribution) + + self.client.search(UID: self.uid, parameters) { (result: MeiliResult) in + switch result { + case .success(let documents): + XCTAssertTrue(documents.query == query) + XCTAssertTrue(documents.limit == limit) + XCTAssertTrue(documents.hits.count == limit) + + let facetsDistribution = documents.facetsDistribution! + + let expected: [String: [String: Int]] = [ + "genres": [ + "Classic Regency nove": 1, + "High fantasy‎": 1, + "Fantasy": 2, + "Novel": 2, + "Bildungsroman": 1 + ] + ] + + XCTAssertEqual(expected, facetsDistribution) + + expectation.fulfill() + case .failure: + XCTFail("Failed to get Books documents") + } + } + + } + + self.wait(for: [expectation], timeout: 2.0) + } + +} diff --git a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift new file mode 100644 index 00000000..b41d6279 --- /dev/null +++ b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift @@ -0,0 +1,1246 @@ +@testable import MeiliSearch +import XCTest +import Foundation + +class SettingsTests: XCTestCase { + + private var client: MeiliSearch! + private let uid: String = "books_test" + + // MARK: Setup + + override func setUp() { + super.setUp() + + if client == nil { + client = try! MeiliSearch( + Config.default(apiKey: "masterKey")) + } + + pool(client) + + let expectation = XCTestExpectation(description: "Create index if it does not exist") + + self.client.deleteIndex(UID: uid) { _ in + Thread.sleep(forTimeInterval: TimeInterval(0.1)) + self.client.getOrCreateIndex(UID: self.uid) { result in + switch result { + case .success: + expectation.fulfill() + case .failure(let error): + print(error) + XCTFail() + } + } + } + + self.wait(for: [expectation], timeout: 10.0) + } + + // MARK: Attributes for faceting + + func testGetAttributesForFaceting() { + + let expectation = XCTestExpectation(description: "Get current attributes for faceting") + + let attributesForFaceting: [String] = ["id", "title"] + + self.client.updateAttributesForFaceting(UID: self.uid, attributesForFaceting) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getAttributesForFaceting(UID: self.uid) { result in + switch result { + case .success(let attributes): + + XCTAssertEqual(attributesForFaceting.count, attributes.count) + + let lhs: [String] = attributesForFaceting.sorted() + let rhs: [String] = attributes.sorted() + + XCTAssertEqual(lhs, rhs) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testUpdateAttributesForFaceting() { + + let expectation = XCTestExpectation(description: "Update settings for attributes for faceting") + + let initialAttributesForFaceting: [String] = ["id", "title"] + + self.client.updateAttributesForFaceting(UID: self.uid, initialAttributesForFaceting) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getAttributesForFaceting(UID: self.uid) { result in + + switch result { + case .success(let attributes): + + XCTAssertEqual(initialAttributesForFaceting, attributes) + let newAttributesForFaceting: [String] = ["title"] + + self.client.updateAttributesForFaceting(UID: self.uid, newAttributesForFaceting) { result in + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getAttributesForFaceting(UID: self.uid) { result in + + switch result { + case .success(let attributes): + + XCTAssertNotEqual(initialAttributesForFaceting, attributes) + XCTAssertEqual(newAttributesForFaceting, attributes) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 2.0) + } + + func testResetAttributesForFaceting() { + + let expectation = XCTestExpectation(description: "Reset settings for attributes for faceting") + + self.client.resetAttributesForFaceting(UID: self.uid) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getAttributesForFaceting(UID: self.uid) { result in + + switch result { + case .success(let attributes): + + XCTAssertTrue(attributes.isEmpty) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Displayed attributes + + func testGetDisplayedAttributes() { + + let expectation = XCTestExpectation(description: "Get current displayed attributes") + + let displayedAttributes = ["id", "title"] + + self.client.updateDisplayedAttributes(UID: self.uid, displayedAttributes) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getDisplayedAttributes(UID: self.uid) { result in + + switch result { + case .success(let attributes): + + XCTAssertEqual(displayedAttributes.count, attributes.count) + + let lhs: [String] = displayedAttributes.sorted() + let rhs: [String] = attributes.sorted() + + XCTAssertEqual(lhs, rhs) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testUpdateDisplayedAttributes() { + + let expectation = XCTestExpectation(description: "Update settings for displayed attributes") + + let initialDisplayedAttributes: [String] = ["id"] + + self.client.updateDisplayedAttributes(UID: self.uid, initialDisplayedAttributes) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getDisplayedAttributes(UID: self.uid) { result in + + switch result { + case .success(let attributes): + + XCTAssertEqual(initialDisplayedAttributes, attributes) + let newDisplayedAttributes: [String] = ["title"] + + self.client.updateDisplayedAttributes(UID: self.uid, newDisplayedAttributes) { result in + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getDisplayedAttributes(UID: self.uid) { result in + + switch result { + case .success(let attributes): + + XCTAssertNotEqual(initialDisplayedAttributes, attributes) + XCTAssertEqual(newDisplayedAttributes, attributes) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 10.0) + } + + func testResetDisplayedAttributes() { + + let expectation = XCTestExpectation(description: "Reset settings for displayed attributes") + + self.client.resetDisplayedAttributes(UID: self.uid) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getDisplayedAttributes(UID: self.uid) { result in + + switch result { + case .success(let attribute): + + XCTAssertEqual(["*"], attribute) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Distinct attributes + + func testGetDistinctAttribute() { + + let expectation = XCTestExpectation(description: "Get current distinct attribute") + + let distinctAttribute = "product_id" + + self.client.updateDistinctAttribute(UID: self.uid, distinctAttribute) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getDistinctAttribute(UID: self.uid) { result in + + switch result { + case .success(let attribute): + + XCTAssertEqual(distinctAttribute, attribute) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testUpdateDistinctAttribute() { + + let expectation = XCTestExpectation(description: "Update settings for distinct attribute") + + let intialDistinctAttribute = "id" + + self.client.updateDistinctAttribute(UID: self.uid, intialDistinctAttribute) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getDistinctAttribute(UID: self.uid) { result in + + switch result { + case .success(let attribute): + + XCTAssertEqual(intialDistinctAttribute, attribute) + let newDistinctAttribute: String = "title" + + self.client.updateDistinctAttribute(UID: self.uid, newDistinctAttribute) { result in + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getDistinctAttribute(UID: self.uid) { result in + + switch result { + case .success(let attribute): + + XCTAssertNotEqual(intialDistinctAttribute, attribute) + XCTAssertEqual(newDistinctAttribute, attribute) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 10.0) + } + + func testResetDistinctAttributes() { + + let expectation = XCTestExpectation(description: "Reset settings for distinct attributes") + + self.client.resetDistinctAttribute(UID: self.uid) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getDistinctAttribute(UID: self.uid) { result in + + switch result { + case .success(let attribute): + + XCTAssertNil(attribute) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Ranking rules + + func testGetRankingRules() { + + let expectation = XCTestExpectation(description: "Get current ranking rules") + + let newRankingRules: [String] = [ + "typo", + "words", + "proximity" + ] + + self.client.updateRankingRules(UID: self.uid, newRankingRules) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getRankingRules(UID: self.uid) { result in + switch result { + case .success(let rankingRules): + + XCTAssertEqual(newRankingRules.count, rankingRules.count) + + let lhs: [String] = newRankingRules.sorted() + let rhs: [String] = rankingRules.sorted() + + XCTAssertEqual(lhs, rhs) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testUpdateRankingRules() { + + let expectation = XCTestExpectation(description: "Update settings for ranking rules") + + let initialRankingRules: [String] = [ + "typo", + "words", + "proximity" + ] + + self.client.updateRankingRules(UID: self.uid, initialRankingRules) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getRankingRules(UID: self.uid) { result in + + switch result { + case .success(let rankingRules): + + XCTAssertEqual(initialRankingRules, rankingRules) + let newRankingRules: [String] = [ + "words", + "typo", + "proximity" + ] + + self.client.updateRankingRules(UID: self.uid, newRankingRules) { result in + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getRankingRules(UID: self.uid) { result in + + switch result { + case .success(let rankingRules): + + XCTAssertNotEqual(initialRankingRules, rankingRules) + XCTAssertEqual(newRankingRules, rankingRules) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 2.0) + } + + func testResetRankingRules() { + + let expectation = XCTestExpectation(description: "Reset settings for ranking rules") + + self.client.resetRankingRules(UID: self.uid) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getRankingRules(UID: self.uid) { result in + + switch result { + case .success(let rankingRules): + + XCTAssertEqual(["typo", "words", "proximity", "attribute", "wordsPosition", "exactness"], rankingRules) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Searchable attributes + + func testGetSearchableAttributes() { + + let expectation = XCTestExpectation(description: "Get current searchable attributes") + + let newSearchableAttributes: [String] = [ + "id", + "title", + "comment" + ] + + self.client.updateSearchableAttributes(UID: self.uid, newSearchableAttributes) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getSearchableAttributes(UID: self.uid) { result in + switch result { + case .success(let searchableAttributes): + + XCTAssertEqual(newSearchableAttributes.count, searchableAttributes.count) + + let lhs: [String] = newSearchableAttributes.sorted() + let rhs: [String] = searchableAttributes.sorted() + + XCTAssertEqual(lhs, rhs) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testUpdateSearchableAttributes() { + + let expectation = XCTestExpectation(description: "Update settings for searchable attributes") + + let initialRankingRules: [String] = [ + "id", + "title", + "comment" + ] + + self.client.updateSearchableAttributes(UID: self.uid, initialRankingRules) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getSearchableAttributes(UID: self.uid) { result in + + switch result { + case .success(let rankingRules): + + XCTAssertEqual(initialRankingRules, rankingRules) + let newRankingRules: [String] = [ + "id", + "title" + ] + + self.client.updateSearchableAttributes(UID: self.uid, newRankingRules) { result in + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getSearchableAttributes(UID: self.uid) { result in + + switch result { + case .success(let rankingRules): + + XCTAssertNotEqual(initialRankingRules, rankingRules) + XCTAssertEqual(newRankingRules, rankingRules) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 2.0) + } + + func testResetSearchableAttributes() { + + let expectation = XCTestExpectation(description: "Reset settings for searchable attributes") + + self.client.resetSearchableAttributes(UID: self.uid) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getSearchableAttributes(UID: self.uid) { result in + + switch result { + case .success(let rankingRules): + + XCTAssertEqual(["*"], rankingRules) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Settings + + func testGetSettings() { + + let expectation = XCTestExpectation(description: "Get current settings") + + let newSettings: Setting = Setting( + rankingRules: ["typo", "words", "proximity", "attribute", "wordsPosition", "exactness"], + searchableAttributes: ["*"], + displayedAttributes: ["*"], + stopWords: [], + synonyms: [:], + distinctAttribute: nil) + + self.client.updateSetting(UID: self.uid, newSettings) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getSetting(UID: self.uid) { result in + switch result { + case .success(let settings): + XCTAssertEqual(newSettings, settings) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testUpdateSettings() { + + let expectation = XCTestExpectation(description: "Update settings") + + let initialSettings: Setting = Setting( + rankingRules: ["typo", "words", "proximity", "attribute", "wordsPosition", "exactness"], + searchableAttributes: ["*"], + displayedAttributes: ["*"], + stopWords: [], + synonyms: [:], + distinctAttribute: nil) + + self.client.updateSetting(UID: self.uid, initialSettings) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getSetting(UID: self.uid) { result in + + switch result { + case .success(let setting): + + XCTAssertEqual(initialSettings, setting) + + let newSettings: Setting = Setting( + rankingRules: ["words", "typo", "proximity", "attribute", "wordsPosition", "exactness"], + searchableAttributes: ["id", "title"], + displayedAttributes: ["*"], + stopWords: ["the", "a"], + synonyms: [:], + distinctAttribute: nil) + + self.client.updateSetting(UID: self.uid, newSettings) { result in + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getSetting(UID: self.uid) { result in + + switch result { + case .success(let finalSetting): + + XCTAssertNotEqual(initialSettings, finalSetting) + XCTAssertEqual(newSettings.rankingRules.sorted(), finalSetting.rankingRules.sorted()) + XCTAssertEqual(newSettings.searchableAttributes.sorted(), finalSetting.searchableAttributes.sorted()) + XCTAssertEqual(newSettings.displayedAttributes.sorted(), finalSetting.displayedAttributes.sorted()) + XCTAssertEqual(newSettings.stopWords.sorted(), finalSetting.stopWords.sorted()) + XCTAssertEqual(Array(newSettings.synonyms.keys).sorted(by: <), Array(finalSetting.synonyms.keys).sorted(by: <)) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 10.0) + } + + func testResetSettings() { + + let expectation = XCTestExpectation(description: "Reset settings") + + self.client.resetSetting(UID: self.uid) { result in + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getSetting(UID: self.uid) { result in + + switch result { + case .success(let settings): + + let expected = Setting( + rankingRules: ["typo", "words", "proximity", "attribute", "wordsPosition", "exactness"], + searchableAttributes: ["*"], + displayedAttributes: ["*"], + stopWords: [], + synonyms: [:], + distinctAttribute: nil) + + XCTAssertEqual(expected, settings) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Stop words + + func testGetStopWords() { + + let expectation = XCTestExpectation(description: "Get current stop words") + + let newStopWords: [String] = ["the", "a", "an"] + + self.client.updateStopWords(UID: self.uid, newStopWords) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getStopWords(UID: self.uid) { result in + switch result { + case .success(let stopWords): + XCTAssertEqual(newStopWords.sorted(), stopWords.sorted()) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testUpdateStopWords() { + + let expectation = XCTestExpectation(description: "Update stop words") + + let initialStopWords: [String] = ["the", "a", "an"] + + self.client.updateStopWords(UID: self.uid, initialStopWords) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getStopWords(UID: self.uid) { result in + + switch result { + case .success(let stopWords): + + XCTAssertEqual(initialStopWords.sorted(), stopWords.sorted()) + + let newStopWords: [String] = ["the"] + + self.client.updateStopWords(UID: self.uid, newStopWords) { result in + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getStopWords(UID: self.uid) { result in + + switch result { + case .success(let finalStopWords): + + XCTAssertNotEqual(initialStopWords.sorted(), finalStopWords.sorted()) + XCTAssertEqual(newStopWords.sorted(), finalStopWords.sorted()) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 10.0) + } + + func testResetStopWords() { + + let expectation = XCTestExpectation(description: "Reset stop words") + + self.client.resetStopWords(UID: self.uid) { result in + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getStopWords(UID: self.uid) { result in + + switch result { + case .success(let stopWords): + XCTAssertTrue(stopWords.isEmpty) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + + // MARK: Synonyms + + func testGetSynonyms() { + + let expectation = XCTestExpectation(description: "Get current synonyms") + + let newSynonyms: [String: [String]] = [ + "wolverine": ["xmen", "logan"], + "logan": ["wolverine", "xmen"], + "wow": ["world of warcraft"] + ] + + self.client.updateSynonyms(UID: self.uid, newSynonyms) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getSynonyms(UID: self.uid) { result in + switch result { + case .success(let synonyms): + + let lhs = Array(newSynonyms.keys).sorted(by: <) + let rhs = Array(synonyms.keys).sorted(by: <) + XCTAssertEqual(lhs, rhs) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + self.wait(for: [expectation], timeout: 1.0) + } + + func testUpdateSynonyms() { + + let expectation = XCTestExpectation(description: "Update synonyms") + + let initialSynonyms: [String: [String]] = [ + "wolverine": ["xmen", "logan"], + "logan": ["wolverine", "xmen"], + "wow": ["world of warcraft"] + ] + + self.client.updateSynonyms(UID: self.uid, initialSynonyms) { result in + + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getSynonyms(UID: self.uid) { result in + + switch result { + case .success(let synonyms): + + let lhs = Array(initialSynonyms.keys).sorted(by: <) + let rhs = Array(synonyms.keys).sorted(by: <) + XCTAssertEqual(lhs, rhs) + + let newSynonyms: [String: [String]] = [ + "wolverine": ["xmen", "logan"], + "logan": ["wolverine", "xmen"], + "wow": ["world of warcraft"], + "rct": ["rollercoaster tycoon"] + ] + + self.client.updateSynonyms(UID: self.uid, newSynonyms) { result in + switch result { + case .success: + + Thread.sleep(forTimeInterval: 0.5) + + self.client.getSynonyms(UID: self.uid) { result in + + switch result { + case .success(let updatedSynonyms): + + let rhs = Array(updatedSynonyms.keys).sorted(by: <) + XCTAssertNotEqual(Array(initialSynonyms.keys).sorted(by: <), rhs) + XCTAssertEqual(Array(newSynonyms.keys).sorted(by: <), rhs) + + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 2.0) + } + + func testResetSynonyms() { + + let expectation = XCTestExpectation(description: "Reset synonyms") + + self.client.resetSynonyms(UID: self.uid) { result in + switch result { + case .success: + + Thread.sleep(forTimeInterval: TimeInterval(0.5)) + + self.client.getSynonyms(UID: self.uid) { result in + + switch result { + case .success(let synonyms): + + XCTAssertTrue(synonyms.isEmpty) + expectation.fulfill() + + case .failure(let error): + print(error) + XCTFail() + } + + } + + case .failure(let error): + print(error) + XCTFail() + } + } + + self.wait(for: [expectation], timeout: 1.0) + } + +} diff --git a/Tests/MeiliSearchIntegrationTests/UpdatesTests.swift b/Tests/MeiliSearchIntegrationTests/UpdatesTests.swift new file mode 100644 index 00000000..767660de --- /dev/null +++ b/Tests/MeiliSearchIntegrationTests/UpdatesTests.swift @@ -0,0 +1,148 @@ +@testable import MeiliSearch +import XCTest +import Foundation + +private struct Movie: Codable, Equatable { + + let id: Int + let title: String + let comment: String? + + enum CodingKeys: String, CodingKey { + case id + case title + case comment + } + + init(id: Int, title: String, comment: String? = nil) { + self.id = id + self.title = title + self.comment = comment + } + +} + +class UpdatesTests: XCTestCase { + + private var client: MeiliSearch! + private let uid: String = "books_test" + + // MARK: Setup + + override func setUp() { + super.setUp() + + if client == nil { + client = try! MeiliSearch( + Config.default(apiKey: "masterKey")) + } + + pool(client) + + let expectation = XCTestExpectation(description: "Create index if it does not exist") + + self.client.deleteIndex(UID: uid) { _ in + self.client.getOrCreateIndex(UID: self.uid) { result in + switch result { + case .success: + expectation.fulfill() + case .failure(let error): + print(error) + XCTFail() + } + } + } + + self.wait(for: [expectation], timeout: 10.0) + } + + func testGetUpdateStatus() { + + let expectation = XCTestExpectation(description: "Get update status for transaction") + + let movie: Movie = Movie(id: 10, title: "test", comment: "test movie") + let documents: Data = try! JSONEncoder().encode([movie]) + + self.client.addDocuments(UID: self.uid, documents: documents, primaryKey: nil) { result in + + switch result { + case .success(let update): + + func getUpdate() { + + self.client.getUpdate(UID: self.uid, update) { result in + + switch result { + case .success(let update): + + if update.status == "enqueued" { + getUpdate() + return + } + + expectation.fulfill() + case .failure(let error): + print(error) + XCTFail() + } + + } + + } + + getUpdate() + + case .failure: + XCTFail("Failed to update movie index") + } + } + + self.wait(for: [expectation], timeout: 10.0) + + } + + func testGetAllUpdatesStatus() { + + let expectation = XCTestExpectation(description: "Get update status for all transaction") + + let jsonEncoder = JSONEncoder() + + for i in 0...10 { + let movie: Movie = Movie(id: i, title: "test\(i)", comment: "test movie\(i)") + let documents: Data = try! jsonEncoder.encode([movie]) + self.client.addDocuments(UID: self.uid, documents: documents, primaryKey: nil) { _ in } + } + + func getAllUpdates() { + + self.client.getAllUpdates(UID: self.uid) { result in + + switch result { + case .success(let updates): + + let enqueues = updates + .filter { (update: Update.Result) in update.status != "processed" } + .count + + if enqueues > 0 { + getAllUpdates() + return + } + + expectation.fulfill() + case .failure(let error): + print(error) + XCTFail() + } + + } + + } + + getAllUpdates() + + self.wait(for: [expectation], timeout: 10.0) + + } + +} diff --git a/Tests/MeiliSearchIntegrationTests/XCTestManifests.swift b/Tests/MeiliSearchIntegrationTests/XCTestManifests.swift index 314c4438..8b0afe08 100755 --- a/Tests/MeiliSearchIntegrationTests/XCTestManifests.swift +++ b/Tests/MeiliSearchIntegrationTests/XCTestManifests.swift @@ -13,7 +13,11 @@ import XCTest #if !os(macOS) public func allTests() -> [XCTestCaseEntry] { [ + testCase(IndexesTests.allTests), testCase(DocumentsTests.allTests), + testCase(SearchTests.allTests), + testCase(SettingsTests.allTests), + testCase(UpdatesTests.allTests) ] } #endif diff --git a/Tests/MeiliSearchUnitTests/SearchTests.swift b/Tests/MeiliSearchUnitTests/SearchTests.swift index 4a3a9d75..e218ffc0 100644 --- a/Tests/MeiliSearchUnitTests/SearchTests.swift +++ b/Tests/MeiliSearchUnitTests/SearchTests.swift @@ -54,6 +54,7 @@ class SearchTests: XCTestCase { "offset": 0, "limit": 20, "processingTimeMs": 2, + "nbHits": 2, "query": "botman" } """ @@ -113,6 +114,7 @@ class SearchTests: XCTestCase { "offset": 0, "limit": 20, "processingTimeMs": 2, + "nbHits": 2, "query": "botman" } """ diff --git a/Tests/MeiliSearchUnitTests/SettingsTests.swift b/Tests/MeiliSearchUnitTests/SettingsTests.swift index 20a9bd4c..7f51524e 100644 --- a/Tests/MeiliSearchUnitTests/SettingsTests.swift +++ b/Tests/MeiliSearchUnitTests/SettingsTests.swift @@ -509,7 +509,9 @@ class SettingsTests: XCTestCase { //Prepare the mock server - let stubDistinctAttribute = "movie_id" + let stubDistinctAttribute: String = """ + "movie_id" + """ session.pushData(stubDistinctAttribute) @@ -522,7 +524,7 @@ class SettingsTests: XCTestCase { self.client.getDistinctAttribute(UID: UID) { result in switch result { case .success(let distinctAttribute): - XCTAssertEqual(stubDistinctAttribute, distinctAttribute) + XCTAssertEqual("movie_id", distinctAttribute!) expectation.fulfill() case .failure: XCTFail("Failed to get distinct attribute") From 76a1b33efb4972ec256a9b0ae0505f9ba5d6c04e Mon Sep 17 00:00:00 2001 From: Pedro Paulo de Amorim Date: Wed, 7 Oct 2020 13:40:51 +0100 Subject: [PATCH 2/5] Remove unecessary CodingKeys --- Sources/MeiliSearch/Model/Key.swift | 5 ----- Sources/MeiliSearch/Model/SearchResult.swift | 17 ----------------- Sources/MeiliSearch/Model/Stat.swift | 12 ------------ Sources/MeiliSearch/Model/Update.swift | 14 -------------- Sources/MeiliSearch/Model/Version.swift | 6 ------ 5 files changed, 54 deletions(-) diff --git a/Sources/MeiliSearch/Model/Key.swift b/Sources/MeiliSearch/Model/Key.swift index fe851b43..ef6e48ad 100644 --- a/Sources/MeiliSearch/Model/Key.swift +++ b/Sources/MeiliSearch/Model/Key.swift @@ -14,9 +14,4 @@ public struct Key: Codable, Equatable { ///Public key used to access a determined set of API routes. public let `public`: String - enum CodingKeys: String, CodingKey { - case `private` - case `public` - } - } diff --git a/Sources/MeiliSearch/Model/SearchResult.swift b/Sources/MeiliSearch/Model/SearchResult.swift index a0165d6a..8bb2a104 100644 --- a/Sources/MeiliSearch/Model/SearchResult.swift +++ b/Sources/MeiliSearch/Model/SearchResult.swift @@ -35,23 +35,6 @@ public struct SearchResult: Codable, Equatable where T: Codable, T: Equatable /// Query string from the search. public let query: String - // MARK: Codable Keys - - /** - Codable key mapping - */ - enum CodingKeys: String, CodingKey { - case hits - case offset - case limit - case nbHits - case exhaustiveNbHits - case facetsDistribution - case exhaustiveFacetsCount - case processingTimeMs - case query - } - // MARK: Dynamic Codable /** diff --git a/Sources/MeiliSearch/Model/Stat.swift b/Sources/MeiliSearch/Model/Stat.swift index ca4ef769..70fb58b8 100644 --- a/Sources/MeiliSearch/Model/Stat.swift +++ b/Sources/MeiliSearch/Model/Stat.swift @@ -16,12 +16,6 @@ public struct AllStats: Codable, Equatable { /// Dictionary of all Indexes containing the stat for each Index. public let indexes: [String: Stat] - enum CodingKeys: String, CodingKey { - case databaseSize - case lastUpdate - case indexes - } - } /** @@ -40,10 +34,4 @@ public struct Stat: Codable, Equatable { /// Usage frequency for each Index field. public let fieldsFrequency: [String: Int] - enum CodingKeys: String, CodingKey { - case numberOfDocuments - case isIndexing - case fieldsFrequency - } - } diff --git a/Sources/MeiliSearch/Model/Update.swift b/Sources/MeiliSearch/Model/Update.swift index a2ebb73c..45932528 100644 --- a/Sources/MeiliSearch/Model/Update.swift +++ b/Sources/MeiliSearch/Model/Update.swift @@ -34,15 +34,6 @@ public struct Update: Codable, Equatable { ///Date when the update has been processed. public let processedAt: Date? - enum CodingKeys: String, CodingKey { - case status - case updateId - case type - case duration - case enqueuedAt - case processedAt - } - ///Typr of `Update`. public struct `Type`: Codable, Equatable { @@ -54,11 +45,6 @@ public struct Update: Codable, Equatable { /// ID of update type. public let number: Int - enum CodingKeys: String, CodingKey { - case name - case number - } - } } diff --git a/Sources/MeiliSearch/Model/Version.swift b/Sources/MeiliSearch/Model/Version.swift index cc5b7658..785da687 100644 --- a/Sources/MeiliSearch/Model/Version.swift +++ b/Sources/MeiliSearch/Model/Version.swift @@ -16,10 +16,4 @@ public struct Version: Codable, Equatable { /// Package version, human readable, overly documented. public let pkgVersion: String - enum CodingKeys: String, CodingKey { - case commitSha - case buildDate - case pkgVersion - } - } From 666e9a811d74b844cd0eae23b407f7d4d3d901a9 Mon Sep 17 00:00:00 2001 From: Pedro Paulo Amorim Date: Thu, 8 Oct 2020 10:24:10 +0100 Subject: [PATCH 3/5] Remove CodingKeys for DistinctAttributePayload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clémentine Urquizar --- Sources/MeiliSearch/Settings.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index 2619c949..9c38c9aa 100644 --- a/Sources/MeiliSearch/Settings.swift +++ b/Sources/MeiliSearch/Settings.swift @@ -397,10 +397,6 @@ struct Settings { /// Distinct attribute key public let distinctAttribute: String - enum CodingKeys: String, CodingKey { - case distinctAttribute - } - } func getDistinctAttribute( From b9ee70da56ca015eb5d39c17e06d7f967816e68b Mon Sep 17 00:00:00 2001 From: Pedro Paulo de Amorim Date: Thu, 8 Oct 2020 11:31:17 +0100 Subject: [PATCH 4/5] Reduce complexity of status tests --- .../UpdatesTests.swift | 50 ++++++------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/Tests/MeiliSearchIntegrationTests/UpdatesTests.swift b/Tests/MeiliSearchIntegrationTests/UpdatesTests.swift index 767660de..f29b2c3d 100644 --- a/Tests/MeiliSearchIntegrationTests/UpdatesTests.swift +++ b/Tests/MeiliSearchIntegrationTests/UpdatesTests.swift @@ -68,30 +68,19 @@ class UpdatesTests: XCTestCase { switch result { case .success(let update): - func getUpdate() { - self.client.getUpdate(UID: self.uid, update) { result in switch result { case .success(let update): - - if update.status == "enqueued" { - getUpdate() - return - } - - expectation.fulfill() + XCTAssertTrue(["enqueued", "processed", "fail"].contains(update.status)) case .failure(let error): print(error) XCTFail() } + expectation.fulfill() } - } - - getUpdate() - case .failure: XCTFail("Failed to update movie index") } @@ -113,34 +102,23 @@ class UpdatesTests: XCTestCase { self.client.addDocuments(UID: self.uid, documents: documents, primaryKey: nil) { _ in } } - func getAllUpdates() { - - self.client.getAllUpdates(UID: self.uid) { result in + self.client.getAllUpdates(UID: self.uid) { result in - switch result { - case .success(let updates): - - let enqueues = updates - .filter { (update: Update.Result) in update.status != "processed" } - .count - - if enqueues > 0 { - getAllUpdates() - return - } - - expectation.fulfill() - case .failure(let error): - print(error) - XCTFail() - } + switch result { + case .success(let updates): + let statuses: [String] = ["enqueued", "processed", "fail"] + updates.forEach { (update: Update.Result) in + XCTAssertTrue(statuses.contains(update.status)) + } - } + case .failure(let error): + print(error) + XCTFail() + } + expectation.fulfill() } - getAllUpdates() - self.wait(for: [expectation], timeout: 10.0) } From 7f3c1a49335c9333d4423b98f789543f70014009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9mentine=20Urquizar?= Date: Thu, 8 Oct 2020 16:54:32 +0200 Subject: [PATCH 5/5] Make the tests easier to read and maintain (#64) * Add forgotten tests * Simplify GET tests for Settings * Add attribtuesForFaceting in tests * Simplify UPDATE tests for Settings * Rename wrong variables --- Sources/MeiliSearch/Model/Setting.swift | 4 + .../SettingsTests.swift | 726 ++++-------------- .../MeiliSearchUnitTests/SettingsTests.swift | 3 + 3 files changed, 173 insertions(+), 560 deletions(-) diff --git a/Sources/MeiliSearch/Model/Setting.swift b/Sources/MeiliSearch/Model/Setting.swift index 92e7f3fa..b9b52017 100644 --- a/Sources/MeiliSearch/Model/Setting.swift +++ b/Sources/MeiliSearch/Model/Setting.swift @@ -26,6 +26,9 @@ public struct Setting: Codable, Equatable { /// Optional distinct attribute set for a given `Index`. public let distinctAttribute: String? + /// List of attributes used for the faceting + public let attributesForFaceting: [String] + } extension Setting { @@ -39,6 +42,7 @@ extension Setting { stopWords = (try? values?.decodeIfPresent([String].self, forKey: .stopWords)) ?? [] synonyms = (try? values?.decodeIfPresent([String: [String]].self, forKey: .synonyms)) ?? [:] distinctAttribute = try? values?.decodeIfPresent(String.self, forKey: .distinctAttribute) + attributesForFaceting = (try? values?.decodeIfPresent([String].self, forKey: .attributesForFaceting)) ?? [] } } diff --git a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift index b41d6279..b84457ac 100644 --- a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift @@ -6,6 +6,21 @@ class SettingsTests: XCTestCase { private var client: MeiliSearch! private let uid: String = "books_test" + private let defaultRankingRules: [String] = [ + "typo", + "words", + "proximity", + "attribute", + "wordsPosition", + "exactness" + ] + private let defaultDistinctAttribute: String? = nil + private let defaultDisplayedAttributes: [String] = ["*"] + private let defaultSearchableAttributes: [String] = ["*"] + private let defaultAttributesForFaceting: [String] = [] + private let defaultStopWords: [String] = [] + private let defaultSynonyms: [String: [String]] = [:] + private var defaultGlobalSettings: Setting? = nil // MARK: Setup @@ -35,6 +50,17 @@ class SettingsTests: XCTestCase { } self.wait(for: [expectation], timeout: 10.0) + + self.defaultGlobalSettings = Setting( + rankingRules: self.defaultRankingRules, + searchableAttributes: self.defaultSearchableAttributes, + displayedAttributes: self.defaultDisplayedAttributes, + stopWords: self.defaultStopWords, + synonyms: self.defaultSynonyms, + distinctAttribute: self.defaultDistinctAttribute, + attributesForFaceting: self.defaultAttributesForFaceting + ) + } // MARK: Attributes for faceting @@ -43,39 +69,18 @@ class SettingsTests: XCTestCase { let expectation = XCTestExpectation(description: "Get current attributes for faceting") - let attributesForFaceting: [String] = ["id", "title"] - - self.client.updateAttributesForFaceting(UID: self.uid, attributesForFaceting) { result in - + self.client.getAttributesForFaceting(UID: self.uid) { result in switch result { - case .success: - - Thread.sleep(forTimeInterval: 0.5) - - self.client.getAttributesForFaceting(UID: self.uid) { result in - switch result { - case .success(let attributes): + case .success(let attributes): - XCTAssertEqual(attributesForFaceting.count, attributes.count) + XCTAssertEqual(self.defaultAttributesForFaceting, attributes) - let lhs: [String] = attributesForFaceting.sorted() - let rhs: [String] = attributes.sorted() - - XCTAssertEqual(lhs, rhs) - - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - } + expectation.fulfill() case .failure(let error): print(error) XCTFail() } - } self.wait(for: [expectation], timeout: 1.0) @@ -85,10 +90,9 @@ class SettingsTests: XCTestCase { let expectation = XCTestExpectation(description: "Update settings for attributes for faceting") - let initialAttributesForFaceting: [String] = ["id", "title"] - - self.client.updateAttributesForFaceting(UID: self.uid, initialAttributesForFaceting) { result in + let newAttributesForFaceting: [String] = ["title"] + self.client.updateAttributesForFaceting(UID: self.uid, newAttributesForFaceting) { result in switch result { case .success: @@ -99,37 +103,9 @@ class SettingsTests: XCTestCase { switch result { case .success(let attributes): - XCTAssertEqual(initialAttributesForFaceting, attributes) - let newAttributesForFaceting: [String] = ["title"] + XCTAssertEqual(newAttributesForFaceting, attributes) - self.client.updateAttributesForFaceting(UID: self.uid, newAttributesForFaceting) { result in - switch result { - case .success: - - Thread.sleep(forTimeInterval: 0.5) - - self.client.getAttributesForFaceting(UID: self.uid) { result in - - switch result { - case .success(let attributes): - - XCTAssertNotEqual(initialAttributesForFaceting, attributes) - XCTAssertEqual(newAttributesForFaceting, attributes) - - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - - } - - case .failure(let error): - print(error) - XCTFail() - } - } + expectation.fulfill() case .failure(let error): print(error) @@ -163,7 +139,7 @@ class SettingsTests: XCTestCase { switch result { case .success(let attributes): - XCTAssertTrue(attributes.isEmpty) + XCTAssertEqual(self.defaultAttributesForFaceting, attributes) expectation.fulfill() case .failure(let error): @@ -188,40 +164,20 @@ class SettingsTests: XCTestCase { let expectation = XCTestExpectation(description: "Get current displayed attributes") - let displayedAttributes = ["id", "title"] - - self.client.updateDisplayedAttributes(UID: self.uid, displayedAttributes) { result in + self.client.getDisplayedAttributes(UID: self.uid) { result in switch result { - case .success: + case .success(let attributes): - Thread.sleep(forTimeInterval: TimeInterval(0.5)) - - self.client.getDisplayedAttributes(UID: self.uid) { result in - - switch result { - case .success(let attributes): - - XCTAssertEqual(displayedAttributes.count, attributes.count) - - let lhs: [String] = displayedAttributes.sorted() - let rhs: [String] = attributes.sorted() + XCTAssertEqual(self.defaultDisplayedAttributes, attributes) - XCTAssertEqual(lhs, rhs) - - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - - } + expectation.fulfill() case .failure(let error): print(error) XCTFail() } + } self.wait(for: [expectation], timeout: 1.0) @@ -231,10 +187,9 @@ class SettingsTests: XCTestCase { let expectation = XCTestExpectation(description: "Update settings for displayed attributes") - let initialDisplayedAttributes: [String] = ["id"] - - self.client.updateDisplayedAttributes(UID: self.uid, initialDisplayedAttributes) { result in + let newDisplayedAttributes: [String] = ["title"] + self.client.updateDisplayedAttributes(UID: self.uid, newDisplayedAttributes) { result in switch result { case .success: @@ -245,37 +200,9 @@ class SettingsTests: XCTestCase { switch result { case .success(let attributes): - XCTAssertEqual(initialDisplayedAttributes, attributes) - let newDisplayedAttributes: [String] = ["title"] - - self.client.updateDisplayedAttributes(UID: self.uid, newDisplayedAttributes) { result in - switch result { - case .success: - - Thread.sleep(forTimeInterval: TimeInterval(0.5)) + XCTAssertEqual(newDisplayedAttributes, attributes) - self.client.getDisplayedAttributes(UID: self.uid) { result in - - switch result { - case .success(let attributes): - - XCTAssertNotEqual(initialDisplayedAttributes, attributes) - XCTAssertEqual(newDisplayedAttributes, attributes) - - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - - } - - case .failure(let error): - print(error) - XCTFail() - } - } + expectation.fulfill() case .failure(let error): print(error) @@ -309,7 +236,7 @@ class SettingsTests: XCTestCase { switch result { case .success(let attribute): - XCTAssertEqual(["*"], attribute) + XCTAssertEqual(self.defaultDisplayedAttributes, attribute) expectation.fulfill() case .failure(let error): @@ -334,34 +261,20 @@ class SettingsTests: XCTestCase { let expectation = XCTestExpectation(description: "Get current distinct attribute") - let distinctAttribute = "product_id" - - self.client.updateDistinctAttribute(UID: self.uid, distinctAttribute) { result in + self.client.getDistinctAttribute(UID: self.uid) { result in switch result { - case .success: - - Thread.sleep(forTimeInterval: TimeInterval(0.5)) + case .success(let attribute): - self.client.getDistinctAttribute(UID: self.uid) { result in + XCTAssertEqual(self.defaultDistinctAttribute, attribute) - switch result { - case .success(let attribute): - - XCTAssertEqual(distinctAttribute, attribute) - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - - } + expectation.fulfill() case .failure(let error): print(error) XCTFail() } + } self.wait(for: [expectation], timeout: 1.0) @@ -371,10 +284,9 @@ class SettingsTests: XCTestCase { let expectation = XCTestExpectation(description: "Update settings for distinct attribute") - let intialDistinctAttribute = "id" - - self.client.updateDistinctAttribute(UID: self.uid, intialDistinctAttribute) { result in + let newDistinctAttribute: String = "title" + self.client.updateDistinctAttribute(UID: self.uid, newDistinctAttribute) { result in switch result { case .success: @@ -385,37 +297,9 @@ class SettingsTests: XCTestCase { switch result { case .success(let attribute): - XCTAssertEqual(intialDistinctAttribute, attribute) - let newDistinctAttribute: String = "title" - - self.client.updateDistinctAttribute(UID: self.uid, newDistinctAttribute) { result in - switch result { - case .success: - - Thread.sleep(forTimeInterval: TimeInterval(0.5)) - - self.client.getDistinctAttribute(UID: self.uid) { result in - - switch result { - case .success(let attribute): - - XCTAssertNotEqual(intialDistinctAttribute, attribute) - XCTAssertEqual(newDistinctAttribute, attribute) - - expectation.fulfill() + XCTAssertEqual(newDistinctAttribute, attribute) - case .failure(let error): - print(error) - XCTFail() - } - - } - - case .failure(let error): - print(error) - XCTFail() - } - } + expectation.fulfill() case .failure(let error): print(error) @@ -428,6 +312,7 @@ class SettingsTests: XCTestCase { print(error) XCTFail() } + } self.wait(for: [expectation], timeout: 10.0) @@ -449,7 +334,8 @@ class SettingsTests: XCTestCase { switch result { case .success(let attribute): - XCTAssertNil(attribute) + XCTAssertEqual(self.defaultDistinctAttribute, attribute) + expectation.fulfill() case .failure(let error): @@ -474,43 +360,18 @@ class SettingsTests: XCTestCase { let expectation = XCTestExpectation(description: "Get current ranking rules") - let newRankingRules: [String] = [ - "typo", - "words", - "proximity" - ] - - self.client.updateRankingRules(UID: self.uid, newRankingRules) { result in - + self.client.getRankingRules(UID: self.uid) { result in switch result { - case .success: - - Thread.sleep(forTimeInterval: 0.5) - - self.client.getRankingRules(UID: self.uid) { result in - switch result { - case .success(let rankingRules): - - XCTAssertEqual(newRankingRules.count, rankingRules.count) + case .success(let rankingRules): - let lhs: [String] = newRankingRules.sorted() - let rhs: [String] = rankingRules.sorted() + XCTAssertEqual(self.defaultRankingRules, rankingRules) - XCTAssertEqual(lhs, rhs) - - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - } + expectation.fulfill() case .failure(let error): print(error) XCTFail() } - } self.wait(for: [expectation], timeout: 1.0) @@ -520,14 +381,13 @@ class SettingsTests: XCTestCase { let expectation = XCTestExpectation(description: "Update settings for ranking rules") - let initialRankingRules: [String] = [ - "typo", + let newRankingRules: [String] = [ "words", + "typo", "proximity" ] - self.client.updateRankingRules(UID: self.uid, initialRankingRules) { result in - + self.client.updateRankingRules(UID: self.uid, newRankingRules) { result in switch result { case .success: @@ -538,41 +398,9 @@ class SettingsTests: XCTestCase { switch result { case .success(let rankingRules): - XCTAssertEqual(initialRankingRules, rankingRules) - let newRankingRules: [String] = [ - "words", - "typo", - "proximity" - ] - - self.client.updateRankingRules(UID: self.uid, newRankingRules) { result in - switch result { - case .success: - - Thread.sleep(forTimeInterval: 0.5) - - self.client.getRankingRules(UID: self.uid) { result in - - switch result { - case .success(let rankingRules): - - XCTAssertNotEqual(initialRankingRules, rankingRules) - XCTAssertEqual(newRankingRules, rankingRules) - - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - - } + XCTAssertEqual(newRankingRules, rankingRules) - case .failure(let error): - print(error) - XCTFail() - } - } + expectation.fulfill() case .failure(let error): print(error) @@ -606,7 +434,7 @@ class SettingsTests: XCTestCase { switch result { case .success(let rankingRules): - XCTAssertEqual(["typo", "words", "proximity", "attribute", "wordsPosition", "exactness"], rankingRules) + XCTAssertEqual(self.defaultRankingRules, rankingRules) expectation.fulfill() case .failure(let error): @@ -631,43 +459,18 @@ class SettingsTests: XCTestCase { let expectation = XCTestExpectation(description: "Get current searchable attributes") - let newSearchableAttributes: [String] = [ - "id", - "title", - "comment" - ] - - self.client.updateSearchableAttributes(UID: self.uid, newSearchableAttributes) { result in - + self.client.getSearchableAttributes(UID: self.uid) { result in switch result { - case .success: + case .success(let searchableAttributes): - Thread.sleep(forTimeInterval: 0.5) + XCTAssertEqual(self.defaultSearchableAttributes, searchableAttributes) - self.client.getSearchableAttributes(UID: self.uid) { result in - switch result { - case .success(let searchableAttributes): - - XCTAssertEqual(newSearchableAttributes.count, searchableAttributes.count) - - let lhs: [String] = newSearchableAttributes.sorted() - let rhs: [String] = searchableAttributes.sorted() - - XCTAssertEqual(lhs, rhs) - - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - } + expectation.fulfill() case .failure(let error): print(error) XCTFail() } - } self.wait(for: [expectation], timeout: 1.0) @@ -677,14 +480,12 @@ class SettingsTests: XCTestCase { let expectation = XCTestExpectation(description: "Update settings for searchable attributes") - let initialRankingRules: [String] = [ + let newSearchableAttributes: [String] = [ "id", - "title", - "comment" + "title" ] - self.client.updateSearchableAttributes(UID: self.uid, initialRankingRules) { result in - + self.client.updateSearchableAttributes(UID: self.uid, newSearchableAttributes) { result in switch result { case .success: @@ -693,42 +494,11 @@ class SettingsTests: XCTestCase { self.client.getSearchableAttributes(UID: self.uid) { result in switch result { - case .success(let rankingRules): - - XCTAssertEqual(initialRankingRules, rankingRules) - let newRankingRules: [String] = [ - "id", - "title" - ] - - self.client.updateSearchableAttributes(UID: self.uid, newRankingRules) { result in - switch result { - case .success: - - Thread.sleep(forTimeInterval: 0.5) - - self.client.getSearchableAttributes(UID: self.uid) { result in - - switch result { - case .success(let rankingRules): - - XCTAssertNotEqual(initialRankingRules, rankingRules) - XCTAssertEqual(newRankingRules, rankingRules) - - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } + case .success(let searchableAttributes): - } + XCTAssertEqual(newSearchableAttributes, searchableAttributes) - case .failure(let error): - print(error) - XCTFail() - } - } + expectation.fulfill() case .failure(let error): print(error) @@ -741,6 +511,7 @@ class SettingsTests: XCTestCase { print(error) XCTFail() } + } self.wait(for: [expectation], timeout: 2.0) @@ -760,9 +531,9 @@ class SettingsTests: XCTestCase { self.client.getSearchableAttributes(UID: self.uid) { result in switch result { - case .success(let rankingRules): + case .success(let searchableAttributes): - XCTAssertEqual(["*"], rankingRules) + XCTAssertEqual(self.defaultSearchableAttributes, searchableAttributes) expectation.fulfill() case .failure(let error): @@ -781,115 +552,48 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: 1.0) } - // MARK: Settings + // // MARK: Stop words - func testGetSettings() { - - let expectation = XCTestExpectation(description: "Get current settings") - - let newSettings: Setting = Setting( - rankingRules: ["typo", "words", "proximity", "attribute", "wordsPosition", "exactness"], - searchableAttributes: ["*"], - displayedAttributes: ["*"], - stopWords: [], - synonyms: [:], - distinctAttribute: nil) + func testGetStopWords() { - self.client.updateSetting(UID: self.uid, newSettings) { result in + let expectation = XCTestExpectation(description: "Get current stop words") + self.client.getStopWords(UID: self.uid) { result in switch result { - case .success: - Thread.sleep(forTimeInterval: 0.5) - - self.client.getSetting(UID: self.uid) { result in - switch result { - case .success(let settings): - XCTAssertEqual(newSettings, settings) - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - } + case .success(let stopWords): + XCTAssertEqual(self.defaultStopWords, stopWords) + expectation.fulfill() case .failure(let error): print(error) XCTFail() } - } self.wait(for: [expectation], timeout: 1.0) } - func testUpdateSettings() { - - let expectation = XCTestExpectation(description: "Update settings") + func testUpdateStopWords() { - let initialSettings: Setting = Setting( - rankingRules: ["typo", "words", "proximity", "attribute", "wordsPosition", "exactness"], - searchableAttributes: ["*"], - displayedAttributes: ["*"], - stopWords: [], - synonyms: [:], - distinctAttribute: nil) + let expectation = XCTestExpectation(description: "Update stop words") - self.client.updateSetting(UID: self.uid, initialSettings) { result in + let newStopWords: [String] = ["the"] + self.client.updateStopWords(UID: self.uid, newStopWords) { result in switch result { case .success: Thread.sleep(forTimeInterval: 0.5) - self.client.getSetting(UID: self.uid) { result in + self.client.getStopWords(UID: self.uid) { result in switch result { - case .success(let setting): - - XCTAssertEqual(initialSettings, setting) - - let newSettings: Setting = Setting( - rankingRules: ["words", "typo", "proximity", "attribute", "wordsPosition", "exactness"], - searchableAttributes: ["id", "title"], - displayedAttributes: ["*"], - stopWords: ["the", "a"], - synonyms: [:], - distinctAttribute: nil) - - self.client.updateSetting(UID: self.uid, newSettings) { result in - switch result { - case .success: - - Thread.sleep(forTimeInterval: 0.5) - - self.client.getSetting(UID: self.uid) { result in + case .success(let finalStopWords): - switch result { - case .success(let finalSetting): + XCTAssertEqual(newStopWords, finalStopWords) - XCTAssertNotEqual(initialSettings, finalSetting) - XCTAssertEqual(newSettings.rankingRules.sorted(), finalSetting.rankingRules.sorted()) - XCTAssertEqual(newSettings.searchableAttributes.sorted(), finalSetting.searchableAttributes.sorted()) - XCTAssertEqual(newSettings.displayedAttributes.sorted(), finalSetting.displayedAttributes.sorted()) - XCTAssertEqual(newSettings.stopWords.sorted(), finalSetting.stopWords.sorted()) - XCTAssertEqual(Array(newSettings.synonyms.keys).sorted(by: <), Array(finalSetting.synonyms.keys).sorted(by: <)) - - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - - } - - case .failure(let error): - print(error) - XCTFail() - } - } + expectation.fulfill() case .failure(let error): print(error) @@ -902,35 +606,27 @@ class SettingsTests: XCTestCase { print(error) XCTFail() } + } self.wait(for: [expectation], timeout: 10.0) } - func testResetSettings() { + func testResetStopWords() { - let expectation = XCTestExpectation(description: "Reset settings") + let expectation = XCTestExpectation(description: "Reset stop words") - self.client.resetSetting(UID: self.uid) { result in + self.client.resetStopWords(UID: self.uid) { result in switch result { case .success: Thread.sleep(forTimeInterval: TimeInterval(0.5)) - self.client.getSetting(UID: self.uid) { result in + self.client.getStopWords(UID: self.uid) { result in switch result { - case .success(let settings): - - let expected = Setting( - rankingRules: ["typo", "words", "proximity", "attribute", "wordsPosition", "exactness"], - searchableAttributes: ["*"], - displayedAttributes: ["*"], - stopWords: [], - synonyms: [:], - distinctAttribute: nil) - - XCTAssertEqual(expected, settings) + case .success(let stopWords): + XCTAssertEqual(self.defaultStopWords, stopWords) expectation.fulfill() case .failure(let error): @@ -949,93 +645,54 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: 1.0) } - // MARK: Stop words - - func testGetStopWords() { - - let expectation = XCTestExpectation(description: "Get current stop words") + // MARK: Synonyms - let newStopWords: [String] = ["the", "a", "an"] + func testGetSynonyms() { - self.client.updateStopWords(UID: self.uid, newStopWords) { result in + let expectation = XCTestExpectation(description: "Get current synonyms") + self.client.getSynonyms(UID: self.uid) { result in switch result { - case .success: - - Thread.sleep(forTimeInterval: 0.5) - - self.client.getStopWords(UID: self.uid) { result in - switch result { - case .success(let stopWords): - XCTAssertEqual(newStopWords.sorted(), stopWords.sorted()) - expectation.fulfill() + case .success(let synonyms): - case .failure(let error): - print(error) - XCTFail() - } - } + XCTAssertEqual(self.defaultSynonyms, synonyms) + expectation.fulfill() case .failure(let error): print(error) XCTFail() } - } self.wait(for: [expectation], timeout: 1.0) } - func testUpdateStopWords() { - - let expectation = XCTestExpectation(description: "Update stop words") + func testUpdateSynonyms() { - let initialStopWords: [String] = ["the", "a", "an"] + let expectation = XCTestExpectation(description: "Update synonyms") - self.client.updateStopWords(UID: self.uid, initialStopWords) { result in + let newSynonyms: [String: [String]] = [ + "wolverine": ["xmen", "logan"], + "logan": ["wolverine", "xmen"], + "wow": ["world of warcraft"], + "rct": ["rollercoaster tycoon"] + ] + self.client.updateSynonyms(UID: self.uid, newSynonyms) { result in switch result { case .success: Thread.sleep(forTimeInterval: 0.5) - self.client.getStopWords(UID: self.uid) { result in + self.client.getSynonyms(UID: self.uid) { result in switch result { - case .success(let stopWords): - - XCTAssertEqual(initialStopWords.sorted(), stopWords.sorted()) - - let newStopWords: [String] = ["the"] - - self.client.updateStopWords(UID: self.uid, newStopWords) { result in - switch result { - case .success: - - Thread.sleep(forTimeInterval: 0.5) + case .success(let updatedSynonyms): - self.client.getStopWords(UID: self.uid) { result in + let rhs = Array(updatedSynonyms.keys).sorted(by: <) + XCTAssertEqual(Array(newSynonyms.keys).sorted(by: <), rhs) - switch result { - case .success(let finalStopWords): - - XCTAssertNotEqual(initialStopWords.sorted(), finalStopWords.sorted()) - XCTAssertEqual(newStopWords.sorted(), finalStopWords.sorted()) - - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - - } - - case .failure(let error): - print(error) - XCTFail() - } - } + expectation.fulfill() case .failure(let error): print(error) @@ -1050,24 +707,25 @@ class SettingsTests: XCTestCase { } } - self.wait(for: [expectation], timeout: 10.0) + self.wait(for: [expectation], timeout: 2.0) } - func testResetStopWords() { + func testResetSynonyms() { - let expectation = XCTestExpectation(description: "Reset stop words") + let expectation = XCTestExpectation(description: "Reset synonyms") - self.client.resetStopWords(UID: self.uid) { result in + self.client.resetSynonyms(UID: self.uid) { result in switch result { case .success: Thread.sleep(forTimeInterval: TimeInterval(0.5)) - self.client.getStopWords(UID: self.uid) { result in + self.client.getSynonyms(UID: self.uid) { result in switch result { - case .success(let stopWords): - XCTAssertTrue(stopWords.isEmpty) + case .success(let synonyms): + + XCTAssertEqual(self.defaultSynonyms, synonyms) expectation.fulfill() case .failure(let error): @@ -1086,112 +744,59 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: 1.0) } - // MARK: Synonyms - - func testGetSynonyms() { - - let expectation = XCTestExpectation(description: "Get current synonyms") + // MARK: Global Settings - let newSynonyms: [String: [String]] = [ - "wolverine": ["xmen", "logan"], - "logan": ["wolverine", "xmen"], - "wow": ["world of warcraft"] - ] + func testGetSettings() { - self.client.updateSynonyms(UID: self.uid, newSynonyms) { result in + let expectation = XCTestExpectation(description: "Get current settings") + self.client.getSetting(UID: self.uid) { result in switch result { - case .success: - - Thread.sleep(forTimeInterval: 0.5) - - self.client.getSynonyms(UID: self.uid) { result in - switch result { - case .success(let synonyms): - - let lhs = Array(newSynonyms.keys).sorted(by: <) - let rhs = Array(synonyms.keys).sorted(by: <) - XCTAssertEqual(lhs, rhs) - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - } + case .success(let settings): + XCTAssertEqual(self.defaultGlobalSettings, settings) + expectation.fulfill() case .failure(let error): print(error) XCTFail() } - } self.wait(for: [expectation], timeout: 1.0) } - func testUpdateSynonyms() { - - let expectation = XCTestExpectation(description: "Update synonyms") + func testUpdateSettings() { - let initialSynonyms: [String: [String]] = [ - "wolverine": ["xmen", "logan"], - "logan": ["wolverine", "xmen"], - "wow": ["world of warcraft"] - ] + let expectation = XCTestExpectation(description: "Update settings") - self.client.updateSynonyms(UID: self.uid, initialSynonyms) { result in + let newSettings: Setting = Setting( + rankingRules: ["words", "typo", "proximity", "attribute", "wordsPosition", "exactness"], + searchableAttributes: ["id", "title"], + displayedAttributes: ["*"], + stopWords: ["the", "a"], + synonyms: [:], + distinctAttribute: nil, + attributesForFaceting: ["title"]) + self.client.updateSetting(UID: self.uid, newSettings) { result in switch result { case .success: Thread.sleep(forTimeInterval: 0.5) - self.client.getSynonyms(UID: self.uid) { result in + self.client.getSetting(UID: self.uid) { result in switch result { - case .success(let synonyms): - - let lhs = Array(initialSynonyms.keys).sorted(by: <) - let rhs = Array(synonyms.keys).sorted(by: <) - XCTAssertEqual(lhs, rhs) - - let newSynonyms: [String: [String]] = [ - "wolverine": ["xmen", "logan"], - "logan": ["wolverine", "xmen"], - "wow": ["world of warcraft"], - "rct": ["rollercoaster tycoon"] - ] + case .success(let finalSetting): - self.client.updateSynonyms(UID: self.uid, newSynonyms) { result in - switch result { - case .success: + XCTAssertEqual(newSettings.rankingRules.sorted(), finalSetting.rankingRules.sorted()) + XCTAssertEqual(newSettings.searchableAttributes.sorted(), finalSetting.searchableAttributes.sorted()) + XCTAssertEqual(newSettings.displayedAttributes.sorted(), finalSetting.displayedAttributes.sorted()) + XCTAssertEqual(newSettings.stopWords.sorted(), finalSetting.stopWords.sorted()) + XCTAssertEqual(newSettings.attributesForFaceting, finalSetting.attributesForFaceting) + XCTAssertEqual(Array(newSettings.synonyms.keys).sorted(by: <), Array(finalSetting.synonyms.keys).sorted(by: <)) - Thread.sleep(forTimeInterval: 0.5) - - self.client.getSynonyms(UID: self.uid) { result in - - switch result { - case .success(let updatedSynonyms): - - let rhs = Array(updatedSynonyms.keys).sorted(by: <) - XCTAssertNotEqual(Array(initialSynonyms.keys).sorted(by: <), rhs) - XCTAssertEqual(Array(newSynonyms.keys).sorted(by: <), rhs) - - expectation.fulfill() - - case .failure(let error): - print(error) - XCTFail() - } - - } - - case .failure(let error): - print(error) - XCTFail() - } - } + expectation.fulfill() case .failure(let error): print(error) @@ -1204,27 +809,28 @@ class SettingsTests: XCTestCase { print(error) XCTFail() } + } - self.wait(for: [expectation], timeout: 2.0) + self.wait(for: [expectation], timeout: 10.0) } - func testResetSynonyms() { + func testResetSettings() { - let expectation = XCTestExpectation(description: "Reset synonyms") + let expectation = XCTestExpectation(description: "Reset settings") - self.client.resetSynonyms(UID: self.uid) { result in + self.client.resetSetting(UID: self.uid) { result in switch result { case .success: Thread.sleep(forTimeInterval: TimeInterval(0.5)) - self.client.getSynonyms(UID: self.uid) { result in + self.client.getSetting(UID: self.uid) { result in switch result { - case .success(let synonyms): + case .success(let settings): - XCTAssertTrue(synonyms.isEmpty) + XCTAssertEqual(self.defaultGlobalSettings, settings) expectation.fulfill() case .failure(let error): diff --git a/Tests/MeiliSearchUnitTests/SettingsTests.swift b/Tests/MeiliSearchUnitTests/SettingsTests.swift index 7f51524e..0783abde 100644 --- a/Tests/MeiliSearchUnitTests/SettingsTests.swift +++ b/Tests/MeiliSearchUnitTests/SettingsTests.swift @@ -957,6 +957,9 @@ class SettingsTests: XCTestCase { ("testGetRankingRules", testGetRankingRules), ("testUpdateRankingRules", testUpdateRankingRules), ("testResetRankingRules", testResetRankingRules), + ("testGetDistinctAttribute", testGetDistinctAttribute), + ("testUpdateDistinctAttribute", testUpdateDistinctAttribute), + ("testResetDistinctAttribute", testResetDistinctAttribute), ("testGetSearchableAttributes", testGetSearchableAttributes), ("testUpdateSearchableAttributes", testUpdateSearchableAttributes), ("testResetSearchableAttributes", testResetSearchableAttributes),