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

Add missing tests for new JS files in UI. #76

Merged
merged 11 commits into from Sep 24, 2018
7,235 changes: 3,384 additions & 3,851 deletions frontend/package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion frontend/package.json
Expand Up @@ -52,7 +52,10 @@
"!**/node_modules/**",
"!**/public/**",
"!**/artifacts/**",
"!**/__tests__/*.js"
"!**/__tests__/*.js",
"!**/src/ui/styles/**/*.js",
"!**/src/index.js",
"!**/src/registerServiceWorker.js"
],
"coverageThreshold": {
"global": {
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/App.js
Expand Up @@ -9,8 +9,8 @@ import Section from './ui/layout/Section'
import IndexPage from './ui/pages/IndexPage'
import HelpPage from './ui/pages/HelpPage'
import ErrorPage from './ui/pages/ErrorPage'
import BankAccountsPage from './ui/pages/BankAccountsPage'
import MyBankAccountsPage from './ui/pages/MyBankAccountsPage'
import MyPlaidBankAccountsPage from './ui/pages/MyPlaidBankAccountsPage'
import MyVerifiedBankAccountsPage from './ui/pages/MyVerifiedBankAccountsPage'
import Web3Provider from './Web3Provider'

const noWeb3Render = () => <ErrorPage error="noWeb3Render" />
Expand All @@ -22,11 +22,15 @@ const routesRender = (web3, accounts) => {
<Route exact path="/help" component={() => <HelpPage />} />
<Route
path="/bankaccountslist/:token"
component={props => <BankAccountsPage props={props} web3={web3} account={accounts[0]} />}
component={props => (
<MyPlaidBankAccountsPage props={props} web3={web3} account={accounts[0]} />
)}
/>
<Route
path="/mybankaccountslist"
component={props => <MyBankAccountsPage props={props} web3={web3} account={accounts[0]} />}
component={props => (
<MyVerifiedBankAccountsPage props={props} web3={web3} account={accounts[0]} />
)}
/>
</section>
)
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/PoBAContract.js
@@ -0,0 +1,68 @@
import contract from 'truffle-contract'
import pobaArtifact from './artifacts/PoBA.json'
// eslint-disable-line import/no-unresolved
const PoBA = contract(pobaArtifact)

class PoBAContract {
constructor(provider) {
this.contract = null
PoBA.setProvider(provider)

this.init = this.init.bind(this)
this.getVerifiedBankAccounts = this.getVerifiedBankAccounts.bind(this)
this.registerBankAccount = this.registerBankAccount.bind(this)
this.unregisterBankAccount = this.unregisterBankAccount.bind(this)
}

async init() {
this.contract = await PoBA.deployed()
return this
}

async getVerifiedBankAccounts(walletAddress) {
const promises = []
try {
const accountsLengthResult = await this.contract.accountsLength(walletAddress)
const accountsLength = accountsLengthResult.c[0]
for (let index = 0; index < accountsLength; index++) {
promises.push(this.contract.getBankAccounts(walletAddress, index))
}
} catch (e) {
console.error('Error getting verified bank accounts', e)
}
return Promise.all(promises)
}

async registerBankAccount(args, walletAddress) {
return this.contract.register(
args.bankAccount.account,
args.bankAccount.institution,
args.identityNames,
args.v,
args.r,
args.s,
{ from: walletAddress }
)
}

async unregisterBankAccount(args, walletAddress) {
// Default estimation of gas is too low, multiply it by 2
const gasEstimate = await this.contract.unregisterBankAccount.estimateGas(
args.account,
args.bankName,
args.identityNames,
{ from: walletAddress }
)
return this.contract.unregisterBankAccount(args.account, args.bankName, args.identityNames, {
from: walletAddress,
gas: gasEstimate * 2
})
}
}

const getInstance = async provider => {
const instance = new PoBAContract(provider)
return instance.init()
}

export default getInstance
19 changes: 19 additions & 0 deletions frontend/src/PoBAServer.js
@@ -0,0 +1,19 @@
import axios from 'axios'

export const getBankAccounts = async token => {
const result = await axios.get(`/api/accounts/bank-accounts/${token}`)
const { ach, eft } = result.data.accounts.numbers
const { institution } = result.data.accounts.item.institution
const accounts = [...ach, ...eft]
return accounts.map(account => Object.assign({ institution: institution.name }, account))
}

export const getSignedBankAccount = async (accountId, ethAccount, token) => {
const result = await axios.post('/api/accounts/sign-account', {
accountId,
ethAccount,
token
})

return result.data
}
17 changes: 17 additions & 0 deletions frontend/src/__tests__/App.js
@@ -0,0 +1,17 @@
import React from 'react'
import { configure, shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import App from '../App'

configure({ adapter: new Adapter() })

describe('App component', () => {
it('renders correctly', () => {
const wrapper = shallow(<App />)

expect(wrapper.find('BrowserRouter')).toHaveLength(1)
expect(wrapper.find('Web3Provider')).toHaveLength(1)
expect(wrapper.find('Header')).toHaveLength(1)
expect(wrapper.find('Footer')).toHaveLength(1)
})
})
86 changes: 86 additions & 0 deletions frontend/src/ui/containers/PlaidBankAccounts.js
@@ -0,0 +1,86 @@
import React, { Component } from 'react'
import Loading from '../presentational/Loading'
import PlaidBankAccountsList from '../presentational/PlaidBankAccountsList'
import { errorAlert, successAlert } from '../presentational/alerts'

const ERROR_MSG_CONTRACT_NOT_DEPLOYED = 'Contract is not deployed on this network'
const ERROR_MSG_VERIFYING_BANK_ACCOUNT = 'There was a problem verifying the the bank account'

class PlaidBankAccounts extends Component {
constructor(props) {
super(props)

this.state = {
ethAccount: props.account,
plaidToken: props.plaidToken,
bankAccounts: [],
loading: false
}
this.PoBAServer = this.props.PoBAServer

this.fetchBankAccounts = this.fetchBankAccounts.bind(this)
}

async componentDidMount() {
try {
this.PoBAContract = await this.props.getPoBAContract()
await this.fetchBankAccounts(this.state.plaidToken)
} catch (e) {
console.error(ERROR_MSG_CONTRACT_NOT_DEPLOYED, e)
errorAlert(ERROR_MSG_CONTRACT_NOT_DEPLOYED)
}
}

async chooseBankAccount(accountId) {
this.setState({ loading: true })
try {
const { plaidToken, ethAccount } = this.state
const txData = await this.PoBAServer.getSignedBankAccount(accountId, ethAccount, plaidToken)
const registerResult = await this.PoBAContract.registerBankAccount(txData, ethAccount)
if (registerResult) {
successAlert()
// @TODO: set flag in bankAccount to signal that it is verified
// this.getVerifiedBankAccounts(this.state.ethAccount)
} else {
throw new Error(ERROR_MSG_VERIFYING_BANK_ACCOUNT)
}
} catch (e) {
console.error(ERROR_MSG_VERIFYING_BANK_ACCOUNT, e)
errorAlert(ERROR_MSG_VERIFYING_BANK_ACCOUNT)
} finally {
this.setState({ loading: false })
}
}

async fetchBankAccounts(token) {
this.setState({ loading: true })
const accounts = await this.PoBAServer.getBankAccounts(token)
const verifiedBankAccounts = await this.PoBAContract.getVerifiedBankAccounts(
this.state.ethAccount
)
const verifiedBankAccountsNumbers = verifiedBankAccounts.map(account => account[0])
const bankAccounts = accounts.map(account => {
const verified = !!verifiedBankAccountsNumbers.includes(account.account)
return Object.assign({ verified }, account)
})
this.setState({
loading: false,
bankAccounts
})
}

render() {
const { loading, bankAccounts } = this.state
return (
<div className="bank-accounts-page">
<Loading show={loading} />
<PlaidBankAccountsList
bankAccounts={bankAccounts}
onClick={bankAccount => this.chooseBankAccount(bankAccount.account_id)}
/>
</div>
)
}
}

export default PlaidBankAccounts
84 changes: 84 additions & 0 deletions frontend/src/ui/containers/VerifiedBankAccounts.js
@@ -0,0 +1,84 @@
import React, { Component } from 'react'
import Loading from '../presentational/Loading'
import VerifiedBankAccountsList from '../presentational/VerifiedBankAccountsList'
import { errorAlert } from '../presentational/alerts'

const ERROR_MSG_CONTRACT_NOT_DEPLOYED = 'Contract is not deployed on this network'
const ERROR_MSG_REMOVE_BANK_ACCOUNT = 'Error removing the verified bank account'
const ERROR_MSG_GET_BANK_ACCOUNTS = 'Error getting verified bank accounts'

class VerifiedBankAccounts extends Component {
constructor(props) {
super(props)

this.state = {
ethAccount: props.account,
verifiedBankAccounts: [],
loading: false
}

this.getVerifiedBankAccounts = this.getVerifiedBankAccounts.bind(this)
}

async componentDidMount() {
try {
this.PoBAContract = await this.props.getPoBAContract()
await this.getVerifiedBankAccounts(this.state.ethAccount)
} catch (e) {
console.error(ERROR_MSG_CONTRACT_NOT_DEPLOYED, e)
errorAlert(ERROR_MSG_CONTRACT_NOT_DEPLOYED)
}
}

async removeBankAccount(bankAccount) {
this.setState({ loading: true })
try {
const walletAddress = this.state.ethAccount
await this.PoBAContract.unregisterBankAccount(bankAccount, walletAddress)
await this.getVerifiedBankAccounts(walletAddress)
} catch (e) {
errorAlert(ERROR_MSG_REMOVE_BANK_ACCOUNT)
console.error(ERROR_MSG_REMOVE_BANK_ACCOUNT, e)
} finally {
this.setState({ loading: false })
}
}

async getVerifiedBankAccounts(ethAccount) {
this.setState({ loading: true })
try {
const verifiedBankAccountsData = await this.PoBAContract.getVerifiedBankAccounts(ethAccount)
const verifiedBankAccounts = verifiedBankAccountsData.map(bankAccountData => ({
account: bankAccountData[0],
bankName: bankAccountData[1],
identityNames: bankAccountData[2].toString(),
verifiedDate: bankAccountData[3].toString()
}))
this.setState({ verifiedBankAccounts })
} catch (e) {
errorAlert(ERROR_MSG_GET_BANK_ACCOUNTS)
console.error(ERROR_MSG_GET_BANK_ACCOUNTS, e)
} finally {
this.setState({ loading: false })
}
}

render() {
const { loading, verifiedBankAccounts } = this.state
return (
<div>
<Loading show={loading} />
{verifiedBankAccounts.length > 0 ? (
<VerifiedBankAccountsList
bankAccounts={verifiedBankAccounts}
onClick={bankAccount => this.removeBankAccount(bankAccount)}
/>
) : (
<p className="no-results">Could not find bank accounts for the given address.</p>
)}
</div>
)
}
}

export default VerifiedBankAccounts
50 changes: 50 additions & 0 deletions frontend/src/ui/containers/__tests__/PlaidBankAccounts.js
@@ -0,0 +1,50 @@
import React from 'react'
import { configure, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import PlaidBankAccounts from '../PlaidBankAccounts'

configure({ adapter: new Adapter() })

const mockedPoBAServer = {
getSignedBankAccount: () => null,
getBankAccounts: () => {
const mockedAccount = {
account: 'ACCOUNT',
institution: 'BANK'
}
return Promise.resolve([mockedAccount])
}
}
const mockedPoBAContract = {
getVerifiedBankAccounts: () => {
return Promise.resolve([])
},
registerBankAccount: () => {
return Promise.resolve(true)
}
}

describe('PlaidBankAccounts', () => {
const mockedProperties = {
account: 'ACCOUNT',
plaidToken: 'PLAIDTOKEN',
PoBAServer: mockedPoBAServer,
getPoBAContract: () => Promise.resolve(mockedPoBAContract)
}
const wrapper = mount(<PlaidBankAccounts {...mockedProperties} />)
wrapper.instance().componentDidMount()

it('should render the bank accounts list when the PoPA backend responds with back accounts', done => {
wrapper.update()
expect(wrapper.find('.bank-account-list')).toHaveLength(1)
done()
})

it("should invoke backend's getSignedBankAccount & contract's registerBankAccount on Verify", done => {
const spy = jest.spyOn(mockedPoBAServer, 'getSignedBankAccount')
const verifyButtonWrapper = wrapper.find('.bank-account-list-item__verify')
verifyButtonWrapper.simulate('click')
expect(spy).toHaveBeenCalled()
done()
})
})
25 changes: 25 additions & 0 deletions frontend/src/ui/containers/__tests__/PlaidButton.js
@@ -0,0 +1,25 @@
import React from 'react'
import { configure, mount, shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import PlaidButton from '../PlaidButton'

configure({ adapter: new Adapter() })

describe('PlaidButton', () => {
it('renders correctly', () => {
const wrapper = mount(<PlaidButton />)
expect(wrapper.find('.plaid-link-wrapper')).toHaveLength(1)
expect(wrapper.find('PlaidLink')).toHaveLength(1)
})
it('renders a Redirect (to bank accouts list) upon state.plaidToken set', done => {
const wrapper = shallow(<PlaidButton />)
expect(wrapper.find('.plaid-link-wrapper')).toHaveLength(1)
expect(wrapper.find('PlaidLink')).toHaveLength(1)
wrapper.setState({
plaidToken: 'something'
})
wrapper.update()
expect(wrapper.find('Redirect')).toHaveLength(1)
done()
})
})