diff --git a/Samples/ComposableElements/ComposableElements/Untitled.swift b/Samples/ComposableElements/ComposableElements/Untitled.swift new file mode 100644 index 00000000..e69de29b diff --git a/Sources/Skyflow/collect/CollectAPICallback.swift b/Sources/Skyflow/collect/CollectAPICallback.swift index 7181418f..74cae186 100644 --- a/Sources/Skyflow/collect/CollectAPICallback.swift +++ b/Sources/Skyflow/collect/CollectAPICallback.swift @@ -21,28 +21,120 @@ internal class CollectAPICallback: Callback { self.options = options self.contextOptions = contextOptions } + internal func onSuccess(_ responseBody: Any) { - guard let url = URL(string: self.apiClient.vaultURL + self.apiClient.vaultID) else { - self.callback.onFailure(ErrorCodes.INVALID_URL().getErrorObject(contextOptions: self.contextOptions)) + let insertRecords = records["records"] as? [[String: Any]] + let hasInsert = insertRecords?.isEmpty == false + let updateRecords = records["update"] as? [String: Any] + let hasUpdate = updateRecords?.isEmpty == false + let group = DispatchGroup() + var insertResponse: [String: Any]? = nil + var updateResponses: [[String: Any]] = [] + var requestError: [Any]? = [] + var requestUpdateError: [Any]? = [] + + let callbackQueue = DispatchQueue.main + + if !hasInsert && !hasUpdate { + self.callback.onSuccess([:]) return } - - do { - let (request, session) = try self.getRequestSession(url: url) - - - let task = session.dataTask(with: request) { data, response, error in + + if hasInsert { + group.enter() + guard let url = URL(string: self.apiClient.vaultURL + self.apiClient.vaultID) else { + self.callback.onFailure(ErrorCodes.INVALID_URL().getErrorObject(contextOptions: self.contextOptions)) + return + } + do { + let (request, session) = try self.getRequestSession(url: url) + let task = session.dataTask(with: request) { data, response, error in + defer { + group.leave() + } + do { + let response = try self.processResponse(data: data, response: response, error: error) + if response["error"] != nil { + requestError?.append(response) + } else { + insertResponse = response + } + } catch { + requestError?.append(error) + } + } + task.resume() + } catch let error { + requestError?.append(error) + } + } + if hasUpdate, let updateArray = records["update"] as? [String: [String: Any]] { + for (_, updateRecord) in updateArray { + group.enter() + guard let table = updateRecord["table"] as? String, let skyflowID = updateRecord["skyflowID"] as? String else { + group.leave() + continue + } + let urlString = self.apiClient.vaultURL + self.apiClient.vaultID + "/" + table + "/" + skyflowID + guard let url = URL(string: urlString) else { + group.leave() + requestError?.append(ErrorCodes.INVALID_URL().getErrorObject(contextOptions: self.contextOptions)) + continue + } do { - let response = try self.processResponse(data: data, response: response, error: error) - self.callback.onSuccess(response) - } catch { - self.callback.onFailure(error) + var singleUpdateRecords: [String: Any] = [:] + singleUpdateRecords["fields"] = updateRecord["fields"] + singleUpdateRecords["table"] = table + singleUpdateRecords["skyflowID"] = skyflowID + let (request, session) = try self.getRequestSessionForUpdate(url: url, updateRecords: singleUpdateRecords) + let task = session.dataTask(with: request) { data, response, error in + defer { + group.leave() } + do { + let response = try self.processUpdateResponse(data: data, response: response, error: error, table: table) + if response["error"] != nil { + requestUpdateError?.append(response) + } else { + updateResponses.append(response) + } + } catch { + requestUpdateError?.append(error) + } + } + task.resume() + } catch let error { + group.leave() + requestUpdateError?.append(error) + continue } } - task.resume() - } catch let error { - self.callback.onFailure(error) - return + } + + group.notify(queue: callbackQueue) { + var mergedRecords: [Any] = [] + var mergedErrors: [Any] = [] + if let insert = insertResponse?["records"] as? [Any] { + mergedRecords.append(contentsOf: insert) + } + for updateResp in updateResponses { + if let update = updateResp["records"] as? [Any] { + mergedRecords.append(contentsOf: update) + } + } + if requestUpdateError != nil { + mergedErrors.append(contentsOf: requestUpdateError!) + } + if requestError != nil { + mergedErrors.append(contentsOf: requestError!) + } + if mergedRecords.isEmpty { + self.callback.onFailure(["errors": mergedErrors]) + } else if requestError?.isEmpty == true { + self.callback.onSuccess(["records": mergedRecords]) + } + if !mergedErrors.isEmpty && !mergedRecords.isEmpty { + self.callback.onFailure(["records": mergedRecords, "errors": mergedErrors]) + } } } @@ -86,31 +178,104 @@ internal class CollectAPICallback: Callback { } + // Helper for single update request + private func getRequestSessionForUpdate(url: URL, updateRecords: [String: Any]) throws -> (URLRequest, URLSession) { + var jsonString = "" + do { + let deviceDetails = FetchMetrices().getMetrices() + let jsonData = try JSONSerialization.data(withJSONObject: deviceDetails, options: []) + jsonString = String(data: jsonData, encoding: .utf8) ?? "" + } catch { + jsonString = "" + } + var request = URLRequest(url: url) + request.httpMethod = "PUT" + do { + let data = try JSONSerialization.data(withJSONObject: self.apiClient.constructUpdateRequestBody(records: updateRecords, options: options)) + request.httpBody = data + } + request.setValue(("Bearer " + self.apiClient.token), forHTTPHeaderField: "Authorization") + request.setValue(jsonString, forHTTPHeaderField: "sky-metadata") + return (request, URLSession(configuration: .default)) + } + + func processUpdateResponse(data: Data?, response: URLResponse?, error: Error?, table: String) throws -> [String: Any] { + if error != nil || response == nil { + return ["error": ["message": (error)?.localizedDescription ?? "Unknown error"]] + } + if let httpResponse = response as? HTTPURLResponse { + let range = 400...599 + if range ~= httpResponse.statusCode { + var description = "Update call failed with the following status code" + String(httpResponse.statusCode) + if let safeData = data { + do { + let desc = try JSONSerialization.jsonObject(with: safeData, options: .allowFragments) as! [String: Any] + let error = desc["error"] as? [String: Any] + if let error = error, let message = error["message"] as? String { + description = message + } + if let requestId = httpResponse.allHeaderFields["x-request-id"] { + description += " - request-id: \(requestId)" + } + } catch { + return ["error": ["message": String(data: safeData, encoding: .utf8) ?? "Unknown error", "code": httpResponse.statusCode]] + } + } + return ["error": ["message": description, "code": httpResponse.statusCode]] + } + } + guard let safeData = data else { + return ["records": []] + } + let jsonData = try JSONSerialization.jsonObject(with: safeData, options: .allowFragments) as! [String: Any] + var record: [String: Any] = [:] + var id = "" + if let skyflowId = jsonData["skyflow_id"] as? String{ + id = skyflowId + } else { + id = String(describing: jsonData["skyflow_id"] ?? "") + } + + if self.options.tokens { + let fieldsDict = jsonData["tokens"] as? [String: Any] + if fieldsDict != nil { + let fieldsData = try JSONSerialization.data(withJSONObject: fieldsDict!) + let fieldsObj = try JSONSerialization.jsonObject(with: fieldsData, options: .allowFragments) + var fieldsSkyflowId: [String: Any] = self.buildFieldsDict(dict: fieldsObj as? [String: Any] ?? [:]) + fieldsSkyflowId["skyflow_id"] = id + record["fields"] = fieldsSkyflowId + } + } else { + record["skyflow_id"] = id + } + record["table"] = table + return ["records": [record]] + } + func processResponse(data: Data?, response: URLResponse?, error: Error?) throws -> [String: Any] { if error != nil || response == nil { - throw error! + return ["error": ["message": (error)?.localizedDescription ?? "Unknown error"]] } if let httpResponse = response as? HTTPURLResponse { let range = 400...599 if range ~= httpResponse.statusCode { - var description = "Insert call failed with the following status code" + String(httpResponse.statusCode) - var errorObject: Error = ErrorCodes.APIError(code: httpResponse.statusCode, message: description).getErrorObject(contextOptions: self.contextOptions) - + var description = "Insert call failed with the following status code " + String(httpResponse.statusCode) if let safeData = data { do { - let desc = try JSONSerialization.jsonObject(with: safeData, options: .allowFragments) as! [String: Any] - let error = desc["error"] as! [String: Any] - description = error["message"] as! String + let errorResponse = try JSONSerialization.jsonObject(with: safeData, options: .allowFragments) as! [String: Any] + if let errorDetails = errorResponse["error"] as? [String: Any], + let message = errorDetails["message"] as? String { + description = message + } if let requestId = httpResponse.allHeaderFields["x-request-id"] { description += " - request-id: \(requestId)" } - errorObject = ErrorCodes.APIError(code: httpResponse.statusCode, message: description).getErrorObject(contextOptions: self.contextOptions) } catch { - errorObject = ErrorCodes.APIError(code: httpResponse.statusCode, message: String(data: safeData, encoding: .utf8)!).getErrorObject(contextOptions: self.contextOptions) + return ["error": ["message": String(data: safeData, encoding: .utf8) ?? "Unknown error", "code": httpResponse.statusCode]] } } - throw errorObject + return ["error": ["message": description, "code": httpResponse.statusCode]] } } diff --git a/Sources/Skyflow/collect/CollectRequestBody.swift b/Sources/Skyflow/collect/CollectRequestBody.swift index 43dcde6e..ceb0fb0c 100644 --- a/Sources/Skyflow/collect/CollectRequestBody.swift +++ b/Sources/Skyflow/collect/CollectRequestBody.swift @@ -52,81 +52,135 @@ internal class CollectRequestBody { } } - internal static func createRequestBody(elements: [TextField], additionalFields: [String: Any]? = nil, callback: Callback, contextOptions: ContextOptions) -> [String: Any]? { + internal static func createRequestBody( + elements: [TextField], + additionalFields: [String: Any]? = nil, + callback: Callback, + contextOptions: ContextOptions + ) -> [String: Any]? { var tableMap: [String: Int] = [:] var payload: [[String: Any]] = [] +// var updateSkyflowIdMap: [String: Int] = [:] + var updatePayload: [String: Any] = [:] self.callback = callback self.breakFlag = false self.tableSet = Set() var index: Int = 0 +// var updateIndex: Int = 0 var inputPayload: [[String: Any]] = [] - + if additionalFields != nil { inputPayload = additionalFields?["records"] as! [[String: Any]] - for entry in inputPayload { + for entry in inputPayload { let entryDict = entry let tableName = entryDict["table"] as! String let fields = entryDict["fields"] as! [String: Any] - if tableMap[tableName] != nil { - let inputEntry = payload[tableMap[tableName]!] - mergedDict = inputEntry["fields"] as! [String: Any] - self.mergeFields(tableName: tableName, prefix: "", dict: fields, contextOptions: contextOptions) - if self.breakFlag { - return nil + if entryDict["skyflowID"] != nil { + // add to update payload + let skyflowID = entryDict["skyflowID"] as! String + if updatePayload[skyflowID] != nil { + var temp = updatePayload[skyflowID] as! [String: Any] + // merge existing fields with new field + var existingFields = temp["fields"] as! [String: Any] + for (key, val) in fields { + existingFields[key] = val + } + temp["fields"] = existingFields + updatePayload[skyflowID] = temp + } else { + var temp: [String: Any] = [ + "table": tableName, + "fields": fields, + "skyflowID": skyflowID + ] + updatePayload[skyflowID] = temp } - payload[tableMap[tableName]!]["fields"] = mergedDict - mergedDict = [:] } else { - tableMap[tableName] = index - let temp: [String: Any] = [ - "table": tableName, - "fields": fields - ] - self.addFieldsToTableSet(tableName: tableName, prefix: "", fields: fields, contextOptions: contextOptions) - if self.breakFlag { - return nil + if tableMap[tableName] != nil { + let inputEntry = payload[tableMap[tableName]!] + mergedDict = inputEntry["fields"] as! [String: Any] + self.mergeFields(tableName: tableName, prefix: "", dict: fields, contextOptions: contextOptions) + if self.breakFlag { + return nil + } + payload[tableMap[tableName]!]["fields"] = mergedDict + mergedDict = [:] + } else { + tableMap[tableName] = index + let temp: [String: Any] = [ + "table": tableName, + "fields": fields + ] + self.addFieldsToTableSet(tableName: tableName, prefix: "", fields: fields, contextOptions: contextOptions) + if self.breakFlag { + return nil + } + payload.append(temp) + index += 1 } - payload.append(temp) - index += 1 } } } - + for element in elements { - if tableMap[(element.tableName)!] != nil { - var temp = payload[tableMap[(element.tableName)!]!] - temp[keyPath: "fields." + (element.columnName)!] = element.getValue() - let tableSetEntry = element.tableName! + "-" + element.columnName - if tableSet.contains(tableSetEntry) { - var hasElementValueMatchRule: Bool = false - for validation in element.userValidationRules.rules { - if validation is ElementValueMatchRule { - hasElementValueMatchRule = true - break; + let tableName = element.tableName! + let columnName = element.columnName! + let value = element.getValue() + let skyflowID = element.skyflowID // Assumes TextField has this property + + if let skyflowID = skyflowID, !skyflowID.isEmpty { + if updatePayload[skyflowID] != nil { + var temp = updatePayload[skyflowID] as! [String: Any] + // merge existing fields with new field + var existingFields = temp["fields"] as! [String: Any] + existingFields[columnName] = value + temp["fields"] = existingFields + updatePayload[skyflowID] = temp + } else { + var temp: [String: Any] = [ + "table": tableName, + "fields": [columnName: value], + "skyflowID": skyflowID + ] + updatePayload[skyflowID] = temp + } + } else { + // Only add to payload + if tableMap[(element.tableName)!] != nil { + var temp = payload[tableMap[(element.tableName)!]!] + temp[keyPath: "fields." + (element.columnName)!] = element.getValue() + let tableSetEntry = element.tableName! + "-" + element.columnName + if tableSet.contains(tableSetEntry) { + var hasElementValueMatchRule: Bool = false + for validation in element.userValidationRules.rules { + if validation is ElementValueMatchRule { + hasElementValueMatchRule = true + break; + } } + if(!hasElementValueMatchRule) + { + self.callback?.onFailure(ErrorCodes.DUPLICATE_ELEMENT_FOUND(values: [ element.columnName, element.tableName!]).getErrorObject(contextOptions: contextOptions)) + return nil + } + continue; } - if(!hasElementValueMatchRule) - { - self.callback?.onFailure(ErrorCodes.DUPLICATE_ELEMENT_FOUND(values: [ element.columnName, element.tableName!]).getErrorObject(contextOptions: contextOptions)) - return nil - } - continue; + self.tableSet.insert(tableSetEntry) + payload[tableMap[(element.tableName)!]!] = temp + } else { + tableMap[(element.tableName)!] = index + index += 1 + var temp: [String: Any] = [ + "table": element.tableName!, + "fields": [:] + ] + temp[keyPath: "fields." + element.columnName!] = element.getValue() + self.tableSet.insert(element.tableName! + "-" + element.columnName) + payload.append(temp) } - self.tableSet.insert(tableSetEntry) - payload[tableMap[(element.tableName)!]!] = temp - } else { - tableMap[(element.tableName)!] = index - index += 1 - var temp: [String: Any] = [ - "table": element.tableName!, - "fields": [:] - ] - temp[keyPath: "fields." + element.columnName!] = element.getValue() - self.tableSet.insert(element.tableName! + "-" + element.columnName) - payload.append(temp) } } - return ["records": payload] + return ["records": payload, "update": updatePayload] } internal static func getUniqueColumn(tableName: String, upsert: [[String: Any]]) -> String{ var uniqueColumn = ""; diff --git a/Sources/Skyflow/core/APIClient.swift b/Sources/Skyflow/core/APIClient.swift index 7bfb86c4..e0849c50 100644 --- a/Sources/Skyflow/core/APIClient.swift +++ b/Sources/Skyflow/core/APIClient.swift @@ -63,6 +63,14 @@ internal class APIClient { let collectApiCallback = CollectAPICallback(callback: callback, apiClient: self, records: records, options: options, contextOptions: contextOptions) self.getAccessToken(callback: collectApiCallback, contextOptions: contextOptions) } + internal func constructUpdateRequestBody(records: [String: Any], options: ICOptions) -> [String: Any] { + var postPayload: [String : Any] = [:] + var fields: [String: Any] = [:] + fields["fields"] = records["fields"] + postPayload["record"] = fields + postPayload["tokenization"] = options.tokens + return postPayload + } internal func constructBatchRequestBody(records: [String: Any], options: ICOptions) -> [String: Any] { var postPayload: [Any] = [] diff --git a/Sources/Skyflow/elements/SkyflowElement.swift b/Sources/Skyflow/elements/SkyflowElement.swift index b05ee36b..0ac78fef 100644 --- a/Sources/Skyflow/elements/SkyflowElement.swift +++ b/Sources/Skyflow/elements/SkyflowElement.swift @@ -16,6 +16,7 @@ public class SkyflowElement: UIView { internal var fieldType: ElementType! internal var columnName: String! internal var tableName: String? + internal var skyflowID: String? internal var horizontalConstraints = [NSLayoutConstraint]() internal var verticalConstraint = [NSLayoutConstraint]() internal var collectInput: CollectElementInput! @@ -63,6 +64,7 @@ public class SkyflowElement: UIView { columnName = collectInput.column fieldType = collectInput.type isRequired = options.required + skyflowID = collectInput.skyflowID } internal func getOutput() -> String? { diff --git a/Sources/Skyflow/elements/core/CollectElementInput.swift b/Sources/Skyflow/elements/core/CollectElementInput.swift index 22aad5b8..419d6b8d 100644 --- a/Sources/Skyflow/elements/core/CollectElementInput.swift +++ b/Sources/Skyflow/elements/core/CollectElementInput.swift @@ -17,10 +17,11 @@ public struct CollectElementInput { var placeholder: String var type: ElementType? var validations: ValidationSet + var skyflowID: String public init(table: String = "", column: String = "", inputStyles: Styles? = Styles(), labelStyles: Styles? = Styles(), errorTextStyles: Styles? = Styles(), iconStyles: Styles? = Styles(), label: String? = "", - placeholder: String? = "", validations: ValidationSet=ValidationSet()) { + placeholder: String? = "", validations: ValidationSet=ValidationSet(), skyflowID: String? = "") { self.table = table self.column = column self.inputStyles = inputStyles! @@ -30,11 +31,12 @@ public struct CollectElementInput { self.label = label! self.placeholder = placeholder! self.validations = validations + self.skyflowID = skyflowID! } public init(table: String = "", column: String = "", inputStyles: Styles? = Styles(), labelStyles: Styles? = Styles(), errorTextStyles: Styles? = Styles(), iconStyles: Styles? = Styles(), label: String? = "", - placeholder: String? = "", type: ElementType?, validations: ValidationSet=ValidationSet()) { + placeholder: String? = "", type: ElementType?, validations: ValidationSet=ValidationSet(), skyflowID: String? = "") { self.table = table self.column = column self.inputStyles = inputStyles! @@ -45,12 +47,13 @@ public struct CollectElementInput { self.placeholder = placeholder! self.type = type self.validations = validations + self.skyflowID = skyflowID! } @available(*, deprecated, message: "altText param is deprecated") public init(table: String = "", column: String = "", inputStyles: Styles? = Styles(), labelStyles: Styles? = Styles(), errorTextStyles: Styles? = Styles(), iconStyles: Styles? = Styles(), label: String? = "", - placeholder: String? = "", altText: String? = "", type: ElementType?, validations: ValidationSet=ValidationSet()) { + placeholder: String? = "", altText: String? = "", type: ElementType?, validations: ValidationSet=ValidationSet(), skyflowID: String? = "") { self.table = table self.column = column self.inputStyles = inputStyles! @@ -61,5 +64,6 @@ public struct CollectElementInput { self.placeholder = placeholder! self.type = type self.validations = validations + self.skyflowID = skyflowID! } } diff --git a/Tests/skyflow-iOS-errorTests/Skyflow_iOS_collectErrorTests.swift b/Tests/skyflow-iOS-errorTests/Skyflow_iOS_collectErrorTests.swift index e734ccc4..3df67f16 100644 --- a/Tests/skyflow-iOS-errorTests/Skyflow_iOS_collectErrorTests.swift +++ b/Tests/skyflow-iOS-errorTests/Skyflow_iOS_collectErrorTests.swift @@ -23,8 +23,8 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { override func setUp() { self.skyflow = Client( - Configuration(vaultID: ProcessInfo.processInfo.environment["VAULT_ID"]!, - vaultURL: ProcessInfo.processInfo.environment["VAULT_URL"]!, + Configuration(vaultID: "id", + vaultURL: "http://demo.com", tokenProvider: DemoTokenProvider(), options: Options(logLevel: .DEBUG)) ) self.firstFields = ["cvv": "123", @@ -340,5 +340,161 @@ final class Skyflow_iOS_collectErrorTests: XCTestCase { let responseData = callback.receivedResponse XCTAssertEqual(responseData, ErrorCodes.DUPLICATE_ADDITIONAL_FIELD_FOUND(value: "duplicate").description) } + func testCreateRequestInsertBodyDuplicateInAdditionalFields() { + let window = UIWindow() + let container = skyflow.container(type: ContainerType.COLLECT, options: nil) + let options = CollectElementOptions(required: false) + let collectInput1 = CollectElementInput(table: "persons", column: "card_number", placeholder: "card number", type: .CARD_NUMBER) + let cardNumber = container?.create(input: collectInput1, options: options) + cardNumber?.textField.secureText = "4111 1111 1111 1111" + window.addSubview(cardNumber!) + + let collectInput2 = CollectElementInput(table: "persons", column: "cvv", placeholder: "cvv", type: .CVV) + let cvv = container?.create(input: collectInput2, options: options) + cvv?.textField.secureText = "211" + window.addSubview(cvv!) + let expectation = XCTestExpectation(description: "Container insert call - All valid") + let callback = DemoAPICallback(expectation: expectation) + + let fields: [String: Any] = [ + "records": [[ + "table": "persons", + "fields": [ + "cvv": "123", + ]], + [ + "table": "persons", + "fields": [ + "duplicate": "123", + ]] + ]] + CollectRequestBody.createRequestBody(elements: [cardNumber!, cvv!], additionalFields: fields,callback: callback, contextOptions: ContextOptions(interface: .COLLECT_CONTAINER)) + wait(for: [expectation], timeout: 10.0) + + let responseData = callback.receivedResponse + XCTAssertEqual(responseData, ErrorCodes.DUPLICATE_ELEMENT_FOUND(values: ["cvv", "persons"]).description) + } + func testCreateRequestBodyWithOnlyInserts() { + let window = UIWindow() + let container = skyflow.container(type: ContainerType.COLLECT, options: nil) + let options = CollectElementOptions(required: false) + let collectInput1 = CollectElementInput(table: "persons", column: "card_number", placeholder: "card number", type: .CARD_NUMBER) + let cardNumber = container?.create(input: collectInput1, options: options) + cardNumber?.textField.secureText = "4111 1111 1111 1111" + window.addSubview(cardNumber!) + + let collectInput2 = CollectElementInput(table: "persons", column: "cvv", placeholder: "cvv", type: .CVV) + let cvv = container?.create(input: collectInput2, options: options) + cvv?.textField.secureText = "211" + window.addSubview(cvv!) + let callback = DemoAPICallback(expectation: XCTestExpectation(description: "Insert only")) + let requestBody = CollectRequestBody.createRequestBody(elements: [cardNumber!, cvv!], callback: callback, contextOptions: ContextOptions()) + XCTAssertNotNil(requestBody) + XCTAssertNotNil(requestBody?["records"]) + XCTAssertEqual(requestBody?["records"] as! [NSDictionary], [ + ["table": "persons", "fields": ["card_number": "", "cvv": ""]] + ]) + XCTAssertEqual(requestBody?["update"] as? [String: String], [:]) + } + + func testCreateRequestBodyWithOnlyUpdates() { + let additionalFields: [String: Any] = [ + "records": [ + ["table": "table1", "fields": ["column1": "value1"], "skyflowID": "id1"], + ["table": "table1", "fields": ["column2": "value2"], "skyflowID": "id1"] + ] + ] + let callback = DemoAPICallback(expectation: XCTestExpectation(description: "Update only")) + let requestBody = CollectRequestBody.createRequestBody(elements: [], additionalFields: additionalFields, callback: callback, contextOptions: ContextOptions()) + XCTAssertNotNil(requestBody) + XCTAssertNotNil(requestBody?["update"]) + XCTAssertEqual(requestBody?["update"] as? NSDictionary, ["id1": ["table": "table1", "fields": ["column1": "value1", "column2": "value2"], "skyflowID": "id1"]]) + XCTAssertEqual(requestBody?["records"] as? [[String: String]], []) + } + + func testCreateRequestBodyWithOnlyUpdates2() { + let window = UIWindow() + let container = skyflow.container(type: ContainerType.COLLECT, options: nil) + let options = CollectElementOptions(required: false) + let collectInput1 = CollectElementInput(table: "persons", column: "card_number", placeholder: "card number", type: .CARD_NUMBER) + let cardNumber = container?.create(input: collectInput1, options: options) + cardNumber?.textField.secureText = "4111 1111 1111 1111" + window.addSubview(cardNumber!) + + let collectInput2 = CollectElementInput(table: "persons", column: "cvv", placeholder: "cvv", type: .CVV) + let cvv = container?.create(input: collectInput2, options: options) + cvv?.textField.secureText = "211" + window.addSubview(cvv!) + + let callback = DemoAPICallback(expectation: XCTestExpectation(description: "Insert only")) + let requestBody = CollectRequestBody.createRequestBody(elements: [cardNumber!, cvv!], callback: callback, contextOptions: ContextOptions()) + XCTAssertNotNil(requestBody) + XCTAssertNotNil(requestBody?["records"]) + XCTAssertEqual(requestBody?["records"] as! [NSDictionary], [ + ["table": "persons", "fields": ["card_number": "", "cvv": ""]] + ]) + XCTAssertEqual(requestBody?["update"] as? [String: String], [:]) + } + + func testCreateRequestBodyWithOnlyUpdates3() { + let window = UIWindow() + let container = skyflow.container(type: ContainerType.COLLECT, options: nil) + let options = CollectElementOptions(required: false) + let collectInput1 = CollectElementInput(table: "persons", column: "card_number", placeholder: "card number", type: .CARD_NUMBER, skyflowID: "id1") + let cardNumber = container?.create(input: collectInput1, options: options) + cardNumber?.textField.secureText = "4111 1111 1111 1111" + window.addSubview(cardNumber!) + + let collectInput2 = CollectElementInput(table: "persons", column: "cvv", placeholder: "cvv", type: .CVV, skyflowID: "id2") + let cvv = container?.create(input: collectInput2, options: options) + cvv?.textField.secureText = "211" + window.addSubview(cvv!) + + let additionalFields: [String: Any] = [ + "records": [ + ["table": "table1", "fields": ["column1": "value1"], "skyflowID": "id1"], + ["table": "table1", "fields": ["column2": "value2"], "skyflowID": "id1"] + ] + ] + let callback = DemoAPICallback(expectation: XCTestExpectation(description: "Update only")) + let requestBody = CollectRequestBody.createRequestBody(elements: [cardNumber!, cvv!], additionalFields: additionalFields, callback: callback, contextOptions: ContextOptions()) + XCTAssertNotNil(requestBody) + XCTAssertNotNil(requestBody?["update"]) + XCTAssertEqual(requestBody?["update"] as? NSDictionary, ["id1": ["table": "table1", "fields": ["column1": "value1", "column2": "value2", "card_number": ""], "skyflowID": "id1"], "id2": ["table": "persons", "fields": ["cvv": ""], "skyflowID": "id2"]]) + XCTAssertEqual(requestBody?["records"] as? [[String: String]], []) + } + + func testCreateRequestBodyWithMixedInsertsAndUpdates() { + let window = UIWindow() + let container = skyflow.container(type: ContainerType.COLLECT, options: nil) + let options = CollectElementOptions(required: false) + let collectInput1 = CollectElementInput(table: "persons", column: "card_number", placeholder: "card number", type: .CARD_NUMBER) + let cardNumber = container?.create(input: collectInput1, options: options) + cardNumber?.textField.secureText = "4111 1111 1111 1111" + window.addSubview(cardNumber!) + + let collectInput2 = CollectElementInput(table: "persons", column: "cvv", placeholder: "cvv", type: .CVV, skyflowID: "id2") + let cvv = container?.create(input: collectInput2, options: options) + cvv?.textField.secureText = "211" + window.addSubview(cvv!) + + let additionalFields: [String: Any] = [ + "records": [ + ["table": "table1", "fields": ["column3": "value3"], "skyflowID": "id1"] + ] + ] + let callback = DemoAPICallback(expectation: XCTestExpectation(description: "Mixed inserts and updates")) + let requestBody = CollectRequestBody.createRequestBody(elements: [cardNumber!, cvv!], additionalFields: additionalFields, callback: callback, contextOptions: ContextOptions()) + XCTAssertNotNil(requestBody) + XCTAssertNotNil(requestBody?["records"]) + let records = requestBody?["records"] as? [[String: Any]] + XCTAssertEqual(records?.count, 1) + XCTAssertEqual(records?[0] as! NSDictionary as NSDictionary, ["table": "persons", "fields": ["card_number": ""]]) + XCTAssertNotNil(requestBody?["update"]) + let update = requestBody?["update"] as? [String: Any] + let updateRecords = update?["id1"] as! [String: Any] + XCTAssertEqual(updateRecords as NSDictionary, ["table": "table1", "fields": ["column3": "value3"], "skyflowID": "id1"]) + } + } diff --git a/Tests/skyflow-iOS-utilTests/skyflow_iOS_collectUtilTests.swift b/Tests/skyflow-iOS-utilTests/skyflow_iOS_collectUtilTests.swift index f6952c44..40bbe132 100644 --- a/Tests/skyflow-iOS-utilTests/skyflow_iOS_collectUtilTests.swift +++ b/Tests/skyflow-iOS-utilTests/skyflow_iOS_collectUtilTests.swift @@ -34,8 +34,10 @@ final class skyflow_iOS_collectUtilTests: XCTestCase { self.collectCallback.onSuccess("string") wait(for: [expectation], timeout: 20.0) - let result = callback.receivedResponse - XCTAssert(result.contains("unsupported URL")) + let result = callback.data["errors"] as! [[String: Any]] + let errorObject = result[0]["error"] as! [String: Any] + let msg = errorObject["message"] as! String + XCTAssert(msg.contains("unsupported URL")) } func testGetRequestSession() { @@ -117,9 +119,8 @@ final class skyflow_iOS_collectUtilTests: XCTestCase { let data = try JSONSerialization.data(withJSONObject: response, options: .fragmentsAllowed) let response = HTTPURLResponse(url: URL(string: "https://example.org")!, statusCode: 200, httpVersion: "1.1", headerFields: nil) - let processedData = try self.collectCallback.processResponse(data: data, response: response, error: NSError(domain: "", code: 400, userInfo: nil)) as! [String: [[String: String]]] - XCTFail("Should have thrown error") - + let processedData = try self.collectCallback.processResponse(data: data, response: response, error: NSError(domain: "", code: 400, userInfo: nil)) + as! [String: [String: String]] } catch { } } @@ -130,9 +131,14 @@ final class skyflow_iOS_collectUtilTests: XCTestCase { let data = try JSONSerialization.data(withJSONObject: response, options: .fragmentsAllowed) let response = HTTPURLResponse(url: URL(string: "https://example.org")!, statusCode: 500, httpVersion: "1.1", headerFields: ["x-request-id": "RID"]) - try self.collectCallback.processResponse(data: data, response: response, error: nil) - XCTFail("Not throwing on Api Error") - +// XCTFail("Not throwing on Api Error") + do { + var res = try self.collectCallback.processResponse(data: data, response: response, error: nil) + let message = (res["error"] as! [String: Any])["message"] as! String + XCTAssertEqual(message, "Internal Server Error - request-id: RID") + } catch { + XCTFail("sHOULD Not throwing on Api Error") + } } catch { XCTAssertEqual(error.localizedDescription, "Internal Server Error - request-id: RID") } @@ -186,5 +192,387 @@ final class skyflow_iOS_collectUtilTests: XCTestCase { XCTAssertTrue(deviceDetails["sdk_name_version"] as! String == "") } } + func testUpdateNewFlow() { + let expectation = XCTestExpectation(description: "Update new flow should succeed and merge response") + let callback = DemoAPICallback(expectation: expectation) + let updateRecord: [String: Any] = [ + "table": "table", + "skyflowID": "id1", + "fields": ["field": "newValue"] + ] + let collectCallback = CollectAPICallback( + callback: callback, + apiClient: APIClient(vaultID: "vault", vaultURL: "https://example.org/", tokenProvider: DemoTokenProvider()), + records: ["update": ["id1": updateRecord]], + options: ICOptions(tokens: true, additionalFields: nil), + contextOptions: ContextOptions() + ) + // Simulate a successful update response + let responseDict: [String: Any] = [ + "skyflow_id": "id1", + "tokens": ["field": "newValue"] + ] + let responseData = try! JSONSerialization.data(withJSONObject: responseDict, options: .fragmentsAllowed) + let urlResponse = HTTPURLResponse(url: URL(string: "https://example.org/table/id1")!, statusCode: 200, httpVersion: "1.1", headerFields: nil) + do { + let processed = try collectCallback.processUpdateResponse(data: responseData, response: urlResponse, error: nil, table: "table") + let records = processed["records"] as! [[String: Any]] + print("records after processing update response", records) + XCTAssertEqual(records.count, 1) + XCTAssertEqual(records[0]["table"] as? String, "table") + let fields = records[0]["fields"] as? [String: String] + XCTAssertEqual((fields?["skyflow_id"] ?? "") as String, "id1") + XCTAssertEqual(fields?["field"] as? String, "newValue") + } catch { + XCTFail("Update flow failed: \(error)") + } + } + func testPartialInsertAndUpdateScenario() { + let expectation = XCTestExpectation(description: "Partial insert and update scenario should succeed") + let callback = DemoAPICallback(expectation: expectation) + + let insertRecord: [String: Any] = [ + "table": "table", + "fields": ["field": "value"] + ] + + let updateRecord: [String: Any] = [ + "table": "table", + "skyflowID": "id1", + "fields": ["field": "newValue"] + ] + + let collectCallback = CollectAPICallback( + callback: callback, + apiClient: APIClient(vaultID: "vault", vaultURL: "https://example.org/", tokenProvider: DemoTokenProvider()), + records: ["records": [insertRecord], "update": ["id1": updateRecord]], + options: ICOptions(tokens: true, additionalFields: nil), + contextOptions: ContextOptions() + ) + + // Simulate a successful insert response + let insertResponseDict = ["responses": [["records": [["skyflow_id": "SID"]]], ["fields": ["field": "value"]]]] + let insertResponseData = try! JSONSerialization.data(withJSONObject: insertResponseDict, options: .fragmentsAllowed) + let insertUrlResponse = HTTPURLResponse(url: URL(string: "https://example.org/vault")!, statusCode: 200, httpVersion: "1.1", headerFields: nil) + + // Simulate a successful update response + let updateResponseDict: [String: Any] = [ + "skyflow_id": "id1", + "tokens": ["field": "newValue"] + ] + let updateResponseData = try! JSONSerialization.data(withJSONObject: updateResponseDict, options: .fragmentsAllowed) + let updateUrlResponse = HTTPURLResponse(url: URL(string: "https://example.org/table/id1")!, statusCode: 200, httpVersion: "1.1", headerFields: nil) + + do { + let processedInsert = try collectCallback.processResponse(data: insertResponseData, response: insertUrlResponse, error: nil) + let processedUpdate = try collectCallback.processUpdateResponse(data: updateResponseData, response: updateUrlResponse, error: nil, table: "table") + + let insertRecords = processedInsert["records"] as! [[String: Any]] + let updateRecords = processedUpdate["records"] as! [[String: Any]] + + XCTAssertEqual(insertRecords.count, 1) + let ifields = insertRecords[0]["fields"] as? [String: String] + XCTAssertEqual(ifields?["skyflow_id"] as? String, "SID") + + XCTAssertEqual(updateRecords.count, 1) + XCTAssertEqual(updateRecords[0]["table"] as? String, "table") + let fields = updateRecords[0]["fields"] as? [String: String] + XCTAssertEqual(fields?["skyflow_id"], "id1") + XCTAssertEqual(fields?["field"], "newValue") + } catch { + XCTFail("Partial insert and update scenario failed: \(error)") + } + } + func testOnlyInsertSuccess() { + let expectation = XCTestExpectation(description: "Only insert records should succeed") + let callback = DemoAPICallback(expectation: expectation) + + let insertRecord: [String: Any] = [ + "table": "table", + "fields": ["field": "value"] + ] + + let collectCallback = CollectAPICallback( + callback: callback, + apiClient: APIClient(vaultID: "vault", vaultURL: "https://example.org/", tokenProvider: DemoTokenProvider()), + records: ["records": [insertRecord]], + options: ICOptions(tokens: true, additionalFields: nil), + contextOptions: ContextOptions() + ) + + // Simulate a successful insert response + let insertResponseDict = ["responses": [["records": [["skyflow_id": "SID"]]], ["fields": ["field": "value"]]]] + + let insertResponseData = try! JSONSerialization.data(withJSONObject: insertResponseDict, options: .fragmentsAllowed) + let insertUrlResponse = HTTPURLResponse(url: URL(string: "https://example.org/vault")!, statusCode: 200, httpVersion: "1.1", headerFields: nil) + + do { + let processedInsert = try collectCallback.processResponse(data: insertResponseData, response: insertUrlResponse, error: nil) + let insertRecords = processedInsert["records"] as! [[String: Any]] + + XCTAssertEqual(insertRecords.count, 1) + let fields = insertRecords[0]["fields"] as? [String: String] + XCTAssertEqual(fields?["skyflow_id"] as? String, "SID") + } catch { + XCTFail("Insert scenario failed: \(error)") + } + } + + func testOnlyUpdateSuccess() { + let expectation = XCTestExpectation(description: "Only update records should succeed") + let callback = DemoAPICallback(expectation: expectation) + + let updateRecord: [String: Any] = [ + "table": "table", + "skyflowID": "id1", + "fields": ["field": "newValue"] + ] + + let collectCallback = CollectAPICallback( + callback: callback, + apiClient: APIClient(vaultID: "vault", vaultURL: "https://example.org/", tokenProvider: DemoTokenProvider()), + records: ["update": ["id1": updateRecord]], + options: ICOptions(tokens: true, additionalFields: nil), + contextOptions: ContextOptions() + ) + + // Simulate a successful update response + let updateResponseDict: [String: Any] = [ + "skyflow_id": "id1", + "tokens": ["field": "newValue"] + ] + let updateResponseData = try! JSONSerialization.data(withJSONObject: updateResponseDict, options: .fragmentsAllowed) + let updateUrlResponse = HTTPURLResponse(url: URL(string: "https://example.org/table/id1")!, statusCode: 200, httpVersion: "1.1", headerFields: nil) + + do { + let processedUpdate = try collectCallback.processUpdateResponse(data: updateResponseData, response: updateUrlResponse, error: nil, table: "table") + let updateRecords = processedUpdate["records"] as! [[String: Any]] + + XCTAssertEqual(updateRecords.count, 1) + XCTAssertEqual(updateRecords[0]["table"] as? String, "table") + let fields = updateRecords[0]["fields"] as? [String: String] + XCTAssertEqual(fields?["skyflow_id"], "id1") + XCTAssertEqual(fields?["field"], "newValue") + } catch { + XCTFail("Update scenario failed: \(error)") + } + } + + func testInsertAndUpdateSuccess() { + let expectation = XCTestExpectation(description: "Insert and update records should succeed") + let callback = DemoAPICallback(expectation: expectation) + + let insertRecord: [String: Any] = [ + "table": "table", + "fields": ["field": "value"] + ] + + let updateRecord: [String: Any] = [ + "table": "table", + "skyflowID": "id1", + "fields": ["field": "newValue"] + ] + + let collectCallback = CollectAPICallback( + callback: callback, + apiClient: APIClient(vaultID: "vault", vaultURL: "https://example.org/", tokenProvider: DemoTokenProvider()), + records: ["records": [insertRecord], "update": ["id1": updateRecord]], + options: ICOptions(tokens: true, additionalFields: nil), + contextOptions: ContextOptions() + ) + + // Simulate a successful insert response +// let insertResponseDict: [String: Any] = [ +// "records": [["skyflow_id": "inserted_id"]] +// ] + let insertResponseDict = ["responses": [["records": [["skyflow_id": "inserted_id"]]], ["fields": ["field": "value"]]]] + + let insertResponseData = try! JSONSerialization.data(withJSONObject: insertResponseDict, options: .fragmentsAllowed) + let insertUrlResponse = HTTPURLResponse(url: URL(string: "https://example.org/vault")!, statusCode: 200, httpVersion: "1.1", headerFields: nil) + // Simulate a successful update response + let updateResponseDict: [String: Any] = [ + "skyflow_id": "id1", + "tokens": ["field": "newValue"] + ] + let updateResponseData = try! JSONSerialization.data(withJSONObject: updateResponseDict, options: .fragmentsAllowed) + let updateUrlResponse = HTTPURLResponse(url: URL(string: "https://example.org/table/id1")!, statusCode: 200, httpVersion: "1.1", headerFields: nil) + + do { + let processedInsert = try collectCallback.processResponse(data: insertResponseData, response: insertUrlResponse, error: nil) + let processedUpdate = try collectCallback.processUpdateResponse(data: updateResponseData, response: updateUrlResponse, error: nil, table: "table") + + let insertRecords = processedInsert["records"] as! [[String: Any]] + let updateRecords = processedUpdate["records"] as! [[String: Any]] + + XCTAssertEqual(insertRecords.count, 1) + var ifields = insertRecords[0]["fields"] as? [String: String] + XCTAssertEqual(ifields?["skyflow_id"] as? String, "inserted_id") + + XCTAssertEqual(updateRecords.count, 1) + XCTAssertEqual(updateRecords[0]["table"] as? String, "table") + let fields = updateRecords[0]["fields"] as? [String: String] + XCTAssertEqual(fields?["skyflow_id"], "id1") + XCTAssertEqual(fields?["field"], "newValue") + } catch { + XCTFail("Insert and update scenario failed: \(error)") + } + } + + func testInsertSuccessAndUpdateFailure() { + let expectation = XCTestExpectation(description: "Insert success and update failure") + let callback = DemoAPICallback(expectation: expectation) + + let insertRecord: [String: Any] = [ + "table": "table", + "fields": ["field": "value"] + ] + + let updateRecord: [String: Any] = [ + "table": "table", + "skyflowID": "id1", + "fields": ["field": "newValue"] + ] + + let collectCallback = CollectAPICallback( + callback: callback, + apiClient: APIClient(vaultID: "vault", vaultURL: "https://example.org/", tokenProvider: DemoTokenProvider()), + records: ["records": [insertRecord], "update": ["id1": updateRecord]], + options: ICOptions(tokens: true, additionalFields: nil), + contextOptions: ContextOptions() + ) + + // Simulate a successful insert response + let insertResponseDict = ["responses": [["records": [["skyflow_id": "inserted_id"]]], ["fields": ["field": "value"]]]] + let insertResponseData = try! JSONSerialization.data(withJSONObject: insertResponseDict, options: .fragmentsAllowed) + let insertUrlResponse = HTTPURLResponse(url: URL(string: "https://example.org/vault")!, statusCode: 200, httpVersion: "1.1", headerFields: nil) + + // Simulate a failed update response + let updateError = NSError(domain: "", code: 400, userInfo: [NSLocalizedDescriptionKey: "Update failed"]) + + do { + let processedInsert = try collectCallback.processResponse(data: insertResponseData, response: insertUrlResponse, error: nil) + let insertRecords = processedInsert["records"] as! [[String: Any]] + + XCTAssertEqual(insertRecords.count, 1) + var fields = insertRecords[0]["fields"] as? [String: Any] + XCTAssertEqual(fields?["skyflow_id"] as? String, "inserted_id") + + let insertResponse = try collectCallback.processUpdateResponse(data: nil, response: nil, error: updateError, table: "table") as! [String: [String: String]] + XCTAssertEqual(insertResponse, ["error": ["message": "Update failed"]]) + } catch { + XCTFail("Insert success and update failure scenario failed: \(error)") + } + } + + func testInsertFailureAndUpdateSuccess() { + let expectation = XCTestExpectation(description: "Insert failure and update success") + let callback = DemoAPICallback(expectation: expectation) + + let insertRecord: [String: Any] = [ + "table": "table", + "fields": ["field": "value"] + ] + + let updateRecord: [String: Any] = [ + "table": "table", + "skyflowID": "id1", + "fields": ["field": "newValue"] + ] + + let collectCallback = CollectAPICallback( + callback: callback, + apiClient: APIClient(vaultID: "vault", vaultURL: "https://example.org/", tokenProvider: DemoTokenProvider()), + records: ["records": [insertRecord], "update": ["id1": updateRecord]], + options: ICOptions(tokens: true, additionalFields: nil), + contextOptions: ContextOptions() + ) + + // Simulate a failed insert response + let insertErrorResponseDict: [String: Any] = [ + "error": ["message": "Insert failed"] + ] + let insertErrorResponseData = try! JSONSerialization.data(withJSONObject: insertErrorResponseDict, options: .fragmentsAllowed) + let insertErrorUrlResponse = HTTPURLResponse(url: URL(string: "https://example.org/vault")!, statusCode: 404, httpVersion: "1.1", headerFields: nil) + + // Simulate a successful update response + let updateResponseDict: [String: Any] = [ + "skyflow_id": "id1", + "tokens": ["field": "newValue"] + ] + let updateResponseData = try! JSONSerialization.data(withJSONObject: updateResponseDict, options: .fragmentsAllowed) + let updateUrlResponse = HTTPURLResponse(url: URL(string: "https://example.org/table/id1")!, statusCode: 200, httpVersion: "1.1", headerFields: nil) + + do { + let insertProccessed = try collectCallback.processResponse(data: insertErrorResponseData, response: insertErrorUrlResponse, error: nil) as! [String: [String: Any]] + XCTAssertEqual(insertProccessed["error"]?["message"] as? String, "Insert failed") + XCTAssertEqual(insertProccessed["error"]?["code"] as? Int, 404) + + let processedUpdate = try collectCallback.processUpdateResponse(data: updateResponseData, response: updateUrlResponse, error: nil, table: "table") + let updateRecords = processedUpdate["records"] as! [[String: Any]] + + XCTAssertEqual(updateRecords.count, 1) + XCTAssertEqual(updateRecords[0]["table"] as? String, "table") + let fields = updateRecords[0]["fields"] as? [String: String] + XCTAssertEqual(fields?["skyflow_id"], "id1") + XCTAssertEqual(fields?["field"], "newValue") + } catch { + XCTFail("Insert failure and update success scenario failed: \(error)") + } + } + func testInsertFailureAndUpdateFailure() { + let expectation = XCTestExpectation(description: "Insert failure and update failure") + let callback = DemoAPICallback(expectation: expectation) + + let insertRecord: [String: Any] = [ + "table": "table", + "fields": ["field": "value"] + ] + + let updateRecord: [String: Any] = [ + "table": "table", + "skyflowID": "id1", + "fields": ["field": "newValue"] + ] + + let collectCallback = CollectAPICallback( + callback: callback, + apiClient: APIClient(vaultID: "vault", vaultURL: "https://example.org/", tokenProvider: DemoTokenProvider()), + records: ["records": [insertRecord], "update": ["id1": updateRecord]], + options: ICOptions(tokens: true, additionalFields: nil), + contextOptions: ContextOptions() + ) + + // Simulate a failed insert response + let insertErrorResponseDict: [String: Any] = [ + "error": ["message": "Insert failed"] + ] + let insertErrorResponseData = try! JSONSerialization.data(withJSONObject: insertErrorResponseDict, options: .fragmentsAllowed) + let insertErrorUrlResponse = HTTPURLResponse(url: URL(string: "https://example.org/vault")!, statusCode: 404, httpVersion: "1.1", headerFields: nil) + + // Simulate a successful update response + let updateResponseDict: [String: Any] = [ + "skyflow_id": "id1", + "tokens": ["field": "newValue"] + ] + let updateErrorResponseDict: [String: Any] = [ + "error": ["message": "Update failed"] + ] + let updateResponseData = try! JSONSerialization.data(withJSONObject: updateErrorResponseDict, options: .fragmentsAllowed) + let updateUrlResponse = HTTPURLResponse(url: URL(string: "https://example.org/table/id1")!, statusCode: 404, httpVersion: "1.1", headerFields: nil) + + do { + let insertProccessed = try collectCallback.processResponse(data: insertErrorResponseData, response: insertErrorUrlResponse, error: nil) as! [String: [String: Any]] + XCTAssertEqual(insertProccessed["error"]?["message"] as? String, "Insert failed") + XCTAssertEqual(insertProccessed["error"]?["code"] as? Int, 404) + + let processedUpdate = try collectCallback.processUpdateResponse(data: updateResponseData, response: updateUrlResponse, error: nil, table: "table") as! [String: [String: Any]] + + XCTAssertEqual(processedUpdate["error"]?["message"] as? String, "Update failed") + XCTAssertEqual(processedUpdate["error"]?["code"] as? Int, 404) + + } catch { + XCTFail("Insert failure and update success scenario failed: \(error)") + } + } } diff --git a/Tests/skyflow-iOS-utilTests/skyflow_iOS_utilTests.swift b/Tests/skyflow-iOS-utilTests/skyflow_iOS_utilTests.swift index e07e9fc0..6e872245 100644 --- a/Tests/skyflow-iOS-utilTests/skyflow_iOS_utilTests.swift +++ b/Tests/skyflow-iOS-utilTests/skyflow_iOS_utilTests.swift @@ -71,6 +71,20 @@ final class skyflow_iOS_utilTests: XCTestCase { XCTAssertTrue(tokenization) } + func testConstructUpdateRequestBody() { + let apiClient = APIClient(vaultID: "vault123", vaultURL: "https://example.com", tokenProvider: DemoTokenProvider()) + + let updateRecords: [String: Any] = [ + "update": [ + ["table": "users", "fields": ["name": "John Doe", "email": "john.doe@example.com"], "skyflowID": "id123"], + ["table": "users", "fields": ["name": "Jane Doe", "email": "jane.doe@example.com"], "skyflowID": "id124"] + ] + ] + let singleUpdateRecord = ["table": "users", "fields": ["name": "John Doe", "email": "john.doe@example.com"], "skyflowID": "id123"] as [String : Any] + let result = apiClient.constructUpdateRequestBody(records: singleUpdateRecord, options: ICOptions(tokens: false)) + let records = result["record"] as! [String: Any] + let tokenization = result["tokenization"] as! Bool + XCTAssertEqual(records as NSDictionary, ["fields": ["name": "John Doe", "email": "john.doe@example.com"]]) + XCTAssertFalse(tokenization) + } } - -