Initial commit
This commit is contained in:
285
spec/api/v2/account/balances_spec.rb
Normal file
285
spec/api/v2/account/balances_spec.rb
Normal file
@@ -0,0 +1,285 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Account::Balances, type: :request do
|
||||
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:deposit_btc) { create(:deposit, :deposit_btc, member: member, amount: 10) }
|
||||
let(:deposit_eth) { create(:deposit, :deposit_eth, member: member, amount: 30.5) }
|
||||
let(:withdraw) { create(:btc_withdraw, member: member, sum: 5) }
|
||||
let(:token) { jwt_for(member) }
|
||||
|
||||
let(:response_body) { { 'currency' => 'eth', 'balance' => '30.5', 'locked' => '0.0', 'deposit_address' => nil } }
|
||||
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns({'member'=>{'read'=>['Operations::Account']}})
|
||||
end
|
||||
|
||||
before do
|
||||
deposit_btc.accept!
|
||||
deposit_btc.process!
|
||||
deposit_btc.dispatch
|
||||
deposit_eth.accept!
|
||||
deposit_eth.process!
|
||||
deposit_eth.dispatch
|
||||
withdraw.accept!
|
||||
end
|
||||
|
||||
describe 'GET api/v2/account/balances' do
|
||||
|
||||
before do
|
||||
member.get_account('usd')
|
||||
member.get_account('eth')
|
||||
member.get_account('trst')
|
||||
member.get_account('ring')
|
||||
member.get_account('eur')
|
||||
Currency.find(:eur).update!(visible: true)
|
||||
end
|
||||
|
||||
context 'all balances' do
|
||||
before { api_get '/api/v2/account/balances', token: token }
|
||||
|
||||
|
||||
it 'returns current user balances' do
|
||||
expect(response).to have_http_status 200
|
||||
result = JSON.parse(response.body)
|
||||
expect(result).to contain_exactly(
|
||||
{ 'currency' => 'btc', 'balance' => '5.0', 'locked' => '5.0', 'deposit_address' => nil },
|
||||
{ 'currency' => 'eth', 'balance' => '30.5', 'locked' => '0.0', 'deposit_address' => nil },
|
||||
{ 'currency' => 'usd', 'balance' => '0.0', 'locked' => '0.0' },
|
||||
{ 'currency' => 'trst', 'balance' => '0.0', 'locked' => '0.0', 'deposit_address' => nil },
|
||||
{ 'currency' => 'ring', 'balance' => '0.0', 'locked' => '0.0', 'deposit_address' => nil },
|
||||
{ 'currency' => 'eur', 'balance' => '0.0', 'locked' => '0.0' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'use nonzero parameter == true' do
|
||||
before { api_get '/api/v2/account/balances', token: token, params: {nonzero: true} }
|
||||
|
||||
it 'returns nonzero balances' do
|
||||
expect(response).to have_http_status 200
|
||||
result = JSON.parse(response.body)
|
||||
expect(result).to contain_exactly(
|
||||
{ 'currency' => 'btc', 'balance' => '5.0', 'locked' => '5.0', 'deposit_address' => nil },
|
||||
{ 'currency' => 'eth', 'balance' => '30.5', 'locked' => '0.0', 'deposit_address' => nil },
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'use nonzero parameter == false' do
|
||||
before { api_get '/api/v2/account/balances', token: token, params: {nonzero: false} }
|
||||
|
||||
it 'returns all balances' do
|
||||
expect(response).to have_http_status 200
|
||||
result = JSON.parse(response.body)
|
||||
expect(result).to contain_exactly(
|
||||
{ 'currency' => 'btc', 'balance' => '5.0', 'locked' => '5.0', 'deposit_address' => nil },
|
||||
{ 'currency' => 'eth', 'balance' => '30.5', 'locked' => '0.0', 'deposit_address' => nil },
|
||||
{ 'currency' => 'usd', 'balance' => '0.0', 'locked' => '0.0' },
|
||||
{ 'currency' => 'trst', 'balance' => '0.0', 'locked' => '0.0', 'deposit_address' => nil },
|
||||
{ 'currency' => 'ring', 'balance' => '0.0', 'locked' => '0.0', 'deposit_address' => nil },
|
||||
{ 'currency' => 'eur', 'balance' => '0.0', 'locked' => '0.0' },
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'use nonzero parameter == string' do
|
||||
before { api_get '/api/v2/account/balances', token: token, params: {nonzero: "token"} }
|
||||
|
||||
|
||||
it 'returns all balances' do
|
||||
expect(response).to have_http_status 422
|
||||
result = JSON.parse(response.body)
|
||||
expect(result).to contain_exactly(["errors", ["account.balances.invalid_nonzero"]])
|
||||
end
|
||||
end
|
||||
|
||||
context 'pagination' do
|
||||
before { api_get '/api/v2/account/balances', {token: token, params: {limit: 2} } }
|
||||
|
||||
it 'limited user balances' do
|
||||
result = JSON.parse(response.body)
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response.headers.fetch('Total').to_i).to eq member.accounts.count
|
||||
|
||||
expect(result.size).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'disable currency' do
|
||||
|
||||
before do
|
||||
Currency.find(:eth).update(visible: false)
|
||||
api_get '/api/v2/account/balances', token: token
|
||||
end
|
||||
|
||||
it 'returns only balances of enabled currencies' do
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.count).to eq 5
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'filters' do
|
||||
context 'currency_code' do
|
||||
it 'filters by currency_code 1' do
|
||||
api_get '/api/v2/account/balances', token: token, params: { search: {currency_code: 't'}}
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('currency')).to contain_exactly('btc', 'eth', 'trst')
|
||||
end
|
||||
|
||||
it 'filters by currency_code 2' do
|
||||
api_get '/api/v2/account/balances', token: token, params: { search: {currency_code: 'TrSt'}}
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('currency')).to contain_exactly('trst')
|
||||
end
|
||||
|
||||
it 'filters by currency_code 3' do
|
||||
api_get '/api/v2/account/balances', token: token, params: { search: {currency_code: 'abc'}}
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.blank?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'currency_name' do
|
||||
it 'filters by currency_name 1' do
|
||||
api_get '/api/v2/account/balances', token: token, params: { search: {currency_name: 'Et'}}
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('currency')).to contain_exactly('eth', 'trst')
|
||||
end
|
||||
|
||||
it 'filters by currency_name 2' do
|
||||
api_get '/api/v2/account/balances', token: token, params: { search: {currency_name: 'dollar'}}
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('currency')).to contain_exactly('usd')
|
||||
end
|
||||
|
||||
it 'filters by currency_name 3' do
|
||||
api_get '/api/v2/account/balances', token: token, params: { search: {currency_name: 'abc'}}
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.blank?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'currency_code & currency_name' do
|
||||
it 'filters by code or name 1' do
|
||||
api_get '/api/v2/account/balances', token: token, params: { search: {currency_name: 'abc', currency_code: 'TrSt'}}
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('currency')).to contain_exactly('trst')
|
||||
end
|
||||
|
||||
it 'filters by code or name 2' do
|
||||
api_get '/api/v2/account/balances', token: token, params: { search: {currency_name: 'Trust', currency_code: 'abc'}}
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('currency')).to contain_exactly('trst')
|
||||
end
|
||||
|
||||
it 'filters by code or name 3' do
|
||||
api_get '/api/v2/account/balances', token: token, params: { search: {currency_name: 'Eu', currency_code: 'ri'}}
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('currency')).to contain_exactly('ring', 'eur', 'eth')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
before { api_get '/api/v2/account/balances', {token: token, params: {limit: 2} } }
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
context 'email changed' do
|
||||
let(:new_member_email) { Faker::Internet.email }
|
||||
|
||||
it do
|
||||
old_member_email = member.email
|
||||
member.email = new_member_email
|
||||
api_get '/api/v2/account/balances', {token: jwt_for(member), params: {limit: 2} }
|
||||
expect(response).to be_successful
|
||||
|
||||
member.reload
|
||||
expect(member.email).to eq new_member_email
|
||||
expect(member.email).to_not eq old_member_email
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET api/v2/account/balances/:currency' do
|
||||
|
||||
before { api_get '/api/v2/account/balances/eth', token: token }
|
||||
|
||||
it 'returns current user balance by currency' do
|
||||
expect(response).to have_http_status 200
|
||||
result = JSON.parse(response.body)
|
||||
expect(result).to match response_body
|
||||
end
|
||||
|
||||
context 'currency code with dot' do
|
||||
let!(:currency) { create(:currency, :xagm_cx) }
|
||||
let!(:account) { ::Account.create(currency_id: 'xagm.cx', member_id: member.id)}
|
||||
|
||||
it 'returns current user balance by currency' do
|
||||
api_get "/api/v2/account/balances/#{currency.code}", token: token
|
||||
|
||||
expect(response).to have_http_status 200
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['currency']).to eq currency.code
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid currency' do
|
||||
|
||||
before { api_get '/api/v2/account/balances/somecoin', token: token }
|
||||
|
||||
it do
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('account.currency.doesnt_exist')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'disable currency' do
|
||||
|
||||
before do
|
||||
Currency.find(:eth).update(visible: false)
|
||||
api_get '/api/v2/account/balances/eth', token: token
|
||||
end
|
||||
|
||||
it do
|
||||
expect(response).to have_http_status 422
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
before { api_get '/api/v2/account/balances/eth', token: token }
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
867
spec/api/v2/account/beneficiaries_spec.rb
Normal file
867
spec/api/v2/account/beneficiaries_spec.rb
Normal file
@@ -0,0 +1,867 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Account::Beneficiaries, 'GET', type: :request do
|
||||
let(:endpoint) { '/api/v2/account/beneficiaries' }
|
||||
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:token) { jwt_for(member) }
|
||||
|
||||
let!(:pending_beneficiaries_for_member) do
|
||||
create_list(:beneficiary, 2, member: member, state: :pending)
|
||||
end
|
||||
|
||||
let!(:active_beneficiaries_for_member) do
|
||||
create_list(:beneficiary, 3, member: member, state: :active)
|
||||
end
|
||||
|
||||
let!(:archived_beneficiaries_for_member) do
|
||||
create_list(:beneficiary, 2, member: member, state: :archived)
|
||||
end
|
||||
|
||||
let!(:other_member_beneficiaries) do
|
||||
create_list(:beneficiary, 5)
|
||||
end
|
||||
|
||||
def response_body
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns({'member'=>{'read'=>['Beneficiary'],'update'=>['Beneficiary'],
|
||||
'create'=> ['Beneficiary'],'destroy'=> ['Beneficiary']}})
|
||||
end
|
||||
|
||||
context 'without JWT' do
|
||||
it do
|
||||
get endpoint
|
||||
expect(response.status).to eq 401
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Not enough level spec.
|
||||
# TODO: Paginate spec.
|
||||
|
||||
context 'without currency and state' do
|
||||
it do
|
||||
api_get endpoint, token: token
|
||||
expect(response.status).to eq 200
|
||||
total_for_member = pending_beneficiaries_for_member.count + active_beneficiaries_for_member.count
|
||||
expect(response_body.size).to eq total_for_member
|
||||
end
|
||||
end
|
||||
|
||||
context 'non-existing currency' do
|
||||
it do
|
||||
api_get endpoint, params: { currency: :uah }, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.currency.doesnt_exist')
|
||||
end
|
||||
end
|
||||
|
||||
context 'existing currency' do
|
||||
let!(:btc_beneficiaries_for_member) do
|
||||
create_list(:beneficiary, 3, member: member)
|
||||
end
|
||||
|
||||
it do
|
||||
api_get endpoint, params: { currency: :btc }, token: token
|
||||
expect(response.status).to eq 200
|
||||
expect(response_body.all? { |b| b['currency'] == 'btc' }).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid state' do
|
||||
it do
|
||||
api_get endpoint, params: { state: :invalid }, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.invalid_state')
|
||||
end
|
||||
end
|
||||
|
||||
context 'existing state' do
|
||||
it do
|
||||
api_get endpoint, params: { state: :pending }, token: token
|
||||
expect(response.status).to eq 200
|
||||
expect(response_body.all? { |b| b['state'] == 'pending' }).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'both currency and state' do
|
||||
let!(:active_btc_beneficiaries_for_member) do
|
||||
create_list(:beneficiary, 3, member: member, state: :active)
|
||||
end
|
||||
|
||||
it do
|
||||
api_get endpoint, params: { currency: :btc, state: :active }, token: token
|
||||
expect(response.status).to eq 200
|
||||
expect(response_body.all? { |b| b['currency'] == 'btc' && b['state'] == 'active' }).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
let!(:active_btc_beneficiaries_for_member) do
|
||||
create_list(:beneficiary, 3, member: member, state: :active)
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get endpoint, params: { currency: :btc, state: :active }, token: token
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe API::V2::Account::Beneficiaries, 'GET /:id', type: :request do
|
||||
let(:endpoint) { '/api/v2/account/beneficiaries' }
|
||||
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:token) { jwt_for(member) }
|
||||
|
||||
def response_body
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
context 'pending beneficiary' do
|
||||
let(:endpoint) { "/api/v2/account/beneficiaries/#{pending_beneficiary.id}" }
|
||||
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member) }
|
||||
|
||||
it do
|
||||
api_get endpoint, token: token
|
||||
expect(response.status).to eq 200
|
||||
expect(response_body['id']).to eq pending_beneficiary.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'active beneficiary' do
|
||||
let(:endpoint) { "/api/v2/account/beneficiaries/#{active_beneficiary.id}" }
|
||||
|
||||
let!(:active_beneficiary) { create(:beneficiary, state: :active, member: member) }
|
||||
|
||||
it do
|
||||
api_get endpoint, token: token
|
||||
expect(response.status).to eq 200
|
||||
expect(response_body['id']).to eq active_beneficiary.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'fiat beneficiary' do
|
||||
let!(:fiat_beneficiary) { create(:beneficiary, currency: Currency.find('usd'), member: member) }
|
||||
let(:endpoint) { "/api/v2/account/beneficiaries/#{fiat_beneficiary.id}" }
|
||||
|
||||
it do
|
||||
api_get endpoint, token: token
|
||||
expect(response.status).to eq 200
|
||||
expect(response_body['id']).to eq fiat_beneficiary.id
|
||||
expect(response_body['data']['account_number']).to eq fiat_beneficiary.masked_account_number
|
||||
end
|
||||
end
|
||||
|
||||
context 'archived beneficiary' do
|
||||
let(:endpoint) { "/api/v2/account/beneficiaries/#{archived_beneficiary.id}" }
|
||||
|
||||
let!(:archived_beneficiary) { create(:beneficiary, state: :archived, member: member) }
|
||||
|
||||
it do
|
||||
api_get endpoint, token: token
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
|
||||
context 'other member beneficiary' do
|
||||
let(:endpoint) { "/api/v2/account/beneficiaries/#{pending_beneficiary.id}" }
|
||||
|
||||
let(:member2) { create(:member, :level_3) }
|
||||
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member2) }
|
||||
|
||||
it do
|
||||
api_get endpoint, token: token
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
let(:endpoint) { "/api/v2/account/beneficiaries/#{pending_beneficiary.id}" }
|
||||
|
||||
let(:member2) { create(:member, :level_3) }
|
||||
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member2) }
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get endpoint, token: token
|
||||
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe API::V2::Account::Beneficiaries, 'POST', type: :request do
|
||||
let(:endpoint) { '/api/v2/account/beneficiaries' }
|
||||
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:token) { jwt_for(member) }
|
||||
|
||||
let(:beneficiary_data) do
|
||||
{
|
||||
currency: :btc,
|
||||
name: 'Personal Bitcoin wallet',
|
||||
description: 'Multisignature Bitcoin Wallet',
|
||||
data: {
|
||||
address: Faker::Blockchain::Bitcoin.address
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def response_body
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
context 'without JWT' do
|
||||
it do
|
||||
post endpoint
|
||||
expect(response.status).to eq 401
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid params' do
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_post endpoint, params: beneficiary_data.merge(description: Faker::String.random(120)), token: token
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
context 'missing required params' do
|
||||
%i[currency name data].each do |rp|
|
||||
context rp do
|
||||
it do
|
||||
api_post endpoint, params: beneficiary_data.except(rp), token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error("account.beneficiary.missing_#{rp}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'currency doesn\'t exist' do
|
||||
it do
|
||||
api_post endpoint, params: beneficiary_data.merge(currency: :uah), token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.currency.doesnt_exist')
|
||||
end
|
||||
end
|
||||
|
||||
context 'name is too long' do
|
||||
it do
|
||||
api_post endpoint, params: beneficiary_data.merge(name: Faker::String.random(65)), token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.too_long_name')
|
||||
end
|
||||
end
|
||||
|
||||
context 'description is too long' do
|
||||
it do
|
||||
api_post endpoint, params: beneficiary_data.merge(description: Faker::String.random(256)), token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.too_long_description')
|
||||
end
|
||||
end
|
||||
|
||||
context 'data has invalid type' do
|
||||
it do
|
||||
api_post endpoint, params: beneficiary_data.merge(data: 'data'), token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.non_json_data')
|
||||
end
|
||||
end
|
||||
|
||||
context 'crypto beneficiary' do
|
||||
context 'nil address in data' do
|
||||
it do
|
||||
beneficiary_data[:data][:address] = nil
|
||||
api_post endpoint, params: beneficiary_data, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.missing_address_in_data')
|
||||
end
|
||||
end
|
||||
|
||||
context 'data without address' do
|
||||
it do
|
||||
beneficiary_data[:data].delete(:address)
|
||||
beneficiary_data[:data][:memo] = :memo
|
||||
|
||||
api_post endpoint, params: beneficiary_data, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.missing_address_in_data')
|
||||
end
|
||||
end
|
||||
|
||||
context 'disabled withdrawal for currency' do
|
||||
let(:currency) { Currency.find(:btc) }
|
||||
before do
|
||||
currency.update(withdrawal_enabled: false)
|
||||
end
|
||||
it do
|
||||
api_post endpoint, params: beneficiary_data, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.currency.withdrawal_disabled')
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid character in address' do
|
||||
before do
|
||||
beneficiary_data[:data][:address] = "'" + Faker::Blockchain::Bitcoin.address
|
||||
end
|
||||
it do
|
||||
api_post endpoint, params: beneficiary_data, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.failed_to_create')
|
||||
end
|
||||
end
|
||||
|
||||
context 'duplicated address' do
|
||||
context 'same currency' do
|
||||
before do
|
||||
create(:beneficiary,
|
||||
member: member,
|
||||
currency_id: beneficiary_data[:currency],
|
||||
data: {address: beneficiary_data.dig(:data, :address)})
|
||||
end
|
||||
|
||||
it do
|
||||
api_post endpoint, params: beneficiary_data, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.duplicate_address')
|
||||
end
|
||||
end
|
||||
|
||||
context 'different currencies' do
|
||||
before do
|
||||
create(:beneficiary,
|
||||
member: member,
|
||||
currency_id: :eth,
|
||||
data: {address: beneficiary_data.dig(:data, :address)})
|
||||
end
|
||||
|
||||
it do
|
||||
api_post endpoint, params: beneficiary_data, token: token
|
||||
expect(response.status).to eq 201
|
||||
end
|
||||
end
|
||||
|
||||
context 'truncates spaces in address' do
|
||||
let(:address) { Faker::Blockchain::Bitcoin.address }
|
||||
|
||||
before do
|
||||
beneficiary_data[:data][:address] = " " + address + " "
|
||||
end
|
||||
it do
|
||||
api_post endpoint, params: beneficiary_data, token: token
|
||||
expect(response.status).to eq 201
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(Beneficiary.find(result['id']).data['address']).to eq(address)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'destination tag in address' do
|
||||
before do
|
||||
beneficiary_data[:data][:address] = Faker::Blockchain::Bitcoin.address + "?dt=4"
|
||||
end
|
||||
it do
|
||||
api_post endpoint, params: beneficiary_data, token: token
|
||||
expect(response.status).to eq 201
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(Beneficiary.find(result['id']).data['address']).to eq(beneficiary_data[:data][:address])
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Test nil full_name in data for both fiat and crypto.
|
||||
end
|
||||
|
||||
context 'fiat beneficiary' do
|
||||
let(:fiat_beneficiary_data) do
|
||||
{
|
||||
currency: :usd,
|
||||
name: Faker::Bank.name,
|
||||
description: Faker::Company.catch_phrase,
|
||||
data: generate(:fiat_beneficiary_data)
|
||||
}
|
||||
end
|
||||
|
||||
context 'nil address in data' do
|
||||
it do
|
||||
fiat_beneficiary_data[:data].delete(:address)
|
||||
api_post endpoint, params: fiat_beneficiary_data, token: token
|
||||
expect(response.status).to eq 201
|
||||
expect(response_body['data']['account_number']).not_to eq fiat_beneficiary_data[:data][:account_number]
|
||||
end
|
||||
end
|
||||
|
||||
context 'nil data' do
|
||||
it do
|
||||
fiat_beneficiary_data[:data] = nil
|
||||
api_post endpoint, params: fiat_beneficiary_data.except(:data), token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.empty_data')
|
||||
end
|
||||
end
|
||||
|
||||
context 'duplicated address' do
|
||||
context 'same currency' do
|
||||
before do
|
||||
create(:beneficiary,
|
||||
member: member,
|
||||
currency_id: fiat_beneficiary_data[:currency],
|
||||
data: fiat_beneficiary_data[:data])
|
||||
end
|
||||
|
||||
it do
|
||||
api_post endpoint, params: fiat_beneficiary_data, token: token
|
||||
expect(response.status).to eq 201
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'valid params' do
|
||||
it 'creates beneficiary for member' do
|
||||
expect do
|
||||
api_post endpoint, params: beneficiary_data, token: token
|
||||
end.to change{ member.beneficiaries.count }.by(1)
|
||||
end
|
||||
|
||||
it 'creates beneficiary with pending state' do
|
||||
api_post endpoint, params: beneficiary_data, token: token
|
||||
expect(response.status).to eq 201
|
||||
id = response_body['id']
|
||||
expect(Beneficiary.find_by!(id: id).state).to eq 'pending'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe API::V2::Account::Beneficiaries, 'PATCH /activate', type: :request do
|
||||
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:token) { jwt_for(member) }
|
||||
|
||||
def response_body
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
context 'invalid params' do
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member) }
|
||||
|
||||
let(:activation_data) do
|
||||
{ id: pending_beneficiary.id,
|
||||
pin: pending_beneficiary.pin }
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{pending_beneficiary.id}/activate"
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_patch endpoint, params: activation_data, token: token
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
context 'id has invalid type' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/id/activate"
|
||||
end
|
||||
|
||||
it do
|
||||
api_patch endpoint, params: activation_data.merge(id: :id), token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.non_integer_id')
|
||||
end
|
||||
end
|
||||
|
||||
context 'pin has invalid type' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{pending_beneficiary.id}/activate"
|
||||
end
|
||||
|
||||
it do
|
||||
api_patch endpoint, params: activation_data.merge(pin: :pin), token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.non_integer_pin')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'pending beneficiary' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{pending_beneficiary.id}/activate"
|
||||
end
|
||||
|
||||
let(:activation_data) do
|
||||
{ id: pending_beneficiary.id,
|
||||
pin: pending_beneficiary.pin }
|
||||
end
|
||||
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member) }
|
||||
|
||||
context 'valid pin' do
|
||||
it do
|
||||
api_patch endpoint, params: activation_data, token: token
|
||||
expect(response.status).to eq 200
|
||||
expect(response_body['id']).to eq pending_beneficiary.id
|
||||
expect(response_body['state']).to eq 'active'
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid pin' do
|
||||
it do
|
||||
activation_data[:pin] = activation_data[:pin] + 1
|
||||
api_patch endpoint, params: activation_data, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.invalid_pin')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'active beneficiary' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{active_beneficiary.id}/activate"
|
||||
end
|
||||
|
||||
let(:activation_data) do
|
||||
{ id: active_beneficiary.id,
|
||||
pin: active_beneficiary.pin }
|
||||
end
|
||||
|
||||
let!(:active_beneficiary) { create(:beneficiary, state: :active, member: member) }
|
||||
|
||||
context 'valid pin' do
|
||||
it do
|
||||
api_patch endpoint, params: activation_data, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.cant_activate')
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid pin' do
|
||||
it do
|
||||
activation_data[:pin] = activation_data[:pin] + 1
|
||||
api_patch endpoint, params: activation_data, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.cant_activate')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'archived beneficiary' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{archived_beneficiary.id}/activate"
|
||||
end
|
||||
|
||||
let(:activation_data) do
|
||||
{ id: archived_beneficiary.id,
|
||||
pin: archived_beneficiary.pin }
|
||||
end
|
||||
|
||||
let!(:archived_beneficiary) { create(:beneficiary, state: :archived, member: member) }
|
||||
|
||||
context 'any pin' do
|
||||
it do
|
||||
api_patch endpoint, params: activation_data, token: token
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'other user beneficiary' do
|
||||
let(:member2) { create(:member, :level_3) }
|
||||
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{pending_beneficiary.id}/activate"
|
||||
end
|
||||
|
||||
let(:activation_data) do
|
||||
{ id: pending_beneficiary.id,
|
||||
pin: pending_beneficiary.pin }
|
||||
end
|
||||
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member2) }
|
||||
|
||||
context 'any pin' do
|
||||
it do
|
||||
api_patch endpoint, params: activation_data, token: token
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe API::V2::Account::Beneficiaries, 'PATCH /resend_pin', type: :request do
|
||||
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:token) { jwt_for(member) }
|
||||
|
||||
def response_body
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
context 'invalid params' do
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member) }
|
||||
|
||||
let(:resend_data) do
|
||||
{ id: pending_beneficiary.id }
|
||||
end
|
||||
|
||||
context 'id has invalid type' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/id/resend_pin"
|
||||
end
|
||||
|
||||
it do
|
||||
api_patch endpoint, params: resend_data.merge(id: :id), token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.non_integer_id')
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{pending_beneficiary.id}/resend_pin"
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_patch endpoint, params: resend_data, token: token
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'pending beneficiary' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{pending_beneficiary.id}/resend_pin"
|
||||
end
|
||||
|
||||
let(:resend_data) do
|
||||
{ id: pending_beneficiary.id }
|
||||
end
|
||||
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member) }
|
||||
|
||||
context '1 minute from last request on create or resend passed' do
|
||||
it do
|
||||
pending_beneficiary.update(sent_at: 1.minute.ago)
|
||||
api_patch endpoint, params: resend_data, token: token
|
||||
expect(response.status).to eq 204
|
||||
end
|
||||
end
|
||||
|
||||
context '1 minute from last request on create or resend not passed' do
|
||||
it do
|
||||
api_patch endpoint, params: resend_data, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.cant_resend_within_1_minute')
|
||||
expect(response_body.include?("sent_at")).to eq true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'active beneficiary' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{active_beneficiary.id}/resend_pin"
|
||||
end
|
||||
|
||||
let(:resend_data) do
|
||||
{ id: active_beneficiary.id }
|
||||
end
|
||||
|
||||
let!(:active_beneficiary) { create(:beneficiary, state: :active, member: member) }
|
||||
|
||||
context '1 minute from last request on create or resend passed' do
|
||||
it do
|
||||
api_patch endpoint, params: resend_data, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.cant_resend')
|
||||
end
|
||||
end
|
||||
|
||||
context '1 minute from last request on create or resend not passed' do
|
||||
it do
|
||||
api_patch endpoint, params: resend_data, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.cant_resend')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'archived beneficiary' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{archived_beneficiary.id}/resend_pin"
|
||||
end
|
||||
|
||||
let(:resend_data) do
|
||||
{ id: archived_beneficiary.id }
|
||||
end
|
||||
|
||||
let!(:archived_beneficiary) { create(:beneficiary, state: :archived, member: member) }
|
||||
|
||||
it do
|
||||
api_patch endpoint, params: resend_data, token: token
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
|
||||
context 'other user beneficiary' do
|
||||
let(:member2) { create(:member, :level_3) }
|
||||
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{pending_beneficiary.id}/resend_pin"
|
||||
end
|
||||
|
||||
let(:activation_data) do
|
||||
{ id: pending_beneficiary.id,
|
||||
pin: pending_beneficiary.pin }
|
||||
end
|
||||
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member2) }
|
||||
|
||||
it do
|
||||
api_patch endpoint, params: activation_data, token: token
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe API::V2::Account::Beneficiaries, 'DELETE /:id', type: :request do
|
||||
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:token) { jwt_for(member) }
|
||||
|
||||
def response_body
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
context 'invalid params' do
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member) }
|
||||
|
||||
let(:activation_data) do
|
||||
{ id: pending_beneficiary.id,
|
||||
pin: pending_beneficiary.pin }
|
||||
end
|
||||
|
||||
context 'id has invalid type' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/id"
|
||||
end
|
||||
|
||||
it do
|
||||
api_delete endpoint, token: token
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('account.beneficiary.non_integer_id')
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{pending_beneficiary.id}"
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_delete endpoint, token: token
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'pending beneficiary' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{pending_beneficiary.id}"
|
||||
end
|
||||
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member) }
|
||||
|
||||
it do
|
||||
api_delete endpoint, token: token
|
||||
expect(response.status).to eq 204
|
||||
expect(response.body).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'active beneficiary' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{active_beneficiary.id}"
|
||||
end
|
||||
|
||||
let!(:active_beneficiary) { create(:beneficiary, state: :active, member: member) }
|
||||
|
||||
it do
|
||||
api_delete endpoint, token: token
|
||||
expect(response.status).to eq 204
|
||||
expect(response.body).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'archived beneficiary' do
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{archived_beneficiary.id}"
|
||||
end
|
||||
|
||||
let!(:archived_beneficiary) { create(:beneficiary, state: :archived, member: member) }
|
||||
|
||||
it do
|
||||
api_delete endpoint, token: token
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
|
||||
context 'other user beneficiary' do
|
||||
let(:member2) { create(:member, :level_3) }
|
||||
|
||||
let(:endpoint) do
|
||||
"/api/v2/account/beneficiaries/#{pending_beneficiary.id}"
|
||||
end
|
||||
|
||||
let!(:pending_beneficiary) { create(:beneficiary, member: member2) }
|
||||
|
||||
it do
|
||||
api_delete endpoint, token: token
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
271
spec/api/v2/account/deposits_spec.rb
Normal file
271
spec/api/v2/account/deposits_spec.rb
Normal file
@@ -0,0 +1,271 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Account::Deposits, type: :request do
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:other_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'=>['Deposit', 'PaymentAddress']}})
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/account/deposits' do
|
||||
before do
|
||||
create(:deposit_btc, member: member, updated_at: 5.days.ago)
|
||||
create(:deposit_usd, member: member, updated_at: 5.days.ago)
|
||||
create(:deposit_usd, member: member, txid: 1, amount: 520, updated_at: 5.hour.ago)
|
||||
create(:deposit_btc, member: member, txid: 'test', amount: 111, updated_at: 2.hour.ago)
|
||||
create(:deposit_usd, member: other_member, txid: 10)
|
||||
end
|
||||
|
||||
it 'requires authentication' do
|
||||
api_get '/api/v2/account/deposits'
|
||||
expect(response.code).to eq '401'
|
||||
end
|
||||
|
||||
it 'returns with auth token deposits' do
|
||||
api_get '/api/v2/account/deposits', token: token
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'returns all deposits num' do
|
||||
api_get '/api/v2/account/deposits', token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 4
|
||||
|
||||
expect(response.headers.fetch('Total')).to eq '4'
|
||||
end
|
||||
|
||||
it 'returns limited deposits' do
|
||||
api_get '/api/v2/account/deposits', params: { limit: 2, page: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 2
|
||||
expect(response.headers.fetch('Total')).to eq '4'
|
||||
|
||||
api_get '/api/v2/account/deposits', params: { limit: 1, page: 2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 1
|
||||
expect(response.headers.fetch('Total')).to eq '4'
|
||||
end
|
||||
|
||||
it 'filters deposits by state' do
|
||||
api_get '/api/v2/account/deposits', params: { state: 'canceled' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 0
|
||||
|
||||
d = create(:deposit_btc, member: member, aasm_state: :canceled)
|
||||
api_get '/api/v2/account/deposits', params: { state: 'canceled' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['txid']).to eq d.txid
|
||||
end
|
||||
|
||||
it 'filters deposits by multiple states' do
|
||||
create(:deposit_btc, member: member, aasm_state: :rejected)
|
||||
api_get '/api/v2/account/deposits', params: { state: ['canceled', 'rejected'] }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 1
|
||||
|
||||
create(:deposit_btc, member: member, aasm_state: :canceled)
|
||||
api_get '/api/v2/account/deposits', params: { state: ['canceled', 'rejected'] }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 2
|
||||
end
|
||||
|
||||
it 'returns deposits for the last two days' do
|
||||
api_get '/api/v2/account/deposits', params: { limit: 5, page: 1, time_from: 2.days.ago.to_i }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 2
|
||||
expect(response.headers.fetch('Total')).to eq '2'
|
||||
end
|
||||
|
||||
it 'returns deposits before 2 days ago' do
|
||||
api_get '/api/v2/account/deposits', params: { time_to: 2.days.ago.to_i }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 2
|
||||
expect(response.headers.fetch('Total')).to eq '2'
|
||||
end
|
||||
|
||||
it 'returns deposits for currency usd' do
|
||||
api_get '/api/v2/account/deposits', params: { currency: 'usd' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 2
|
||||
expect(result.all? { |d| d['currency'] == 'usd' }).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns deposits with txid filter' do
|
||||
api_get '/api/v2/account/deposits', params: { txid: Deposit.first.txid }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 1
|
||||
expect(result.all? { |d| d['txid'] == Deposit.first.txid }).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns deposits for currency btc' do
|
||||
api_get '/api/v2/account/deposits', params: { currency: 'btc' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response.headers.fetch('Total')).to eq '2'
|
||||
expect(result.all? { |d| d['currency'] == 'btc' }).to be_truthy
|
||||
end
|
||||
|
||||
it 'return 404 if txid not exist' do
|
||||
api_get '/api/v2/account/deposits/5', token: token
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'returns 404 if txid not belongs_to you ' do
|
||||
api_get '/api/v2/account/deposits/10', token: token
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'returns deposit txid if exist' do
|
||||
api_get '/api/v2/account/deposits/1', token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response.code).to eq '200'
|
||||
expect(result['amount']).to eq '520.0'
|
||||
end
|
||||
|
||||
it 'returns deposit no time limit ' do
|
||||
api_get '/api/v2/account/deposits/test', token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response.code).to eq '200'
|
||||
expect(result['amount']).to eq '111.0'
|
||||
end
|
||||
|
||||
it 'denies access to unverified member' do
|
||||
api_get '/api/v2/account/deposits', token: level_0_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('account.deposit.not_permitted')
|
||||
end
|
||||
|
||||
context 'fail' do
|
||||
it 'validates time_from param' do
|
||||
api_get '/api/v2/account/deposits', params: { time_from: 'btc' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.deposit.non_integer_time_from')
|
||||
end
|
||||
|
||||
it 'validates time_to param' do
|
||||
api_get '/api/v2/account/deposits', params: { time_to: [] }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.deposit.non_integer_time_to')
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get '/api/v2/account/deposits/test', token: token
|
||||
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/account/deposit_address/:currency' do
|
||||
let(:currency) { :bch }
|
||||
|
||||
context 'failed' do
|
||||
it 'validates currency' do
|
||||
api_get "/api/v2/account/deposit_address/dildocoin", token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('account.currency.doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validates currency address format' do
|
||||
api_get '/api/v2/account/deposit_address/btc', params: { address_format: 'cash' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('account.deposit_address.doesnt_support_cash_address_format')
|
||||
end
|
||||
|
||||
it 'validates currency with address_format param' do
|
||||
api_get '/api/v2/account/deposit_address/abc', params: { address_format: 'cash' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('account.currency.doesnt_exist')
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get '/api/v2/account/deposit_address/btc', token: token
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'successful' do
|
||||
context 'eth address' do
|
||||
let(:currency) { :eth }
|
||||
let(:wallet) { Wallet.deposit.joins(:currencies).find_by(currencies: { id: currency }) }
|
||||
before { member.payment_address(wallet.id).update!(address: '2N2wNXrdo4oEngp498XGnGCbru29MycHogR') }
|
||||
|
||||
it 'expose data about eth address' do
|
||||
api_get "/api/v2/account/deposit_address/#{currency}", token: token
|
||||
expect(response.body).to eq '{"currencies":["eth"],"address":"2n2wnxrdo4oengp498xgngcbru29mychogr","state":"active"}'
|
||||
end
|
||||
|
||||
it 'pending user address state' do
|
||||
member.payment_address(wallet.id).update!(address: nil)
|
||||
api_get "/api/v2/account/deposit_address/#{currency}", token: token
|
||||
expect(response.body).to eq '{"currencies":["eth"],"address":null,"state":"pending"}'
|
||||
end
|
||||
|
||||
context 'currency code with dot' do
|
||||
let!(:currency) { create(:currency, :xagm_cx) }
|
||||
|
||||
it 'returns information about specified deposit address' do
|
||||
api_get "/api/v2/account/deposit_address/#{currency.code}", token: token
|
||||
expect(response).to have_http_status 200
|
||||
expect(response.body).to eq '{"currencies":["eth","xagm.cx"],"address":"2n2wnxrdo4oengp498xgngcbru29mychogr","state":"active"}'
|
||||
end
|
||||
end
|
||||
|
||||
it 'exposes non-remote addresses' do
|
||||
member.payment_address(wallet.id).update!(remote: true)
|
||||
api_get "/api/v2/account/deposit_address/#{currency}", token: token
|
||||
expect(response.body).to eq '{"currencies":["eth"],"address":null,"state":"pending"}'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'disabled deposit for currency' do
|
||||
let(:currency) { :btc }
|
||||
|
||||
before { Currency.find(currency).update!(deposit_enabled: false) }
|
||||
|
||||
it 'returns error' do
|
||||
api_get "/api/v2/account/deposit_address/#{currency}", token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('account.currency.deposit_disabled')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
196
spec/api/v2/account/internal_transfer_spec.rb
Normal file
196
spec/api/v2/account/internal_transfer_spec.rb
Normal file
@@ -0,0 +1,196 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Account::InternalTransfers, type: :request do
|
||||
let(:endpoint) { '/api/v2/account/internal_transfers' }
|
||||
let(:member) { create(:member, :level_3, email: 'example@gmail.com', uid: 'ID73BF61C8H0', username: 'membertest') }
|
||||
let(:member_receiver) { create(:member, :level_3, email: 'receiver@gmail.com', uid: 'ID84BF61C8H0', username: 'test1') }
|
||||
let(:token) { jwt_for(member) }
|
||||
|
||||
describe 'GET /api/v2/account/internal_transfers' do
|
||||
let!(:internal_transfer_btc) { create_list(:internal_transfer_btc, 4, :with_deposit_liability, sender: member) }
|
||||
let!(:internal_transfer_usd) { create_list(:internal_transfer_usd, 6, :with_deposit_liability, sender: member_receiver) }
|
||||
let!(:internal_transfer_usd) { create_list(:internal_transfer_usd, 3, :with_deposit_liability, receiver: member) }
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get endpoint, token: token, params: { limit: 100 }
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
it 'requires authentication' do
|
||||
get endpoint
|
||||
expect(response.code).to eq '401'
|
||||
end
|
||||
|
||||
it 'validates currency param' do
|
||||
api_get endpoint, params: { currency: 'FOO' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.currency.doesnt_exist')
|
||||
end
|
||||
|
||||
it 'returns internal transfers for all currencies by default' do
|
||||
api_get endpoint, params: { limit: 100 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '7'
|
||||
expect(result.map { |x| x['currency'] }.uniq.sort).to eq %w[ btc usd ]
|
||||
end
|
||||
|
||||
it 'returns all internal transfers' do
|
||||
api_get endpoint, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 7
|
||||
expect(response.headers.fetch('Total')).to eq '7'
|
||||
end
|
||||
|
||||
it 'returns internal transfers of BTC currency' do
|
||||
api_get endpoint, params: { currency: 'btc' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.count).to eq 4
|
||||
end
|
||||
|
||||
it 'returns internal transfers of USD currency' do
|
||||
api_get endpoint, params: { currency: 'usd' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.count).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
describe "create internal transfer" do
|
||||
let(:currency) { Currency.visible.sample; Currency.find(:eth) }
|
||||
let(:amount) { 0.15 }
|
||||
|
||||
let :data do
|
||||
{ username_or_uid: member_receiver.uid,
|
||||
currency: currency.code,
|
||||
amount: amount,
|
||||
otp: 123456 }
|
||||
end
|
||||
|
||||
let(:account) { member.get_account(currency) }
|
||||
let(:balance) { 1.2 }
|
||||
before { account.plus_funds(balance) }
|
||||
|
||||
before { Vault::TOTP.stubs(:validate?).returns(true) }
|
||||
|
||||
it 'validates missing params' do
|
||||
data.except!(:otp, :amount, :currency, :username_or_uid)
|
||||
api_post endpoint, params: data, token: token
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.internaltransfer.missing_otp')
|
||||
expect(response).to include_api_error('account.internaltransfer.missing_amount')
|
||||
expect(response).to include_api_error('account.internaltransfer.missing_currency')
|
||||
expect(response).to include_api_error('account.internaltransfer.empty_username_or_uid')
|
||||
end
|
||||
|
||||
it 'requires otp' do
|
||||
data[:otp] = nil
|
||||
api_post endpoint, params: data, token: token
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.internaltransfer.empty_otp')
|
||||
end
|
||||
|
||||
it 'validates otp code' do
|
||||
Vault::TOTP.stubs(:validate?).returns(false)
|
||||
api_post endpoint, params: data, token: token
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.internal_transfer.invalid_otp')
|
||||
end
|
||||
|
||||
it 'requires amount' do
|
||||
data[:amount] = nil
|
||||
api_post endpoint, params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.internal_transfer.non_positive_amount')
|
||||
end
|
||||
|
||||
it 'validates negative amount' do
|
||||
data[:amount] = -1
|
||||
api_post endpoint, params: data, token: token
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.internal_transfer.non_positive_amount')
|
||||
end
|
||||
|
||||
it 'validates enough balance' do
|
||||
data[:amount] = 100
|
||||
api_post endpoint, params: data, token: token
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.internal_transfer.insufficient_balance')
|
||||
end
|
||||
|
||||
it 'validates amount type' do
|
||||
data[:amount] = 'one'
|
||||
api_post endpoint, params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.internal_transfer.non_decimal_amount')
|
||||
end
|
||||
|
||||
it 'requires currency' do
|
||||
data[:currency] = nil
|
||||
api_post endpoint, params: data, token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.currency.doesnt_exist')
|
||||
end
|
||||
|
||||
it 'creates new internal_transfer' do
|
||||
api_post endpoint, params: data, token: token
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
record = InternalTransfer.last
|
||||
|
||||
expect(record.sender_id).to eq member.id
|
||||
expect(record.receiver_id).to eq member_receiver.id
|
||||
expect(record.amount).to eq amount
|
||||
expect(record.currency).to eq currency
|
||||
end
|
||||
|
||||
it 'creates new internal_transfer using username' do
|
||||
data[:username_or_uid] = member_receiver.username
|
||||
api_post endpoint, params: data, token: token
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
record = InternalTransfer.last
|
||||
|
||||
expect(record.sender_id).to eq member.id
|
||||
expect(record.receiver_id).to eq member_receiver.id
|
||||
expect(record.amount).to eq amount
|
||||
expect(record.currency).to eq currency
|
||||
end
|
||||
|
||||
it 'should change balance for receiver and sender after transfer' do
|
||||
api_post endpoint, params: data, token: token
|
||||
account.reload.balance
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
record = InternalTransfer.last
|
||||
|
||||
expect(account.balance).to eq (balance - amount)
|
||||
expect(member_receiver.get_account(currency.code).balance).to eq amount
|
||||
end
|
||||
|
||||
it 'not allow create new internal_transfer to yourself' do
|
||||
data[:username_or_uid] = member.uid
|
||||
api_post endpoint, params: data, token: token
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('account.internal_transfer.can_not_tranfer_to_yourself')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
103
spec/api/v2/account/stats_spec.rb
Normal file
103
spec/api/v2/account/stats_spec.rb
Normal file
@@ -0,0 +1,103 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Account::Stats, type: :request do
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:token) { jwt_for(member) }
|
||||
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns({'member'=>{'read'=>['StatsMemberPnl']}})
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/account/stats/pnl' do
|
||||
let!(:eth) { Currency.find('eth') }
|
||||
let!(:btc) { Currency.find('btc') }
|
||||
let!(:pnl1) { create(:stats_member_pnl, pnl_currency_id: eth.id, currency_id: btc.id,
|
||||
total_credit: 0.1, total_credit_fees: 0.01, total_debit_fees: 0.02, total_credit_value: 0.3, total_debit: 0.2,
|
||||
total_debit_value: 10.0, average_balance_price: 0.42, member: member)}
|
||||
|
||||
let!(:pnl2) { create(:stats_member_pnl, pnl_currency_id: btc.id, currency_id: eth.id,
|
||||
total_credit: 0.1, total_credit_fees: 0.01, total_debit_fees: 0.02, total_credit_value: 0.3, total_debit: 0.2,
|
||||
total_debit_value: 10.0, average_balance_price: 0.21, member: member)}
|
||||
|
||||
it 'returns all user pnls for all pnl currencies' do
|
||||
api_get '/api/v2/account/stats/pnl', token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response_body.count).to eq 2
|
||||
expect(response_body[0]['currency']).to eq(pnl1.currency_id)
|
||||
expect(response_body[0]['pnl_currency']).to eq(pnl1.pnl_currency_id)
|
||||
expect(response_body[0]['total_credit'].to_f).to eq(pnl1.total_credit)
|
||||
expect(response_body[0]['total_credit_value'].to_f).to eq(pnl1.total_credit_value)
|
||||
expect(response_body[0]['total_debit'].to_f).to eq(pnl1.total_debit)
|
||||
expect(response_body[0]['total_debit_value'].to_f).to eq(pnl1.total_debit_value)
|
||||
expect(response_body[0]['average_buy_price'].to_f.round(9)).to eq( (pnl1.total_credit_value / (pnl1.total_credit)).to_f)
|
||||
expect(response_body[0]['average_sell_price'].to_f.round(9)).to eq(pnl1.total_debit_value / (pnl1.total_debit))
|
||||
expect(response_body[0]['average_balance_price'].to_f).to eq(0.42)
|
||||
|
||||
expect(response_body[1]['currency']).to eq(pnl2.currency_id)
|
||||
expect(response_body[1]['pnl_currency']).to eq(pnl2.pnl_currency_id)
|
||||
expect(response_body[1]['total_credit'].to_f).to eq(pnl2.total_credit)
|
||||
expect(response_body[1]['total_credit_value'].to_f).to eq(pnl2.total_credit_value)
|
||||
expect(response_body[1]['total_debit'].to_f).to eq(pnl2.total_debit)
|
||||
expect(response_body[1]['total_debit_value'].to_f).to eq(pnl2.total_debit_value)
|
||||
expect(response_body[1]['average_buy_price'].to_f.round(9)).to eq( (pnl2.total_credit_value / (pnl2.total_credit)).to_f)
|
||||
expect(response_body[1]['average_sell_price'].to_f.round(9)).to eq(pnl2.total_debit_value / (pnl2.total_debit))
|
||||
expect(response_body[1]['average_balance_price'].to_f).to eq(0.21)
|
||||
end
|
||||
|
||||
it 'returns user pnls for pnl currency eth' do
|
||||
api_get '/api/v2/account/stats/pnl?pnl_currency=eth', token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response_body.count).to eq 1
|
||||
expect(response_body[0]['currency']).to eq(pnl1.currency_id)
|
||||
expect(response_body[0]['pnl_currency']).to eq(pnl1.pnl_currency_id)
|
||||
expect(response_body[0]['total_credit'].to_f).to eq(pnl1.total_credit)
|
||||
expect(response_body[0]['total_credit_value'].to_f).to eq(pnl1.total_credit_value)
|
||||
expect(response_body[0]['total_debit'].to_f).to eq(pnl1.total_debit)
|
||||
expect(response_body[0]['total_debit_value'].to_f).to eq(pnl1.total_debit_value)
|
||||
expect(response_body[0]['average_buy_price'].to_f.round(9)).to eq( (pnl1.total_credit_value / (pnl1.total_credit)).to_f)
|
||||
expect(response_body[0]['average_sell_price'].to_f.round(9)).to eq(pnl1.total_debit_value / (pnl1.total_debit))
|
||||
expect(response_body[0]['average_balance_price'].to_f).to eq(0.42)
|
||||
end
|
||||
|
||||
context 'avarage sell price equal to 0' do
|
||||
let!(:usd) { Currency.find('usd') }
|
||||
let!(:pnl) { create(:stats_member_pnl, pnl_currency_id: usd.id, currency_id: btc.id,
|
||||
total_credit: 0.1, total_credit_fees: 0.01, total_debit_fees: 0.0, total_credit_value: 0.3, total_debit: 0.0,
|
||||
total_debit_value: 0.0, average_balance_price: 0.12, member: member)}
|
||||
|
||||
it 'return user pnl with zero avarage sell price' do
|
||||
api_get '/api/v2/account/stats/pnl?pnl_currency=usd', token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response_body.count).to eq 1
|
||||
expect(response_body[0]['currency']).to eq(pnl.currency_id)
|
||||
expect(response_body[0]['pnl_currency']).to eq(pnl.pnl_currency_id)
|
||||
expect(response_body[0]['total_credit'].to_f).to eq(pnl.total_credit)
|
||||
expect(response_body[0]['total_credit_value'].to_f).to eq(pnl.total_credit_value)
|
||||
expect(response_body[0]['total_debit'].to_f).to eq(pnl.total_debit)
|
||||
expect(response_body[0]['total_debit_value'].to_f).to eq(pnl.total_debit_value)
|
||||
expect(response_body[0]['average_buy_price'].to_f.round(9)).to eq( (pnl.total_credit_value / (pnl.total_credit)).to_f)
|
||||
expect(response_body[0]['average_sell_price'].to_f.round(9)).to eq 0
|
||||
expect(response_body[0]['average_balance_price'].to_f).to eq(0.12)
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get '/api/v2/account/stats/pnl?pnl_currency=usd', token: token
|
||||
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
296
spec/api/v2/account/transactions_spec.rb
Normal file
296
spec/api/v2/account/transactions_spec.rb
Normal file
@@ -0,0 +1,296 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Account::Transactions, type: :request do
|
||||
describe 'GET /api/v2/account/transactions' do
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:token) { jwt_for(member) }
|
||||
let(:btc_account) { member.get_account('btc') }
|
||||
let(:usd_account) { member.get_account('usd') }
|
||||
let(:balance) { 100000 }
|
||||
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns({'member'=>{'read'=>['Deposit', 'Withdraw']}})
|
||||
end
|
||||
|
||||
context 'successful' do
|
||||
before do
|
||||
btc_account.plus_funds(balance)
|
||||
usd_account.plus_funds(balance)
|
||||
create_list(:deposit_usd, 4, member: member, updated_at: 5.hour.ago)
|
||||
create_list(:usd_withdraw, 4, member: member, updated_at: 5.hour.ago)
|
||||
create_list(:deposit_btc, 3, member: member, updated_at: 10.hour.ago)
|
||||
create_list(:btc_withdraw, 3, member: member, updated_at: 10.hour.ago)
|
||||
create_list(:deposit_usd, 5, member: member, updated_at: 5.days.ago)
|
||||
create_list(:usd_withdraw, 5, member: member, updated_at: 5.days.ago)
|
||||
end
|
||||
|
||||
it 'returns all deposits and withdraws num' do
|
||||
api_get '/api/v2/account/transactions', token: token
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq 24
|
||||
|
||||
expect(response.headers.fetch('Total')).to eq '24'
|
||||
end
|
||||
|
||||
it 'returns limited deposits and withdraws' do
|
||||
api_get '/api/v2/account/transactions', params: { limit: 2, page: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 2
|
||||
expect(response.headers.fetch('Total')).to eq '24'
|
||||
|
||||
api_get '/api/v2/account/transactions', params: { limit: 1, page: 2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 1
|
||||
expect(response.headers.fetch('Total')).to eq '24'
|
||||
end
|
||||
|
||||
it 'returns deposits and withdraws for the last two days' do
|
||||
api_get '/api/v2/account/transactions', params: { time_from: 2.days.ago.to_i }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 14
|
||||
expect(response.headers.fetch('Total')).to eq '14'
|
||||
end
|
||||
|
||||
it 'returns deposits and withdraws before 2 days ago' do
|
||||
api_get '/api/v2/account/transactions', params: { time_to: 2.days.ago.to_i }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 10
|
||||
expect(response.headers.fetch('Total')).to eq '10'
|
||||
end
|
||||
|
||||
it 'returns newest mixed up withdraws and deposits depending on updated_at' do
|
||||
api_get '/api/v2/account/transactions', params: { limit: 8, page: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.select { |t| t['type'] == 'Withdraw' }.count).to eq 4
|
||||
expect(result.select { |t| t['type'] == 'Deposit' }.count).to eq 4
|
||||
end
|
||||
|
||||
it 'returns the oldest mixed up withdraws and deposits depending on updated_at' do
|
||||
api_get '/api/v2/account/transactions', params: { limit: 8, page: 2, time_from: 2.days.ago.to_i }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.select { |t| t['type'] == 'Withdraw' }.count).to eq 3
|
||||
expect(result.select { |t| t['type'] == 'Deposit' }.count).to eq 3
|
||||
end
|
||||
|
||||
it 'returns sorted transactions in descending order' do
|
||||
api_get '/api/v2/account/transactions', token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
update_time = result.pluck('updated_at')
|
||||
expect(update_time).to eq(update_time.sort { |a, b| b <=> a })
|
||||
end
|
||||
|
||||
it 'returns sorted transactions in ascending order' do
|
||||
api_get '/api/v2/account/transactions', params: { order_by: 'asc' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
update_time = result.pluck('updated_at')
|
||||
|
||||
expect(update_time).to eq(update_time.sort)
|
||||
end
|
||||
|
||||
it 'returns only transactions with BTC currency' do
|
||||
api_get '/api/v2/account/transactions', params: { currency: 'btc' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.count).to eq 6
|
||||
end
|
||||
|
||||
it 'returns only transactions with USD currency' do
|
||||
api_get '/api/v2/account/transactions', params: { currency: 'USD' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.count).to eq 18
|
||||
end
|
||||
|
||||
it 'returns nil in confirmations field for fiat' do
|
||||
api_get '/api/v2/account/transactions', params: { currency: 'USD' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.pluck('confimations').none?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns valid number in confirmations field for coin' do
|
||||
api_get '/api/v2/account/transactions', params: { currency: 'btc' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.pluck('confimations').any? { |c| c.nil? ? true : c > 1 }).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns transaction with txid filter' do
|
||||
api_get '/api/v2/account/transactions', params: { txid: Deposits::Coin.first.txid }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 1
|
||||
expect(result.all? { |d| d['txid'] == Deposits::Coin.first.txid }).to be_truthy
|
||||
end
|
||||
|
||||
context 'state filters' do
|
||||
before do
|
||||
create_list(:deposit_usd, 4, member: member, updated_at: 5.hour.ago, aasm_state: 'accepted')
|
||||
create_list(:deposit_usd, 4, member: member, updated_at: 5.hour.ago, aasm_state: 'rejected')
|
||||
create_list(:usd_withdraw, 4, member: member, updated_at: 5.days.ago, aasm_state: 'accepted')
|
||||
create_list(:usd_withdraw, 4, member: member, updated_at: 5.days.ago, aasm_state: 'rejected')
|
||||
end
|
||||
|
||||
it 'returns transactions with more than one deposit state' do
|
||||
expect(Deposit.count).to eq 20
|
||||
|
||||
api_get '/api/v2/account/transactions', params: { deposit_state: ['accepted', 'submitted'] }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.select { |t| t['type'] == 'Deposit' }.count).to eq 16
|
||||
expect(result.select { |t| t['type'] == 'Deposit' }.pluck('state').uniq).to match_array(['submitted', 'accepted'])
|
||||
end
|
||||
|
||||
it 'returns transactions with one deposit state' do
|
||||
expect(Deposit.count).to eq 20
|
||||
|
||||
api_get '/api/v2/account/transactions', params: { deposit_state: 'submitted' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.select { |t| t['type'] == 'Deposit' }.count).to eq 12
|
||||
expect(result.select { |t| t['type'] == 'Deposit' }.pluck('state').uniq).to eq (['submitted'])
|
||||
end
|
||||
|
||||
it 'returns transactions with more than one withdraw state' do
|
||||
expect(Withdraw.count).to eq 20
|
||||
|
||||
api_get '/api/v2/account/transactions', params: { withdraw_state: ['accepted', 'prepared'] }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.select { |t| t['type'] == 'Withdraw' }.count).to eq 16
|
||||
expect(result.select { |t| t['type'] == 'Withdraw' }.pluck('state').uniq).to match_array(['prepared', 'accepted'])
|
||||
end
|
||||
|
||||
it 'returns transactions with one withdraw state' do
|
||||
expect(Withdraw.count).to eq 20
|
||||
api_get '/api/v2/account/transactions', params: { withdraw_state: 'prepared' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.select { |t| t['type'] == 'Withdraw' }.count).to eq 12
|
||||
expect(result.select { |t| t['type'] == 'Withdraw' }.pluck('state').uniq).to eq (['prepared'])
|
||||
end
|
||||
|
||||
it 'returns transactions with one withdraw state and one deposit state' do
|
||||
expect(Withdraw.count).to eq 20
|
||||
expect(Deposit.count).to eq 20
|
||||
|
||||
api_get '/api/v2/account/transactions', params: { withdraw_state: 'rejected', deposit_state: 'rejected' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.select { |t| t['type'] == 'Deposit' }.count).to eq 4
|
||||
expect(result.select { |t| t['type'] == 'Withdraw' }.count).to eq 4
|
||||
expect(result.select { |t| t['type'] == 'Deposit' }.pluck('state').uniq).to eq (['rejected'])
|
||||
expect(result.select { |t| t['type'] == 'Withdraw' }.pluck('state').uniq).to eq (['rejected'])
|
||||
end
|
||||
|
||||
it 'returns transactions with more than one withdraw state and more that one deposit state' do
|
||||
expect(Withdraw.count).to eq 20
|
||||
expect(Deposit.count).to eq 20
|
||||
|
||||
api_get '/api/v2/account/transactions', params: { withdraw_state: ['rejected', 'accepted'], deposit_state: ['rejected', 'accepted'] }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.select { |t| t['type'] == 'Deposit' }.count).to eq 8
|
||||
expect(result.select { |t| t['type'] == 'Withdraw' }.count).to eq 8
|
||||
expect(result.select { |t| t['type'] == 'Deposit' }.pluck('state').uniq).to match_array(['accepted','rejected'])
|
||||
expect(result.select { |t| t['type'] == 'Withdraw' }.pluck('state').uniq).to match_array(['accepted','rejected'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'fail' do
|
||||
before do
|
||||
btc_account.plus_funds(balance)
|
||||
usd_account.plus_funds(balance)
|
||||
end
|
||||
|
||||
it 'requires authentication' do
|
||||
api_get '/api/v2/account/transactions'
|
||||
|
||||
expect(response.code).to eq '401'
|
||||
end
|
||||
|
||||
it 'validates currency param' do
|
||||
api_get '/api/v2/account/transactions', params: { currency: 'bar' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.transactions.currency_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validates order_by param' do
|
||||
api_get '/api/v2/account/transactions', params: { order_by: 'foo' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.transactions.order_by_invalid')
|
||||
end
|
||||
|
||||
it 'validates time_from param' do
|
||||
api_get '/api/v2/account/transactions', params: { time_from: 'btc' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.transactions.non_integer_time_from')
|
||||
end
|
||||
|
||||
it 'validates time_to param' do
|
||||
api_get '/api/v2/account/transactions', params: { time_to: [] }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.transactions.non_integer_time_to')
|
||||
end
|
||||
|
||||
it 'validates deposit state param' do
|
||||
api_get '/api/v2/account/transactions', params: { deposit_state: [] }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.transactions.invalid_deposit_state')
|
||||
end
|
||||
|
||||
it 'validates withdraw state param' do
|
||||
api_get '/api/v2/account/transactions', params: { withdraw_state: [] }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.transactions.invalid_withdraw_state')
|
||||
end
|
||||
|
||||
it 'validates page param' do
|
||||
api_get '/api/v2/account/transactions', params: { page: -1 }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.transactions.non_positive_page')
|
||||
|
||||
api_get '/api/v2/account/transactions', params: { page: 'btc' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.transactions.non_integer_page')
|
||||
end
|
||||
|
||||
it 'validates limit param' do
|
||||
api_get '/api/v2/account/transactions', params: { limit: 1001 }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('account.transactions.invalid_limit')
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get '/api/v2/account/transactions', params: { limit: 1000 }, token: token
|
||||
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
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