Files
Dena/spec/api/v2/admin/withdraws_spec.rb
2026-08-13 19:50:53 +03:30

324 lines
13 KiB
Ruby

# encoding: UTF-8
# frozen_string_literal: true
describe API::V2::Admin::Withdraws, type: :request do
let(:admin) { create(:member, :admin, :level_3, email: 'example@gmail.com', uid: 'ID73BF61C8H0') }
let(:token) { jwt_for(admin) }
let(:level_3_member) { create(:member, :level_3) }
let(:level_3_member_token) { jwt_for(level_3_member) }
before do
[admin, level_3_member].each do |member|
member.touch_accounts
member.accounts.map { |a| a.update(balance: 500) }
end
create(:usd_withdraw, amount: 10.0, sum: 10.0, member: admin)
create(:usd_withdraw, amount: 9.0, sum: 9.0, member: admin)
create(:usd_withdraw, amount: 100.0, sum: 100.0, member: level_3_member)
create(:btc_withdraw, amount: 42.0, sum: 42.0, txid: 'special_txid', member: admin)
create(:btc_withdraw, amount: 42.0, sum: 42.0, member: admin, aasm_state: :accepted)
create(:btc_withdraw, amount: 11.0, sum: 11.0, member: level_3_member, aasm_state: :skipped)
create(:btc_withdraw, amount: 12.0, sum: 12.0, member: level_3_member, aasm_state: :errored)
end
describe 'GET /api/v2/admin/withdraws' do
let(:url) { '/api/v2/admin/withdraws' }
it 'get all withdraws' do
api_get url, token: token
actual = JSON.parse(response.body)
expected = Withdraw.all
expect(actual.length).to eq expected.length
expect(actual.map { |a| a['state'] }).to match_array expected.map(&:aasm_state)
expect(actual.map { |a| a['id'] }).to match_array expected.map(&:id)
expect(actual.map { |a| a['currency'] }).to match_array expected.map(&:currency_id)
expect(actual.map { |a| a['member'] }).to match_array expected.map(&:member_id)
expect(actual.map { |a| a['type'] }).to match_array(expected.map { |d| d.currency.coin? ? 'coin' : 'fiat' })
expect(actual.map { |a| a['uid'] }).to match_array(expected.map { |d| d.member.uid })
expect(actual.map { |a| a['email'] }).to match_array(expected.map { |d| d.member.email })
end
context 'ordering' do
it 'ascending by id' do
api_get url, token: token, params: { order_by: 'id', ordering: 'asc' }
actual = JSON.parse(response.body)
expected = Withdraw.order(id: 'asc')
expect(actual.map { |a| a['id'] }).to eq expected.map(&:id)
end
it 'descending by sum' do
api_get url, token: token, params: { order_by: 'sum', ordering: 'desc' }
actual = JSON.parse(response.body)
expected = Withdraw.order(sum: 'desc')
expect(actual.map { |a| a['id'] }).to eq expected.map(&:id)
end
end
context 'filtering' do
it 'by member' do
api_get url, token: token, params: { uid: level_3_member.uid }
actual = JSON.parse(response.body)
expected = Withdraw.where(member_id: level_3_member.id)
expect(actual.length).to eq expected.length
expect(actual.map { |a| a['state'] }).to match_array expected.map(&:aasm_state)
expect(actual.map { |a| a['id'] }).to match_array expected.map(&:id)
expect(actual.map { |a| a['currency'] }).to match_array expected.map(&:currency_id)
expect(actual.map { |a| a['member'] }).to all eq level_3_member.id
expect(actual.map { |a| a['type'] }).to match_array(expected.map { |d| d.currency.coin? ? 'coin' : 'fiat' })
expect(actual.map { |a| a['uid'] }).to match_array(expected.map { |d| d.member.uid })
expect(actual.map { |a| a['email'] }).to match_array(expected.map { |d| d.member.email })
end
it 'by state' do
api_get url, token: token, params: { state: :skipped }
actual = JSON.parse(response.body)
expected = Withdraw.where(aasm_state: :skipped)
expect(actual.map { |a| a['state'] }).to all eq 'skipped'
expect(actual.length).to eq expected.count
expect(actual.map { |a| a['id'] }).to match_array expected.map(&:id)
expect(actual.map { |a| a['uid'] }).to match_array(expected.map { |d| d.member.uid })
end
it 'by multiple states' do
api_get url, token: token, params: { state: [:skipped, :accepted] }
actual = JSON.parse(response.body)
expected = Withdraw.where(aasm_state: [:skipped, :accepted])
expect(actual.map { |a| a['state'] }.uniq).to match_array %w[skipped accepted]
expect(actual.length).to eq expected.count
expect(actual.map { |a| a['id'] }).to match_array expected.map(&:id)
expect(actual.map { |a| a['uid'] }).to match_array(expected.map { |d| d.member.uid })
end
it 'by type' do
api_get url, token: token, params: { type: 'coin' }
actual = JSON.parse(response.body)
expected = Withdraw.where(type: 'Withdraws::Coin')
expect(actual.length).to eq expected.length
expect(actual.map { |a| a['state'] }).to match_array expected.map(&:aasm_state)
expect(actual.map { |a| a['id'] }).to match_array expected.map(&:id)
expect(actual.map { |a| a['currency'] }).to match_array expected.map(&:currency_id)
expect(actual.map { |a| a['member'] }).to match_array expected.map(&:member_id)
expect(actual.map { |a| a['type'] }).to all eq 'coin'
end
it 'by txid' do
api_get url, token: token, params: { txid: Withdraw.where(type: 'Withdraws::Coin').first.txid }
actual = JSON.parse(response.body)
expected = Withdraw.where(type: 'Withdraws::Coin').first
expect(actual.length).to eq 1
expect(actual.first['state']).to eq expected.aasm_state
expect(actual.first['id']).to eq expected.id
expect(actual.first['currency']).to eq expected.currency_id
expect(actual.first['member']).to eq expected.member_id
expect(actual.first['type']).to eq 'coin'
end
it 'by wallet_type' do
wallet_type = Wallet.joins(:currencies)
.find_by(currencies: { id: Withdraw.find_by(type: 'Withdraws::Coin').currency_id })
.gateway
api_get url, token: token, params: { wallet_type: wallet_type }
actual = JSON.parse(response.body)
expect(actual.length).to eq Withdraw.where(currency: Currency.joins(:wallets)
.where(wallets: { id: Wallet.where(gateway: wallet_type) })).count
end
end
end
describe 'GET /api/v2/admin/withdraws/:id' do
context 'invalid params' do
context 'non-integer id' do
it do
api_get '/api/v2/admin/withdraws/id', token: token
expect(response).to include_api_error('admin.withdraw.non_integer_id')
end
end
context 'withdraw does not exist' do
it do
api_get "/api/v2/admin/withdraws/#{Withdraw.last.id + 1}", token: token
expect(response).to include_api_error('record.not_found')
end
end
end
context 'with beneficiary' do
context 'has beneficiary' do
let!(:withdraw) { create(:usd_withdraw, :with_beneficiary, :with_deposit_liability) }
it 'includes beneficiary in withdrawal payload' do
beneficiary_json = API::V2::Entities::Beneficiary
.represent(withdraw.beneficiary)
.as_json
.deep_stringify_keys
api_get "/api/v2/admin/withdraws/#{withdraw.id}", token: token
expect(response_body['beneficiary']).to_not be_nil
expect(response_body['beneficiary']).to eq(beneficiary_json)
end
end
context 'does not have beneficiary' do
let!(:withdraw) { create(:usd_withdraw, :with_deposit_liability) }
it 'includes beneficiary in withdrawal payload' do
api_get "/api/v2/admin/withdraws/#{withdraw.id}", token: token
expect(response_body['beneficiary']).to be_nil
end
end
end
context 'with error message' do
context 'does not have error message' do
let!(:withdraw) { create(:usd_withdraw, :with_beneficiary, :with_deposit_liability, aasm_state: :succeed) }
it 'without error message in withdrawal payload' do
api_get "/api/v2/admin/withdraws/#{withdraw.id}", token: token
expect(response_body.include?('error')).to be_falsey
end
end
context 'includes error message' do
let!(:withdraw) { create(:usd_withdraw, :with_beneficiary, :with_deposit_liability, aasm_state: :skipped) }
it 'includes error message in withdrawal payload' do
api_get "/api/v2/admin/withdraws/#{withdraw.id}", token: token
expect(response_body.include?('error')).to be_truthy
end
end
end
end
describe 'PUT /api/v2/admin/withdraws' do
let(:url) { '/api/v2/admin/withdraws' }
let(:fiat) { Withdraw.where(type: 'Withdraws::Fiat').first }
let(:coin) { Withdraw.where(type: 'Withdraws::Coin').first }
context 'updates withdraw' do
it 'updates empty metadata' do
api_put url, token: token, params: { metadata: { info: :some }, id: coin.id }
expect(response_body['metadata']).to eq({'info' => 'some'})
end
it 'updates existing metadata' do
coin.update!(metadata: { data: :data })
api_put url, token: token, params: { metadata: { info: :some }, id: coin.id }
expect(response_body['metadata']).to eq({'data' => 'data', 'info' => 'some'})
end
it 'updates existing metadata' do
coin.update!(metadata: { info: :some })
api_put url, token: token, params: { metadata: { info: :some1 }, id: coin.id }
expect(response_body['metadata']).to eq({ 'info' => 'some1' })
end
end
end
describe 'POST /api/v2/admin/withdraws/actions' do
let(:url) { '/api/v2/admin/withdraws/actions' }
let(:fiat) { Withdraw.where(type: 'Withdraws::Fiat').first }
let(:coin) { Withdraw.where(type: 'Withdraws::Coin').first }
context 'validates params' do
it 'does not pass unsupported action' do
api_post url, token: token, params: { action: 'illegal', id: fiat.id }
expect(response.status).to eq 422
expect(response).to include_api_error('admin.withdraw.invalid_action')
end
it 'passes supported action for coin' do
api_post url, token: token, params: { action: 'process', id: coin.id }
expect(response).not_to include_api_error('admin.withdraw.invalid_action')
end
it 'passes supported action for fiat' do
api_post url, token: token, params: { action: 'reject', id: fiat.id }
expect(response).not_to include_api_error('admin.withdraw.invalid_action')
end
it 'does not pass coin action for fiat' do
api_post url, token: token, params: { action: 'load', id: fiat.id }
expect(response.status).to eq 422
expect(response).to include_api_error('admin.withdraw.cannot_load')
end
end
context 'updates withdraw' do
before { [coin, fiat].map(&:accept!) }
it 'process coin' do
api_post url, token: token, params: { action: 'process', id: coin.id }
expect(coin.reload.aasm_state).to eq('processing')
end
it 'reject fiat' do
api_post url, token: token, params: { action: 'reject', id: fiat.id }
expect(fiat.reload.aasm_state).to eq('rejected')
expect(response).to be_successful
end
it 'fail coin' do
coin.accept!
coin.process!
api_post url, token: token, params: { action: 'fail', id: coin.id }
expect(coin.reload.aasm_state).to eq('failed')
expect(response).to be_successful
end
it 'load coin with txid' do
BlockchainService.any_instance.expects(:fetch_transaction).once.returns(Peatio::Transaction.new)
coin.accept!
api_post url, token: token, params: { action: 'load', id: coin.id, txid: 'new_txid' }
expect(coin.reload.txid).to eq('new_txid')
expect(coin.aasm_state).to eq('confirming')
expect(response).to be_successful
end
it 'load fiat with txid' do
fiat.accept!
expect {
api_post url, token: token, params: { action: 'load', id: fiat.id, txid: 'new_txid' }
}.not_to change { fiat }
expect(response).to include_api_error('admin.withdraw.redundant_txid')
end
it 'load coin without txid with txid as param' do
BlockchainService.any_instance.expects(:fetch_transaction).once.returns(Peatio::Transaction.new)
coin.update(txid: nil)
coin.accept!
api_post url, token: token, params: { action: 'load', id: coin.id, txid: 'new_txid' }
expect(coin.reload.txid).to eq('new_txid')
expect(coin.aasm_state).to eq('confirming')
expect(response).to be_successful
end
it 'load coin without txid' do
coin.update(txid: nil)
coin.accept!
expect {
api_post url, token: token, params: { action: 'load', id: coin.id }
}.not_to change { coin }
expect(response).to include_api_error('admin.withdraw.cannot_load')
end
end
end
end