From 872aeda9b8605597209f30d361e03c33af4074e7 Mon Sep 17 00:00:00 2001 From: Mederic <32560642+mederic-p@users.noreply.github.com> Date: Mon, 9 Apr 2018 11:09:57 +0700 Subject: [PATCH] T196 Handle more events on transaction request listener (#27) * Add transaction request approved and rejected events * Update version number * Update README --- OmiseGO.podspec | 4 +- .../Helpers/DummySocketEventDelegate.swift | 2 +- .../TransactionRequestLiveTests.swift | 141 ++++++++++++------ Podfile | 2 +- Podfile.lock | 8 +- README.md | 22 ++- Source/Info.plist | 2 +- Source/Websocket/SocketDelegate.swift | 2 + Source/Websocket/SocketDispatcher.swift | 12 +- 9 files changed, 133 insertions(+), 62 deletions(-) diff --git a/OmiseGO.podspec b/OmiseGO.podspec index 27131af..7a08f7a 100644 --- a/OmiseGO.podspec +++ b/OmiseGO.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'OmiseGO' - s.version = '0.9.10' + s.version = '0.9.11' s.license = 'Apache' s.summary = 'The OmiseGO iOS SDK allows developers to easily interact with a node of the OmiseGO eWallet.' s.homepage = 'https://github.com/omisego/ios-sdk' @@ -11,7 +11,7 @@ Pod::Spec.new do |s| s.platform = :ios, '10.0' s.swift_version = '4.0' - s.dependency 'Starscream', '~> 3.0.2' + s.dependency 'Starscream', '~> 3.0' s.source_files = 'Source/**/*.swift' end diff --git a/OmiseGOTests/Helpers/DummySocketEventDelegate.swift b/OmiseGOTests/Helpers/DummySocketEventDelegate.swift index dd58b46..2668e86 100644 --- a/OmiseGOTests/Helpers/DummySocketEventDelegate.swift +++ b/OmiseGOTests/Helpers/DummySocketEventDelegate.swift @@ -11,7 +11,7 @@ import XCTest class DummySocketEventDelegate { - let eventExpectation: XCTestExpectation? + var eventExpectation: XCTestExpectation? let joinExpectation: XCTestExpectation? init(eventExpectation: XCTestExpectation? = nil, joinExpectation: XCTestExpectation? = nil) { diff --git a/OmiseGOTests/LiveTests/TransactionRequestLiveTests.swift b/OmiseGOTests/LiveTests/TransactionRequestLiveTests.swift index e5f8709..7f8db0a 100644 --- a/OmiseGOTests/LiveTests/TransactionRequestLiveTests.swift +++ b/OmiseGOTests/LiveTests/TransactionRequestLiveTests.swift @@ -29,47 +29,86 @@ class TransactionRequestLiveTests: LiveTestCase { // 6) Confirm the consumption request // 7) Assert a same_address error as we did all actions with the same balance - func testGenerateTransactionRequestThenConsume() { + func testGenerateTransactionRequestThenConsumeWithARequiredConfirmation() { let creationCorrelationId = UUID().uuidString // FLOW: // 1) Generate the transaction request - guard let transactionRequest = self.generateTransactionRequest(creationCorrelationId: creationCorrelationId) else { return } - // 2) Test the get method - self.getTransactionRequest(transactionRequestId: transactionRequest.id, creationCorrelationId: creationCorrelationId) - // 3) Subscribe to events on this transaction request (listen for consumptions) - let consumptionDelegate = self.startToListenForConsumption(forTransactionRequest: transactionRequest) - // 4) Try to consume the transaction request + guard let transactionRequest = self.generateTransactionRequest(creationCorrelationId: creationCorrelationId, + requiresConfirmation: true) else { return } + // 2) Subscribe to events on this transaction request (listen for consumptions) + let transactionRequestEventDelegate = self.startListeningForEvent(forTransactionRequest: transactionRequest) + // 3) Try to consume the transaction request // As it requires confirmation, an event should be send to listeners + transactionRequestEventDelegate.eventExpectation = self.expectation(description: "Receives a consumption request") guard self.consumeTransactionRequest(transactionRequest: transactionRequest) != nil else { return } - // 5) Wait for the consumption to be sent - let consumptionRequest = self.waitForConsumption(withDelegate: consumptionDelegate) - // 6) Confirm the consumption + // 4) Wait for the consumption to be sent + let consumptionRequest = self.waitForConsumption(withDelegate: transactionRequestEventDelegate) + // 5) Confirm the consumption + transactionRequestEventDelegate.eventExpectation = self.expectation(description: "Receives an approved, but failed consumption") self.confirmConsumption(withConsumption: consumptionRequest) + // 6) Wait for the failed consumption to be sent + let failedConsumption = self.waitForApproval(withDelegate: transactionRequestEventDelegate) + XCTAssertEqual(failedConsumption.status, .failed) + XCTAssertTrue(failedConsumption.approved) + XCTAssertNotNil(failedConsumption.finalizedAt) + } + + func testGetATransactionRequest() { + let creationCorrelationId = UUID().uuidString + guard let transactionRequest = self.generateTransactionRequest(creationCorrelationId: creationCorrelationId, requiresConfirmation: true) else { return } + self.getTransactionRequest(transactionRequestId: transactionRequest.id, creationCorrelationId: creationCorrelationId) + } + + /// This test asserts that an approved consumption event is immediately sent without the need to approve the consumption + func testGenerateTransactionRequestThenConsumeWithAnAutomaticConfirmation() { + let creationCorrelationId = UUID().uuidString + // FLOW: + // 1) Generate the transaction request + guard let transactionRequest = self.generateTransactionRequest(creationCorrelationId: creationCorrelationId, + requiresConfirmation: false) else { return } + // 2) Subscribe to events on this transaction request + let transactionRequestEventDelegate = self.startListeningForEvent(forTransactionRequest: transactionRequest) + // 3) Try to consume the transaction request + // As it requires confirmation, an event should be send to listeners + transactionRequestEventDelegate.eventExpectation = self.expectation(description: "Receives an approved, but failed consumption") + let consumption = self.consumeTransactionRequest(transactionRequest: transactionRequest) + XCTAssertNil(consumption) + // 4) Wait for the failed consumption to be sent + let failedConsumption = self.waitForApproval(withDelegate: transactionRequestEventDelegate) + XCTAssertEqual(failedConsumption.status, .failed) + XCTAssertTrue(failedConsumption.approved) + XCTAssertNotNil(failedConsumption.finalizedAt) } + /// This test check that the rejection flow works as expected func testGenerateTransactionRequestThenRejectConsumption() { let creationCorrelationId = UUID().uuidString // FLOW: // 1) Generate the transaction request - guard let transactionRequest = self.generateTransactionRequest(creationCorrelationId: creationCorrelationId) else { return } - // 2) Test the get method - self.getTransactionRequest(transactionRequestId: transactionRequest.id, creationCorrelationId: creationCorrelationId) - // 3) Subscribe to events on this transaction request (listen for consumptions) - let consumptionDelegate = self.startToListenForConsumption(forTransactionRequest: transactionRequest) - // 4) Try to consume the transaction request + guard let transactionRequest = self.generateTransactionRequest(creationCorrelationId: creationCorrelationId, + requiresConfirmation: true) else { return } + // 2) Subscribe to events on this transaction request (listen for consumptions) + let transactionRequestEventDelegate = self.startListeningForEvent(forTransactionRequest: transactionRequest) + // 3) Try to consume the transaction request // As it requires confirmation, an event should be send to listeners + transactionRequestEventDelegate.eventExpectation = self.expectation(description: "Receives a consumption request") guard let transactionConsumption = self.consumeTransactionRequest(transactionRequest: transactionRequest) else { return } + // 4) Wait for the consumption to be sent + let consumptionRequest = self.waitForConsumption(withDelegate: transactionRequestEventDelegate) // 5) Subscribe to events on this transaction consumption (listen for confirmations) - let confirmationDelegate = self.startToListenForConfirmation(forConsumption: transactionConsumption) - // 6) Wait for the consumption to be sent - let consumptionRequest = self.waitForConsumption(withDelegate: consumptionDelegate) - // 7) Reject the consumption + let transactionConsumptionEventDelegate = self.startListeningForEvent(forConsumption: transactionConsumption) + // 6) Reject the consumption + transactionRequestEventDelegate.eventExpectation = self.expectation(description: "Receives a rejected consumption") + transactionConsumptionEventDelegate.eventExpectation = self.expectation(description: "Receives a rejected consumption") self.rejectConsumption(withConsumption: consumptionRequest) - // 8) Wait for the rejection to be sent - let rejectedConsumption = self.waitForRejection(withDelegate: confirmationDelegate) - XCTAssertEqual(rejectedConsumption.status, .rejected) - XCTAssertFalse(rejectedConsumption.approved) - XCTAssertNotNil(rejectedConsumption.finalizedAt) + // 7) Wait for the rejection to be sent + let rejectedConsumptionSentOnTransactionConsumptionListener = self.waitForRejection(withDelegate: transactionConsumptionEventDelegate) + let rejectedConsumptionSentOnTransactionRequestListener = self.waitForRejection(withDelegate: transactionRequestEventDelegate) + + XCTAssertEqual(rejectedConsumptionSentOnTransactionConsumptionListener, rejectedConsumptionSentOnTransactionRequestListener) + XCTAssertEqual(rejectedConsumptionSentOnTransactionConsumptionListener.status, .rejected) + XCTAssertFalse(rejectedConsumptionSentOnTransactionConsumptionListener.approved) + XCTAssertNotNil(rejectedConsumptionSentOnTransactionConsumptionListener.finalizedAt) } func testChannelNotFound() { @@ -92,7 +131,7 @@ class TransactionRequestLiveTests: LiveTestCase { extension TransactionRequestLiveTests { - func generateTransactionRequest(creationCorrelationId: String) -> TransactionRequest? { + func generateTransactionRequest(creationCorrelationId: String, requiresConfirmation: Bool) -> TransactionRequest? { let generateExpectation = self.expectation(description: "Generate transaction request") let transactionRequestParams = TransactionRequestCreateParams( type: .receive, @@ -100,7 +139,7 @@ extension TransactionRequestLiveTests { amount: 1, address: nil, correlationId: creationCorrelationId, - requireConfirmation: true, + requireConfirmation: requiresConfirmation, maxConsumptions: 2, consumptionLifetime: nil, expirationDate: Date().addingTimeInterval(60), @@ -168,16 +207,27 @@ extension TransactionRequestLiveTests { defer { consumeExpectation.fulfill() } switch result { case .success(data: let transactionConsumption): - transactionConsumptionResult = transactionConsumption - let mintedToken = transactionConsumption.mintedToken - XCTAssertEqual(mintedToken.id, self.validMintedTokenId) - XCTAssertEqual(transactionConsumption.amount, 1) - XCTAssertEqual(transactionConsumption.correlationId, consumeCorrelationId) - XCTAssertEqual(transactionConsumption.idempotencyToken, idempotencyToken) - XCTAssertEqual(transactionConsumption.transactionRequestId, transactionRequest.id) - XCTAssertEqual(transactionConsumption.status, .pending) + if transactionRequest.requireConfirmation { + transactionConsumptionResult = transactionConsumption + let mintedToken = transactionConsumption.mintedToken + XCTAssertEqual(mintedToken.id, self.validMintedTokenId) + XCTAssertEqual(transactionConsumption.amount, 1) + XCTAssertEqual(transactionConsumption.correlationId, consumeCorrelationId) + XCTAssertEqual(transactionConsumption.idempotencyToken, idempotencyToken) + XCTAssertEqual(transactionConsumption.transactionRequestId, transactionRequest.id) + XCTAssertEqual(transactionConsumption.status, .pending) + } else { + XCTFail("Should raise a same address error") + } case .fail(error: let error): - XCTFail("\(error)") + if transactionRequest.requireConfirmation { + XCTFail("\(error)") + } else { + switch error { + case .api(apiError: let apiError): XCTAssertEqual(apiError.code, .sameAddress) + default: XCTFail("Expected to receive same_address error") + } + } } } XCTAssertNotNil(consumeRequest) @@ -185,20 +235,18 @@ extension TransactionRequestLiveTests { return transactionConsumptionResult } - func startToListenForConsumption(forTransactionRequest transactionRequest: TransactionRequest) -> DummySocketEventDelegate { - let eventExpectation = self.expectation(description: "Start listening for consumption") + func startListeningForEvent(forTransactionRequest transactionRequest: TransactionRequest) -> DummySocketEventDelegate { let joinExpectation = self.expectation(description: "Join channel") - let delegate = DummySocketEventDelegate(eventExpectation: eventExpectation, joinExpectation: joinExpectation) + let delegate = DummySocketEventDelegate(joinExpectation: joinExpectation) transactionRequest.startListeningEvents(withClient: self.testSocketClient, eventDelegate: delegate) wait(for: [joinExpectation], timeout: 15.0) XCTAssertTrue(delegate.didJoin) return delegate } - func startToListenForConfirmation(forConsumption transactionConsumption: TransactionConsumption) -> DummySocketEventDelegate { - let eventExpectation = self.expectation(description: "Start listening for confirmation") + func startListeningForEvent(forConsumption transactionConsumption: TransactionConsumption) -> DummySocketEventDelegate { let joinExpectation = self.expectation(description: "Join channel") - let delegate = DummySocketEventDelegate(eventExpectation: eventExpectation, joinExpectation: joinExpectation) + let delegate = DummySocketEventDelegate(joinExpectation: joinExpectation) transactionConsumption.startListeningEvents(withClient: self.testSocketClient, eventDelegate: delegate) wait(for: [joinExpectation], timeout: 15.0) XCTAssertTrue(delegate.didJoin) @@ -206,16 +254,14 @@ extension TransactionRequestLiveTests { } func waitForConsumption(withDelegate delegate: DummySocketEventDelegate) -> TransactionConsumption { - let expectation = delegate.eventExpectation! - wait(for: [expectation], timeout: 15.0) + wait(for: [delegate.eventExpectation!], timeout: 15.0) XCTAssertNotNil(delegate.didReceiveTransactionConsumptionRequest) XCTAssertEqual(delegate.didReceiveEvent!, SocketEvent.transactionConsumptionRequest) return delegate.didReceiveTransactionConsumptionRequest! } func waitForApproval(withDelegate delegate: DummySocketEventDelegate) -> TransactionConsumption { - let expectation = delegate.eventExpectation! - wait(for: [expectation], timeout: 15.0) + wait(for: [delegate.eventExpectation!], timeout: 15.0) XCTAssertNotNil(delegate.didReceiveTransactionConsumptionApproved) XCTAssertNotNil(delegate.didReceiveEvent) XCTAssertEqual(delegate.didReceiveEvent!, SocketEvent.transactionConsumptionApproved) @@ -223,8 +269,7 @@ extension TransactionRequestLiveTests { } func waitForRejection(withDelegate delegate: DummySocketEventDelegate) -> TransactionConsumption { - let expectation = delegate.eventExpectation! - wait(for: [expectation], timeout: 15.0) + wait(for: [delegate.eventExpectation!], timeout: 15.0) XCTAssertNotNil(delegate.didReceiveTransactionConsumptionRejected) XCTAssertNotNil(delegate.didReceiveEvent) XCTAssertEqual(delegate.didReceiveEvent!, SocketEvent.transactionConsumptionRejected) diff --git a/Podfile b/Podfile index 458360f..a019db8 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,7 @@ def shared_pods # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! # websocket library - pod 'Starscream', '~> 3.0.2' + pod 'Starscream', '~> 3.0' end target 'OmiseGO' do diff --git a/Podfile.lock b/Podfile.lock index 0a7458e..894804a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,12 +1,12 @@ PODS: - - Starscream (3.0.4) + - Starscream (3.0.5) DEPENDENCIES: - - Starscream (~> 3.0.2) + - Starscream (~> 3.0) SPEC CHECKSUMS: - Starscream: f5da93fe6984c77b694366bf7299b7dc63a76f26 + Starscream: faf918b2f2eff7d5dd21180646bf015a669673bd -PODFILE CHECKSUM: 60bd2f337ffb2f565edc37401613b7b316f33a95 +PODFILE CHECKSUM: b65b55b7361189d8c9b6d31f609db7eba1c61ae8 COCOAPODS: 1.4.0 diff --git a/README.md b/README.md index 80dae50..66f641a 100644 --- a/README.md +++ b/README.md @@ -467,11 +467,22 @@ Where: - `client` is a `SocketClient` - `eventDelegate` is a `TransactionRequestEventDelegate` that will receive incoming events. -An object conforming to `TransactionRequestEventDelegate` needs to implement the 3 common methods mentioned above and also `didReceiveTransactionConsumptionRequest(_ transactionConsumption: TransactionConsumption, forEvent event: SocketEvent)`. +An object conforming to `TransactionRequestEventDelegate` needs to implement the 3 common methods mentioned above and also: + +- `didReceiveTransactionConsumptionRequest(_ transactionConsumption: TransactionConsumption, forEvent event: SocketEvent)`. This method will be called when a `TransactionConsumption` is trying to consume the `TransactionRequest`. This allows the requester to [confirm](#confirm-a-transaction-consumption) or not the consumption if legitimate. +- `didReceiveTransactionConsumptionApproval(_ transactionConsumption: TransactionConsumption, forEvent event: SocketEvent)`. + +If the `TransactionRequest` requires a confirmation then this method will be called when a `TransactionConsumption` is approved by the requester. +If it doesn't require a confirmation it will be called when the request is consumed. + +- `didReceiveTransactionConsumptionRejection(_ transactionConsumption: TransactionConsumption, forEvent event: SocketEvent)`. + +This method will be called when a `TransactionConsumption` is rejected by the requester. + #### Transaction consumption events @@ -483,10 +494,15 @@ Where: - `client` is a `SocketClient` - `eventDelegate` is a `TransactionConsumptionEventDelegate` that will receive incoming events. -An object conforming to `TransactionConsumptionEventDelegate` needs to implement the 3 common methods mentioned above and also `didReceiveTransactionConsumptionConfirmation(_ transactionConsumption: TransactionConsumption, forEvent event: SocketEvent)`. +An object conforming to `TransactionConsumptionEventDelegate` needs to implement the 3 common methods mentioned above and also: + +- `didReceiveTransactionConsumptionApproval(_ transactionConsumption: TransactionConsumption, forEvent event: SocketEvent)`. + +This method will be called when a `TransactionConsumption` is approved by the requester. -This method will be called when a `TransactionConsumption` is confirmed by the requester. +- `didReceiveTransactionConsumptionRejection(_ transactionConsumption: TransactionConsumption, forEvent event: SocketEvent)`. +This method will be called when a `TransactionConsumption` is rejected by the requester. #### User events diff --git a/Source/Info.plist b/Source/Info.plist index c0d5e7f..7522f76 100644 --- a/Source/Info.plist +++ b/Source/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.10 + 0.9.11 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/Source/Websocket/SocketDelegate.swift b/Source/Websocket/SocketDelegate.swift index 35f0e2e..f831e23 100644 --- a/Source/Websocket/SocketDelegate.swift +++ b/Source/Websocket/SocketDelegate.swift @@ -23,6 +23,8 @@ public protocol UserEventDelegate: EventDelegate { public protocol TransactionRequestEventDelegate: EventDelegate { func didReceiveTransactionConsumptionRequest(_ transactionConsumption: TransactionConsumption, forEvent event: SocketEvent) + func didReceiveTransactionConsumptionApproval(_ transactionConsumption: TransactionConsumption, forEvent event: SocketEvent) + func didReceiveTransactionConsumptionRejection(_ transactionConsumption: TransactionConsumption, forEvent event: SocketEvent) } public protocol TransactionConsumptionEventDelegate: EventDelegate { diff --git a/Source/Websocket/SocketDispatcher.swift b/Source/Websocket/SocketDispatcher.swift index 7afefe4..f70f303 100644 --- a/Source/Websocket/SocketDispatcher.swift +++ b/Source/Websocket/SocketDispatcher.swift @@ -57,8 +57,16 @@ enum SocketDispatcher { payload: GenericObjectEnum, event: SocketEvent) { switch payload { - case .transactionConsumption(object: let transactionConsumption) where event == .transactionConsumptionRequest: - handler?.didReceiveTransactionConsumptionRequest(transactionConsumption, forEvent: event) + case .transactionConsumption(object: let transactionConsumption): + switch event { + case .transactionConsumptionRequest: + handler?.didReceiveTransactionConsumptionRequest(transactionConsumption, forEvent: event) + case .transactionConsumptionApproved: + handler?.didReceiveTransactionConsumptionApproval(transactionConsumption, forEvent: event) + case .transactionConsumptionRejected: + handler?.didReceiveTransactionConsumptionRejection(transactionConsumption, forEvent: event) + default: break + } case .error(error: let error): self.dispatchError(error) default: break