Initial commit
This commit is contained in:
406
spec/api/v2/account/withdraws_spec.rb
Normal file
406
spec/api/v2/account/withdraws_spec.rb
Normal file
@@ -0,0 +1,406 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Account::Withdraws, type: :request do
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:token) { jwt_for(member) }
|
||||
let(:level_0_member) { create(:member, :level_0) }
|
||||
let(:level_0_member_token) { jwt_for(level_0_member) }
|
||||
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns({'member'=>{'read'=>['Withdraw'],'create'=>['Withdraw']}})
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/account/withdraws' do
|
||||
let!(:btc_withdraws) { create_list(:btc_withdraw, 20, :with_deposit_liability, member: member, updated_at: 5.days.ago) }
|
||||
let!(:usd_withdraws) { create_list(:usd_withdraw, 20, :with_deposit_liability, member: member, updated_at: 2.hour.ago) }
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get '/api/v2/account/withdraws', token: token
|
||||
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
it 'requires authentication' do
|
||||
get '/api/v2/account/withdraws'
|
||||
expect(response.code).to eq '401'
|
||||
end
|
||||
|
||||
it 'validates currency param' do
|
||||
api_get '/api/v2/account/withdraws', params: { currency: 'FOO' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.currency.doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validates page param' do
|
||||
api_get '/api/v2/account/withdraws', params: { page: -1 }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.withdraw.non_positive_page')
|
||||
end
|
||||
|
||||
it 'validates limit param' do
|
||||
api_get '/api/v2/account/withdraws', params: { limit: 9999 }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.withdraw.invalid_limit')
|
||||
end
|
||||
|
||||
it 'validates time_from param' do
|
||||
api_get '/api/v2/account/withdraws', params: { time_from: 'btc' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.withdraw.non_integer_time_from')
|
||||
end
|
||||
|
||||
it 'validates time_to param' do
|
||||
api_get '/api/v2/account/withdraws', params: { time_to: [] }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.withdraw.non_integer_time_to')
|
||||
end
|
||||
|
||||
it 'returns withdraws for all currencies by default' do
|
||||
api_get '/api/v2/account/withdraws', params: { limit: 100 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '40'
|
||||
expect(result.map { |x| x['currency'] }.uniq.sort).to eq %w[ btc usd ]
|
||||
end
|
||||
|
||||
it 'returns withdraws specified currency' do
|
||||
api_get '/api/v2/account/withdraws', params: { currency: 'btc', limit: 100 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '20'
|
||||
expect(result.map { |x| x['currency'] }.uniq.sort).to eq %w[ btc ]
|
||||
end
|
||||
|
||||
|
||||
it 'returns withdraws with txid filter' do
|
||||
api_get '/api/v2/account/withdraws', params: { rid: btc_withdraws.first.rid }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 1
|
||||
expect(result.all? { |d| d['rid'] == btc_withdraws.first.rid }).to be_truthy
|
||||
end
|
||||
|
||||
|
||||
it 'filters withdraws by multiple states' do
|
||||
create(:usd_withdraw, member: member, aasm_state: :rejected)
|
||||
api_get '/api/v2/account/withdraws', params: { state: ['canceled', 'rejected'] }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 1
|
||||
|
||||
create(:usd_withdraw, member: member, aasm_state: :canceled)
|
||||
api_get '/api/v2/account/withdraws', params: { state: ['canceled', 'rejected'] }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 2
|
||||
end
|
||||
|
||||
it 'returns withdraws for the last two days' do
|
||||
api_get '/api/v2/account/withdraws', params: { time_from: 2.days.ago.to_i }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 20
|
||||
expect(response.headers.fetch('Total')).to eq '20'
|
||||
end
|
||||
|
||||
it 'returns withdraws before 2 days ago' do
|
||||
api_get '/api/v2/account/withdraws', params: { time_to: 2.days.ago.to_i }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 20
|
||||
expect(response.headers.fetch('Total')).to eq '20'
|
||||
end
|
||||
|
||||
it 'paginates withdraws' do
|
||||
ordered_withdraws = btc_withdraws.sort_by(&:id).reverse
|
||||
|
||||
api_get '/api/v2/account/withdraws', params: { currency: 'btc', limit: 10, page: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '20'
|
||||
expect(result.first['id']).to eq ordered_withdraws[0].id
|
||||
|
||||
api_get '/api/v2/account/withdraws', params: { currency: 'btc', limit: 10, page: 2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '20'
|
||||
expect(result.first['id']).to eq ordered_withdraws[10].id
|
||||
end
|
||||
|
||||
it 'sorts withdraws' do
|
||||
ordered_withdraws = btc_withdraws.sort_by(&:id).reverse
|
||||
|
||||
api_get '/api/v2/account/withdraws', params: { currency: 'btc', limit: 100 }, token: token
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.map { |x| x['id'] }).to eq ordered_withdraws.map(&:id)
|
||||
end
|
||||
|
||||
it 'denies access to unverified member' do
|
||||
api_get '/api/v2/account/withdraws', token: level_0_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('account.withdraw.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'create withdraw' do
|
||||
let(:currency) { Currency.visible.sample; Currency.find(:usd) }
|
||||
let(:amount) { 0.15 }
|
||||
|
||||
let(:beneficiary) do
|
||||
create(:beneficiary, member: member, state: :active, currency: currency)
|
||||
end
|
||||
|
||||
let :data do
|
||||
{ uid: member.uid,
|
||||
currency: currency.code,
|
||||
amount: amount,
|
||||
beneficiary_id: beneficiary.id,
|
||||
otp: 123456 }
|
||||
end
|
||||
|
||||
let(:account) { member.get_account(currency) }
|
||||
let(:balance) { 1.2 }
|
||||
let(:long_note) { (0...257).map { (65 + rand(26)).chr }.join }
|
||||
before { account.plus_funds(balance) }
|
||||
before { Vault::TOTP.stubs(:validate?).returns(true) }
|
||||
|
||||
context 'disabled account withdrawal API' do
|
||||
before { ENV['ENABLE_ACCOUNT_WITHDRAWAL_API'] = 'false' }
|
||||
after { ENV['ENABLE_ACCOUNT_WITHDRAWAL_API'] = 'true' }
|
||||
it 'doesn\'t allow account withdrawal API call' do
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.disabled_api')
|
||||
end
|
||||
end
|
||||
|
||||
context 'extremely precise values' do
|
||||
before { Currency.any_instance.stubs(:withdraw_fee).returns(BigDecimal(0)) }
|
||||
before { Currency.any_instance.stubs(:precision).returns(16) }
|
||||
it 'keeps precision for amount' do
|
||||
data[:amount] = '0.0000000123456789'
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(201)
|
||||
expect(Withdraw.last.sum.to_s).to eq data[:amount]
|
||||
end
|
||||
end
|
||||
|
||||
it 'validates missing params' do
|
||||
data.except!(:otp, :amount, :currency, :beneficiary_id)
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.missing_otp')
|
||||
expect(response).to include_api_error('account.withdraw.missing_amount')
|
||||
expect(response).to include_api_error('account.withdraw.missing_currency')
|
||||
expect(response).to include_api_error('account.withdraw.missing_beneficiary_id')
|
||||
end
|
||||
|
||||
context 'invalid beneficiary_id' do
|
||||
context 'non-existing' do
|
||||
it do
|
||||
data[:beneficiary_id] = data[:beneficiary_id] + 1
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.beneficiary.doesnt_exist')
|
||||
end
|
||||
end
|
||||
|
||||
context 'archived' do
|
||||
before { beneficiary.update(state: :archived) }
|
||||
it do
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.beneficiary.doesnt_exist')
|
||||
end
|
||||
end
|
||||
|
||||
context 'pending' do
|
||||
before { beneficiary.update(state: :pending) }
|
||||
it do
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.beneficiary.invalid_state_for_withdrawal')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'requires beneficiary_id' do
|
||||
data[:beneficiary_id] = nil
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.empty_beneficiary_id')
|
||||
end
|
||||
|
||||
it 'validates beneficiary_id type' do
|
||||
data[:beneficiary_id] = 'beneficiary_id'
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.non_integer_beneficiary_id')
|
||||
end
|
||||
|
||||
it 'requires otp' do
|
||||
data[:otp] = nil
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.empty_otp')
|
||||
end
|
||||
|
||||
it 'validates otp code' do
|
||||
Vault::TOTP.stubs(:validate?).returns(false)
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.invalid_otp')
|
||||
end
|
||||
|
||||
it 'requires amount' do
|
||||
data[:amount] = nil
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.non_positive_amount')
|
||||
end
|
||||
|
||||
it 'validates negative amount' do
|
||||
data[:amount] = -1
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.non_positive_amount')
|
||||
end
|
||||
|
||||
it 'validates enough balance' do
|
||||
data[:amount] = 100
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.insufficient_balance')
|
||||
end
|
||||
|
||||
it 'validates amount type' do
|
||||
data[:amount] = 'one'
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.non_decimal_amount')
|
||||
end
|
||||
|
||||
it 'validates amount precision' do
|
||||
data[:amount] = 0.123456789123456789
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.invalid_amount')
|
||||
end
|
||||
|
||||
it 'requires currency' do
|
||||
data[:currency] = nil
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.currency.doesnt_exist')
|
||||
end
|
||||
|
||||
it 'disabled currency' do
|
||||
data[:currency] = :eur
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.currency.doesnt_exist')
|
||||
end
|
||||
|
||||
context 'disabled withdrawal for currency' do
|
||||
let(:currency) { Currency.find('btc') }
|
||||
|
||||
before { currency.update!(withdrawal_enabled: false) }
|
||||
|
||||
it 'returns error' do
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('account.currency.withdrawal_disabled')
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates new withdraw and immediately submits it' do
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
record = Withdraw.last
|
||||
expect(record.sum).to eq amount
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(record.account).to eq account
|
||||
expect(record.account.balance).to eq(1.2 - amount)
|
||||
expect(record.account.locked).to eq amount
|
||||
end
|
||||
|
||||
it 'creates new withdraw with note' do
|
||||
api_post '/api/v2/account/withdraws', params: data.merge(note: 'Test note'), token: token
|
||||
expect(response).to have_http_status(201)
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['note']).to eq 'Test note'
|
||||
|
||||
record = Withdraw.last
|
||||
expect(record.note).to eq 'Test note'
|
||||
end
|
||||
|
||||
it 'doesnt create new withdraw with too long note' do
|
||||
api_post '/api/v2/account/withdraws', params: data.merge(note: long_note), token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.withdraw.too_long_note')
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_post '/api/v2/account/withdraws', params: data, token: token
|
||||
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /withdraws/sums' do
|
||||
let!(:btc_withdraws) { create_list(:btc_withdraw, 2, :with_deposit_liability, member: member) }
|
||||
let!(:usd_withdraws) { create_list(:usd_withdraw, 2, :with_deposit_liability, member: member) }
|
||||
|
||||
before do
|
||||
btc_withdraws.map(&:accept!)
|
||||
usd_withdraws.map(&:accept!)
|
||||
end
|
||||
|
||||
it 'returns withdrawals sums' do
|
||||
api_get '/api/v2/account/withdraws/sums', token: token
|
||||
|
||||
expect(response_body.key?('last_24_hours')).to be_truthy
|
||||
expect(response_body.key?('last_1_month')).to be_truthy
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get '/api/v2/account/withdraws/sums', token: token
|
||||
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user