diff --git a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift index 639a1ea..19679a6 100644 --- a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift +++ b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift @@ -61,4 +61,9 @@ open class WMTUserOperation: WMTOperation, Codable { /// Proximity Check Data to be passed when OTP is handed to the app public var proximityCheck: WMTProximityCheck? + + /// Enum-like reason why the status has changed. + /// + /// Max 32 characters are expected. Possible values depend on the backend implementation and configuration. + public let statusReason: String? } diff --git a/WultraMobileTokenSDKTests/IntegrationProxy.swift b/WultraMobileTokenSDKTests/IntegrationProxy.swift index 6869fce..e8bffc5 100644 --- a/WultraMobileTokenSDKTests/IntegrationProxy.swift +++ b/WultraMobileTokenSDKTests/IntegrationProxy.swift @@ -89,6 +89,12 @@ class IntegrationProxy { } } + func cancelOperation(operationId: String, reason: String, completion: @escaping (CancelObject?) -> Void) { + DispatchQueue.global().async { + completion(self.makeRequest(url: URL(string: "\(self.config.cloudServerUrl)/v2/operations/\(operationId)?statusReason=\(reason)")!, body: "", httpMethod: "DELETE")) + } + } + func createNonPersonalisedPACOperation(_ factors: Factors = .F_2FA, completion: @escaping (NonPersonalisedTOTPOperationObject?) -> Void) { DispatchQueue.global().async { let opBody: String @@ -261,6 +267,10 @@ private struct CommitObject: Codable { let status: String } +struct CancelObject: Codable { + let status: String +} + struct OperationObject: Codable { let operationId: String let userId: String diff --git a/WultraMobileTokenSDKTests/IntegrationTests.swift b/WultraMobileTokenSDKTests/IntegrationTests.swift index be55130..b3af71b 100644 --- a/WultraMobileTokenSDKTests/IntegrationTests.swift +++ b/WultraMobileTokenSDKTests/IntegrationTests.swift @@ -169,6 +169,43 @@ class IntegrationTests: XCTestCase { waitForExpectations(timeout: 5, handler: nil) } + func testOperationCanceledWithReason() { + let exp = expectation(description: "Cancel operation with reason") + let cancelReason = "PREARRANGED_REASON" + + proxy.createOperation { op in + guard let op else { + XCTFail("Failed to create operation") + exp.fulfill() + return + } + self.proxy.cancelOperation(operationId: op.operationId, reason: cancelReason) { cancelOp in + if cancelOp != nil { + let auth = PowerAuthAuthentication.possessionWithPassword(password: self.pin) + DispatchQueue.main.async { + _ = self.ops.getHistory(authentication: auth) { result in + switch result { + case .success(let ops): + if let opFromList = ops.first(where: { $0.operation.id == op.operationId }) { + XCTAssertEqual(opFromList.operation.statusReason, cancelReason, "statusReason and cancelReason must be the same") + } else { + XCTFail("Created operation was not in the history") + } + case .failure: + XCTFail("History was not retrieved") + } + exp.fulfill() + } + } + } else { + XCTFail("Failed to cancel operation") + exp.fulfill() + } + } + } + waitForExpectations(timeout: 20, handler: nil) + } + /// Operation IDs should be equal func testClaim() { let exp = expectation(description: "Operation Claim should return UserOperation with operation.id") diff --git a/docs/Using-Operations-Service.md b/docs/Using-Operations-Service.md index ce0caf0..85fddb4 100644 --- a/docs/Using-Operations-Service.md +++ b/docs/Using-Operations-Service.md @@ -519,6 +519,11 @@ class WMTUserOperation: WMTOperation { /// Proximity Check Data to be passed when OTP is handed to the app public var proximityCheck: WMTProximityCheck? + + /// Enum-like reason why the status has changed. + /// + /// Max 32 characters are expected. Possible values depend on the backend implementation and configuration. + public let statusReason: String? } ```