Skip to content
This repository has been archived by the owner on Apr 6, 2020. It is now read-only.

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
* development:
  remove caching from travis
  new patch file for travis..
  Lock patch-package and redux-saga-mock versions
  Add test cases for claiming gas
  Add RPC test helpers, fix getbalance and update claims
  Update redux-saga-mock patch to fix stubbing in an ALL effect
  • Loading branch information
ixje committed Nov 16, 2017
2 parents 330ed2e + c0a2129 commit d6396fe
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 35 deletions.
4 changes: 0 additions & 4 deletions .travis.yml
Expand Up @@ -6,10 +6,6 @@ node_js:
before_install:
- npm i -g npm@5.4.2

cache:
directories:
- "node_modules"

script: npm install

after_success:
Expand Down
43 changes: 36 additions & 7 deletions __tests__/helpers.js
Expand Up @@ -14,7 +14,6 @@ export const mockGetDBHeight = function(height_return_value) {

export const mockGetBalance = function(balanceNEO, balanceGAS) {
let matcher = nock('http://testnet-api.wallet.cityofzion.io')
.persist()
.filteringPath(function(path) {
if (path.includes('/v2/address/balance/')) {
return '/v2/address/balance/'
Expand All @@ -24,7 +23,7 @@ export const mockGetBalance = function(balanceNEO, balanceGAS) {
})
.get('/v2/address/balance/')

if (balanceNEO && balanceGAS) {
if (balanceNEO !== undefined && balanceGAS !== undefined) {
return matcher.reply(200, {
GAS: {
balance: balanceGAS,
Expand Down Expand Up @@ -65,9 +64,8 @@ export const mockGetHistory = function(history) {
}
}

export const mockClaims = function(claims) {
export const mockClaims = function(claims, total_claim = 0, total_unspent_claim = 0) {
let matcher = nock('http://testnet-api.wallet.cityofzion.io')
.persist()
.filteringPath(function(path) {
if (path.includes('/v2/address/claims/')) {
return '/v2/address/claims/'
Expand All @@ -82,8 +80,8 @@ export const mockClaims = function(claims) {
address: 'AStZHy8E6StCqYQbzMqi4poH7YNDHQKxvt',
claims: claims,
net: 'TestNet',
total_claim: 0,
total_unspent_claim: 0
total_claim: total_claim,
total_unspent_claim: total_unspent_claim
})
} else {
return matcher
Expand All @@ -99,9 +97,40 @@ export const mockTicker = function(bid, ask, last) {
matcher.reply(200, {
success: true,
message: '',
result: { Bid: 1337.00000001, Ask: 1337.00000002, Last: 1337.00000003 }
result: { Bid: bid, Ask: ask, Last: last }
})
} else {
return matcher
}
}

export const mockGetRPCEndpoint = function(nodeAddress) {
let matcher = nock('http://testnet-api.wallet.cityofzion.io')
.filteringPath(function(path) {
if (path.includes('/v2/network/best_node')) {
return '/v2/network/best_node'
} else {
return path
}
})
.get('/v2/network/best_node')

if (nodeAddress) {
return matcher.reply(200, {
net: 'TestNet',
node: nodeAddress // "http://test3.cityofzion.io:8880"
})
} else {
return matcher
}
}

export const mockQueryRPC = function(rpcAddress, id, result) {
let matcher = nock(rpcAddress)
.get('/')
.reply(200, {
jsonrpc: '2.0',
id: id,
result: result // bool
})
}
141 changes: 135 additions & 6 deletions __tests__/sagas.js
Expand Up @@ -4,14 +4,15 @@ import { mockSaga } from 'redux-saga-mock'
import nock from 'nock'
import 'isomorphic-fetch'

import { watchCreateWallet, createWalletFlow, watchLoginWallet, walletUseFlow, backgroundSyncData, retrieveData } from '../app/sagas/wallet'
import { createWalletFlow, walletUseFlow, backgroundSyncData, claimGASFlow, rootWalletSaga } from '../app/sagas/wallet'
import { ActionConstants as actionMsg } from '../app/actions'
import { ActionCreators as actions } from '../app/actions'
import { generateEncryptedWIF } from '../app/api/crypto'
import reducer from '../app/reducers'
import { initialState } from '../app/store'
import { DropDownHolder } from '../app/utils/DropDownHolder'
import { mockClaims, mockGetBalance, mockGetDBHeight, mockGetHistory, mockTicker } from './helpers'
import { mockClaims, mockGetBalance, mockGetDBHeight, mockGetHistory, mockTicker, mockGetRPCEndpoint, mockQueryRPC } from './helpers'
import { sendAsset, claimAllGAS } from '../app/api/network'

// use official test vectors from https://github.com/neo-project/proposals/blob/master/nep-2.mediawiki#test-vectors
const unencryptedWIF = 'L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP'
Expand All @@ -29,13 +30,19 @@ SagaTester.prototype.findAction = function(actionMsgToFind) {
})
}

SagaTester.prototype.filterAction = function(actionMsgToFind) {
return this.getCalledActions().filter(val => {
return val.type === actionMsgToFind
})
}

describe('wallet creation', () => {
describe('watcher', () => {
it('should accept any create action', () => {
const sagaTester = new SagaTester({
initialState: {}
}) // not using any initial state
const testSaga = mockSaga(watchCreateWallet)
const testSaga = mockSaga(rootWalletSaga)

// setup
testSaga.stubFork(createWalletFlow, () => {
Expand All @@ -60,7 +67,7 @@ describe('wallet creation', () => {
initialState: initialState,
reducers: reducer
})
testSaga = mockSaga(watchCreateWallet)
testSaga = mockSaga(rootWalletSaga)
})

it('should generate a valid encrypted WIF from only a passphrase', async () => {
Expand Down Expand Up @@ -116,7 +123,7 @@ describe('wallet use', () => {
initialState: deepClone(initialState),
reducers: reducer
})
testSaga = mockSaga(watchLoginWallet)
testSaga = mockSaga(rootWalletSaga)
})

it('should accept just 1 login at a time', () => {
Expand Down Expand Up @@ -194,7 +201,7 @@ describe('wallet use', () => {
initialState: initialState,
reducers: reducer
})
testSaga = mockSaga(watchLoginWallet)
testSaga = mockSaga(rootWalletSaga)
nock.cleanAll()
})

Expand Down Expand Up @@ -492,5 +499,127 @@ describe('wallet use', () => {
expect(sagaTester.wasCalled(actionMsg.wallet.GET_AVAILABLE_GAS_CLAIM_ERROR)).toBe(true)
expect(action.error.message).toContain('Return data malformed')
})

it('should be possible claim GAS multiple times', async () => {
testSaga.stubFork(claimGASFlow, async () => {
sagaTester.dispatch({ type: 'TEST_CLAIM_GAS' })
})

sagaTester.start(testSaga)
sagaTester.dispatch(actions.wallet.claim())
sagaTester.dispatch(actions.wallet.claim())
await sagaTester.waitFor('TEST_CLAIM_GAS')

expect(sagaTester.numCalled('TEST_CLAIM_GAS')).toBe(2)
})

it('should release any unspent claim', async () => {
testSaga.stubCall(sendAsset, () => {
return true
})

testSaga.stubCall(claimAllGAS, () => {
return { result: true }
})

mockGetDBHeight(1) // does not persist
mockGetBalance(10, 0).persist()
mockTicker(1337, 1337.2, 1337.1) // persists
mockGetHistory([]) // persists
mockClaims([], 0, 10) // does not persist

let rpcEndpoint = 'http://test3.cityofzion.io:8880'
mockGetRPCEndpoint(rpcEndpoint).persist()
mockQueryRPC(rpcEndpoint, 2, true)

mockGetDBHeight(2)
mockClaims([], 10, 0)

sagaTester.start(testSaga)
sagaTester.dispatch(actions.wallet.loginWithPrivateKey(unencryptedWIF))

// wait with the claim action until we've gathered some wallet data
await sagaTester.waitFor(actionMsg.wallet.GET_BALANCE_SUCCESS)
sagaTester.dispatch(actions.wallet.claim())
await sagaTester.waitFor(actionMsg.wallet.TRANSACTION_TO_SELF_CLEARED)

// loging out to quickly abort
sagaTester.dispatch({ type: actionMsg.wallet.LOGOUT })

expect(sagaTester.numCalled(actionMsg.wallet.UNSPEND_CLAIM_TO_CLEAR)).toBe(1)
claimSuccesses = sagaTester.filterAction(actionMsg.wallet.GET_AVAILABLE_GAS_CLAIM_SUCCESS)
// first time we have unavailable claims to clear
expect(claimSuccesses[0].claimAmounts.unavailable).toBe(10)
expect(claimSuccesses[0].claimAmounts.available).toBe(0)
// second time around they should be cleared
expect(claimSuccesses[1].claimAmounts.unavailable).toBe(0)
expect(claimSuccesses[1].claimAmounts.available).toBe(10)
})

it('should be able to claim the available unclaimed GAS and confirm it with the blockchain', async () => {
// first block data
mockGetDBHeight(1) // does not persist
mockGetBalance(0, 0)
mockTicker(1337, 1337.2, 1337.1) // persists
mockGetHistory([]) // persists
mockClaims([], 0, 90000000) // does not persist (claim amount is in network format e.g. 0.9 * 100M)

testSaga.stubCall(claimAllGAS, () => {
return { result: true }
})

// second block data
mockGetDBHeight(2)
mockGetBalance(0, 0)
mockClaims([], 0, 10)

// 3rd block data
mockGetDBHeight(3)
mockGetBalance(0, 0.9)
mockClaims([], 90000000, 0) // mimick confirmed

// start and drive sagas
sagaTester.start(testSaga)
sagaTester.dispatch(actions.wallet.loginWithPrivateKey(unencryptedWIF))

// wait with the claim action until we've gathered some wallet data
await sagaTester.waitFor(actionMsg.wallet.GET_BALANCE_SUCCESS)
sagaTester.dispatch(actions.wallet.claim())
await sagaTester.waitFor(actionMsg.wallet.CLAIM_GAS_CONFIRMED_BY_BLOCKCHAIN)

walletState = sagaTester.getState().wallet
// loging out to quickly abort
sagaTester.dispatch({ type: actionMsg.wallet.LOGOUT })

// verify
expect(walletState.claimAmount).toBe(0.9)
expect(walletState.gas).toBe(0.9)
})

it('should gracefully abort the claim flow when the gasClaim RPC call returns False', async () => {
// first block data
mockGetDBHeight(1) // does not persist
mockGetBalance(0, 0)
mockTicker(1337, 1337.2, 1337.1) // persists
mockGetHistory([]) // persists
mockClaims([], 0, 90000000) // does not persist (claim amount is in network format e.g. 0.9 * 100M)

testSaga.stubCall(claimAllGAS, () => {
return { result: false }
})

// start and drive sagas
sagaTester.start(testSaga)
sagaTester.dispatch(actions.wallet.loginWithPrivateKey(unencryptedWIF))

// wait with the claim action until we've gathered some wallet data
await sagaTester.waitFor(actionMsg.wallet.GET_BALANCE_SUCCESS)
sagaTester.dispatch(actions.wallet.claim())
await sagaTester.waitFor('JEST_BG_TASK_CANCELLED')

let action = sagaTester.findAction(actionMsg.wallet.CLAIM_GAS_ERROR)
expect(sagaTester.wasCalled(actionMsg.wallet.CLAIM_GAS_ERROR)).toBe(true)
expect(action.error.message).toContain('Claim failed')
})
})
})
42 changes: 34 additions & 8 deletions app/sagas/wallet.js
Expand Up @@ -55,8 +55,11 @@ function* watchSendAsset() {
yield takeEvery(actions.wallet.SEND_ASSET, sendAssetFlow)
}

function* watchClaimGAS() {
yield takeEvery(actions.wallet.CLAIM_GAS, claimGASFlow)
export function* watchClaimGAS() {
while (true) {
const params = yield take(actions.wallet.CLAIM_GAS)
yield fork(claimGASFlow, params)
}
}
/*
*
Expand All @@ -74,9 +77,9 @@ function* bgTaskController() {
actions.wallet.GET_BALANCE_ERROR,
actions.wallet.GET_MARKET_PRICE_ERROR,
actions.wallet.GET_TRANSACTION_HISTORY_ERROR,
actions.wallet.GET_AVAILABLE_GAS_CLAIM_ERROR
actions.wallet.GET_AVAILABLE_GAS_CLAIM_ERROR,
actions.wallet.CLAIM_GAS_ERROR
])

yield cancel(bgTaskHandler)

// because of https://github.com/wix/redux-saga-tester/issues/38
Expand Down Expand Up @@ -269,10 +272,10 @@ function* claim(wif) {
if (response.result == true) {
yield put({ type: actions.wallet.CLAIM_GAS_SUCCESS })
} else {
yield put({ type: actions.wallet.CLAIM_GAS_ERROR, error: 'Claim failed' })
throw new Error('Claim failed')
}
} catch (error) {
yield put({ type: actions.wallet.CLAIM_GAS_ERROR, error: error })
yield put({ type: actions.wallet.CLAIM_GAS_ERROR, error })
}
}

Expand All @@ -292,7 +295,7 @@ function* waitForTransactionToSelfToClear(previousUnspendClaim) {
}
}

function* claimGASFlow() {
export function* claimGASFlow() {
const wallet = yield select(getWallet)

/* If we have NEO balance in our wallet, then part or all of the available claim can be "unspent_claim".
Expand All @@ -311,8 +314,13 @@ function* claimGASFlow() {

yield take(actions.wallet.TRANSACTION_TO_SELF_CLEARED)
}
yield call(claim, wallet.wif)
claimGASTask = yield fork(claim, wallet.wif)

const action = yield take([actions.wallet.CLAIM_GAS_SUCCESS, actions.wallet.CLAIM_GAS_ERROR])
if (action.type == actions.wallet.CLAIM_GAS_ERROR) {
cancel(claimGASTask)
return
}
// Wait for GAS balance to be confirmed
const oldGASBalance = wallet.gas
while (true) {
Expand All @@ -329,3 +337,21 @@ function* claimGASFlow() {
}
}
}

// function* monitorNetworkHealth() {
// while(true) {
// // do some network test
// if (ok) {
// set ONLINE_FLAG
// if (was bgsync) {
// restart bgsync
// }
// }
// if (!ok) {
// set OFFLINE flag
// if (bgsync) {
// stop bgsync
// }
// }
// }
// }
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -44,9 +44,9 @@
"isomorphic-fetch": "^2.2.1",
"jest": "^21.1.0",
"nock": "^9.0.27",
"patch-package": "^3.4.6",
"patch-package": "3.4.6",
"react-test-renderer": "16.0.0-alpha.12",
"redux-saga-mock": "^1.2.0",
"redux-saga-mock": "1.2.0",
"redux-saga-tester": "^1.0.373"
},
"jest": {
Expand Down

0 comments on commit d6396fe

Please sign in to comment.