Skip to content

Commit

Permalink
Merge #54
Browse files Browse the repository at this point in the history
54: Create integrations tests for all MeiliSearch http routes  r=curquiza a=ppamorim

# Summary

- [x] Client tests  #32
- [x] Indexes tests #32 
- [x] Documents tests #32
- [x] add Settings integrations tests
   - [x] attributes for faceting tests
   - [x] displayed attributes tests
   - [x] distinct attributes tests
   - [x] ranking rules tests
   - [x] searchable attributes tests
   - [x] settings route tests
   - [x] stop words tests
   - [x] synonyms tests
- [x] update route tests
- [X] search route tests 
   - [X] limit tests
   - [X] offset tests
   - [X] attributes to crop tests
   - [X] cropLength tests
   - [X] matches tests
   - [X] attributesToHighlight tests
   - [X] attributesToRetrieve tests
   - [X] filters tests
   - [X] facetsDistribution tests
   - [X] facetFilters tests

## Reasons why the tests are failing

~~On the test `testGetDistinctAttribute` and `testUpdateDistinctAttribute`:~~

 ~~- The attribute that returns from the server contains a double "", causing the error: `XCTAssertEqual failed: ("product_id") is not equal to (""product_id"")`~~

 ~~On the test `testGetSettings` and `testUpdateSettings`:~~

 ~~- Probably due to the `acceptNewFields`, this has been removed.~~


Co-authored-by: Pedro Paulo de Amorim <[email protected]>
Co-authored-by: Pedro Paulo Amorim <[email protected]>
Co-authored-by: Clémentine Urquizar <[email protected]>
  • Loading branch information
3 people authored Oct 8, 2020
2 parents 47e6a57 + 7f3c1a4 commit 06c8527
Show file tree
Hide file tree
Showing 23 changed files with 1,882 additions and 131 deletions.
10 changes: 9 additions & 1 deletion Sources/MeiliSearch/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Index, Swift.Error>) -> Void) {
Expand Down Expand Up @@ -526,7 +534,7 @@ public struct MeiliSearch {
*/
public func getDistinctAttribute(
UID: String,
_ completion: @escaping (Result<String, Swift.Error>) -> Void) {
_ completion: @escaping (Result<String?, Swift.Error>) -> Void) {
self.settings.getDistinctAttribute(UID, completion)
}

Expand Down
1 change: 0 additions & 1 deletion Sources/MeiliSearch/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ struct Constants {
static let customJSONDecoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(Formatter.iso8601)

return decoder
}()
}
9 changes: 1 addition & 8 deletions Sources/MeiliSearch/Documents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,7 @@ struct Documents {
_ customDecoder: JSONDecoder? = nil,
completion: (Result<T, Swift.Error>) -> 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 {
Expand Down
13 changes: 13 additions & 0 deletions Sources/MeiliSearch/Indexes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ struct Indexes {
self.request = request
}

// MARK: Functions

func get(
_ UID: String,
_ completion: @escaping (Result<Index, Swift.Error>) -> Void) {
Expand Down Expand Up @@ -162,6 +164,8 @@ struct Indexes {

}

// MARK: Codable

private static func decodeJSON(
_ data: Data,
_ completion: (Result<Index, Swift.Error>) -> Void) {
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions Sources/MeiliSearch/Model/Key.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ 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

}
82 changes: 68 additions & 14 deletions Sources/MeiliSearch/Model/SearchParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]?

Expand All @@ -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.
Expand All @@ -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,
Expand Down Expand Up @@ -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)
}
}

}
64 changes: 63 additions & 1 deletion Sources/MeiliSearch/Model/SearchResult.swift
Original file line number Diff line number Diff line change
@@ -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<T>: Codable, Equatable where T: Codable, T: Equatable {

Expand All @@ -16,10 +17,71 @@ public struct SearchResult<T>: 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: 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<StringKey> = try values.nestedContainer(keyedBy: StringKey.self, forKey: .facetsDistribution)
var dic: [String: [String: Int]] = Dictionary(minimumCapacity: nested.allKeys.count)
try nested.allKeys.forEach { (key: KeyedDecodingContainer<StringKey>.Key) in
let facet: KeyedDecodingContainer<StringKey> = try nested.nestedContainer(keyedBy: StringKey.self, forKey: key)
var inner: [String: Int] = Dictionary(minimumCapacity: facet.allKeys.count)
try facet.allKeys.forEach { (innerKey: KeyedDecodingContainer<StringKey>.Key) in
inner[innerKey.stringValue] = try facet.decode(Int.self, forKey: innerKey)
}
dic[key.stringValue] = inner
}
facetsDistribution = dic
} else {
facetsDistribution = nil
}

}

}
14 changes: 13 additions & 1 deletion Sources/MeiliSearch/Model/Setting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,26 @@ 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?

/// List of attributes used for the faceting
public let attributesForFaceting: [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)
attributesForFaceting = (try? values?.decodeIfPresent([String].self, forKey: .attributesForFaceting)) ?? []
}

}
4 changes: 2 additions & 2 deletions Sources/MeiliSearch/Model/Update.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ 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?

///Typr of `Update`.
public struct `Type`: Codable, Equatable {
Expand Down
Loading

0 comments on commit 06c8527

Please sign in to comment.