diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 558e18cf..6c941ffe 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -1580,3 +1580,20 @@ swap_indexes_1: |- ("indexA", "indexB"), ("indexX", "indexY") ]) +create_snapshot_1: |- + let task = try await self.client.createSnapshot() +get_proximity_precision_settings_1: |- + let precisionValue = try await self.client.index("books").getProximityPrecision() +update_proximity_precision_settings_1: |- + let task = try await self.client.index("books").updateProximityPrecision(.byWord) +reset_proximity_precision_settings_1: |- + let task = try await self.client.index("books").resetProximityPrecision() +get_search_cutoff_1: |- + let precisionValue = try await self.client.index("books").getSearchCutoffMs() +update_search_cutoff_1: |- + let task = try await self.client.index("books").updateSearchCutoffMs(150) +reset_search_cutoff_1: |- + let task = try await self.client.index("books").resetSearchCutoffMs() +search_parameter_guide_show_ranking_score_details_1: |- + let searchParameters = SearchParameters(query: "dragon", showRankingScoreDetails: true) + let movies: Searchable = try await client.index("movies").search(searchParameters) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 100d3068..41325d6b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: matrix: os: ["macos-latest", "ubuntu-20.04"] env: - GITHUB_PAT: ${{ secrets.MEILIBOT_PAT_REPO }} + GITHUB_PAT: ${{ secrets.MEILI_BOT_GH_PAT }} steps: - uses: actions/checkout@v4 - name: Download the latest stable version of Meilisearch diff --git a/.gitignore b/.gitignore index ac713358..1106e3b7 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,10 @@ iOSInjectionProject/ .swiftpm .vscode/ + +# MeiliSearch + +meilisearch +data.ms +dumps/*.dump +snapshots/*.snapshot \ No newline at end of file diff --git a/MeiliSearch.podspec b/MeiliSearch.podspec index 6196d5a0..a6f39f38 100644 --- a/MeiliSearch.podspec +++ b/MeiliSearch.podspec @@ -48,7 +48,7 @@ Pod::Spec.new do |s| s.source_files = 'Sources/**/*.{h,m,swift}' s.swift_versions = ['5.2'] - s.ios.deployment_target = '10.0' - s.osx.deployment_target = '10.10' + s.ios.deployment_target = '12.0' + s.osx.deployment_target = '10.13' end diff --git a/Sources/MeiliSearch/Async/Client+async.swift b/Sources/MeiliSearch/Async/Client+async.swift index f4762115..d785cd18 100644 --- a/Sources/MeiliSearch/Async/Client+async.swift +++ b/Sources/MeiliSearch/Async/Client+async.swift @@ -243,4 +243,15 @@ extension MeiliSearch { } } } + + /** + See `createSnapshot(_:)` + */ + public func createSnapshot() async throws -> TaskInfo { + try await withCheckedThrowingContinuation { continuation in + self.createSnapshot { result in + continuation.resume(with: result) + } + } + } } diff --git a/Sources/MeiliSearch/Async/Indexes+async.swift b/Sources/MeiliSearch/Async/Indexes+async.swift index 91c3691e..d72a2b7d 100644 --- a/Sources/MeiliSearch/Async/Indexes+async.swift +++ b/Sources/MeiliSearch/Async/Indexes+async.swift @@ -640,6 +640,72 @@ extension Indexes { } } + /** + See `getProximityPrecision(_:)` + */ + public func getProximityPrecision() async throws -> ProximityPrecision { + try await withCheckedThrowingContinuation { continuation in + self.getProximityPrecision { result in + continuation.resume(with: result) + } + } + } + + /** + See `updateProximityPrecision(_:_:)` + */ + public func updateProximityPrecision(_ proximityPrecision: ProximityPrecision) async throws -> TaskInfo { + try await withCheckedThrowingContinuation { continuation in + self.updateProximityPrecision(proximityPrecision) { result in + continuation.resume(with: result) + } + } + } + + /** + See `resetProximityPrecision(_:)` + */ + public func resetProximityPrecision() async throws -> TaskInfo { + try await withCheckedThrowingContinuation { continuation in + self.resetProximityPrecision { result in + continuation.resume(with: result) + } + } + } + + /** + See `getSearchCutoffMs(_:)` + */ + public func getSearchCutoffMs() async throws -> Int? { + try await withCheckedThrowingContinuation { continuation in + self.getSearchCutoffMs { result in + continuation.resume(with: result) + } + } + } + + /** + See `updateSearchCutoffMs(_:_:)` + */ + public func updateSearchCutoffMs(_ newValue: Int) async throws -> TaskInfo { + try await withCheckedThrowingContinuation { continuation in + self.updateSearchCutoffMs(newValue) { result in + continuation.resume(with: result) + } + } + } + + /** + See `resetSearchCutoffMs(_:)` + */ + public func resetSearchCutoffMs() async throws -> TaskInfo { + try await withCheckedThrowingContinuation { continuation in + self.resetSearchCutoffMs { result in + continuation.resume(with: result) + } + } + } + /** See `stats(_:)` */ diff --git a/Sources/MeiliSearch/Client.swift b/Sources/MeiliSearch/Client.swift index 5215bb59..5c21c221 100755 --- a/Sources/MeiliSearch/Client.swift +++ b/Sources/MeiliSearch/Client.swift @@ -23,6 +23,7 @@ public struct MeiliSearch { private let stats: Stats private let system: System private let dumps: Dumps + private let snapshots: Snapshots private let tasks: Tasks // MARK: Initializers @@ -42,6 +43,7 @@ public struct MeiliSearch { self.stats = Stats(self.request) self.system = System(self.request) self.dumps = Dumps(self.request) + self.snapshots = Snapshots(self.request) self.tasks = Tasks(self.request) } @@ -381,4 +383,15 @@ public struct MeiliSearch { public func createDump(_ completion: @escaping (Result) -> Void) { self.dumps.create(completion) } + + /** + Triggers the snapshot creation process. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` value. + If the request was successful or `Error` if a failure occurred. + */ + public func createSnapshot(_ completion: @escaping (Result) -> Void) { + self.snapshots.create(completion) + } } diff --git a/Sources/MeiliSearch/Indexes.swift b/Sources/MeiliSearch/Indexes.swift index d020590b..81450da1 100755 --- a/Sources/MeiliSearch/Indexes.swift +++ b/Sources/MeiliSearch/Indexes.swift @@ -1102,6 +1102,86 @@ public struct Indexes { self.settings.resetTypoTolerance(self.uid, completion) } + // MARK: Proximity Precision + + /** + Get the proximity precision value. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains a `TypoToleranceResult` + value if the request was successful, or `Error` if a failure occurred. + */ + public func getProximityPrecision( + _ completion: @escaping (Result) -> Void) { + self.settings.getSetting(uid: uid, key: "proximity-precision", completion: completion) + } + + /** + Update the proximity precision value. + + - parameter proximityPrecision: The new value. + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func updateProximityPrecision( + _ proximityPrecision: ProximityPrecision, + _ completion: @escaping (Result) -> Void) { + self.settings.updateSetting(uid: uid, key: "proximity-precision", data: proximityPrecision.rawValue, completion: completion) + } + + /** + Reset the proximity precision value. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func resetProximityPrecision( + _ completion: @escaping (Result) -> Void) { + self.settings.resetSetting(uid: uid, key: "proximity-precision", completion: completion) + } + + // MARK: Search Cutoff + + /** + Get the search cut-off value. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains a `TypoToleranceResult` + value if the request was successful, or `Error` if a failure occurred. + */ + public func getSearchCutoffMs( + _ completion: @escaping (Result) -> Void) { + self.settings.getSetting(uid: uid, key: "search-cutoff-ms", completion: completion) + } + + /** + Update the search cut-off value. + + - parameter newValue: The new cut-off in milliseconds. + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func updateSearchCutoffMs( + _ newValue: Int, + _ completion: @escaping (Result) -> Void) { + self.settings.updateSetting(uid: uid, key: "search-cutoff-ms", data: newValue, completion: completion) + } + + /** + Reset the search cut-off value. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func resetSearchCutoffMs( + _ completion: @escaping (Result) -> Void) { + self.settings.resetSetting(uid: uid, key: "search-cutoff-ms", completion: completion) + } + // MARK: Stats /** diff --git a/Sources/MeiliSearch/Model/ProximityPrecision.swift b/Sources/MeiliSearch/Model/ProximityPrecision.swift new file mode 100644 index 00000000..740d1d69 --- /dev/null +++ b/Sources/MeiliSearch/Model/ProximityPrecision.swift @@ -0,0 +1,6 @@ +import Foundation + +public enum ProximityPrecision: String, Codable { + case byWord + case byAttribute +} diff --git a/Sources/MeiliSearch/Model/SearchParameters.swift b/Sources/MeiliSearch/Model/SearchParameters.swift index 2c569ddd..1ba8ca7f 100644 --- a/Sources/MeiliSearch/Model/SearchParameters.swift +++ b/Sources/MeiliSearch/Model/SearchParameters.swift @@ -65,6 +65,9 @@ public struct SearchParameters: Codable, Equatable { /// Whether to return the search ranking score or not. public let showRankingScore: Bool? + /// Whether to return the ranking score details or not. + public let showRankingScoreDetails: Bool? + // MARK: Initializers public init( @@ -85,7 +88,9 @@ public struct SearchParameters: Codable, Equatable { sort: [String]? = nil, facets: [String]? = nil, showMatchesPosition: Bool? = nil, - showRankingScore: Bool? = nil) { + showRankingScore: Bool? = nil, + showRankingScoreDetails: Bool? = nil + ) { self.query = query self.offset = offset self.limit = limit @@ -104,6 +109,7 @@ public struct SearchParameters: Codable, Equatable { self.facets = facets self.showMatchesPosition = showMatchesPosition self.showRankingScore = showRankingScore + self.showRankingScoreDetails = showRankingScoreDetails } // MARK: Query Initializers @@ -139,5 +145,6 @@ public struct SearchParameters: Codable, Equatable { case hitsPerPage case page case showRankingScore + case showRankingScoreDetails } } diff --git a/Sources/MeiliSearch/Model/SearchResult.swift b/Sources/MeiliSearch/Model/SearchResult.swift index f1b485f3..1dfe5df4 100644 --- a/Sources/MeiliSearch/Model/SearchResult.swift +++ b/Sources/MeiliSearch/Model/SearchResult.swift @@ -38,6 +38,7 @@ public class Searchable: Equatable, Codable where T: Codable, T: Equatable { public struct SearchHit: Equatable, Codable where T: Codable, T: Equatable { public let document: T public internal(set) var rankingScore: Double? + public internal(set) var rankingScoreDetails: [String: SearchScoreRankingDetails]? /// Dynamic member lookup is used to allow easy access to instance members of the hit result, maintaining a level of backwards compatibility. public subscript(dynamicMember keyPath: KeyPath) -> V { @@ -48,12 +49,14 @@ public struct SearchHit: Equatable, Codable where T: Codable, T: Equatable { enum CodingKeys: String, CodingKey { case rankingScore = "_rankingScore" + case rankingScoreDetails = "_rankingScoreDetails" } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.document = try T(from: decoder) self.rankingScore = try container.decodeIfPresent(Double.self, forKey: .rankingScore) + self.rankingScoreDetails = try container.decodeIfPresent([String: SearchScoreRankingDetails].self, forKey: .rankingScoreDetails) } public func encode(to encoder: Encoder) throws { @@ -62,6 +65,7 @@ public struct SearchHit: Equatable, Codable where T: Codable, T: Equatable { var containerTwo = encoder.container(keyedBy: CodingKeys.self) try containerTwo.encodeIfPresent(rankingScore, forKey: .rankingScore) + try containerTwo.encodeIfPresent(rankingScoreDetails, forKey: .rankingScoreDetails) } } diff --git a/Sources/MeiliSearch/Model/SearchScoreRankingDetails.swift b/Sources/MeiliSearch/Model/SearchScoreRankingDetails.swift new file mode 100644 index 00000000..b735ea00 --- /dev/null +++ b/Sources/MeiliSearch/Model/SearchScoreRankingDetails.swift @@ -0,0 +1,18 @@ +import Foundation + +public struct SearchScoreRankingDetails: Codable, Equatable { + public enum MatchType: String, Codable { + case exactMatch, matchesStart, noExactMatch + } + + public let order: Int + public let score: Double? + public let maxTypoCount: Int? + public let typoCount: Int? + public let maxMatchingWords: Int? + public let matchingWords: Int? + public let value: String? + public let matchType: MatchType? + public let queryWordDistanceScore: Double? + public let attributeRankingOrderScore: Double? +} diff --git a/Sources/MeiliSearch/Model/Setting.swift b/Sources/MeiliSearch/Model/Setting.swift index 7d46daba..79fdff65 100644 --- a/Sources/MeiliSearch/Model/Setting.swift +++ b/Sources/MeiliSearch/Model/Setting.swift @@ -36,6 +36,12 @@ public struct Setting: Codable, Equatable { /// Settings for typo tolerance public let typoTolerance: TypoTolerance? + /// Precision level when calculating the proximity ranking rule + public let proximityPrecision: ProximityPrecision? + + /// Maximum duration of a search query + public let searchCutoffMs: Int? + /// List of tokens that will be considered as word separators by Meilisearch. public let separatorTokens: [String]? @@ -63,7 +69,9 @@ public struct Setting: Codable, Equatable { nonSeparatorTokens: [String]? = nil, dictionary: [String]? = nil, pagination: Pagination? = nil, - typoTolerance: TypoTolerance? = nil + typoTolerance: TypoTolerance? = nil, + proximityPrecision: ProximityPrecision? = nil, + searchCutoffMs: Int? = nil ) { self.rankingRules = rankingRules self.searchableAttributes = searchableAttributes @@ -78,5 +86,7 @@ public struct Setting: Codable, Equatable { self.dictionary = dictionary self.pagination = pagination self.typoTolerance = typoTolerance + self.proximityPrecision = proximityPrecision + self.searchCutoffMs = searchCutoffMs } } diff --git a/Sources/MeiliSearch/Model/SettingResult.swift b/Sources/MeiliSearch/Model/SettingResult.swift index ae5efecf..8a322f0b 100644 --- a/Sources/MeiliSearch/Model/SettingResult.swift +++ b/Sources/MeiliSearch/Model/SettingResult.swift @@ -42,4 +42,10 @@ public struct SettingResult: Codable, Equatable { /// Settings for typo tolerance public let typoTolerance: TypoToleranceResult + + /// Precision level when calculating the proximity ranking rule + public let proximityPrecision: ProximityPrecision + + /// Maximum duration of a search query + public let searchCutoffMs: Int? } diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index 1291c26d..b99beb64 100644 --- a/Sources/MeiliSearch/Settings.swift +++ b/Sources/MeiliSearch/Settings.swift @@ -419,7 +419,7 @@ struct Settings { // MARK: Reusable Requests - private func getSetting( + func getSetting( uid: String, key: String?, completion: @escaping (Result) -> Void @@ -445,7 +445,7 @@ struct Settings { } } - private func updateSetting( + func updateSetting( uid: String, key: String?, data: T, @@ -476,7 +476,7 @@ struct Settings { } } - private func resetSetting( + func resetSetting( uid: String, key: String?, completion: @escaping (Result) -> Void diff --git a/Sources/MeiliSearch/Snapshots.swift b/Sources/MeiliSearch/Snapshots.swift new file mode 100644 index 00000000..645ee304 --- /dev/null +++ b/Sources/MeiliSearch/Snapshots.swift @@ -0,0 +1,37 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +/** + The snapshots route allows the creation of database snapshots. + */ +struct Snapshots { + // MARK: Properties + + let request: Request + + // MARK: Initializers + + init (_ request: Request) { + self.request = request + } + + // MARK: Create + + func create(_ completion: @escaping (Result) -> Void) { + self.request.post(api: "/snapshots", Data()) { result in + switch result { + case .success(let data): + do { + let result: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) + completion(.success(result)) + } catch { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } +} diff --git a/Tests/MeiliSearchIntegrationTests/SearchTests.swift b/Tests/MeiliSearchIntegrationTests/SearchTests.swift index 45af94eb..5ea70415 100644 --- a/Tests/MeiliSearchIntegrationTests/SearchTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SearchTests.swift @@ -104,6 +104,20 @@ class SearchTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } + func testSearchWithRankingDetails() async throws { + let result: Searchable = try await index.search(SearchParameters(query: "Moreninha", showRankingScoreDetails: true)) + XCTAssertTrue(result.hits.count > 0) + + let hit = try XCTUnwrap(result.hits.first) + let details = try XCTUnwrap(hit.rankingScoreDetails) + + XCTAssertNotNil(details["exactness"]) + XCTAssertNotNil(details["typo"]) + XCTAssertNotNil(details["words"]) + XCTAssertNotNil(details["proximity"]) + XCTAssertNotNil(details["attribute"]) + } + // MARK: Attributes to search on func testAttributesSearchOn() { let expectation = XCTestExpectation(description: "Search for Books with limited attributes") diff --git a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift index 00d749cb..6446a54a 100644 --- a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift @@ -80,7 +80,9 @@ class SettingsTests: XCTestCase { nonSeparatorTokens: self.defaultNonSeparatorTokens, dictionary: self.defaultDictionary, pagination: self.defaultPagination, - typoTolerance: self.defaultTypoTolerance + typoTolerance: self.defaultTypoTolerance, + proximityPrecision: .byWord, + searchCutoffMs: nil ) self.defaultGlobalReturnedSettings = SettingResult( @@ -96,7 +98,9 @@ class SettingsTests: XCTestCase { nonSeparatorTokens: self.defaultNonSeparatorTokens, dictionary: self.defaultDictionary, pagination: self.defaultPagination, - typoTolerance: self.defaultTypoToleranceResult + typoTolerance: self.defaultTypoToleranceResult, + proximityPrecision: .byWord, + searchCutoffMs: nil ) } @@ -952,6 +956,56 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } + // MARK: Proximity Precision + + func testProximityPrecision() async throws { + do { // GET + let currentValue = try await self.index.getProximityPrecision() + XCTAssertEqual(currentValue, .byWord) // default value + } + + do { // PUT + let taskInfo = try await self.index.updateProximityPrecision(.byAttribute) + _ = try await self.client.waitForTask(task: taskInfo) + + let newValue = try await self.index.getProximityPrecision() + XCTAssertEqual(newValue, .byAttribute) + } + + do { // DELETE + let taskInfo = try await self.index.resetProximityPrecision() + _ = try await self.client.waitForTask(task: taskInfo) + + let newValue = try await self.index.getProximityPrecision() + XCTAssertEqual(newValue, .byWord) // default value + } + } + + // MARK: Search Cut-off Milliseconds + + func testSearchCutOff() async throws { + do { // GET + let currentValue = try await self.index.getSearchCutoffMs() + XCTAssertEqual(currentValue, nil) // default value + } + + do { // PUT + let taskInfo = try await self.index.updateSearchCutoffMs(150) + _ = try await self.client.waitForTask(task: taskInfo) + + let newValue = try await self.index.getSearchCutoffMs() + XCTAssertEqual(newValue, 150) + } + + do { // DELETE + let taskInfo = try await self.index.resetSearchCutoffMs() + _ = try await self.client.waitForTask(task: taskInfo) + + let newValue = try await self.index.getSearchCutoffMs() + XCTAssertEqual(newValue, nil) // default value + } + } + // MARK: Stop words func testGetStopWords() { @@ -1284,7 +1338,9 @@ class SettingsTests: XCTestCase { let newSettings = Setting( rankingRules: ["words", "typo", "proximity", "attribute", "sort", "exactness"], searchableAttributes: ["id", "title"], - stopWords: ["a"] + stopWords: ["a"], + proximityPrecision: .byWord, + searchCutoffMs: 200 ) let overrideSettings = Setting( @@ -1304,7 +1360,9 @@ class SettingsTests: XCTestCase { nonSeparatorTokens: [], dictionary: [], pagination: .init(maxTotalHits: 1000), - typoTolerance: defaultTypoToleranceResult + typoTolerance: defaultTypoToleranceResult, + proximityPrecision: .byWord, + searchCutoffMs: 200 ) let expectation = XCTestExpectation(description: "Update settings") @@ -1320,6 +1378,8 @@ class SettingsTests: XCTestCase { XCTAssertEqual(expectedSettingResult.rankingRules, details.rankingRules) XCTAssertEqual(expectedSettingResult.searchableAttributes, details.searchableAttributes) XCTAssertEqual(expectedSettingResult.stopWords, details.stopWords) + XCTAssertEqual(expectedSettingResult.searchCutoffMs, details.searchCutoffMs) + XCTAssertEqual(expectedSettingResult.proximityPrecision, details.proximityPrecision) } else { XCTFail("settingsUpdate details should be set by task") } @@ -1402,7 +1462,9 @@ class SettingsTests: XCTestCase { nonSeparatorTokens: ["#"], dictionary: ["J.K"], pagination: .init(maxTotalHits: 500), - typoTolerance: defaultTypoToleranceResult + typoTolerance: defaultTypoToleranceResult, + proximityPrecision: .byWord, + searchCutoffMs: nil ) self.index.updateSettings(newSettings) { result in diff --git a/Tests/MeiliSearchIntegrationTests/SnapshotsTests.swift b/Tests/MeiliSearchIntegrationTests/SnapshotsTests.swift new file mode 100644 index 00000000..8a5a403c --- /dev/null +++ b/Tests/MeiliSearchIntegrationTests/SnapshotsTests.swift @@ -0,0 +1,39 @@ +@testable import MeiliSearch +import XCTest +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +class SnapshotsTests: XCTestCase { + private var client: MeiliSearch! + private var session: URLSessionProtocol! + + // MARK: Setup + + override func setUpWithError() throws { + try super.setUpWithError() + if client == nil { + session = URLSession(configuration: .ephemeral) + client = try MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) + } + } + + func testCreateAndGetDump() { + let expectation = XCTestExpectation(description: "Request snapshot status") + + self.client.createSnapshot { result in + switch result { + case .success(let snapshotTask): + XCTAssertEqual(snapshotTask.status, Task.Status.enqueued) + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to request snapshot creation \(error)") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } +} diff --git a/Tests/MeiliSearchIntegrationTests/Utils.swift b/Tests/MeiliSearchIntegrationTests/Utils.swift index 8e780f8a..5fb0384c 100644 --- a/Tests/MeiliSearchIntegrationTests/Utils.swift +++ b/Tests/MeiliSearchIntegrationTests/Utils.swift @@ -46,7 +46,7 @@ public func waitForTask( request() } -public func createGenericIndex(client: MeiliSearch, uid: String, _ completion: @escaping(Result) -> Void) { +public func createGenericIndex(client: MeiliSearch, uid: String, _ completion: @escaping (Result) -> Void) { client.deleteIndex(uid) { result in switch result { case .success: @@ -71,7 +71,7 @@ public func createGenericIndex(client: MeiliSearch, uid: String, _ completion: @ } } -public func deleteIndex(client: MeiliSearch, uid: String, _ completion: @escaping(Result) -> Void) { +public func deleteIndex(client: MeiliSearch, uid: String, _ completion: @escaping (Result) -> Void) { client.deleteIndex(uid) { result in switch result { case .success(let task): @@ -90,12 +90,12 @@ public func deleteIndex(client: MeiliSearch, uid: String, _ completion: @escapin } } -public func addDocuments(client: MeiliSearch, uid: String, primaryKey: String?, _ completion: @escaping(Result) -> Void) { +public func addDocuments(client: MeiliSearch, uid: String, primaryKey: String?, _ completion: @escaping (Result) -> Void) { let movie = Movie(id: 1, title: "test", comment: "test movie") addDocuments(client: client, uid: uid, dataset: [movie], primaryKey: primaryKey, completion) } -public func addDocuments(client: MeiliSearch, uid: String, dataset: [T], primaryKey: String?, _ completion: @escaping(Result) -> Void) { +public func addDocuments(client: MeiliSearch, uid: String, dataset: [T], primaryKey: String?, _ completion: @escaping (Result) -> Void) { let jsonEncoder = JSONEncoder() let documents: Data diff --git a/Tests/MeiliSearchUnitTests/ClientTests.swift b/Tests/MeiliSearchUnitTests/ClientTests.swift index f56f0987..02b3821f 100644 --- a/Tests/MeiliSearchUnitTests/ClientTests.swift +++ b/Tests/MeiliSearchUnitTests/ClientTests.swift @@ -89,7 +89,7 @@ class ClientTests: XCTestCase { let config = Config(host: self.validHost, apiKey: self.key, session: self.session) let req = Request(config) - req.post(api: "/", "{}".data(using: .utf8) ?? Data(), requiredDataResponseHandler) + req.post(api: "/", Data("{}".utf8), requiredDataResponseHandler) } func testPATCHHeaders() { @@ -98,7 +98,7 @@ class ClientTests: XCTestCase { let config = Config(host: self.validHost, apiKey: self.key, session: self.session) let req = Request(config) - req.patch(api: "/", "{}".data(using: .utf8) ?? Data(), requiredDataResponseHandler) + req.patch(api: "/", Data("{}".utf8), requiredDataResponseHandler) } func testPUTHeaders() { @@ -107,6 +107,6 @@ class ClientTests: XCTestCase { let config = Config(host: self.validHost, apiKey: self.key, session: self.session) let req = Request(config) - req.put(api: "/", "{}".data(using: .utf8) ?? Data(), requiredDataResponseHandler) + req.put(api: "/", Data("{}".utf8), requiredDataResponseHandler) } } diff --git a/Tests/MeiliSearchUnitTests/MockURLSession.swift b/Tests/MeiliSearchUnitTests/MockURLSession.swift index eb46919a..638041a2 100644 --- a/Tests/MeiliSearchUnitTests/MockURLSession.swift +++ b/Tests/MeiliSearchUnitTests/MockURLSession.swift @@ -63,7 +63,7 @@ class MockURLSession: URLSessionProtocol { class MockURLSessionDataTask: URLSessionDataTaskProtocol { var request: URLRequest? - private (set) var resumeWasCalled = false + private(set) var resumeWasCalled = false func resume() { resumeWasCalled = true diff --git a/Tests/MeiliSearchUnitTests/SettingsTests.swift b/Tests/MeiliSearchUnitTests/SettingsTests.swift index 4bd353ad..a125a205 100644 --- a/Tests/MeiliSearchUnitTests/SettingsTests.swift +++ b/Tests/MeiliSearchUnitTests/SettingsTests.swift @@ -47,7 +47,9 @@ class SettingsTests: XCTestCase { }, "disableOnWords": [], "disableOnAttributes": [] - } + }, + "proximityPrecision": "byWord", + "searchCutoffMs": null } """ diff --git a/Tests/MeiliSearchUnitTests/SnapshotsTests.swift b/Tests/MeiliSearchUnitTests/SnapshotsTests.swift new file mode 100644 index 00000000..368771ee --- /dev/null +++ b/Tests/MeiliSearchUnitTests/SnapshotsTests.swift @@ -0,0 +1,27 @@ +@testable import MeiliSearch +import XCTest + +class SnapshotsTests: XCTestCase { + private var client: MeiliSearch! + private let session = MockURLSession() + + override func setUpWithError() throws { + try super.setUpWithError() + client = try MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) + } + + func testCreateSnapshot() async throws { + // Prepare the mock server + + let json = """ + { "taskUid": 278, "indexUid": null, "status": "enqueued", "type": "snapshotCreation", "enqueuedAt": "2022-07-21T21:43:12.419917471Z" } + """ + + let stubSnapshot: TaskInfo = try decodeJSON(from: json) + session.pushData(json) + + // Start the test with the mocked server + let snapshot = try await self.client.createSnapshot() + XCTAssertEqual(stubSnapshot, snapshot) + } +}