Skip to content

Commit

Permalink
T196 Handle more events on transaction request listener (#27)
Browse files Browse the repository at this point in the history
* Add transaction request approved and rejected events

* Update version number

* Update README
  • Loading branch information
mederic-p committed Apr 9, 2018
1 parent 6357f3c commit 872aeda
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 62 deletions.
4 changes: 2 additions & 2 deletions 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'
Expand All @@ -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
2 changes: 1 addition & 1 deletion OmiseGOTests/Helpers/DummySocketEventDelegate.swift
Expand Up @@ -11,7 +11,7 @@ import XCTest

class DummySocketEventDelegate {

let eventExpectation: XCTestExpectation?
var eventExpectation: XCTestExpectation?
let joinExpectation: XCTestExpectation?

init(eventExpectation: XCTestExpectation? = nil, joinExpectation: XCTestExpectation? = nil) {
Expand Down
141 changes: 93 additions & 48 deletions OmiseGOTests/LiveTests/TransactionRequestLiveTests.swift
Expand Up @@ -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() {
Expand All @@ -92,15 +131,15 @@ 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,
mintedTokenId: self.validMintedTokenId,
amount: 1,
address: nil,
correlationId: creationCorrelationId,
requireConfirmation: true,
requireConfirmation: requiresConfirmation,
maxConsumptions: 2,
consumptionLifetime: nil,
expirationDate: Date().addingTimeInterval(60),
Expand Down Expand Up @@ -168,63 +207,69 @@ 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)
wait(for: [consumeExpectation], timeout: 15.0)
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)
return delegate
}

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)
return delegate.didReceiveTransactionConsumptionApproved!
}

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)
Expand Down
2 changes: 1 addition & 1 deletion Podfile
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions 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
22 changes: 19 additions & 3 deletions README.md
Expand Up @@ -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

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion Source/Info.plist
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.9.10</string>
<string>0.9.11</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
Expand Down
2 changes: 2 additions & 0 deletions Source/Websocket/SocketDelegate.swift
Expand Up @@ -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 {
Expand Down

0 comments on commit 872aeda

Please sign in to comment.