From 8fe28559d93ed85b8fd04c5eda34f958e8f17887 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 17:03:31 +0100 Subject: [PATCH 1/9] Improve Type Safety around Tasks --- Sources/MeiliSearch/Error.swift | 4 + Sources/MeiliSearch/Model/Task.swift | 131 ----------- Sources/MeiliSearch/Model/Task/Task.swift | 65 +++++ .../MeiliSearch/Model/Task/TaskDetails.swift | 110 +++++++++ .../Model/{ => Task}/TaskInfo.swift | 5 +- .../MeiliSearch/Model/Task/TaskStatus.swift | 45 ++++ Sources/MeiliSearch/Model/Task/TaskType.swift | 97 ++++++++ .../Model/{ => Task}/TasksResults.swift | 5 +- .../DocumentsTests.swift | 53 ++--- .../IndexesTests.swift | 18 +- .../SettingsTests.swift | 222 +++++++----------- .../TaskTests.swift | 11 +- Tests/MeiliSearchUnitTests/IndexesTests.swift | 3 +- Tests/MeiliSearchUnitTests/TasksTests.swift | 3 +- 14 files changed, 441 insertions(+), 331 deletions(-) delete mode 100644 Sources/MeiliSearch/Model/Task.swift create mode 100644 Sources/MeiliSearch/Model/Task/Task.swift create mode 100644 Sources/MeiliSearch/Model/Task/TaskDetails.swift rename Sources/MeiliSearch/Model/{ => Task}/TaskInfo.swift (92%) create mode 100644 Sources/MeiliSearch/Model/Task/TaskStatus.swift create mode 100644 Sources/MeiliSearch/Model/Task/TaskType.swift rename Sources/MeiliSearch/Model/{ => Task}/TasksResults.swift (77%) diff --git a/Sources/MeiliSearch/Error.swift b/Sources/MeiliSearch/Error.swift index 774d12fa..f48d7b99 100644 --- a/Sources/MeiliSearch/Error.swift +++ b/Sources/MeiliSearch/Error.swift @@ -9,9 +9,13 @@ import Foundation public extension MeiliSearch { // MARK: Error struct MSErrorResponse: Decodable, Encodable, Equatable { + /// A human-readable description of the error public let message: String + /// The error code (https://www.meilisearch.com/docs/reference/errors/error_codes) public let code: String + /// The error type (https://www.meilisearch.com/docs/reference/errors/overview#errors) public let type: String + /// A link to the relevant section of the documentation public let link: String? } diff --git a/Sources/MeiliSearch/Model/Task.swift b/Sources/MeiliSearch/Model/Task.swift deleted file mode 100644 index 13a85a3b..00000000 --- a/Sources/MeiliSearch/Model/Task.swift +++ /dev/null @@ -1,131 +0,0 @@ -import Foundation -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - -/** - `Task` instances represent the current transaction status, use the `uid` value to - verify the status of your transaction. - */ -public struct Task: Codable, Equatable { - - // MARK: Properties - - /// Unique ID for the current `Task`. - public let uid: Int - - /// Unique ID for the current `Task`. - public let indexUid: String - - /// Returns if the task has been successful or not. - public let status: Task.Status - - /// Type of the task. - public let type: String - - /// Details of the task. - public let details: Details? - - /// Duration of the task process. - public let duration: String? - - /// Date when the task has been enqueued. - public let enqueuedAt: String - - /// Date when the task has been processed. - public let processedAt: Date? - - /// Type of `Task`. - public struct Details: Codable, Equatable { - - // MARK: Properties - - /// Number of documents sent - public let receivedDocuments: Int? - - /// Number of documents successfully indexed/updated in Meilisearch - public let indexedDocuments: Int? - - /// Number of deleted documents - public let deletedDocuments: Int? - - /// Primary key on index creation - public let primaryKey: String? - - /// Ranking rules on settings actions - public let rankingRules: [String]? - - /// Searchable attributes on settings actions - public let searchableAttributes: [String]? - - /// Displayed attributes on settings actions - public let displayedAttributes: [String]? - - /// Filterable attributes on settings actions - public let filterableAttributes: [String]? - - /// Sortable attributes on settings actions - public let sortableAttributes: [String]? - - /// Stop words on settings actions - public let stopWords: [String]? - - /// Synonyms on settings actions - public let synonyms: [String: [String]]? - - /// Distinct attribute on settings actions - public let distinctAttribute: String? - - /// List of tokens that will be considered as word separators by Meilisearch. - public let separatorTokens: [String]? - - /// List of tokens that will not be considered as word separators by Meilisearch. - public let nonSeparatorTokens: [String]? - - /// List of words on which the segmentation will be overridden. - public let dictionary: [String]? - - /// Settings for index level pagination rules - public let pagination: Pagination? - - /// Typo tolerance on settings actions - public let typoTolerance: TypoTolerance? - } - /// Error information in case of failed update. - public let error: MeiliSearch.MSErrorResponse? - - public enum Status: Codable, Equatable { - /// When a task was successfully enqueued and is waiting to be processed. - case enqueued - /// When a task is still being processed. - case processing - /// When a task was successfully processed. - case succeeded - /// When a task had an error and could not be completed for some reason. - case failed - - public enum StatusError: Error { - case unknown - } - - public init(from decoder: Decoder) throws { - let container: SingleValueDecodingContainer = try decoder.singleValueContainer() - let rawStatus: String = try container.decode(String.self) - - switch rawStatus { - case "enqueued": - self = Status.enqueued - case "processing": - self = Status.processing - case "succeeded": - self = Status.succeeded - case "failed": - self = Status.failed - default: - throw StatusError.unknown - } - } - - public func encode(to encoder: Encoder) throws { } - } -} diff --git a/Sources/MeiliSearch/Model/Task/Task.swift b/Sources/MeiliSearch/Model/Task/Task.swift new file mode 100644 index 00000000..029ff880 --- /dev/null +++ b/Sources/MeiliSearch/Model/Task/Task.swift @@ -0,0 +1,65 @@ +import Foundation + +/** + `Task` instances represent the current transaction status, use the `uid` value to + verify the status of your transaction. + */ +public struct Task: Decodable, Equatable { + /// Unique ID for the current `Task`. + public let uid: Int + + /// Unique identifier of the targeted index + public let indexUid: String + + /// Returns if the task has been successful or not. + public let status: Status + + /// Type of the task. + public let type: TaskType + + /// Details of the task. + public let details: Details? + + /// Duration of the task process. + public let duration: String? + + /// Date when the task has been enqueued. + public let enqueuedAt: Date + + /// Date when the task has been started. + public let startedAt: Date? + + /// Date when the task has been finished, regardless of status. + public let finishedAt: Date? + + /// ID of the the `Task` which caused this to be canceled. + public let canceledBy: Int? + + /// Error information in case of failed update. + public let error: MeiliSearch.MSErrorResponse? + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(TaskType.self, forKey: .type) + + self.uid = try container.decode(Int.self, forKey: .uid) + self.indexUid = try container.decode(String.self, forKey: .indexUid) + self.status = try container.decode(Status.self, forKey: .status) + self.type = type + self.duration = try container.decodeIfPresent(String.self, forKey: .duration) + self.enqueuedAt = try container.decode(Date.self, forKey: .enqueuedAt) + self.startedAt = try container.decodeIfPresent(Date.self, forKey: .startedAt) + self.finishedAt = try container.decodeIfPresent(Date.self, forKey: .finishedAt) + self.canceledBy = try container.decodeIfPresent(Int.self, forKey: .canceledBy) + self.error = try container.decodeIfPresent(MeiliSearch.MSErrorResponse.self, forKey: .error) + + // we ignore errors thrown by `superDecoder` to handle cases where no details are provided by the API + // for example when the type is `snapshotCreation`. + let detailsDecoder = try? container.superDecoder(forKey: .details) + self.details = try Details(decoder: detailsDecoder, type: type) + } + + enum CodingKeys: String, CodingKey { + case uid, indexUid, status, type, details, duration, enqueuedAt, startedAt, finishedAt, canceledBy, error + } +} diff --git a/Sources/MeiliSearch/Model/Task/TaskDetails.swift b/Sources/MeiliSearch/Model/Task/TaskDetails.swift new file mode 100644 index 00000000..3aadbd60 --- /dev/null +++ b/Sources/MeiliSearch/Model/Task/TaskDetails.swift @@ -0,0 +1,110 @@ +import Foundation + +public extension Task { + enum Details: Equatable { + case indexCreation(TaskIndexCreationDetails) + case indexUpdate(TaskIndexUpdateDetails) + case indexDeletion(TaskIndexDeletionDetails) + case indexSwap(TaskIndexSwapDetails) + case documentAdditionOrUpdate(TaskDocumentAdditionOrUpdateDetails) + case documentDeletion(TaskDocumentDeletionDetails) + case settingsUpdate(Setting) + case dumpCreation(TaskDumpCreationDetails) + case taskCancelation(TaskCancellationDetails) + case taskDeletion(TaskDeletionDetails) + + init?(decoder: Decoder?, type: TaskType) throws { + guard let decoder = decoder else { + return nil + } + + switch type { + case .indexCreation: + self = try .indexCreation(.init(from: decoder)) + case .indexUpdate: + self = try .indexUpdate(.init(from: decoder)) + case .indexDeletion: + self = try .indexDeletion(.init(from: decoder)) + case .indexSwap: + self = try .indexSwap(.init(from: decoder)) + case .documentAdditionOrUpdate: + self = try .documentAdditionOrUpdate(.init(from: decoder)) + case .documentDeletion: + self = try .documentDeletion(.init(from: decoder)) + case .settingsUpdate: + self = try .settingsUpdate(.init(from: decoder)) + case .dumpCreation: + self = try .dumpCreation(.init(from: decoder)) + case .taskCancelation: + self = try .taskCancelation(.init(from: decoder)) + case .taskDeletion: + self = try .taskDeletion(.init(from: decoder)) + case .snapshotCreation: + // as per documentation: "The details object is set to null for snapshotCreation tasks." + return nil + case .unknown: + // we do not handle this type, and as such even if details exist we do not support them here + return nil + } + } + } + + struct TaskDocumentAdditionOrUpdateDetails: Decodable, Equatable { + /// Number of documents sent + public let receivedDocuments: Int? + + /// Number of documents successfully indexed/updated in Meilisearch + public let indexedDocuments: Int? + } + + struct TaskDocumentDeletionDetails: Decodable, Equatable { + /// Number of documents queued for deletion + public let providedIds: Int? + /// The filter used to delete documents. nil if it was not specified + public let originalFilter: String? + /// Number of documents deleted. nil while the task status is enqueued or processing + public let deletedDocuments: Int? + } + + struct TaskIndexCreationDetails: Decodable, Equatable { + /// Value of the primaryKey field supplied during index creation. nil if it was not specified + public let primaryKey: String? + } + + struct TaskIndexUpdateDetails: Decodable, Equatable { + /// Value of the primaryKey field supplied during index update. nil if it was not specified + public let primaryKey: String? + } + + struct TaskIndexDeletionDetails: Decodable, Equatable { + /// Number of deleted documents. This should equal the total number of documents in the deleted index. nil while the task status is enqueued or processing + public let deletedDocuments: Int? + } + + struct TaskIndexSwapDetails: Decodable, Equatable { + // To be populated under https://github.com/meilisearch/meilisearch-swift/issues/367 + } + + struct TaskCancellationDetails: Decodable, Equatable { + /// The number of matched tasks. If the API key used for the request doesn’t have access to an index, tasks relating to that index will not be included in matchedTasks + public let matchedTasks: Int? + /// The number of tasks successfully canceled. If the task cancelation fails, this will be 0. nil when the task status is enqueued or processing + public let canceledTasks: Int? + /// The filter used in the cancel task request + public let originalFilter: String? + } + + struct TaskDeletionDetails: Decodable, Equatable { + /// The number of matched tasks. If the API key used for the request doesn’t have access to an index, tasks relating to that index will not be included in matchedTasks + public let matchedTasks: Int? + /// The number of tasks successfully deleted. If the task deletion fails, this will be 0. nil when the task status is enqueued or processing + public let deletedTasks: Int? + /// The filter used in the delete task request + public let originalFilter: String? + } + + struct TaskDumpCreationDetails: Decodable, Equatable { + /// The generated uid of the dump. This is also the name of the generated dump file. nil when the task status is enqueued, processing, canceled, or failed + public let dumpUid: String? + } +} diff --git a/Sources/MeiliSearch/Model/TaskInfo.swift b/Sources/MeiliSearch/Model/Task/TaskInfo.swift similarity index 92% rename from Sources/MeiliSearch/Model/TaskInfo.swift rename to Sources/MeiliSearch/Model/Task/TaskInfo.swift index d64c72a9..56162346 100644 --- a/Sources/MeiliSearch/Model/TaskInfo.swift +++ b/Sources/MeiliSearch/Model/Task/TaskInfo.swift @@ -5,9 +5,6 @@ import Foundation verify the status of your transaction. */ public struct TaskInfo: Codable, Equatable { - - // MARK: Properties - /// Unique ID for the current `TaskInfo`. public let taskUid: Int @@ -18,7 +15,7 @@ public struct TaskInfo: Codable, Equatable { public let status: Task.Status /// Type of the task. - public let type: String + public let type: TaskType /// Date when the task has been enqueued. public let enqueuedAt: String diff --git a/Sources/MeiliSearch/Model/Task/TaskStatus.swift b/Sources/MeiliSearch/Model/Task/TaskStatus.swift new file mode 100644 index 00000000..8c374e1e --- /dev/null +++ b/Sources/MeiliSearch/Model/Task/TaskStatus.swift @@ -0,0 +1,45 @@ +import Foundation + +public extension Task { + enum Status: String, Codable, Equatable { + /// When a task was successfully enqueued and is waiting to be processed. + case enqueued + /// When a task is still being processed. + case processing + /// When a task was successfully processed. + case succeeded + /// When a task had an error and could not be completed for some reason. + case failed + /// When a task has been canceled. + case canceled + + public enum StatusError: Error { + case unknown + } + + public init(from decoder: Decoder) throws { + let container: SingleValueDecodingContainer = try decoder.singleValueContainer() + let rawStatus: String = try container.decode(String.self) + + switch rawStatus { + case "enqueued": + self = Status.enqueued + case "processing": + self = Status.processing + case "succeeded": + self = Status.succeeded + case "failed": + self = Status.failed + case "canceled": + self = Status.canceled + default: + throw StatusError.unknown + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } + } +} diff --git a/Sources/MeiliSearch/Model/Task/TaskType.swift b/Sources/MeiliSearch/Model/Task/TaskType.swift new file mode 100644 index 00000000..fdd99825 --- /dev/null +++ b/Sources/MeiliSearch/Model/Task/TaskType.swift @@ -0,0 +1,97 @@ +import Foundation + +/** + `TaskType`defines the possible types of task which could be returned by the `/tasks` API + */ +public enum TaskType: Codable, Equatable, LosslessStringConvertible { + case indexCreation + case indexUpdate + case indexDeletion + case indexSwap + case documentAdditionOrUpdate + case documentDeletion + case settingsUpdate + case dumpCreation + case taskCancelation + case taskDeletion + case snapshotCreation + + // Captures task types which are not currently known by the Swift package. + // This allows for a future proofing should the MeiliSearch executable introduce new values. + case unknown(String) + + public init(from decoder: Decoder) throws { + let value = try decoder.singleValueContainer().decode(String.self) + self.init(rawValue: value) + } + + internal init(rawValue: String) { + switch rawValue { + case "indexCreation": + self = .indexCreation + case "indexUpdate": + self = .indexUpdate + case "indexDeletion": + self = .indexDeletion + case "indexSwap": + self = .indexSwap + case "documentAdditionOrUpdate": + self = .documentAdditionOrUpdate + case "documentDeletion": + self = .documentDeletion + case "settingsUpdate": + self = .settingsUpdate + case "dumpCreation": + self = .dumpCreation + case "taskCancelation": + self = .taskCancelation + case "taskDeletion": + self = .taskDeletion + case "snapshotCreation": + self = .snapshotCreation + default: + self = .unknown(rawValue) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(description) + } + + // MARK: LosslessStringConvertible + // ensures that when TaskType is converted to String, the "unknown" context is ignored + + public init?(_ description: String) { + self.init(rawValue: description) + } + + public var description: String { + switch self { + case .indexCreation: + return "indexCreation" + case .indexUpdate: + return "indexUpdate" + case .indexDeletion: + return "indexDeletion" + case .indexSwap: + return "indexSwap" + case .documentAdditionOrUpdate: + return "documentAdditionOrUpdate" + case .documentDeletion: + return "documentDeletion" + case .settingsUpdate: + return "settingsUpdate" + case .dumpCreation: + return "dumpCreation" + case .taskCancelation: + return "taskCancelation" + case .taskDeletion: + return "taskDeletion" + case .snapshotCreation: + return "snapshotCreation" + case .unknown(let unknownTaskTypeValue): + return unknownTaskTypeValue + } + } +} diff --git a/Sources/MeiliSearch/Model/TasksResults.swift b/Sources/MeiliSearch/Model/Task/TasksResults.swift similarity index 77% rename from Sources/MeiliSearch/Model/TasksResults.swift rename to Sources/MeiliSearch/Model/Task/TasksResults.swift index aaca9ecc..64e039b2 100644 --- a/Sources/MeiliSearch/Model/TasksResults.swift +++ b/Sources/MeiliSearch/Model/Task/TasksResults.swift @@ -3,8 +3,7 @@ import Foundation /** `TasksResults` is a wrapper used in the tasks routes to handle the returned data. */ - -public struct TasksResults: Codable, Equatable { +public struct TasksResults: Decodable, Equatable { /// Results list containing objects of `Task`. public let results: [Task] /// Integer value used to retrieve the next batch of tasks. @@ -13,4 +12,6 @@ public struct TasksResults: Codable, Equatable { public let from: Int? /// Max number of records to be returned in one request. public let limit: Int + /// Total number of tasks matching the filter or query + public let total: Int } diff --git a/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift b/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift index fd71110a..c3b07be4 100755 --- a/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift @@ -63,26 +63,13 @@ class DocumentsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("documentAdditionOrUpdate", task.type) + XCTAssertEqual("documentAdditionOrUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let indexedDocuments = details.indexedDocuments { - XCTAssertEqual(8, indexedDocuments) - } else { - XCTFail("IndexedDocuments field should not be nil") - } - } else { - XCTFail("IndexedDocuments field should exists in details field of task") - } - - if let details = task.details { - if let receivedDocuments = details.receivedDocuments { - XCTAssertEqual(8, receivedDocuments) - } else { - XCTFail("receivedDocuments field should not be nil") - } + if case .documentAdditionOrUpdate(let details) = task.details { + XCTAssertEqual(8, details.indexedDocuments) + XCTAssertEqual(8, details.receivedDocuments) } else { - XCTFail("receivedDocuments field should exists in details field of task") + XCTFail("documentAdditionOrUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -111,7 +98,7 @@ class DocumentsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("documentAdditionOrUpdate", task.type) + XCTAssertEqual("documentAdditionOrUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -143,7 +130,7 @@ class DocumentsTests: XCTestCase { switch result { case .success(let task): XCTAssertEqual(Task.Status.succeeded, task.status) - XCTAssertEqual("documentAdditionOrUpdate", task.type) + XCTAssertEqual("documentAdditionOrUpdate", task.type.description) self.index.getDocuments(params: DocumentsQuery(limit: 1, offset: 1, fields: ["id", "title"])) { (result: Result, Swift.Error>) in switch result { @@ -204,7 +191,7 @@ class DocumentsTests: XCTestCase { switch result { case .success(let task): XCTAssertEqual(Task.Status.succeeded, task.status) - XCTAssertEqual("documentAdditionOrUpdate", task.type) + XCTAssertEqual("documentAdditionOrUpdate", task.type.description) self.index.getDocument(10 ) { (result: Result) in switch result { @@ -247,7 +234,7 @@ class DocumentsTests: XCTestCase { switch result { case .success(let task): XCTAssertEqual(Task.Status.succeeded, task.status) - XCTAssertEqual("documentAdditionOrUpdate", task.type) + XCTAssertEqual("documentAdditionOrUpdate", task.type.description) self.index.getDocument("10" ) { (result: Result) in switch result { @@ -292,7 +279,7 @@ class DocumentsTests: XCTestCase { switch result { case .success(let task): XCTAssertEqual(Task.Status.succeeded, task.status) - XCTAssertEqual("documentAdditionOrUpdate", task.type) + XCTAssertEqual("documentAdditionOrUpdate", task.type.description) expectation.fulfill() case .failure: XCTFail("Failed to wait for task") @@ -335,7 +322,7 @@ class DocumentsTests: XCTestCase { switch result { case .success(let task): XCTAssertEqual(Task.Status.succeeded, task.status) - XCTAssertEqual("documentDeletion", task.type) + XCTAssertEqual("documentDeletion", task.type.description) deleteExpectation.fulfill() case .failure: XCTFail("Failed to wait for task") @@ -377,17 +364,11 @@ class DocumentsTests: XCTestCase { switch result { case .success(let task): XCTAssertEqual(Task.Status.succeeded, task.status) - XCTAssertEqual("documentDeletion", task.type) - if let details = task.details { - if let deletedDocuments = details.deletedDocuments { - XCTAssertGreaterThanOrEqual(deletedDocuments, 8) - } else { - XCTFail("deletedDocuments field should not be nil") - deleteExpectation.fulfill() - } + XCTAssertEqual("documentDeletion", task.type.description) + if case .documentDeletion(let details) = task.details { + XCTAssertEqual(8, details.deletedDocuments) } else { - XCTFail("deletedDocuments field should exists in details field of task") - deleteExpectation.fulfill() + XCTFail("documentDeletion details should be set by task") } deleteExpectation.fulfill() case .failure: @@ -433,7 +414,7 @@ class DocumentsTests: XCTestCase { switch result { case .success(let task): XCTAssertEqual(Task.Status.succeeded, task.status) - XCTAssertEqual("documentDeletion", task.type) + XCTAssertEqual("documentDeletion", task.type.description) deleteExpectation.fulfill() case .failure: XCTFail("Failed to wait for task") @@ -478,7 +459,7 @@ class DocumentsTests: XCTestCase { switch result { case .success(let task): XCTAssertEqual(Task.Status.succeeded, task.status) - XCTAssertEqual("documentDeletion", task.type) + XCTAssertEqual("documentDeletion", task.type.description) deleteExpectation.fulfill() case .failure: XCTFail("Failed to wait for task") diff --git a/Tests/MeiliSearchIntegrationTests/IndexesTests.swift b/Tests/MeiliSearchIntegrationTests/IndexesTests.swift index d3f3ea9d..e676cdb9 100644 --- a/Tests/MeiliSearchIntegrationTests/IndexesTests.swift +++ b/Tests/MeiliSearchIntegrationTests/IndexesTests.swift @@ -57,7 +57,7 @@ class IndexesTests: XCTestCase { self.client.waitForTask(task: task, options: WaitOptions(timeOut: 10.0)) { result in switch result { case .success(let task): - XCTAssertEqual("indexCreation", task.type) + XCTAssertEqual("indexCreation", task.type.description) XCTAssertEqual(task.status, Task.Status.succeeded) createExpectation.fulfill() case .failure(let error): @@ -93,7 +93,7 @@ class IndexesTests: XCTestCase { createGenericIndex(client: self.client, uid: self.uid ) { result in switch result { case .success(let task): - XCTAssertEqual("indexCreation", task.type) + XCTAssertEqual("indexCreation", task.type.description) XCTAssertEqual(task.status, Task.Status.succeeded) createExpectation.fulfill() case .failure(let error): @@ -111,7 +111,7 @@ class IndexesTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("indexCreation", task.type) + XCTAssertEqual("indexCreation", task.type.description) XCTAssertEqual(task.status, Task.Status.failed) if let error = task.error { XCTAssertEqual(error.code, "index_already_exists") @@ -221,14 +221,10 @@ class IndexesTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("indexUpdate", task.type) + XCTAssertEqual("indexUpdate", task.type.description) XCTAssertEqual(task.status, Task.Status.succeeded) - if let details = task.details { - if let primaryKey = details.primaryKey { - XCTAssertEqual("random", primaryKey) - } else { - XCTFail("Primary key should not be nil") - } + if case .indexUpdate(let details) = task.details, let primaryKey = details.primaryKey { + XCTAssertEqual("random", primaryKey) } else { XCTFail("Primary key should exists in details field of task") } @@ -267,7 +263,7 @@ class IndexesTests: XCTestCase { deleteIndex(client: self.client, uid: self.uid) { result in switch result { case .success(let task): - XCTAssertEqual("indexDeletion", task.type) + XCTAssertEqual("indexDeletion", task.type.description) XCTAssertEqual(task.status, Task.Status.succeeded) deleteException.fulfill() case .failure(let error): diff --git a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift index 6fda8e84..64eeaa62 100644 --- a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift @@ -132,16 +132,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let filterableAttributes = details.filterableAttributes { - XCTAssertEqual(newFilterableAttributes, filterableAttributes) - } else { - XCTFail("filterableAttributes should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newFilterableAttributes, details.filterableAttributes) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -169,7 +165,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -218,16 +214,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let displayedAttributes = details.displayedAttributes { - XCTAssertEqual(newDisplayedAttributes, displayedAttributes) - } else { - XCTFail("displayedAttributes should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newDisplayedAttributes, details.displayedAttributes) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -255,7 +247,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -303,16 +295,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let distinctAttribute = details.distinctAttribute { - XCTAssertEqual(newDistinctAttribute, distinctAttribute) - } else { - XCTFail("distinctAttribute should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newDistinctAttribute, details.distinctAttribute) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -339,7 +327,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -393,16 +381,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let rankingRules = details.rankingRules { - XCTAssertEqual(newRankingRules, rankingRules) - } else { - XCTFail("rankingRules should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newRankingRules, details.rankingRules) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -430,7 +414,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -483,16 +467,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let searchableAttributes = details.searchableAttributes { - XCTAssertEqual(newSearchableAttributes, searchableAttributes) - } else { - XCTFail("searchableAttributes should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newSearchableAttributes, details.searchableAttributes) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -520,7 +500,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -668,16 +648,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let separatorTokens = details.separatorTokens { - XCTAssertEqual(newSeparatorTokens, separatorTokens) - } else { - XCTFail("separatorTokens should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newSeparatorTokens, details.separatorTokens) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -705,7 +681,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -759,16 +735,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let nonSeparatorTokens = details.nonSeparatorTokens { - XCTAssertEqual(newNonSeparatorTokens, nonSeparatorTokens) - } else { - XCTFail("nonSeparatorTokens should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newNonSeparatorTokens, details.nonSeparatorTokens) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -796,7 +768,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -850,16 +822,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let dictionary = details.dictionary { - XCTAssertEqual(newDictionary.sorted(), dictionary.sorted()) - } else { - XCTFail("dictionary should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newDictionary.sorted(), details.dictionary?.sorted()) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -887,7 +855,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -937,16 +905,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let pagination = details.pagination { - XCTAssertEqual(newPaginationSettings, pagination) - } else { - XCTFail("pagination should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newPaginationSettings, details.pagination) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -974,7 +938,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -1024,16 +988,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let stopWords = details.stopWords { - XCTAssertEqual(newStopWords, stopWords) - } else { - XCTFail("stopWords should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newStopWords, details.stopWords) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -1062,16 +1022,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let stopWords = details.stopWords { - XCTAssertEqual(emptyStopWords, stopWords) - } else { - XCTFail("stopWords should be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(emptyStopWords, details.stopWords) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -1100,16 +1056,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if details.stopWords == nil { - XCTAssertEqual(nilStopWords, details.stopWords) - } else { - XCTFail("stopWords should be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(nilStopWords, details.stopWords) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -1137,7 +1089,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -1192,16 +1144,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let synonyms = details.synonyms { - XCTAssertEqual(newSynonyms, synonyms) - } else { - XCTFail("synonyms should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newSynonyms, details.synonyms) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -1230,16 +1178,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let synonyms = details.synonyms { - XCTAssertEqual(newSynonyms, synonyms) - } else { - XCTFail("synonyms should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newSynonyms, details.synonyms) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -1269,16 +1213,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if details.synonyms == nil { - XCTAssertEqual(newSynonyms, details.synonyms) - } else { - XCTFail("synonyms should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newSynonyms, details.synonyms) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -1306,7 +1246,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): @@ -1379,14 +1319,14 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { + if case .settingsUpdate(let details) = task.details { XCTAssertEqual(expectedSettingResult.rankingRules, details.rankingRules) XCTAssertEqual(expectedSettingResult.searchableAttributes, details.searchableAttributes) XCTAssertEqual(expectedSettingResult.stopWords, details.stopWords) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -1412,12 +1352,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { + if case .settingsUpdate(let details) = task.details { XCTAssertEqual(expectedSettingResult.rankingRules, details.rankingRules) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } overrideSettingsExpectation.fulfill() case .failure(let error): @@ -1476,9 +1416,9 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { + if case .settingsUpdate(let details) = task.details { XCTAssertEqual(expectedSettingResult.rankingRules, details.rankingRules) XCTAssertEqual(expectedSettingResult.searchableAttributes, details.searchableAttributes) XCTAssertEqual(expectedSettingResult.displayedAttributes, details.displayedAttributes) @@ -1490,7 +1430,7 @@ class SettingsTests: XCTestCase { XCTAssertEqual(expectedSettingResult.dictionary, details.dictionary) XCTAssertEqual(expectedSettingResult.pagination.maxTotalHits, details.pagination?.maxTotalHits) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -1518,7 +1458,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): diff --git a/Tests/MeiliSearchIntegrationTests/TaskTests.swift b/Tests/MeiliSearchIntegrationTests/TaskTests.swift index e108a145..241e2e54 100644 --- a/Tests/MeiliSearchIntegrationTests/TaskTests.swift +++ b/Tests/MeiliSearchIntegrationTests/TaskTests.swift @@ -43,7 +43,7 @@ class TasksTests: XCTestCase { self.index.getTask(taskUid: task.taskUid) { result in switch result { case .success(let task): - XCTAssertEqual(task.type, "documentAdditionOrUpdate") + XCTAssertEqual(task.type.description, "documentAdditionOrUpdate") addDocExpectation.fulfill() case .failure(let error): dump(error) @@ -75,6 +75,9 @@ class TasksTests: XCTestCase { case .success(let tasks): // Only one because index has been deleted and recreated XCTAssertEqual(tasks.results.count, 1) + XCTAssertEqual(tasks.total, 1) + XCTAssertNotNil(tasks.results[0].startedAt) + XCTAssertNotNil(tasks.results[0].finishedAt) expectation.fulfill() case .failure(let error): dump(error) @@ -105,7 +108,7 @@ class TasksTests: XCTestCase { self.client.getTask(taskUid: task.taskUid) { result in switch result { case .success(let task): - XCTAssertEqual(task.type, "indexCreation") + XCTAssertEqual(task.type.description, "indexCreation") addDocExpectation.fulfill() case .failure(let error): dump(error) @@ -161,7 +164,7 @@ class TasksTests: XCTestCase { self.client.waitForTask(task: task, options: WaitOptions(timeOut: 1, interval: 0.5)) { result in switch result { case .success(let task): - XCTAssertEqual(task.type, "indexCreation") + XCTAssertEqual(task.type.description, "indexCreation") createIndexExpectation.fulfill() case .failure(let error): dump(error) @@ -186,7 +189,7 @@ class TasksTests: XCTestCase { self.client.waitForTask(taskUid: task.taskUid, options: WaitOptions(timeOut: 1, interval: 0.5)) { result in switch result { case .success(let task): - XCTAssertEqual(task.type, "indexCreation") + XCTAssertEqual(task.type.description, "indexCreation") createIndexExpectation.fulfill() case .failure(let error): dump(error) diff --git a/Tests/MeiliSearchUnitTests/IndexesTests.swift b/Tests/MeiliSearchUnitTests/IndexesTests.swift index c32c1f4b..493e7a51 100755 --- a/Tests/MeiliSearchUnitTests/IndexesTests.swift +++ b/Tests/MeiliSearchUnitTests/IndexesTests.swift @@ -240,7 +240,8 @@ class IndexesTests: XCTestCase { "results": [], "limit": 20, "from": 5, - "next": 98 + "next": 98, + "total": 6 } """ diff --git a/Tests/MeiliSearchUnitTests/TasksTests.swift b/Tests/MeiliSearchUnitTests/TasksTests.swift index d45df6f3..0b6fcc23 100644 --- a/Tests/MeiliSearchUnitTests/TasksTests.swift +++ b/Tests/MeiliSearchUnitTests/TasksTests.swift @@ -20,7 +20,8 @@ class TasksTests: XCTestCase { "results": [], "limit": 20, "from": 5, - "next": 98 + "next": 98, + "total": 6 } """ From 1c8756e70e93f7eeb2f95e3aa489241525362cc8 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 17:37:24 +0100 Subject: [PATCH 2/9] Add cancel and delete tasks public APIs --- Sources/MeiliSearch/Client.swift | 28 ++++++++ Sources/MeiliSearch/Keys.swift | 2 +- Sources/MeiliSearch/Model/Task/TaskInfo.swift | 2 +- .../QueryParameters/CancelTasksQuery.swift | 51 +++++++++++++++ .../QueryParameters/DeleteTasksQuery.swift | 64 +++++++++++++++++++ Sources/MeiliSearch/Request.swift | 16 ++++- Sources/MeiliSearch/Tasks.swift | 40 ++++++++++++ .../MeiliSearchUnitTests/DocumentsTests.swift | 22 +++---- .../MeiliSearchUnitTests/SettingsTests.swift | 40 ++++++------ 9 files changed, 229 insertions(+), 36 deletions(-) create mode 100644 Sources/MeiliSearch/QueryParameters/CancelTasksQuery.swift create mode 100644 Sources/MeiliSearch/QueryParameters/DeleteTasksQuery.swift diff --git a/Sources/MeiliSearch/Client.swift b/Sources/MeiliSearch/Client.swift index 7e2f32ba..ec5b6d94 100755 --- a/Sources/MeiliSearch/Client.swift +++ b/Sources/MeiliSearch/Client.swift @@ -200,6 +200,34 @@ public struct MeiliSearch { _ completion: @escaping (Result) -> Void) { self.tasks.getTasks(params: params, completion) } + + /** + Cancel any number of enqueued or processing tasks, stopping them from continuing to run + + - parameter filter: The filter in which chooses which tasks will be canceled + - 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 cancelTasks( + filter: CancelTasksQuery, + completion: @escaping (Result) -> Void) { + self.tasks.cancelTasks(filter, completion) + } + + /** + Delete a finished (succeeded, failed, or canceled) task + + - parameter filter: The filter in which chooses which tasks will be deleted + - 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 deleteTasks( + filter: DeleteTasksQuery, + completion: @escaping (Result) -> Void) { + self.tasks.deleteTasks(filter, completion) + } // MARK: Keys diff --git a/Sources/MeiliSearch/Keys.swift b/Sources/MeiliSearch/Keys.swift index aa32adec..8f0ad8da 100644 --- a/Sources/MeiliSearch/Keys.swift +++ b/Sources/MeiliSearch/Keys.swift @@ -23,7 +23,7 @@ struct Keys { return } do { - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let key: Key = try decoder.decode(Key.self, from: data) completion(.success(key)) } catch { diff --git a/Sources/MeiliSearch/Model/Task/TaskInfo.swift b/Sources/MeiliSearch/Model/Task/TaskInfo.swift index 56162346..70d6af1c 100644 --- a/Sources/MeiliSearch/Model/Task/TaskInfo.swift +++ b/Sources/MeiliSearch/Model/Task/TaskInfo.swift @@ -18,7 +18,7 @@ public struct TaskInfo: Codable, Equatable { public let type: TaskType /// Date when the task has been enqueued. - public let enqueuedAt: String + public let enqueuedAt: Date public enum CodingKeys: String, CodingKey { case taskUid, indexUid, status, type, enqueuedAt diff --git a/Sources/MeiliSearch/QueryParameters/CancelTasksQuery.swift b/Sources/MeiliSearch/QueryParameters/CancelTasksQuery.swift new file mode 100644 index 00000000..11ad5f5a --- /dev/null +++ b/Sources/MeiliSearch/QueryParameters/CancelTasksQuery.swift @@ -0,0 +1,51 @@ +import Foundation + +/** + `CancelTasksQuery` class represent the options used to filter a cancel tasks call. + */ +public class CancelTasksQuery: Queryable { + /// List of strings with all the types the response should contain. + public let types: [String] + /// List of strings with all the statuses the response should contain. + public let statuses: [String] + /// Filter tasks response by a particular list of index Uids strings + public let indexUids: [String] + /// Filter tasks based on a list of task's uids. + public let uids: [Int] + /// Filter tasks based on the date before the task were enqueued at. + public let beforeEnqueuedAt: Date? + /// Filter tasks based on the date after the task were enqueued at. + public let afterEnqueuedAt: Date? + /// Filter tasks based on the date before the task were started. + public let beforeStartedAt: Date? + /// Filter tasks based on the date after the task were started at. + public let afterStartedAt: Date? + + init( + types: [String]? = nil, statuses: [String]? = nil, + indexUids: [String]? = nil, uids: [Int]? = nil, + beforeEnqueuedAt: Date? = nil, afterEnqueuedAt: Date? = nil, + beforeStartedAt: Date? = nil, afterStartedAt: Date? = nil + ) { + self.types = types ?? [] + self.statuses = statuses ?? [] + self.indexUids = indexUids ?? [] + self.uids = uids ?? [] + self.beforeEnqueuedAt = beforeEnqueuedAt + self.afterEnqueuedAt = afterEnqueuedAt + self.beforeStartedAt = beforeStartedAt + self.afterStartedAt = afterStartedAt + } + + internal func buildQuery() -> [String: Codable?] { + [ + "uids": uids.isEmpty ? nil : uids.map(String.init).joined(separator: ","), + "types": types.isEmpty ? nil : types.joined(separator: ","), + "statuses": statuses.isEmpty ? nil : statuses.joined(separator: ","), + "indexUids": indexUids.isEmpty ? nil : indexUids.joined(separator: ","), + "beforeEnqueuedAt": Formatter.formatOptionalDate(date: beforeEnqueuedAt), + "afterEnqueuedAt": Formatter.formatOptionalDate(date: afterEnqueuedAt), + "beforeStartedAt": Formatter.formatOptionalDate(date: beforeStartedAt), + ] + } +} diff --git a/Sources/MeiliSearch/QueryParameters/DeleteTasksQuery.swift b/Sources/MeiliSearch/QueryParameters/DeleteTasksQuery.swift new file mode 100644 index 00000000..5b7ce8bd --- /dev/null +++ b/Sources/MeiliSearch/QueryParameters/DeleteTasksQuery.swift @@ -0,0 +1,64 @@ +import Foundation + +/** + `DeleteTasksQuery` class represent the options used to filter a delete tasks call. + */ +public class DeleteTasksQuery: Queryable { + /// List of strings with all the types the response should contain. + public let types: [String] + /// List of strings with all the statuses the response should contain. + public let statuses: [String] + /// Filter tasks response by a particular list of index Uids strings + public let indexUids: [String] + /// Filter tasks based on a list of task's uids. + public let uids: [Int] + /// Filter tasks based on a list of task's uids which were used to cancel other tasks. + public let canceledBy: [Int] + /// Filter tasks based on the date before the task were enqueued at. + public let beforeEnqueuedAt: Date? + /// Filter tasks based on the date after the task were enqueued at. + public let afterEnqueuedAt: Date? + /// Filter tasks based on the date before the task were started. + public let beforeStartedAt: Date? + /// Filter tasks based on the date after the task were started at. + public let afterStartedAt: Date? + /// Filter tasks based on the date before the task was finished. + public let beforeFinishedAt: Date? + /// Filter tasks based on the date after the task was finished. + public let afterFinishedAt: Date? + + init( + statuses: [String]? = nil, types: [String]? = nil, + indexUids: [String]? = nil, uids: [Int]? = nil, canceledBy: [Int]? = nil, + beforeEnqueuedAt: Date? = nil, afterEnqueuedAt: Date? = nil, + beforeStartedAt: Date? = nil, afterStartedAt: Date? = nil, + beforeFinishedAt: Date? = nil, afterFinishedAt: Date? = nil + ) { + self.statuses = statuses ?? [] + self.types = types ?? [] + self.indexUids = indexUids ?? [] + self.uids = uids ?? [] + self.canceledBy = canceledBy ?? [] + self.beforeEnqueuedAt = beforeEnqueuedAt + self.afterEnqueuedAt = afterEnqueuedAt + self.beforeStartedAt = beforeStartedAt + self.afterStartedAt = afterStartedAt + self.beforeFinishedAt = beforeFinishedAt + self.afterFinishedAt = afterFinishedAt + } + + internal func buildQuery() -> [String: Codable?] { + [ + "uids": uids.isEmpty ? nil : uids.map(String.init).joined(separator: ","), + "types": types.isEmpty ? nil : types.joined(separator: ","), + "statuses": statuses.isEmpty ? nil : statuses.joined(separator: ","), + "indexUids": indexUids.isEmpty ? nil : indexUids.joined(separator: ","), + "canceledBy": canceledBy.isEmpty ? nil : canceledBy.map(String.init).joined(separator: ","), + "beforeEnqueuedAt": Formatter.formatOptionalDate(date: beforeEnqueuedAt), + "afterEnqueuedAt": Formatter.formatOptionalDate(date: afterEnqueuedAt), + "beforeStartedAt": Formatter.formatOptionalDate(date: beforeStartedAt), + "beforeFinishedAt": Formatter.formatOptionalDate(date: beforeFinishedAt), + "afterFinishedAt": Formatter.formatOptionalDate(date: afterFinishedAt), + ] + } +} diff --git a/Sources/MeiliSearch/Request.swift b/Sources/MeiliSearch/Request.swift index bb73d4cc..9b363575 100755 --- a/Sources/MeiliSearch/Request.swift +++ b/Sources/MeiliSearch/Request.swift @@ -96,10 +96,15 @@ public final class Request { func post( api: String, + param: String? = nil, headers: [String: String] = [:], - _ data: Data, + _ data: Data?, _ completion: @escaping (Result) -> Void) { - guard let url = URL(string: config.url(api: api)) else { + var urlString: String = config.url(api: api) + if let param: String = param, !param.isEmpty { + urlString += param + } + guard let url = URL(string: urlString) else { completion(.failure(MeiliSearch.Error.invalidURL())) return } @@ -178,9 +183,14 @@ public final class Request { func delete( api: String, + param: String? = nil, headers: [String: String] = [:], _ completion: @escaping (Result) -> Void) { - guard let url = URL(string: config.url(api: api)) else { + var urlString: String = config.url(api: api) + if let param: String = param, !param.isEmpty { + urlString += param + } + guard let url = URL(string: urlString) else { completion(.failure(MeiliSearch.Error.invalidURL())) return } diff --git a/Sources/MeiliSearch/Tasks.swift b/Sources/MeiliSearch/Tasks.swift index a226fd7e..46f77594 100644 --- a/Sources/MeiliSearch/Tasks.swift +++ b/Sources/MeiliSearch/Tasks.swift @@ -136,4 +136,44 @@ struct Tasks { } } } + + // MARK: Cancel Tasks + + func cancelTasks( + _ params: CancelTasksQuery, + _ completion: @escaping (Result) -> Void) { + self.request.post(api: "/tasks/cancel", param: params.toQuery(), nil) { result in + switch result { + case .success(let data): + do { + let task: Result = try Constants.resultDecoder(data: data) + completion(task) + } catch { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } + + // MARK: Delete Tasks + + func deleteTasks( + _ params: DeleteTasksQuery, + _ completion: @escaping (Result) -> Void) { + self.request.delete(api: "/tasks", param: params.toQuery()) { result in + switch result { + case .success(let data): + do { + let task: Result = try Constants.resultDecoder(data: data) + completion(task) + } catch { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } } diff --git a/Tests/MeiliSearchUnitTests/DocumentsTests.swift b/Tests/MeiliSearchUnitTests/DocumentsTests.swift index ccbf6555..c01d7d58 100755 --- a/Tests/MeiliSearchUnitTests/DocumentsTests.swift +++ b/Tests/MeiliSearchUnitTests/DocumentsTests.swift @@ -40,7 +40,7 @@ class DocumentsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) @@ -75,7 +75,7 @@ class DocumentsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) @@ -118,7 +118,7 @@ class DocumentsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) @@ -155,7 +155,7 @@ class DocumentsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) @@ -198,7 +198,7 @@ class DocumentsTests: XCTestCase { // Prepare the mock server session.pushData(jsonString, code: 200) - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder decoder.dateDecodingStrategy = .formatted(Formatter.iso8601) let data = jsonString.data(using: .utf8)! let stubMovie: Movie = try! decoder.decode(Movie.self, from: data) @@ -232,7 +232,7 @@ class DocumentsTests: XCTestCase { session.pushData(jsonString, code: 200) do { - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder decoder.dateDecodingStrategy = .formatted(Formatter.iso8601) let data = jsonString.data(using: .utf8)! let stubMovie: Movie = try decoder.decode(Movie.self, from: data) @@ -316,7 +316,7 @@ class DocumentsTests: XCTestCase { // Prepare the mock server session.pushData(jsonString, code: 200) - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder decoder.dateDecodingStrategy = .formatted(Formatter.iso8601) let data = jsonString.data(using: .utf8)! let stubMovies: DocumentsResults = try! decoder.decode(DocumentsResults.self, from: data) @@ -342,7 +342,7 @@ class DocumentsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) @@ -370,7 +370,7 @@ class DocumentsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) @@ -396,7 +396,7 @@ class DocumentsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) @@ -424,7 +424,7 @@ class DocumentsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) diff --git a/Tests/MeiliSearchUnitTests/SettingsTests.swift b/Tests/MeiliSearchUnitTests/SettingsTests.swift index 642000d9..3a8cf1ee 100644 --- a/Tests/MeiliSearchUnitTests/SettingsTests.swift +++ b/Tests/MeiliSearchUnitTests/SettingsTests.swift @@ -86,7 +86,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let jsonData = Data(jsonString.utf8) let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: jsonData) @@ -115,7 +115,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let data: Data = Data(jsonString.utf8) let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: data) session.pushData(jsonString) @@ -176,7 +176,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -214,7 +214,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -271,7 +271,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -304,7 +304,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -370,7 +370,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -403,7 +403,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -458,7 +458,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -487,7 +487,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -544,7 +544,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -578,7 +578,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -634,7 +634,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -671,7 +671,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -725,7 +725,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -755,7 +755,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -810,7 +810,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -840,7 +840,7 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, from: Data(jsonString.utf8)) @@ -952,13 +952,13 @@ class SettingsTests: XCTestCase { private func buildStubSetting(from json: String) throws -> Setting { let data = Data(json.utf8) - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder return try decoder.decode(Setting.self, from: data) } private func buildStubSettingResult(from json: String) throws -> SettingResult { let data = Data(json.utf8) - let decoder = JSONDecoder() + let decoder = Constants.customJSONDecoder return try decoder.decode(SettingResult.self, from: data) } } From cc8bb7d7f81103b1f3ae79cea2af4e05a55fdf50 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 17:46:35 +0100 Subject: [PATCH 3/9] Safely Throw Errors in Tests No functionality change, just an improvement to tests to prevent the test runner from crashing (and less complaints from SwiftLint) --- Sources/MeiliSearch/Client.swift | 4 +- Sources/MeiliSearch/Model/Task/Task.swift | 8 +-- .../MeiliSearch/Model/Task/TaskDetails.swift | 22 ++++---- .../MeiliSearch/Model/Task/TaskStatus.swift | 2 +- Sources/MeiliSearch/Model/Task/TaskType.swift | 6 +-- Sources/MeiliSearch/Tasks.swift | 8 +-- .../DocumentsTests.swift | 37 +++++++------- .../DumpsTests.swift | 8 ++- .../IndexesTests.swift | 8 ++- .../KeysTests.swift | 7 ++- .../SearchTests.swift | 9 ++-- .../SettingsTests.swift | 8 ++- .../TaskTests.swift | 7 ++- Tests/MeiliSearchIntegrationTests/Utils.swift | 9 +++- .../MeiliSearchUnitTests/DocumentsTests.swift | 50 +++++++++---------- Tests/MeiliSearchUnitTests/DumpsTests.swift | 13 +++-- Tests/MeiliSearchUnitTests/IndexesTests.swift | 8 ++- Tests/MeiliSearchUnitTests/KeysTests.swift | 9 ++-- Tests/MeiliSearchUnitTests/SearchTests.swift | 21 ++++---- Tests/MeiliSearchUnitTests/StatsTests.swift | 17 +++---- Tests/MeiliSearchUnitTests/SystemTests.swift | 17 +++---- Tests/MeiliSearchUnitTests/TasksTests.swift | 8 ++- 22 files changed, 135 insertions(+), 151 deletions(-) diff --git a/Sources/MeiliSearch/Client.swift b/Sources/MeiliSearch/Client.swift index ec5b6d94..b8567c08 100755 --- a/Sources/MeiliSearch/Client.swift +++ b/Sources/MeiliSearch/Client.swift @@ -200,7 +200,7 @@ public struct MeiliSearch { _ completion: @escaping (Result) -> Void) { self.tasks.getTasks(params: params, completion) } - + /** Cancel any number of enqueued or processing tasks, stopping them from continuing to run @@ -214,7 +214,7 @@ public struct MeiliSearch { completion: @escaping (Result) -> Void) { self.tasks.cancelTasks(filter, completion) } - + /** Delete a finished (succeeded, failed, or canceled) task diff --git a/Sources/MeiliSearch/Model/Task/Task.swift b/Sources/MeiliSearch/Model/Task/Task.swift index 029ff880..b142986c 100644 --- a/Sources/MeiliSearch/Model/Task/Task.swift +++ b/Sources/MeiliSearch/Model/Task/Task.swift @@ -28,16 +28,16 @@ public struct Task: Decodable, Equatable { /// Date when the task has been started. public let startedAt: Date? - + /// Date when the task has been finished, regardless of status. public let finishedAt: Date? /// ID of the the `Task` which caused this to be canceled. public let canceledBy: Int? - + /// Error information in case of failed update. public let error: MeiliSearch.MSErrorResponse? - + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let type = try container.decode(TaskType.self, forKey: .type) @@ -58,7 +58,7 @@ public struct Task: Decodable, Equatable { let detailsDecoder = try? container.superDecoder(forKey: .details) self.details = try Details(decoder: detailsDecoder, type: type) } - + enum CodingKeys: String, CodingKey { case uid, indexUid, status, type, details, duration, enqueuedAt, startedAt, finishedAt, canceledBy, error } diff --git a/Sources/MeiliSearch/Model/Task/TaskDetails.swift b/Sources/MeiliSearch/Model/Task/TaskDetails.swift index 3aadbd60..571a16f7 100644 --- a/Sources/MeiliSearch/Model/Task/TaskDetails.swift +++ b/Sources/MeiliSearch/Model/Task/TaskDetails.swift @@ -12,12 +12,12 @@ public extension Task { case dumpCreation(TaskDumpCreationDetails) case taskCancelation(TaskCancellationDetails) case taskDeletion(TaskDeletionDetails) - + init?(decoder: Decoder?, type: TaskType) throws { guard let decoder = decoder else { return nil } - + switch type { case .indexCreation: self = try .indexCreation(.init(from: decoder)) @@ -48,7 +48,7 @@ public extension Task { } } } - + struct TaskDocumentAdditionOrUpdateDetails: Decodable, Equatable { /// Number of documents sent public let receivedDocuments: Int? @@ -56,7 +56,7 @@ public extension Task { /// Number of documents successfully indexed/updated in Meilisearch public let indexedDocuments: Int? } - + struct TaskDocumentDeletionDetails: Decodable, Equatable { /// Number of documents queued for deletion public let providedIds: Int? @@ -65,26 +65,26 @@ public extension Task { /// Number of documents deleted. nil while the task status is enqueued or processing public let deletedDocuments: Int? } - + struct TaskIndexCreationDetails: Decodable, Equatable { /// Value of the primaryKey field supplied during index creation. nil if it was not specified public let primaryKey: String? } - + struct TaskIndexUpdateDetails: Decodable, Equatable { /// Value of the primaryKey field supplied during index update. nil if it was not specified public let primaryKey: String? } - + struct TaskIndexDeletionDetails: Decodable, Equatable { /// Number of deleted documents. This should equal the total number of documents in the deleted index. nil while the task status is enqueued or processing public let deletedDocuments: Int? } - + struct TaskIndexSwapDetails: Decodable, Equatable { // To be populated under https://github.com/meilisearch/meilisearch-swift/issues/367 } - + struct TaskCancellationDetails: Decodable, Equatable { /// The number of matched tasks. If the API key used for the request doesn’t have access to an index, tasks relating to that index will not be included in matchedTasks public let matchedTasks: Int? @@ -93,7 +93,7 @@ public extension Task { /// The filter used in the cancel task request public let originalFilter: String? } - + struct TaskDeletionDetails: Decodable, Equatable { /// The number of matched tasks. If the API key used for the request doesn’t have access to an index, tasks relating to that index will not be included in matchedTasks public let matchedTasks: Int? @@ -102,7 +102,7 @@ public extension Task { /// The filter used in the delete task request public let originalFilter: String? } - + struct TaskDumpCreationDetails: Decodable, Equatable { /// The generated uid of the dump. This is also the name of the generated dump file. nil when the task status is enqueued, processing, canceled, or failed public let dumpUid: String? diff --git a/Sources/MeiliSearch/Model/Task/TaskStatus.swift b/Sources/MeiliSearch/Model/Task/TaskStatus.swift index 8c374e1e..b55f2dc3 100644 --- a/Sources/MeiliSearch/Model/Task/TaskStatus.swift +++ b/Sources/MeiliSearch/Model/Task/TaskStatus.swift @@ -20,7 +20,7 @@ public extension Task { public init(from decoder: Decoder) throws { let container: SingleValueDecodingContainer = try decoder.singleValueContainer() let rawStatus: String = try container.decode(String.self) - + switch rawStatus { case "enqueued": self = Status.enqueued diff --git a/Sources/MeiliSearch/Model/Task/TaskType.swift b/Sources/MeiliSearch/Model/Task/TaskType.swift index fdd99825..48c3f680 100644 --- a/Sources/MeiliSearch/Model/Task/TaskType.swift +++ b/Sources/MeiliSearch/Model/Task/TaskType.swift @@ -24,7 +24,7 @@ public enum TaskType: Codable, Equatable, LosslessStringConvertible { let value = try decoder.singleValueContainer().decode(String.self) self.init(rawValue: value) } - + internal init(rawValue: String) { switch rawValue { case "indexCreation": @@ -61,11 +61,11 @@ public enum TaskType: Codable, Equatable, LosslessStringConvertible { // MARK: LosslessStringConvertible // ensures that when TaskType is converted to String, the "unknown" context is ignored - + public init?(_ description: String) { self.init(rawValue: description) } - + public var description: String { switch self { case .indexCreation: diff --git a/Sources/MeiliSearch/Tasks.swift b/Sources/MeiliSearch/Tasks.swift index 46f77594..624884c9 100644 --- a/Sources/MeiliSearch/Tasks.swift +++ b/Sources/MeiliSearch/Tasks.swift @@ -136,9 +136,9 @@ struct Tasks { } } } - + // MARK: Cancel Tasks - + func cancelTasks( _ params: CancelTasksQuery, _ completion: @escaping (Result) -> Void) { @@ -156,9 +156,9 @@ struct Tasks { } } } - + // MARK: Delete Tasks - + func deleteTasks( _ params: DeleteTasksQuery, _ completion: @escaping (Result) -> Void) { diff --git a/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift b/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift index c3b07be4..1eed3de9 100755 --- a/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift @@ -6,7 +6,7 @@ import Foundation #endif // swiftlint:disable force_unwrapping -// swiftlint:disable force_try + private let movies: [Movie] = [ Movie(id: 123, title: "Pride and Prejudice", comment: "A great book"), Movie(id: 456, title: "Le Petit Prince", comment: "A french book"), @@ -24,10 +24,10 @@ class DocumentsTests: XCTestCase { private var session: URLSessionProtocol! private let uid: String = "books_test" - override func setUp() { - super.setUp() + override func setUpWithError() throws { + try super.setUpWithError() session = URLSession(configuration: .ephemeral) - client = try! MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) + client = try MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) index = self.client.index(self.uid) let expectation = XCTestExpectation(description: "Create index if it does not exist") self.client.createIndex(uid: uid) { result in @@ -176,9 +176,9 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: 3.0) } - func testAddAndGetOneDocumentWithIntIdentifierAndSucceed() { + func testAddAndGetOneDocumentWithIntIdentifierAndSucceed() throws { let movie = Movie(id: 10, title: "test", comment: "test movie") - let documents: Data = try! JSONEncoder().encode([movie]) + let documents: Data = try JSONEncoder().encode([movie]) let expectation = XCTestExpectation(description: "Add or replace Movies document") self.index.addDocuments( @@ -219,9 +219,9 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testAddAndGetOneDocument() { + func testAddAndGetOneDocument() throws { let movie = Movie(id: 10, title: "test", comment: "test movie") - let documents: Data = try! JSONEncoder().encode([movie]) + let documents: Data = try JSONEncoder().encode([movie]) let expectation = XCTestExpectation(description: "Add or replace Movies document") self.index.addDocuments( @@ -262,10 +262,10 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateDocument() { + func testUpdateDocument() throws { let identifier: Int = 1844 let movie: Movie = movies.first(where: { (movie: Movie) in movie.id == identifier })! - let documents: Data = try! JSONEncoder().encode([movie]) + let documents: Data = try JSONEncoder().encode([movie]) let expectation = XCTestExpectation(description: "Add or update Movies document") @@ -296,8 +296,8 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testDeleteOneDocument() { - let documents: Data = try! JSONEncoder().encode(movies) + func testDeleteOneDocument() throws { + let documents: Data = try JSONEncoder().encode(movies) let expectation = XCTestExpectation(description: "Delete one Movie") self.index.addDocuments( @@ -338,8 +338,8 @@ class DocumentsTests: XCTestCase { self.wait(for: [deleteExpectation], timeout: 3.0) } - func testDeleteAllDocuments() { - let documents: Data = try! JSONEncoder().encode(movies) + func testDeleteAllDocuments() throws { + let documents: Data = try JSONEncoder().encode(movies) let expectation = XCTestExpectation(description: "Add documents") self.index.addDocuments( @@ -386,8 +386,8 @@ class DocumentsTests: XCTestCase { self.wait(for: [deleteExpectation], timeout: TESTS_TIME_OUT) } - func testDeleteBatchDocuments() { - let documents: Data = try! JSONEncoder().encode(movies) + func testDeleteBatchDocuments() throws { + let documents: Data = try JSONEncoder().encode(movies) let expectation = XCTestExpectation(description: "Add documents") self.index.addDocuments( @@ -431,8 +431,8 @@ class DocumentsTests: XCTestCase { } @available(*, deprecated, message: "Testing deprecated methods - marked deprecated to avoid additional warnings below.") - func testDeprecatedDeleteBatchDocuments() { - let documents: Data = try! JSONEncoder().encode(movies) + func testDeprecatedDeleteBatchDocuments() throws { + let documents: Data = try JSONEncoder().encode(movies) let expectation = XCTestExpectation(description: "Add documents") self.index.addDocuments( @@ -476,4 +476,3 @@ class DocumentsTests: XCTestCase { } } // swiftlint:enable force_unwrapping -// swiftlint:enable force_try diff --git a/Tests/MeiliSearchIntegrationTests/DumpsTests.swift b/Tests/MeiliSearchIntegrationTests/DumpsTests.swift index bb5dbe60..fc5db922 100644 --- a/Tests/MeiliSearchIntegrationTests/DumpsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/DumpsTests.swift @@ -5,18 +5,17 @@ import Foundation import FoundationNetworking #endif -// swiftlint:disable force_try class DumpsTests: XCTestCase { private var client: MeiliSearch! private var session: URLSessionProtocol! // MARK: Setup - override func setUp() { - super.setUp() + override func setUpWithError() throws { + try super.setUpWithError() if client == nil { session = URLSession(configuration: .ephemeral) - client = try! MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) + client = try MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) } } @@ -38,4 +37,3 @@ class DumpsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } } -// swiftlint:enable force_try diff --git a/Tests/MeiliSearchIntegrationTests/IndexesTests.swift b/Tests/MeiliSearchIntegrationTests/IndexesTests.swift index e676cdb9..1a35568a 100644 --- a/Tests/MeiliSearchIntegrationTests/IndexesTests.swift +++ b/Tests/MeiliSearchIntegrationTests/IndexesTests.swift @@ -5,19 +5,18 @@ import Foundation import FoundationNetworking #endif -// swiftlint:disable force_try class IndexesTests: XCTestCase { private var client: MeiliSearch! private var session: URLSessionProtocol! private let uid: String = "books_test" private var index: Indexes! - override func setUp() { - super.setUp() + override func setUpWithError() throws { + try super.setUpWithError() if client == nil { session = URLSession(configuration: .ephemeral) - client = try! MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) + client = try MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) } index = self.client.index(self.uid) @@ -275,4 +274,3 @@ class IndexesTests: XCTestCase { self.wait(for: [deleteException], timeout: TESTS_TIME_OUT) } } -// swiftlint:enable force_try diff --git a/Tests/MeiliSearchIntegrationTests/KeysTests.swift b/Tests/MeiliSearchIntegrationTests/KeysTests.swift index f0342e54..c2e301b7 100644 --- a/Tests/MeiliSearchIntegrationTests/KeysTests.swift +++ b/Tests/MeiliSearchIntegrationTests/KeysTests.swift @@ -5,18 +5,17 @@ import Foundation import FoundationNetworking #endif -// swiftlint:disable force_try class KeysTests: XCTestCase { private var client: MeiliSearch! private var session: URLSessionProtocol! // MARK: Setup - override func setUp() { - super.setUp() + override func setUpWithError() throws { + try super.setUpWithError() session = URLSession(configuration: .ephemeral) - client = try! MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) + client = try MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) let semaphore = XCTestExpectation(description: "Setup: delete all keys") diff --git a/Tests/MeiliSearchIntegrationTests/SearchTests.swift b/Tests/MeiliSearchIntegrationTests/SearchTests.swift index be6dc51f..45af94eb 100644 --- a/Tests/MeiliSearchIntegrationTests/SearchTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SearchTests.swift @@ -18,7 +18,7 @@ private let books: [Book] = [ ] // swiftlint:disable force_unwrapping -// swiftlint:disable force_try + private let nestedBooks: [NestedBook] = [ NestedBook(id: 123, title: "Pride and Prejudice", info: InfoNested(comment: "A great book", reviewNb: 100), genres: ["Classic Regency nove"]), NestedBook(id: 456, title: "Le Petit Prince", info: InfoNested(comment: "A french book", reviewNb: 100), genres: ["Novel"]), @@ -35,11 +35,11 @@ class SearchTests: XCTestCase { // MARK: Setup - override func setUp() { - super.setUp() + override func setUpWithError() throws { + try super.setUpWithError() session = URLSession(configuration: .ephemeral) - client = try! MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) + client = try MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) index = self.client.index(self.uid) nestedIndex = self.client.index(self.nested_uid) @@ -1132,4 +1132,3 @@ class SearchTests: XCTestCase { } } // swiftlint:enable force_unwrapping -// swiftlint:enable force_try diff --git a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift index 64eeaa62..ad349884 100644 --- a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift @@ -5,7 +5,6 @@ import Foundation import FoundationNetworking #endif -// swiftlint:disable force_try class SettingsTests: XCTestCase { private var client: MeiliSearch! private var index: Indexes! @@ -48,11 +47,11 @@ class SettingsTests: XCTestCase { // MARK: Setup - override func setUp() { - super.setUp() + override func setUpWithError() throws { + try super.setUpWithError() session = URLSession(configuration: .ephemeral) - client = try! MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) + client = try MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) index = self.client.index(self.uid) let createExpectation = XCTestExpectation(description: "Create Movies index") @@ -1477,4 +1476,3 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } } -// swiftlint:enable force_try diff --git a/Tests/MeiliSearchIntegrationTests/TaskTests.swift b/Tests/MeiliSearchIntegrationTests/TaskTests.swift index 241e2e54..9281aefd 100644 --- a/Tests/MeiliSearchIntegrationTests/TaskTests.swift +++ b/Tests/MeiliSearchIntegrationTests/TaskTests.swift @@ -5,7 +5,6 @@ import Foundation import FoundationNetworking #endif -// swiftlint:disable force_try class TasksTests: XCTestCase { private var client: MeiliSearch! private var index: Indexes! @@ -14,10 +13,10 @@ class TasksTests: XCTestCase { // MARK: Setup - override func setUp() { - super.setUp() + override func setUpWithError() throws { + try super.setUpWithError() session = URLSession(configuration: .ephemeral) - client = try! MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) + client = try MeiliSearch(host: currentHost(), apiKey: "masterKey", session: session) index = self.client.index(self.uid) let createExpectation = XCTestExpectation(description: "Create Movies index") createGenericIndex(client: self.client, uid: self.uid) { result in diff --git a/Tests/MeiliSearchIntegrationTests/Utils.swift b/Tests/MeiliSearchIntegrationTests/Utils.swift index a81207a3..8e780f8a 100644 --- a/Tests/MeiliSearchIntegrationTests/Utils.swift +++ b/Tests/MeiliSearchIntegrationTests/Utils.swift @@ -5,7 +5,6 @@ import Foundation import XCTest @testable import MeiliSearch -// swiftlint:disable force_try private let movies: [Movie] = [ Movie(id: 123, title: "Pride and Prejudice", comment: "A great book"), Movie(id: 456, title: "Le Petit Prince", comment: "A french book"), @@ -99,7 +98,13 @@ public func addDocuments(client: MeiliSearch, uid: String, primaryKey: String?, public func addDocuments(client: MeiliSearch, uid: String, dataset: [T], primaryKey: String?, _ completion: @escaping(Result) -> Void) { let jsonEncoder = JSONEncoder() - let documents: Data = try! jsonEncoder.encode(dataset) + let documents: Data + do { + documents = try jsonEncoder.encode(dataset) + } catch { + completion(.failure(error)) + return + } let index = client.index(uid) client.deleteIndex(uid) { result in diff --git a/Tests/MeiliSearchUnitTests/DocumentsTests.swift b/Tests/MeiliSearchUnitTests/DocumentsTests.swift index c01d7d58..0526b5e0 100755 --- a/Tests/MeiliSearchUnitTests/DocumentsTests.swift +++ b/Tests/MeiliSearchUnitTests/DocumentsTests.swift @@ -6,7 +6,7 @@ import Foundation #endif // swiftlint:disable force_unwrapping -// swiftlint:disable force_try + // swiftlint:disable line_length private struct Movie: Codable, Equatable { let id: Int @@ -28,13 +28,13 @@ class DocumentsTests: XCTestCase { private var uid: String = "movies_test" private let session = MockURLSession() - override func setUp() { - super.setUp() - client = try! MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) + override func setUpWithError() throws { + try super.setUpWithError() + client = try MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) index = self.client.index(self.uid) } - func testAddDocuments() { + func testAddDocuments() throws { let jsonString = """ {"taskUid":0,"indexUid":"books_test","status":"enqueued","type":"documentAdditionOrUpdate","enqueuedAt":"2022-07-21T21:47:50.565717794Z"} """ @@ -42,7 +42,7 @@ class DocumentsTests: XCTestCase { // Prepare the mock server let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! - let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) // Start the test with the mocked server @@ -69,7 +69,7 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testAddDataDocuments() { + func testAddDataDocuments() throws { let jsonString = """ {"taskUid":0,"indexUid":"books_test","status":"enqueued","type":"documentAdditionOrUpdate","enqueuedAt":"2022-07-21T21:47:50.565717794Z"} """ @@ -77,7 +77,7 @@ class DocumentsTests: XCTestCase { // Prepare the mock server let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! - let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) let documentJsonString = """ @@ -112,7 +112,7 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateDataDocuments() { + func testUpdateDataDocuments() throws { let jsonString = """ {"taskUid":0,"indexUid":"books_test","status":"enqueued","type":"documentAdditionOrUpdate","enqueuedAt":"2022-07-21T21:47:50.565717794Z"} """ @@ -120,7 +120,7 @@ class DocumentsTests: XCTestCase { // Prepare the mock server let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! - let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) let documentJsonString = """ [{ @@ -149,7 +149,7 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateDocuments() { + func testUpdateDocuments() throws { let jsonString = """ {"taskUid":0,"indexUid":"books_test","status":"enqueued","type":"documentAdditionOrUpdate","enqueuedAt":"2022-07-21T21:47:50.565717794Z"} """ @@ -157,7 +157,7 @@ class DocumentsTests: XCTestCase { // Prepare the mock server let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! - let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) let movie = Movie( @@ -185,7 +185,7 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testGetDocument() { + func testGetDocument() throws { let jsonString = """ { "id": 25684, @@ -201,7 +201,7 @@ class DocumentsTests: XCTestCase { let decoder = Constants.customJSONDecoder decoder.dateDecodingStrategy = .formatted(Formatter.iso8601) let data = jsonString.data(using: .utf8)! - let stubMovie: Movie = try! decoder.decode(Movie.self, from: data) + let stubMovie: Movie = try decoder.decode(Movie.self, from: data) let identifier: String = "25684" // Start the test with the mocked server @@ -289,7 +289,7 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testGetDocuments() { + func testGetDocuments() throws { let jsonString = """ { "results": [ @@ -319,7 +319,7 @@ class DocumentsTests: XCTestCase { let decoder = Constants.customJSONDecoder decoder.dateDecodingStrategy = .formatted(Formatter.iso8601) let data = jsonString.data(using: .utf8)! - let stubMovies: DocumentsResults = try! decoder.decode(DocumentsResults.self, from: data) + let stubMovies: DocumentsResults = try decoder.decode(DocumentsResults.self, from: data) // Start the test with the mocked server let expectation = XCTestExpectation(description: "Get Movies documents") @@ -336,7 +336,7 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testDeleteDocument() { + func testDeleteDocument() throws { let jsonString = """ {"taskUid":0,"indexUid":"books_test","status":"enqueued","type":"documentAdditionOrUpdate","enqueuedAt":"2022-07-21T21:47:50.565717794Z"} """ @@ -344,7 +344,7 @@ class DocumentsTests: XCTestCase { // Prepare the mock server let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! - let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) let identifier: String = "25684" @@ -364,7 +364,7 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testDeleteAllDocuments() { + func testDeleteAllDocuments() throws { let jsonString = """ {"taskUid":0,"indexUid":"books_test","status":"enqueued","type":"documentAdditionOrUpdate","enqueuedAt":"2022-07-21T21:47:50.565717794Z"} """ @@ -372,7 +372,7 @@ class DocumentsTests: XCTestCase { // Prepare the mock server let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! - let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) // Start the test with the mocked server @@ -390,7 +390,7 @@ class DocumentsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testDeleteBatchDocuments() { + func testDeleteBatchDocuments() throws { let jsonString = """ {"taskUid":0,"indexUid":"books_test","status":"enqueued","type":"documentAdditionOrUpdate","enqueuedAt":"2022-07-21T21:47:50.565717794Z"} """ @@ -398,7 +398,7 @@ class DocumentsTests: XCTestCase { // Prepare the mock server let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! - let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) let documentsIdentifiers: [String] = ["23488", "153738", "437035", "363869"] @@ -418,7 +418,7 @@ class DocumentsTests: XCTestCase { } @available(*, deprecated, message: "Testing deprecated methods - marked deprecated to avoid additional warnings below.") - func testDeprecatedDeleteBatchDocuments() { + func testDeprecatedDeleteBatchDocuments() throws { let jsonString = """ {"taskUid":0,"indexUid":"books_test","status":"enqueued","type":"documentAdditionOrUpdate","enqueuedAt":"2022-07-21T21:47:50.565717794Z"} """ @@ -426,7 +426,7 @@ class DocumentsTests: XCTestCase { // Prepare the mock server let decoder = Constants.customJSONDecoder let jsonData = jsonString.data(using: .utf8)! - let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString, code: 202) let documentsIdentifiers: [Int] = [23488, 153738, 437035, 363869] @@ -447,5 +447,5 @@ class DocumentsTests: XCTestCase { } // swiftlint:enable force_unwrapping // swiftlint:enable force_cast -// swiftlint:enable force_try + // swiftlint:enable line_length diff --git a/Tests/MeiliSearchUnitTests/DumpsTests.swift b/Tests/MeiliSearchUnitTests/DumpsTests.swift index 56f6b694..f2d27660 100644 --- a/Tests/MeiliSearchUnitTests/DumpsTests.swift +++ b/Tests/MeiliSearchUnitTests/DumpsTests.swift @@ -2,17 +2,17 @@ import XCTest // swiftlint:disable force_unwrapping -// swiftlint:disable force_try + class DumpsTests: XCTestCase { private var client: MeiliSearch! private let session = MockURLSession() - override func setUp() { - super.setUp() - client = try! MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) + override func setUpWithError() throws { + try super.setUpWithError() + client = try MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) } - func testCreateDump() { + func testCreateDump() throws { // Prepare the mock server let json = """ @@ -21,7 +21,7 @@ class DumpsTests: XCTestCase { let data = json.data(using: .utf8)! - let stubDump: TaskInfo = try! Constants.customJSONDecoder.decode(TaskInfo.self, from: data) + let stubDump: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) session.pushData(json) @@ -43,4 +43,3 @@ class DumpsTests: XCTestCase { } } // swiftlint:enable force_unwrapping -// swiftlint:enable force_try diff --git a/Tests/MeiliSearchUnitTests/IndexesTests.swift b/Tests/MeiliSearchUnitTests/IndexesTests.swift index 493e7a51..908f296d 100755 --- a/Tests/MeiliSearchUnitTests/IndexesTests.swift +++ b/Tests/MeiliSearchUnitTests/IndexesTests.swift @@ -1,16 +1,15 @@ @testable import MeiliSearch import XCTest -// swiftlint:disable force_try class IndexesTests: XCTestCase { private var client: MeiliSearch! private var index: Indexes! private let uid: String = "movies_test" private let session = MockURLSession() - override func setUp() { - super.setUp() - client = try! MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) + override func setUpWithError() throws { + try super.setUpWithError() + client = try MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) index = client.index(self.uid) } @@ -321,4 +320,3 @@ class IndexesTests: XCTestCase { } } // swiftlint:enable force_unwrapping -// swiftlint:enable force_try diff --git a/Tests/MeiliSearchUnitTests/KeysTests.swift b/Tests/MeiliSearchUnitTests/KeysTests.swift index 9db15635..9eeda04b 100644 --- a/Tests/MeiliSearchUnitTests/KeysTests.swift +++ b/Tests/MeiliSearchUnitTests/KeysTests.swift @@ -1,17 +1,15 @@ @testable import MeiliSearch import XCTest -// swiftlint:disable force_try class KeysTests: XCTestCase { private var client: MeiliSearch! private var index: Indexes! private let uid: String = "movies_test" private let session = MockURLSession() - override func setUp() { - super.setUp() - - client = try! MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) + override func setUpWithError() throws { + try super.setUpWithError() + client = try MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) index = client.index(self.uid) } @@ -50,4 +48,3 @@ class KeysTests: XCTestCase { } } // swiftlint:enable force_unwrapping -// swiftlint:enable force_try diff --git a/Tests/MeiliSearchUnitTests/SearchTests.swift b/Tests/MeiliSearchUnitTests/SearchTests.swift index a1a42f71..cd9ef8e3 100644 --- a/Tests/MeiliSearchUnitTests/SearchTests.swift +++ b/Tests/MeiliSearchUnitTests/SearchTests.swift @@ -2,7 +2,7 @@ import XCTest // swiftlint:disable force_unwrapping -// swiftlint:disable force_try + private struct Movie: Codable, Equatable { let id: Int let title: String @@ -24,13 +24,13 @@ class SearchTests: XCTestCase { private var index: Indexes! private let session = MockURLSession() - override func setUp() { - super.setUp() - client = try! MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) + override func setUpWithError() throws { + try super.setUpWithError() + client = try MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) index = client.index("movies_test") } - func testSearchForBotmanMovie() { + func testSearchForBotmanMovie() throws { let jsonString = """ { "hits": [ @@ -59,7 +59,7 @@ class SearchTests: XCTestCase { // Prepare the mock server let data = jsonString.data(using: .utf8)! - let stubSearchResult: Searchable = try! Constants.customJSONDecoder.decode(Searchable.self, from: data) + let stubSearchResult: Searchable = try Constants.customJSONDecoder.decode(Searchable.self, from: data) session.pushData(jsonString) // Start the test with the mocked server @@ -121,7 +121,7 @@ class SearchTests: XCTestCase { XCTAssertEqual(stubSearchResult, searchResult) } - func testSearchForBotmanMovieFacets() { + func testSearchForBotmanMovieFacets() throws { let jsonString = """ { "hits": [ @@ -150,7 +150,7 @@ class SearchTests: XCTestCase { // Prepare the mock server let data = jsonString.data(using: .utf8)! - let stubSearchResult: Searchable = try! Constants.customJSONDecoder.decode(Searchable.self, from: data) + let stubSearchResult: Searchable = try Constants.customJSONDecoder.decode(Searchable.self, from: data) session.pushData(jsonString) // Start the test with the mocked server @@ -287,7 +287,7 @@ class SearchTests: XCTestCase { wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testSearchWithFinitePagination() { + func testSearchWithFinitePagination() throws { let jsonString = """ { "hits": [ @@ -317,7 +317,7 @@ class SearchTests: XCTestCase { // Prepare the mock server let data = jsonString.data(using: .utf8)! - let stubSearchResult: Searchable = try! Constants.customJSONDecoder.decode(Searchable.self, from: data) + let stubSearchResult: Searchable = try Constants.customJSONDecoder.decode(Searchable.self, from: data) session.pushData(jsonString) // Start the test with the mocked server @@ -348,4 +348,3 @@ class SearchTests: XCTestCase { } } // swiftlint:enable force_unwrapping -// swiftlint:enable force_try diff --git a/Tests/MeiliSearchUnitTests/StatsTests.swift b/Tests/MeiliSearchUnitTests/StatsTests.swift index d9e509c0..601284c5 100755 --- a/Tests/MeiliSearchUnitTests/StatsTests.swift +++ b/Tests/MeiliSearchUnitTests/StatsTests.swift @@ -2,20 +2,20 @@ import XCTest // swiftlint:disable force_unwrapping -// swiftlint:disable force_try + class StatsTests: XCTestCase { private var client: MeiliSearch! private var index: Indexes! private var uid: String = "movies_test" private let session = MockURLSession() - override func setUp() { - super.setUp() - client = try! MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) + override func setUpWithError() throws { + try super.setUpWithError() + client = try MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) index = client.index(self.uid) } - func testStats() { + func testStats() throws { let jsonString = """ { "numberOfDocuments": 19654, @@ -32,7 +32,7 @@ class StatsTests: XCTestCase { // Prepare the mock server let jsonData = jsonString.data(using: .utf8)! - let stubStats: Stat = try! Constants.customJSONDecoder.decode(Stat.self, from: jsonData) + let stubStats: Stat = try Constants.customJSONDecoder.decode(Stat.self, from: jsonData) session.pushData(jsonString) @@ -51,7 +51,7 @@ class StatsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testAllStats() { + func testAllStats() throws { let jsonString = """ { "databaseSize": 447819776, @@ -82,7 +82,7 @@ class StatsTests: XCTestCase { """ // Prepare the mock server let jsonData = jsonString.data(using: .utf8)! - let stubAllStats: AllStats = try! Constants.customJSONDecoder.decode(AllStats.self, from: jsonData) + let stubAllStats: AllStats = try Constants.customJSONDecoder.decode(AllStats.self, from: jsonData) session.pushData(jsonString) @@ -103,4 +103,3 @@ class StatsTests: XCTestCase { } } // swiftlint:enable force_unwrapping -// swiftlint:enable force_try diff --git a/Tests/MeiliSearchUnitTests/SystemTests.swift b/Tests/MeiliSearchUnitTests/SystemTests.swift index b0f44c53..0fa08ac4 100755 --- a/Tests/MeiliSearchUnitTests/SystemTests.swift +++ b/Tests/MeiliSearchUnitTests/SystemTests.swift @@ -2,18 +2,18 @@ import XCTest // swiftlint:disable force_unwrapping -// swiftlint:disable force_try + class SystemTests: XCTestCase { private var client: MeiliSearch! private let session = MockURLSession() - override func setUp() { - super.setUp() - client = try! MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) + override func setUpWithError() throws { + try super.setUpWithError() + client = try MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) } - func testHealthStatusAvailable() { + func testHealthStatusAvailable() throws { // Prepare the mock server let jsonString = """ @@ -24,7 +24,7 @@ class SystemTests: XCTestCase { let jsonData = jsonString.data(using: .utf8)! - let expectedHealthBody: Health = try! Constants.customJSONDecoder.decode(Health.self, from: jsonData) + let expectedHealthBody: Health = try Constants.customJSONDecoder.decode(Health.self, from: jsonData) session.pushData(jsonString, code: 200) @@ -93,7 +93,7 @@ class SystemTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testVersion() { + func testVersion() throws { // Prepare the mock server let jsonString = """ @@ -106,7 +106,7 @@ class SystemTests: XCTestCase { let jsonData = jsonString.data(using: .utf8)! - let stubVersion: Version = try! Constants.customJSONDecoder.decode(Version.self, from: jsonData) + let stubVersion: Version = try Constants.customJSONDecoder.decode(Version.self, from: jsonData) session.pushData(jsonString) @@ -128,4 +128,3 @@ class SystemTests: XCTestCase { } } // swiftlint:enable force_unwrapping -// swiftlint:enable force_try diff --git a/Tests/MeiliSearchUnitTests/TasksTests.swift b/Tests/MeiliSearchUnitTests/TasksTests.swift index 0b6fcc23..27b741a1 100644 --- a/Tests/MeiliSearchUnitTests/TasksTests.swift +++ b/Tests/MeiliSearchUnitTests/TasksTests.swift @@ -1,16 +1,15 @@ @testable import MeiliSearch import XCTest -// swiftlint:disable force_try class TasksTests: XCTestCase { private var client: MeiliSearch! private var index: Indexes! private let uid: String = "movies_test" private let session = MockURLSession() - override func setUp() { - super.setUp() - client = try! MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) + override func setUpWithError() throws { + try super.setUpWithError() + client = try MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) index = client.index(self.uid) } @@ -50,4 +49,3 @@ class TasksTests: XCTestCase { } } // swiftlint:enable force_unwrapping -// swiftlint:enable force_try From 2dd2fce04567fbc712b29ca95f1f606f56c0b19e Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 22:15:29 +0100 Subject: [PATCH 4/9] Add Integration Tests for New APIs --- Sources/MeiliSearch/Model/Task/Task.swift | 4 +- .../TaskTests.swift | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/Sources/MeiliSearch/Model/Task/Task.swift b/Sources/MeiliSearch/Model/Task/Task.swift index b142986c..8cadacb1 100644 --- a/Sources/MeiliSearch/Model/Task/Task.swift +++ b/Sources/MeiliSearch/Model/Task/Task.swift @@ -9,7 +9,7 @@ public struct Task: Decodable, Equatable { public let uid: Int /// Unique identifier of the targeted index - public let indexUid: String + public let indexUid: String? /// Returns if the task has been successful or not. public let status: Status @@ -43,7 +43,7 @@ public struct Task: Decodable, Equatable { let type = try container.decode(TaskType.self, forKey: .type) self.uid = try container.decode(Int.self, forKey: .uid) - self.indexUid = try container.decode(String.self, forKey: .indexUid) + self.indexUid = try container.decodeIfPresent(String.self, forKey: .indexUid) self.status = try container.decode(Status.self, forKey: .status) self.type = type self.duration = try container.decodeIfPresent(String.self, forKey: .duration) diff --git a/Tests/MeiliSearchIntegrationTests/TaskTests.swift b/Tests/MeiliSearchIntegrationTests/TaskTests.swift index 9281aefd..f1eafda3 100644 --- a/Tests/MeiliSearchIntegrationTests/TaskTests.swift +++ b/Tests/MeiliSearchIntegrationTests/TaskTests.swift @@ -154,6 +154,61 @@ class TasksTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } + func testCancelTask() { + let addDocExpectation = XCTestExpectation(description: "Add documents") + addDocuments(client: self.client, uid: self.uid, primaryKey: nil) { result in + switch result { + case .success(let task): + self.client.cancelTasks(filter: .init(uids: [task.uid])) { _ in + self.client.getTasks { result in + switch result { + case .success(let tasks): + XCTAssertGreaterThanOrEqual(tasks.results.count, 2) + XCTAssertEqual(tasks.results[0].type, .taskCancelation) + XCTAssertEqual(tasks.results[1].type, .documentAdditionOrUpdate) + case .failure(let error): + dump(error) + XCTFail("Failed to get tasks") + } + addDocExpectation.fulfill() + } + } + + case .failure: + XCTFail("Failed to add document") + addDocExpectation.fulfill() + } + } + self.wait(for: [addDocExpectation], timeout: TESTS_TIME_OUT) + } + + func testDeleteTask() { + let addDocExpectation = XCTestExpectation(description: "Add documents") + addDocuments(client: self.client, uid: self.uid, primaryKey: nil) { result in + switch result { + case .success(let task): + self.client.deleteTasks(filter: .init(uids: [task.uid])) { _ in + self.client.getTasks { result in + switch result { + case .success(let tasks): + XCTAssertEqual(tasks.results[0].type, .taskDeletion) + XCTAssertNotEqual(tasks.results[1].uid, task.uid) + case .failure(let error): + dump(error) + XCTFail("Failed to get tasks") + } + + addDocExpectation.fulfill() + } + } + case .failure: + XCTFail("Failed to add document") + addDocExpectation.fulfill() + } + } + self.wait(for: [addDocExpectation], timeout: TESTS_TIME_OUT) + } + func testWaitForTask() { let createIndexExpectation = XCTestExpectation(description: "Add documents") From 9dce221210a5303d4d36b7d7401b6ab0530b6d4c Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 22:21:54 +0100 Subject: [PATCH 5/9] Use Types instead of Strings for Task Queries --- .../MeiliSearch/QueryParameters/CancelTasksQuery.swift | 10 +++++----- .../MeiliSearch/QueryParameters/DeleteTasksQuery.swift | 10 +++++----- Sources/MeiliSearch/QueryParameters/TasksQuery.swift | 10 +++++----- Tests/MeiliSearchUnitTests/IndexesTests.swift | 2 +- .../QueryParameters/TasksQueryTests.swift | 2 +- Tests/MeiliSearchUnitTests/TasksTests.swift | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Sources/MeiliSearch/QueryParameters/CancelTasksQuery.swift b/Sources/MeiliSearch/QueryParameters/CancelTasksQuery.swift index 11ad5f5a..4cef573c 100644 --- a/Sources/MeiliSearch/QueryParameters/CancelTasksQuery.swift +++ b/Sources/MeiliSearch/QueryParameters/CancelTasksQuery.swift @@ -5,9 +5,9 @@ import Foundation */ public class CancelTasksQuery: Queryable { /// List of strings with all the types the response should contain. - public let types: [String] + public let types: [TaskType] /// List of strings with all the statuses the response should contain. - public let statuses: [String] + public let statuses: [Task.Status] /// Filter tasks response by a particular list of index Uids strings public let indexUids: [String] /// Filter tasks based on a list of task's uids. @@ -22,7 +22,7 @@ public class CancelTasksQuery: Queryable { public let afterStartedAt: Date? init( - types: [String]? = nil, statuses: [String]? = nil, + types: [TaskType]? = nil, statuses: [Task.Status]? = nil, indexUids: [String]? = nil, uids: [Int]? = nil, beforeEnqueuedAt: Date? = nil, afterEnqueuedAt: Date? = nil, beforeStartedAt: Date? = nil, afterStartedAt: Date? = nil @@ -40,8 +40,8 @@ public class CancelTasksQuery: Queryable { internal func buildQuery() -> [String: Codable?] { [ "uids": uids.isEmpty ? nil : uids.map(String.init).joined(separator: ","), - "types": types.isEmpty ? nil : types.joined(separator: ","), - "statuses": statuses.isEmpty ? nil : statuses.joined(separator: ","), + "types": types.isEmpty ? nil : types.map({ $0.description }).joined(separator: ","), + "statuses": statuses.isEmpty ? nil : statuses.map({ $0.rawValue }).joined(separator: ","), "indexUids": indexUids.isEmpty ? nil : indexUids.joined(separator: ","), "beforeEnqueuedAt": Formatter.formatOptionalDate(date: beforeEnqueuedAt), "afterEnqueuedAt": Formatter.formatOptionalDate(date: afterEnqueuedAt), diff --git a/Sources/MeiliSearch/QueryParameters/DeleteTasksQuery.swift b/Sources/MeiliSearch/QueryParameters/DeleteTasksQuery.swift index 5b7ce8bd..b720bc06 100644 --- a/Sources/MeiliSearch/QueryParameters/DeleteTasksQuery.swift +++ b/Sources/MeiliSearch/QueryParameters/DeleteTasksQuery.swift @@ -5,9 +5,9 @@ import Foundation */ public class DeleteTasksQuery: Queryable { /// List of strings with all the types the response should contain. - public let types: [String] + public let types: [TaskType] /// List of strings with all the statuses the response should contain. - public let statuses: [String] + public let statuses: [Task.Status] /// Filter tasks response by a particular list of index Uids strings public let indexUids: [String] /// Filter tasks based on a list of task's uids. @@ -28,7 +28,7 @@ public class DeleteTasksQuery: Queryable { public let afterFinishedAt: Date? init( - statuses: [String]? = nil, types: [String]? = nil, + statuses: [Task.Status]? = nil, types: [TaskType]? = nil, indexUids: [String]? = nil, uids: [Int]? = nil, canceledBy: [Int]? = nil, beforeEnqueuedAt: Date? = nil, afterEnqueuedAt: Date? = nil, beforeStartedAt: Date? = nil, afterStartedAt: Date? = nil, @@ -50,8 +50,8 @@ public class DeleteTasksQuery: Queryable { internal func buildQuery() -> [String: Codable?] { [ "uids": uids.isEmpty ? nil : uids.map(String.init).joined(separator: ","), - "types": types.isEmpty ? nil : types.joined(separator: ","), - "statuses": statuses.isEmpty ? nil : statuses.joined(separator: ","), + "types": types.isEmpty ? nil : types.map({ $0.description }).joined(separator: ","), + "statuses": statuses.isEmpty ? nil : statuses.map({ $0.rawValue }).joined(separator: ","), "indexUids": indexUids.isEmpty ? nil : indexUids.joined(separator: ","), "canceledBy": canceledBy.isEmpty ? nil : canceledBy.map(String.init).joined(separator: ","), "beforeEnqueuedAt": Formatter.formatOptionalDate(date: beforeEnqueuedAt), diff --git a/Sources/MeiliSearch/QueryParameters/TasksQuery.swift b/Sources/MeiliSearch/QueryParameters/TasksQuery.swift index 7e5980de..d8eda862 100644 --- a/Sources/MeiliSearch/QueryParameters/TasksQuery.swift +++ b/Sources/MeiliSearch/QueryParameters/TasksQuery.swift @@ -13,9 +13,9 @@ public class TasksQuery: Queryable { /// Integer value used to retrieve the next batch of tasks. private var next: Int? /// List of strings with all the types the response should contain. - private var types: [String] + private var types: [TaskType] /// List of strings with all the statuses the response should contain. - private var statuses: [String] + private var statuses: [Task.Status] /// Filter tasks response by a particular list of index Uids strings var indexUids: [String] /// Filter tasks based on a list of task's uids. @@ -37,7 +37,7 @@ public class TasksQuery: Queryable { init( limit: Int? = nil, from: Int? = nil, next: Int? = nil, - statuses: [String]? = nil, types: [String]? = nil, + statuses: [Task.Status]? = nil, types: [TaskType]? = nil, indexUids: [String]? = nil, uids: [Int]? = nil, canceledBy: [Int]? = nil, beforeEnqueuedAt: Date? = nil, afterEnqueuedAt: Date? = nil, afterFinishedAt: Date? = nil, beforeStartedAt: Date? = nil, @@ -65,8 +65,8 @@ public class TasksQuery: Queryable { "from": from, "next": next, "uids": uids.isEmpty ? nil : uids.map(String.init).joined(separator: ","), - "types": types.isEmpty ? nil : types.joined(separator: ","), - "statuses": statuses.isEmpty ? nil : statuses.joined(separator: ","), + "types": types.isEmpty ? nil : types.map({ $0.description }).joined(separator: ","), + "statuses": statuses.isEmpty ? nil : statuses.map({ $0.rawValue }).joined(separator: ","), "indexUids": indexUids.isEmpty ? nil : indexUids.joined(separator: ","), "canceledBy": canceledBy.isEmpty ? nil : canceledBy.map(String.init).joined(separator: ","), "beforeEnqueuedAt": Formatter.formatOptionalDate(date: beforeEnqueuedAt), diff --git a/Tests/MeiliSearchUnitTests/IndexesTests.swift b/Tests/MeiliSearchUnitTests/IndexesTests.swift index 908f296d..a2ea1133 100755 --- a/Tests/MeiliSearchUnitTests/IndexesTests.swift +++ b/Tests/MeiliSearchUnitTests/IndexesTests.swift @@ -250,7 +250,7 @@ class IndexesTests: XCTestCase { // Start the test with the mocked server let expectation = XCTestExpectation(description: "Get keys with parameters") - self.index.getTasks(params: TasksQuery(limit: 20, from: 5, next: 98, types: ["indexCreation"])) { result in + self.index.getTasks(params: TasksQuery(limit: 20, from: 5, next: 98, types: [.indexCreation])) { result in switch result { case .success: let requestQuery = self.session.nextDataTask.request?.url?.query diff --git a/Tests/MeiliSearchUnitTests/QueryParameters/TasksQueryTests.swift b/Tests/MeiliSearchUnitTests/QueryParameters/TasksQueryTests.swift index 78a5ca4c..b2fe61d1 100644 --- a/Tests/MeiliSearchUnitTests/QueryParameters/TasksQueryTests.swift +++ b/Tests/MeiliSearchUnitTests/QueryParameters/TasksQueryTests.swift @@ -8,7 +8,7 @@ class TasksQueryTests: XCTestCase { func testRenderedQuery() { let data: [[String: TasksQuery]] = [ ["?limit=2": TasksQuery(limit: 2)], - ["?from=99&limit=2&types=name,title": TasksQuery(limit: 2, from: 99, types: ["name", "title"])], + ["?from=99&limit=2&types=indexSwap,dumpCreation": TasksQuery(limit: 2, from: 99, types: [.indexSwap, .dumpCreation])], ["?limit=2": TasksQuery(limit: 2, next: nil)], ["?from=2": TasksQuery(from: 2)], ["?indexUids=my-index,123": TasksQuery(indexUids: ["my-index", "123"])], diff --git a/Tests/MeiliSearchUnitTests/TasksTests.swift b/Tests/MeiliSearchUnitTests/TasksTests.swift index 27b741a1..a27a5f69 100644 --- a/Tests/MeiliSearchUnitTests/TasksTests.swift +++ b/Tests/MeiliSearchUnitTests/TasksTests.swift @@ -30,7 +30,7 @@ class TasksTests: XCTestCase { // Start the test with the mocked server let expectation = XCTestExpectation(description: "Get keys with parameters") - self.client.getTasks(params: TasksQuery(limit: 20, from: 5, next: 98, types: ["indexCreation"])) { result in + self.client.getTasks(params: TasksQuery(limit: 20, from: 5, next: 98, types: [.indexCreation])) { result in switch result { case .success: let requestQuery = self.session.nextDataTask.request?.url?.query From aeb6f441fd6c97b565afb90e3f148bdb4c66b17d Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:51:34 +0100 Subject: [PATCH 6/9] Update Tests after Rebase --- .../SettingsTests.swift | 14 +++++--------- Tests/MeiliSearchUnitTests/SettingsTests.swift | 16 ++++++---------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift index ad349884..00d749cb 100644 --- a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift @@ -556,16 +556,12 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) - if let details = task.details { - if let typoTolerance = details.typoTolerance { - XCTAssertEqual(newTypoTolerance, typoTolerance) - } else { - XCTFail("typoTolerance should not be nil") - } + if case .settingsUpdate(let details) = task.details { + XCTAssertEqual(newTypoTolerance, details.typoTolerance) } else { - XCTFail("details should exists in details field of task") + XCTFail("settingsUpdate details should be set by task") } expectation.fulfill() case .failure(let error): @@ -593,7 +589,7 @@ class SettingsTests: XCTestCase { self.client.waitForTask(task: task) { result in switch result { case .success(let task): - XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual("settingsUpdate", task.type.description) XCTAssertEqual(Task.Status.succeeded, task.status) expectation.fulfill() case .failure(let error): diff --git a/Tests/MeiliSearchUnitTests/SettingsTests.swift b/Tests/MeiliSearchUnitTests/SettingsTests.swift index 3a8cf1ee..9edd3d44 100644 --- a/Tests/MeiliSearchUnitTests/SettingsTests.swift +++ b/Tests/MeiliSearchUnitTests/SettingsTests.swift @@ -892,16 +892,14 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateTypoTolerance() { + func testUpdateTypoTolerance() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server - let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( - TaskInfo.self, - from: jsonString.data(using: .utf8)!) + let decoder = Constants.customJSONDecoder + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: Data(jsonString.utf8)) session.pushData(jsonString) let typoTolerance: TypoTolerance = .init(enabled: false) @@ -922,16 +920,14 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testResetTypoTolerance() { + func testResetTypoTolerance() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server - let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( - TaskInfo.self, - from: jsonString.data(using: .utf8)!) + let decoder = Constants.customJSONDecoder + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: Data(jsonString.utf8)) session.pushData(jsonString) // Start the test with the mocked server From 41d7976ad37b6e37d46512e209568ac406f01f48 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:54:37 +0100 Subject: [PATCH 7/9] Improve Resilience of Delete Test Sometimes it would fail stating the task was still there, I believe this was because it hadn't finished deleting. Hopefully it'll now wait. Passed 50 iterations locally. --- .../TaskTests.swift | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Tests/MeiliSearchIntegrationTests/TaskTests.swift b/Tests/MeiliSearchIntegrationTests/TaskTests.swift index f1eafda3..75bc4716 100644 --- a/Tests/MeiliSearchIntegrationTests/TaskTests.swift +++ b/Tests/MeiliSearchIntegrationTests/TaskTests.swift @@ -187,17 +187,25 @@ class TasksTests: XCTestCase { addDocuments(client: self.client, uid: self.uid, primaryKey: nil) { result in switch result { case .success(let task): - self.client.deleteTasks(filter: .init(uids: [task.uid])) { _ in - self.client.getTasks { result in - switch result { - case .success(let tasks): - XCTAssertEqual(tasks.results[0].type, .taskDeletion) - XCTAssertNotEqual(tasks.results[1].uid, task.uid) - case .failure(let error): - dump(error) - XCTFail("Failed to get tasks") - } + self.client.deleteTasks(filter: .init(uids: [task.uid])) { result in + switch result { + case .success(let taskInfo): + self.client.waitForTask(task: taskInfo) { _ in + self.client.getTasks { result in + switch result { + case .success(let tasks): + XCTAssertEqual(tasks.results[0].type, .taskDeletion) + XCTAssertNotEqual(tasks.results[1].uid, task.uid) + case .failure(let error): + dump(error) + XCTFail("Failed to get tasks") + } + addDocExpectation.fulfill() + } + } + case .failure: + XCTFail("Failed to delete document") addDocExpectation.fulfill() } } From f10885c9593be8931f56ce0a34712ed09ee3be7b Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Thu, 28 Sep 2023 16:02:26 +0100 Subject: [PATCH 8/9] Update Code Samples --- .code-samples.meilisearch.yaml | 33 +++++++++++++++++++++------------ README.md | 2 +- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index ddff43bd..c607f114 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -133,7 +133,7 @@ async_guide_filter_by_ids_1: |- } } async_guide_filter_by_statuses_1: |- - client.getTasks(params: TasksQuery(statuses: ["failed", "canceled"])) { result in + client.getTasks(params: TasksQuery(statuses: [.failed, .canceled])) { result in switch result { case .success(let taskResult): print(taskResult) @@ -142,7 +142,7 @@ async_guide_filter_by_statuses_1: |- } } async_guide_filter_by_types_1: |- - client.getTasks(params: TasksQuery(types: ["dumpCreation", "indexSwap"])) { result in + client.getTasks(params: TasksQuery(types: [.dumpCreation, .indexSwap])) { result in switch result { case .success(let taskResult): print(taskResult) @@ -417,15 +417,6 @@ search_post_1: |- print(error) } } -get_task_by_index_1: |- - client.index("movies").getTask(taskUid: 1) { (result) in - switch result { - case .success(let task): - print(task) - case .failure(let error): - print(error) - } - } get_task_1: |- client.getTask(taskUid: 1) { (result) in switch result { @@ -436,7 +427,7 @@ get_task_1: |- } } get_all_tasks_1: |- - client.getTasks() { (result) in + client.getTasks { (result) in switch result { case .success(let tasks): print(tasks) @@ -444,6 +435,24 @@ get_all_tasks_1: |- print(error) } } +delete_tasks_1: |- + client.deleteTasks(filter: DeleteTasksQuery(uids: [1, 2])) { (result) in + switch result { + case .success(let taskInfo): + print(taskInfo) + case .failure(let error): + print(error) + } + } +cancel_tasks_1: |- + client.cancelTasks(filter: CancelTasksQuery(uids: [1, 2])) { (result) in + switch result { + case .success(let taskInfo): + print(taskInfo) + case .failure(let error): + print(error) + } + } get_one_key_1: |- client.getKey(keyOrUid: "6062abda-a5aa-4414-ac91-ecd7944c0f8d") { result in switch result { diff --git a/README.md b/README.md index c245ed87..c9b48ecc 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ To do a simple search using the client, you can create a Swift script like this: ) { result in switch result { case .success(let task): - print(task) // => Task(uid: 0, status: "enqueued", ...) + print(task) // => Task(uid: 0, status: Task.Status.enqueued, ...) case .failure(let error): print(error.localizedDescription) } From 4bcee6351cde3a3491e1bdd56e3b8da5c8ac4c96 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Thu, 28 Sep 2023 20:37:43 +0100 Subject: [PATCH 9/9] Apply fix for unit test Other solutions include: - fetching all documents and asserting total count - deleting all documents before adding again (expect 8) - create a new index just for this one test --- Tests/MeiliSearchIntegrationTests/DocumentsTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift b/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift index 1eed3de9..ff95cb18 100755 --- a/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift @@ -366,7 +366,9 @@ class DocumentsTests: XCTestCase { XCTAssertEqual(Task.Status.succeeded, task.status) XCTAssertEqual("documentDeletion", task.type.description) if case .documentDeletion(let details) = task.details { - XCTAssertEqual(8, details.deletedDocuments) + // It's possible for this to number to be greater than 8 (the number of documents we have inserted) due + // to other integration tests populating the shared index. + XCTAssertGreaterThanOrEqual(details.deletedDocuments ?? -1, 8) } else { XCTFail("documentDeletion details should be set by task") }