Initial commit
This commit is contained in:
26
spec/api/swagger_spec.rb
Normal file
26
spec/api/swagger_spec.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe 'Swagger', type: :request do
|
||||
|
||||
it "returns APIv2 swagger docs" do
|
||||
expect do
|
||||
get "/api/v2/swagger"
|
||||
expect(response).to have_http_status 200
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
it "returns APIv2 management swagger docs" do
|
||||
expect do
|
||||
get "/api/v2/management/swagger"
|
||||
expect(response).to have_http_status 200
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
it "returns APIv2 admin swagger docs" do
|
||||
expect do
|
||||
get "/api/v2/admin/swagger"
|
||||
expect(response).to have_http_status 200
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
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
|
||||
35
spec/api/v2/admin/abilities_spec.rb
Normal file
35
spec/api/v2/admin/abilities_spec.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Abilities, type: :request do
|
||||
describe 'GET /api/v2/admin/abilities' do
|
||||
context 'member role' do
|
||||
let(:admin) { create(:member, :admin, :level_3, email: 'example@gmail.com', uid: 'ID73BF61C8H0') }
|
||||
let(:token) { jwt_for(admin) }
|
||||
it 'get all roles and permissions' do
|
||||
api_get '/api/v2/admin/abilities', token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result).to eq(
|
||||
"create" => ["Deposits::Fiat"],
|
||||
"manage" => ["Operations::Account", "Operations::Asset", "Operations::Expense", "Operations::Liability", "Operations::Revenue", "Member", "Account", "Beneficiary", "PaymentAddress", "Deposit", "Withdraw", "WithdrawLimit", "Blockchain", "Currency", "Engine", "Market", "TradingFee", "Wallet", "Adjustment", "InternalTransfer", "WhitelistedSmartContract"],
|
||||
"read" => ["Trade", "Order"],
|
||||
"update" => ["Order"],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'member role' do
|
||||
let(:member) { create(:member, :level_3, email: 'example@gmail.com', uid: 'ID73BF61C8H0') }
|
||||
let(:token) { jwt_for(member) }
|
||||
it 'get all roles and permissions' do
|
||||
api_get '/api/v2/admin/abilities', token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
420
spec/api/v2/admin/adjustments_spec.rb
Normal file
420
spec/api/v2/admin/adjustments_spec.rb
Normal file
@@ -0,0 +1,420 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Adjustments, type: :request do
|
||||
let(:admin) { create(:member, :admin, :level_3, email: 'example@gmail.com', uid: 'ID73BF61C8H0') }
|
||||
let(:token) { jwt_for(admin) }
|
||||
let(:member) { create(:member) }
|
||||
|
||||
describe 'GET /api/v2/admin/adjustments' do
|
||||
let!(:adjustments) do
|
||||
create(:adjustment, currency_id: 'btc')
|
||||
create(:adjustment, currency_id: 'btc')
|
||||
create(:adjustment, currency_id: 'btc')
|
||||
end
|
||||
let!(:accepted) { create(:adjustment, currency_id: 'btc', receiving_account_number: "BTC-202-#{member.uid}").tap { |a| a.accept!(validator: member) } }
|
||||
let!(:rejected) { create(:adjustment, currency_id: 'btc', receiving_account_number: "BTC-202-#{member.uid}").tap { |a| a.reject!(validator: member) } }
|
||||
|
||||
it 'get all adjustments' do
|
||||
api_get '/api/v2/admin/adjustments', token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers['Total'].to_i).to eq Adjustment.count
|
||||
expect(result.length).to eq Adjustment.count
|
||||
end
|
||||
|
||||
context 'with rejected/accepted' do
|
||||
it 'fetches operations from db' do
|
||||
api_get '/api/v2/admin/adjustments', token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.length).to eq 5
|
||||
expect(result[-1].key?('asset')).to be_truthy
|
||||
expect(result[-1].key?('liability')).to be_truthy
|
||||
expect(result[-1]['state']).to eq('rejected')
|
||||
# We don't create operations for rejected adj.
|
||||
expect(result[-1]['liability']['id'].nil?).to be_truthy
|
||||
expect(result[-1]['asset']['id'].nil?).to be_truthy
|
||||
expect(result[-2].key?('asset')).to be_truthy
|
||||
expect(result[-2].key?('liability')).to be_truthy
|
||||
expect(result[-2]['liability']['id']).to eq accepted.liability.id
|
||||
expect(result[-2]['asset']['id']).to eq accepted.asset.id
|
||||
expect(result[-2]['state']).to eq('accepted')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with filters' do
|
||||
let!(:adjustment_with_category) { create(:adjustment, currency_id: 'btc', category: 'balance_anomaly', receiving_account_number: "BTC-202-#{member.uid}") }
|
||||
let!(:eth_adjustment) { create(:adjustment, currency_id: 'eth', receiving_account_number: "eth-202-#{member.uid}").tap { |a| a.accept!(validator: member) } }
|
||||
|
||||
it 'filter by accepted state' do
|
||||
api_get '/api/v2/admin/adjustments', token: token, params: { state: 'accepted' }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers['Total'].to_i).to eq(Adjustment.where(state: 'accepted').count)
|
||||
expect(result.first['id']).to eq(accepted.id)
|
||||
end
|
||||
|
||||
it 'filter by rejected state' do
|
||||
api_get '/api/v2/admin/adjustments', token: token, params: { state: 'rejected' }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers['Total'].to_i).to eq(Adjustment.where(state: 'rejected').count)
|
||||
expect(result.first['id']).to eq(rejected.id)
|
||||
end
|
||||
|
||||
it 'filters by eth currency' do
|
||||
api_get '/api/v2/admin/adjustments', token: token, params: { currency: 'eth' }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers['Total'].to_i).to eq(Adjustment.where(currency_id: 'eth').count)
|
||||
expect(result.first['id']).to eq(eth_adjustment.id)
|
||||
end
|
||||
|
||||
it 'filters by btc currency' do
|
||||
api_get '/api/v2/admin/adjustments', token: token, params: { currency: 'btc' }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers['Total'].to_i).to eq(Adjustment.where(currency_id: 'btc').count)
|
||||
end
|
||||
|
||||
it 'validates currency' do
|
||||
api_get '/api/v2/admin/adjustments', token: token, params: { currency: 'uah' }
|
||||
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('admin.currency.doesnt_exist')
|
||||
end
|
||||
|
||||
it 'filter by accepted category' do
|
||||
api_get '/api/v2/admin/adjustments', token: token, params: { category: 'balance_anomaly' }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers['Total'].to_i).to eq(Adjustment.where(category: 'balance_anomaly').count)
|
||||
expect(result.first['id']).to eq(adjustment_with_category.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/adjustments/:id' do
|
||||
let!(:adjustment1) { create(:adjustment, currency_id: 'btc') }
|
||||
let!(:adjustment2) { create(:adjustment, currency_id: 'eth', receiving_account_number: "ETH-202-#{member.uid}") }
|
||||
let!(:adjustment3) { create(:adjustment, currency_id: 'eth', receiving_account_number: "ETH-302-#{member.uid}") }
|
||||
|
||||
it 'get specified adjustment' do
|
||||
api_get "/api/v2/admin/adjustments/#{adjustment1.id}", token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['id']).to eq adjustment1.id
|
||||
expect(result['currency']).to eq adjustment1.currency_id
|
||||
end
|
||||
|
||||
it 'get specified adjustment' do
|
||||
api_get "/api/v2/admin/adjustments/#{adjustment2.id}", token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['id']).to eq adjustment2.id
|
||||
expect(result['currency']).to eq adjustment2.currency_id
|
||||
expect(result['receiving_account_code']).to eq '202'
|
||||
expect(result['receiving_member_uid']).to eq member.uid
|
||||
end
|
||||
|
||||
it 'get specified adjustment' do
|
||||
api_get "/api/v2/admin/adjustments/#{adjustment3.id}", token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['id']).to eq adjustment3.id
|
||||
expect(result['currency']).to eq adjustment3.currency_id
|
||||
expect(result['receiving_account_code']).to eq '302'
|
||||
expect(result['receiving_member_uid'].blank?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/adjustments/new' do
|
||||
let(:params) do
|
||||
{
|
||||
reason: 'Adjustment',
|
||||
description: 'sample sdjustment',
|
||||
category: 'asset_registration',
|
||||
amount: 100.0,
|
||||
currency_id: :btc,
|
||||
asset_account_code: 102,
|
||||
receiving_account_code: 202,
|
||||
receiving_member_uid: member.uid
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates new adjustment' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params
|
||||
}.to change { Adjustment.count }.by 1
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'returns new adjustment and prebuild operations' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['reason']).to eq('Adjustment')
|
||||
expect(result['description']).to eq('sample sdjustment')
|
||||
expect(result['category']).to eq('asset_registration')
|
||||
expect(result['amount']).to eq('100.0')
|
||||
expect(result['currency']).to eq('btc')
|
||||
expect(result.key?('asset')).to be_truthy
|
||||
expect(result.key?('liability')).to be_truthy
|
||||
end
|
||||
|
||||
it 'checks account decimal amount' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params.merge(amount: '100btc')
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('admin.adjustment.non_decimal_amount')
|
||||
end
|
||||
|
||||
it 'checks right asset_account_code' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params.merge(asset_account_code: 111)
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('admin.adjustment.invalid_asset_account_code')
|
||||
end
|
||||
|
||||
it 'checks right receiving_account_code' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params.merge(receiving_account_code: 444)
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('admin.adjustment.invalid_receiving_account_code')
|
||||
end
|
||||
|
||||
it 'validates right category' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params.merge(category: 'some_category')
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('admin.adjustment.invalid_category')
|
||||
end
|
||||
|
||||
it 'validates right currency' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params.merge(currency_id: 'uah')
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('admin.adjustment.currency_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validates coin and fiat accounts numbers' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params.merge(currency_id: 'btc', asset_account_code: 101)
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('Prebuild operations are invalid')
|
||||
end
|
||||
|
||||
context 'receiving_member_uid validatations' do
|
||||
it 'requires for liability receiving account' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params.merge(receiving_account_code: 202).except(:receiving_member_uid)
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('admin.adjustment.missing_receiving_member_uid')
|
||||
end
|
||||
|
||||
it 'doesnt requires for revenue receiving account' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params.merge(receiving_account_code: 302).except(:receiving_member_uid)
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['reason']).to eq('Adjustment')
|
||||
end
|
||||
|
||||
|
||||
it 'doesnt requires for expense receiving account' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params.merge(receiving_account_code: 402).except(:receiving_member_uid)
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['reason']).to eq('Adjustment')
|
||||
end
|
||||
|
||||
it 'creates adjustment with expense without member_uid' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params.merge(receiving_account_code: 402)
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('admin.adjustment.redundant_receiving_member_uid')
|
||||
end
|
||||
|
||||
it 'creates adjustment with revenue that contains member uid' do
|
||||
api_post '/api/v2/admin/adjustments/new', token: token, params: params.merge(receiving_account_code: 302)
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['reason']).to eq('Adjustment')
|
||||
expect(result['receiving_member_uid'].blank?).to be_truthy
|
||||
adjustment_db = Adjustment.find(result['id'])
|
||||
account_number_hash = Operations.split_account_number(account_number: adjustment_db.receiving_account_number)
|
||||
expect(account_number_hash[:member_uid].present?).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/adjustments/action (accept)' do
|
||||
let!(:adjustment) { create(:adjustment, currency_id: 'btc', receiving_account_number: "btc-202-#{member.uid}") }
|
||||
|
||||
it 'accepts adjustment' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
}.to change { adjustment.reload.state }.to('accepted')
|
||||
.and change { Operations::Asset.count }.by(1)
|
||||
.and change { Operations::Liability.count }.by(1)
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'udpates member\'s balance' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
}.to change { member.get_account(adjustment.currency).balance }.by(adjustment.amount)
|
||||
end
|
||||
|
||||
it 'does not accept invalid asset_account_code.' do
|
||||
adjustment.update(asset_account_code: 3000)
|
||||
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
|
||||
expect(adjustment.reload.state).to eq('pending')
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.adjustment.cannot_perform_accept_action')
|
||||
end
|
||||
|
||||
it 'does not accept negative adjustment for sum bigger than member\'s balance' do
|
||||
adjustment.update(amount: -10000000.0)
|
||||
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
|
||||
expect(adjustment.reload.state).to eq('pending')
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.adjustment.user_insufficient_balance')
|
||||
end
|
||||
|
||||
it 'does not update member\'s balance if it is lower than negative adjustment' do
|
||||
adjustment.update(amount: -10000000.0)
|
||||
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
}.not_to change { member.get_account(adjustment.currency).balance }
|
||||
end
|
||||
|
||||
context 'already accepted' do
|
||||
let!(:adjustment) { create(:adjustment, currency_id: 'btc', receiving_account_number: "btc-202-#{member.uid}").tap { |a| a.accept!(validator: member) } }
|
||||
|
||||
it 'returns status and error' do
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.adjustment.cannot_perform_accept_action')
|
||||
end
|
||||
|
||||
it 'does not udpate member\'s balance' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
}.not_to change { member.accounts }
|
||||
end
|
||||
|
||||
it 'does not create operations' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
}.not_to change { Operations::Asset.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'already rejected' do
|
||||
let!(:adjustment) { create(:adjustment, currency_id: 'btc', receiving_account_number: "btc-202-#{member.uid}").tap { |a| a.reject!(validator: member) } }
|
||||
|
||||
it 'returns status and error' do
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.adjustment.cannot_perform_accept_action')
|
||||
end
|
||||
|
||||
it 'does not udpate member\'s balance' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
}.not_to change { member.accounts }
|
||||
end
|
||||
|
||||
it 'does not create operations' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
}.not_to change { Operations::Asset.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/adjustments/action (reject)' do
|
||||
let!(:adjustment) { create(:adjustment, currency_id: 'btc', receiving_account_number: "btc-202-#{member.uid}") }
|
||||
|
||||
it 'rejects adjustment' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :reject }
|
||||
}.to change { adjustment.reload.state }.to('rejected')
|
||||
end
|
||||
|
||||
it 'does not create operations' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :reject }
|
||||
}.not_to change { Operations::Asset.count }
|
||||
end
|
||||
|
||||
context 'already rejected' do
|
||||
let!(:adjustment) { create(:adjustment, currency_id: 'btc', receiving_account_number: "btc-202-#{member.uid}").tap { |a| a.reject!(validator: member) } }
|
||||
|
||||
it 'returns status and error' do
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :accept }
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.adjustment.cannot_perform_accept_action')
|
||||
end
|
||||
|
||||
it 'does not udpate member\'s balance' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :reject }
|
||||
}.not_to change { member.accounts }
|
||||
end
|
||||
|
||||
it 'does not create operations' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :reject }
|
||||
}.not_to change { Operations::Asset.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'already accepted' do
|
||||
let!(:adjustment) { create(:adjustment, currency_id: 'btc', receiving_account_number: "btc-202-#{member.uid}").tap { |a| a.accept!(validator: member) } }
|
||||
|
||||
it 'returns status and error' do
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :reject }
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.adjustment.cannot_perform_reject_action')
|
||||
end
|
||||
|
||||
it 'does not udpate member\'s balance' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :reject }
|
||||
}.not_to change { member.accounts }
|
||||
end
|
||||
|
||||
it 'does not create operations' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/adjustments/action', token: token, params: { id: adjustment.id, action: :reject }
|
||||
}.not_to change { Operations::Asset.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
92
spec/api/v2/admin/beneficiaries_spec.rb
Normal file
92
spec/api/v2/admin/beneficiaries_spec.rb
Normal file
@@ -0,0 +1,92 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Beneficiaries, type: :request do
|
||||
let(:admin) { create(:member, :admin, :level_3, email: 'example@gmail.com', uid: 'ID73BF61C8H0') }
|
||||
let(:token) { jwt_for(admin) }
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:member_token) { jwt_for(level_3_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
|
||||
|
||||
let!(:fiat_beneficiary) {
|
||||
create(:beneficiary, state: :active, currency: Currency.find('usd'))
|
||||
}
|
||||
|
||||
describe 'GET /api/v2/admin/beneficiaries' do
|
||||
let(:url) { '/api/v2/admin/beneficiaries' }
|
||||
|
||||
it 'get all beneficiaries' do
|
||||
api_get url, token: token
|
||||
expect(response_body.count).to eq(Beneficiary.count)
|
||||
end
|
||||
|
||||
context 'ordering' do
|
||||
it 'ascending by id' do
|
||||
api_get url, token: token, params: { order_by: 'id', ordering: 'asc' }
|
||||
|
||||
expect(response_body.first['id']).to eq Beneficiary.first.id
|
||||
end
|
||||
|
||||
it 'descending by id' do
|
||||
api_get url, token: token, params: { order_by: 'id', ordering: 'desc' }
|
||||
|
||||
expect(response_body.first['id']).to eq Beneficiary.last.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'filtering' do
|
||||
it 'by member' do
|
||||
api_get url, token: token, params: { uid: member.uid }
|
||||
|
||||
expect(response_body.count).to eq(member.beneficiaries.count)
|
||||
end
|
||||
|
||||
it 'by state' do
|
||||
api_get url, token: token, params: { state: ['pending', 'archived'] }
|
||||
|
||||
expect(response_body.count).to eq(Beneficiary.where(state: ['pending', 'archived']).count)
|
||||
end
|
||||
|
||||
context 'by currency' do
|
||||
it 'by crypto currency' do
|
||||
api_get url, token: token, params: { currency: ['eth', 'btc'] }
|
||||
|
||||
expect(response_body.count).to eq(Beneficiary.where(currency_id: ['eth', 'btc']).count)
|
||||
end
|
||||
|
||||
it 'by fiat currency' do
|
||||
api_get url, token: token, params: { currency: 'usd' }
|
||||
|
||||
expect(response_body.count).to eq(Beneficiary.where(currency_id: 'usd').count)
|
||||
expect(response_body.last['data']).to eq Beneficiary.where(currency_id: 'usd').last.data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'actions' do
|
||||
let(:url) { '/api/v2/admin/beneficiaries/actions' }
|
||||
|
||||
it 'by archived' do
|
||||
api_post url, token: token, params: { action: 'archive', id: fiat_beneficiary.id }
|
||||
|
||||
expect(response_body['data']).to eq fiat_beneficiary.data.with_indifferent_access
|
||||
expect(response_body['state']).to eq('archived')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
450
spec/api/v2/admin/blockchains_spec.rb
Normal file
450
spec/api/v2/admin/blockchains_spec.rb
Normal file
@@ -0,0 +1,450 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Blockchains, 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) }
|
||||
|
||||
describe 'GET /api/v2/admin/blockchains/:id' do
|
||||
let(:blockchain) { Blockchain.find_by(key: 'eth-rinkeby') }
|
||||
|
||||
it 'returns information about specified blockchain' do
|
||||
api_get "/api/v2/admin/blockchains/#{blockchain.id}", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('id')).to eq blockchain.id
|
||||
expect(result.fetch('name')).to eq blockchain.name
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid id' do
|
||||
api_get "/api/v2/admin/blockchains/#{Blockchain.last.id + 42}", token: token
|
||||
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get "/api/v2/admin/blockchains/#{blockchain.id}", token: level_3_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/blockchains/clients' do
|
||||
it 'get list of all available clients' do
|
||||
api_get '/api/v2/admin/blockchains/clients', token: token
|
||||
expect(JSON.parse(response.body)).to match_array Blockchain.clients.map &:to_s
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/blockchains/:id/latest_block' do
|
||||
let(:blockchain) { Blockchain.find_by(key: "eth-rinkeby") }
|
||||
|
||||
it 'returns error in case of invalid id' do
|
||||
api_get "/api/v2/admin/blockchains/#{Blockchain.last.id + 42}/latest_block", token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.blockchain.latest_block')
|
||||
end
|
||||
|
||||
it 'returns error in case of node inaccessibility' do
|
||||
api_get "/api/v2/admin/blockchains/#{blockchain.id}/latest_block", token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.blockchain.latest_block')
|
||||
end
|
||||
|
||||
context 'get latest_block' do
|
||||
let(:blockchain) { Blockchain.find_by(key: "eth-rinkeby") }
|
||||
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:eth_blockchain) do
|
||||
Ethereum::Blockchain.new.tap { |b| b.configure(server: 'http://127.0.0.1:8545') }
|
||||
end
|
||||
|
||||
it 'returns node latest block' do
|
||||
block_number = '0x16b916'
|
||||
|
||||
stub_request(:post, 'http://127.0.0.1:8545')
|
||||
.with(body: { jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: :eth_blockNumber,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: block_number,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
|
||||
api_get "/api/v2/admin/blockchains/#{blockchain.id}/latest_block", token: token
|
||||
|
||||
expect(response.code).to eq '200'
|
||||
expect(response_body).to eq 1489174
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/blockchains' do
|
||||
it 'lists of blockchains' do
|
||||
api_get '/api/v2/admin/blockchains', token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq 3
|
||||
end
|
||||
|
||||
it 'returns blockchains by ascending order' do
|
||||
api_get '/api/v2/admin/blockchains', params: { ordering: 'asc', order_by: 'client'}, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.first['client']).to eq 'bitcoin'
|
||||
end
|
||||
|
||||
it 'returns paginated blockchains' do
|
||||
api_get '/api/v2/admin/blockchains', params: { limit: 2, page: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response.headers.fetch('Total')).to eq '3'
|
||||
expect(result.size).to eq 2
|
||||
expect(result.first['key']).to eq 'eth-kovan'
|
||||
|
||||
api_get '/api/v2/admin/blockchains', params: { limit: 1, page: 2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response.headers.fetch('Total')).to eq '3'
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['key']).to eq 'eth-rinkeby'
|
||||
end
|
||||
|
||||
it 'returns blockchains filtered by key' do
|
||||
api_get '/api/v2/admin/blockchains', params: { key: "eth-kovan" }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '1'
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['key']).to eq 'eth-kovan'
|
||||
end
|
||||
|
||||
it 'returns error in case invalid blockchain key' do
|
||||
api_get '/api/v2/admin/blockchains', params: { key: "inv" }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.blockchain.blockchain_key_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'returns blockchains filtered by client' do
|
||||
api_get '/api/v2/admin/blockchains', params: { client: "parity" }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '1'
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['name']).to eq 'Ethereum Kovan'
|
||||
end
|
||||
|
||||
it 'returns error in case invalid blockchain client' do
|
||||
api_get '/api/v2/admin/blockchains', params: { client: "inv" }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.blockchain.blockchain_client_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'returns blockchains filtered by status' do
|
||||
api_get '/api/v2/admin/blockchains', params: { status: "active" }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '3'
|
||||
expect(result.size).to eq 3
|
||||
expect(result.map { |r| r["status"]}).to all eq "active"
|
||||
end
|
||||
|
||||
it 'returns error in case invalid blockchain status' do
|
||||
api_get '/api/v2/admin/blockchains', params: { status: "inv" }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.blockchain.blockchain_status_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'returns blockchains filtered by name' do
|
||||
api_get '/api/v2/admin/blockchains', params: { name: "Ethereum Kovan" }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '1'
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['name']).to eq 'Ethereum Kovan'
|
||||
end
|
||||
|
||||
it 'returns error in case invalid blockchain name' do
|
||||
api_get '/api/v2/admin/blockchains', params: { name: "inv" }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.blockchain.blockchain_name_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get "/api/v2/admin/blockchains", token: level_3_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/blockchains/new' do
|
||||
it 'creates new blockchain' do
|
||||
api_post '/api/v2/admin/blockchains/new', token: token, params: { key: 'test-blockchain', name: 'Test', client: 'geth',server: 'http://127.0.0.1', height: 123333, explorer_transaction: 'test', explorer_address: 'test'}
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['key']).to eq 'test-blockchain'
|
||||
end
|
||||
|
||||
it 'long blockchain key' do
|
||||
api_post '/api/v2/admin/blockchains/new', token: token, params: { key: Faker::String.random(1024), name: 'Test', client: 'geth',server: 'http://127.0.0.1', height: 123333, explorer_transaction: 'test', explorer_address: 'test'}
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('admin.blockchain.key_too_long')
|
||||
end
|
||||
|
||||
it 'long blockchain name' do
|
||||
api_post '/api/v2/admin/blockchains/new', token: token, params: { key: Faker::String.random(24), name: Faker::String.random(1024), client: 'geth',server: 'http://127.0.0.1', height: 123333, explorer_transaction: 'test', explorer_address: 'test'}
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('admin.blockchain.name_too_long')
|
||||
end
|
||||
|
||||
it 'validate height param' do
|
||||
api_post '/api/v2/admin/blockchains/new', token: token, params: { key: 'test-blockchain', name: 'Test', client: 'geth',server: 'http://127.0.0.1', height: -123333, explorer_transaction: 'test', explorer_address: 'test', status: 'active', min_confirmations: 6, step: 2 }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.blockchain.non_positive_height')
|
||||
end
|
||||
|
||||
it 'validate min_confirmations param' do
|
||||
api_post '/api/v2/admin/blockchains/new', token: token, params: { key: 'test-blockchain', name: 'Test', client: 'geth',server: 'http://127.0.0.1', height: 123333, explorer_transaction: 'test', explorer_address: 'test', status: 'active', min_confirmations: -6, step: 2 }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.blockchain.non_positive_min_confirmations')
|
||||
end
|
||||
|
||||
it 'validate status param' do
|
||||
api_post '/api/v2/admin/blockchains/new', token: token, params: { key: 'test-blockchain', name: 'Test', client: 'geth',server: 'http://127.0.0.1', height: 123333, explorer_transaction: 'test', explorer_address: 'test', status: 'actived', min_confirmations: 6, step: 2 }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.blockchain.invalid_status')
|
||||
end
|
||||
|
||||
it 'validate client param' do
|
||||
api_post '/api/v2/admin/blockchains/new', token: token, params: { key: 'test-blockchain', name: 'Test', client: 'gezz',server: 'http://127.0.0.1', height: 123333, explorer_transaction: 'test', explorer_address: 'test', status: 'active', min_confirmations: 6, step: 2 }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.blockchain.invalid_client')
|
||||
end
|
||||
|
||||
it 'checked required params' do
|
||||
api_post '/api/v2/admin/blockchains/new', token: token, params: { }
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.blockchain.missing_key')
|
||||
expect(response).to include_api_error('admin.blockchain.missing_name')
|
||||
expect(response).to include_api_error('admin.blockchain.missing_client')
|
||||
expect(response).to include_api_error('admin.blockchain.missing_height')
|
||||
end
|
||||
|
||||
it 'validates server' do
|
||||
api_post '/api/v2/admin/blockchains/new', token: token, params: { key: 'test-blockchain', name: 'Test', client: 'geth',server: 'not_a_url', height: 123333, explorer_transaction: 'test', explorer_address: 'test'}
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.blockchain.invalid_server')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/blockchains/new', token: level_3_member_token, params: { key: 'test-blockchain', name: 'Test', client: 'geth', server: 'http://127.0.0.1', height: 123333, explorer_transaction: 'test', explorer_address: 'test', status: 'active', min_confirmations: 6, step: 2 }
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
it 'key already exists' do
|
||||
api_post '/api/v2/admin/blockchains/new', token: token, params: { key: Blockchain.first.key, name: 'Test', client: 'geth',server: 'http://127.0.0.1', height: 123333, explorer_transaction: 'test', explorer_address: 'test'}
|
||||
expect(response.status).to eq 422
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/blockchains/update' do
|
||||
context 'permissions' do
|
||||
let(:support) { create(:member, :admin, :level_3, role: :support, email: 'example@gmail.com', uid: 'ID73BF61C8H1') }
|
||||
let(:support_token) { jwt_for(support) }
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/blockchains/update', params: { key: 'test-blockchain', id: Blockchain.first.id }, token: support_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
it 'returns updated blockchain' do
|
||||
api_post '/api/v2/admin/blockchains/update', params: { name: 'Test Blockchain', id: Blockchain.first.id }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['name']).to eq 'Test Blockchain'
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns updated blockchain' do
|
||||
api_post '/api/v2/admin/blockchains/update', params: { key: 'test-blockchain', id: Blockchain.first.id }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['key']).to eq 'test-blockchain'
|
||||
end
|
||||
|
||||
it 'returns updated blockchain' do
|
||||
api_post '/api/v2/admin/blockchains/update', token: token, params: { key: 'Test-blockchain ', id: Blockchain.first.id }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['key']).to eq 'test-blockchain'
|
||||
end
|
||||
|
||||
it 'long blockchain key' do
|
||||
api_post '/api/v2/admin/blockchains/update', token: token, params: { key: Faker::String.random(1024) }
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('admin.blockchain.key_too_long')
|
||||
end
|
||||
|
||||
it 'long blockchain name' do
|
||||
api_post '/api/v2/admin/blockchains/update', token: token, params: { name: Faker::String.random(1024) }
|
||||
expect(response).not_to be_successful
|
||||
expect(response).to include_api_error('admin.blockchain.name_too_long')
|
||||
end
|
||||
|
||||
it 'validate height param' do
|
||||
api_post '/api/v2/admin/blockchains/update', token: token, params: { height: -123333 }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.blockchain.non_positive_height')
|
||||
end
|
||||
|
||||
it 'checked required params' do
|
||||
api_post '/api/v2/admin/blockchains/update', token: level_3_member_token, params: { key: 'test-blockchain'}
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.blockchain.missing_id')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/blockchains/update', token: level_3_member_token, params: { id: Blockchain.first.id }
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/blockchains/process_block' do
|
||||
context 'returns error' do
|
||||
it 'in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/blockchains/process_block', token: level_3_member_token, params: { block_number: 1, id: Blockchain.last.id }
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
it 'when blockchain doesnt exist' do
|
||||
api_post "/api/v2/admin/blockchains/process_block", params: { block_number: 1, id: Blockchain.last.id + 1 }, token: token
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'when blockchain is not accessible' do
|
||||
api_post "/api/v2/admin/blockchains/process_block", params: { block_number: 1, id: Blockchain.last.id }, token: token
|
||||
|
||||
expect(response).to include_api_error('admin.blockchain.process_block')
|
||||
end
|
||||
end
|
||||
|
||||
context 'successful' do
|
||||
let!(:blockchain) { Blockchain.find_by(key: 'btc-testnet') }
|
||||
let(:service) { BlockchainService.new(blockchain) }
|
||||
let!(:currency) { create(:currency, :btc, id: 'fake') }
|
||||
let(:block_number) { 3 }
|
||||
let!(:member) { create(:member) }
|
||||
let!(:fake_blockchain) { create(:blockchain, 'fake-testnet') }
|
||||
let!(:wallet) { create(:wallet, :fake_deposit) }
|
||||
|
||||
before do
|
||||
Blockchain.any_instance.stubs(:blockchain_api).returns(service)
|
||||
service.stubs(:latest_block_number).returns(4)
|
||||
clear_redis
|
||||
PaymentAddress.create!(member: member,
|
||||
wallet: wallet,
|
||||
address: 'fake_address')
|
||||
end
|
||||
|
||||
context 'deposit' do
|
||||
let(:transaction) { Peatio::Transaction.new(hash: 'fake_txid', from_addresses: ['fake_address'], to_address: 'fake_address', amount: 5, block_number: block_number, currency_id: 'fake', txout: 4, status: 'success') }
|
||||
let(:expected_block) { Peatio::Block.new(block_number, [transaction]) }
|
||||
|
||||
before do
|
||||
service.adapter.stubs(:fetch_block!).returns(expected_block)
|
||||
end
|
||||
|
||||
it 'detects in the block' do
|
||||
expect(Deposits::Coin.where(currency: currency).exists?).to be false
|
||||
|
||||
api_post '/api/v2/admin/blockchains/process_block', token: token, params: { block_number: block_number, id: blockchain.id }
|
||||
result = JSON.parse(response.body)
|
||||
expect(response).to be_successful
|
||||
expect(Deposits::Coin.where(currency: currency).exists?).to be true
|
||||
end
|
||||
|
||||
it 'doesn\'t update height of blockchain' do
|
||||
blockchain_height = blockchain.height
|
||||
expect(blockchain_height).not_to eq (block_number)
|
||||
|
||||
api_post '/api/v2/admin/blockchains/process_block', token: token, params: { block_number: block_number, id: blockchain.id }
|
||||
result = JSON.parse(response.body)
|
||||
expect(response).to be_successful
|
||||
expect(result['height']).not_to eq block_number
|
||||
expect(result['height']).to eq blockchain_height
|
||||
end
|
||||
end
|
||||
|
||||
context 'withdraw' do
|
||||
let!(:member_account) { member.get_account(:fake).tap { |ac| ac.update!(balance: 50, locked: 10) } }
|
||||
let!(:withdrawal) do
|
||||
Withdraw.create!(member: member,
|
||||
currency: currency,
|
||||
amount: 1,
|
||||
txid: "fake_hash",
|
||||
rid: 'fake_address',
|
||||
sum: 1,
|
||||
type: Withdraws::Coin,
|
||||
aasm_state: :confirming)
|
||||
end
|
||||
|
||||
let!(:transaction) do
|
||||
Peatio::Transaction.new(hash: 'fake_hash', to_address: 'fake_address', amount: 1, block_number: block_number, currency_id: currency.id, txout: 10, status: 'pending')
|
||||
end
|
||||
|
||||
let!(:succeed_transaction) do
|
||||
Peatio::Transaction.new(hash: 'fake_hash', to_address: 'fake_address', from_addresses: ['fake_address'], amount: 1, block_number: block_number, currency_id: currency.id, txout: 10, status: 'success')
|
||||
end
|
||||
|
||||
let(:expected_block) { Peatio::Block.new(block_number, [transaction]) }
|
||||
|
||||
before do
|
||||
service.adapter.stubs(:fetch_block!).returns(expected_block)
|
||||
service.adapter.stubs(:fetch_transaction).with(transaction).returns(succeed_transaction)
|
||||
end
|
||||
|
||||
it 'detects successfuly in the block' do
|
||||
expect(Withdraws::Coin.find_by(currency: currency, txid: transaction.hash).succeed?).to be false
|
||||
|
||||
api_post '/api/v2/admin/blockchains/process_block', token: token, params: { block_number: block_number, id: blockchain.id }
|
||||
result = JSON.parse(response.body)
|
||||
expect(response).to be_successful
|
||||
expect(Withdraws::Coin.find_by(currency: currency, txid: transaction.hash).succeed?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
467
spec/api/v2/admin/currencies_spec.rb
Normal file
467
spec/api/v2/admin/currencies_spec.rb
Normal file
@@ -0,0 +1,467 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Currencies, 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) }
|
||||
|
||||
describe 'GET /api/v2/admin/currencies/:code' do
|
||||
let(:fiat) { Currency.find(:usd) }
|
||||
let(:coin) { Currency.find(:btc) }
|
||||
|
||||
let(:expected_for_fiat) do
|
||||
%w[code type deposit_fee withdraw_fee withdraw_limit_24h withdraw_limit_72h min_collection_amount base_factor precision position]
|
||||
end
|
||||
let(:expected_for_coin) do
|
||||
expected_for_fiat.concat(%w[blockchain_key base_factor precision subunits options])
|
||||
end
|
||||
|
||||
it 'returns information about specified currency' do
|
||||
api_get "/api/v2/admin/currencies/#{coin.code}", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('code')).to eq coin.code
|
||||
end
|
||||
|
||||
it 'returns correct keys for fiat' do
|
||||
api_get "/api/v2/admin/currencies/#{fiat.code}", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expected_for_fiat.each { |key| expect(result).to have_key key }
|
||||
|
||||
(expected_for_coin - expected_for_fiat).each do |key|
|
||||
expect(result).not_to have_key key
|
||||
end
|
||||
end
|
||||
|
||||
context 'currency code with dot' do
|
||||
let!(:currency) { create(:currency, :xagm_cx) }
|
||||
|
||||
it 'returns information about specified currency' do
|
||||
api_get "/api/v2/admin/currencies/#{currency.code}", token: token
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('code')).to eq currency.code
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns correct keys for coin' do
|
||||
api_get "/api/v2/admin/currencies/#{coin.code}", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expected_for_coin.each { |key| expect(result).to have_key key }
|
||||
end
|
||||
|
||||
it 'returns ordered by position currencies' do
|
||||
api_get "/api/v2/admin/currencies/", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('position')).to eq Currency.ordered.pluck(:position)
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid code' do
|
||||
api_get '/api/v2/admin/currencies/invalid', token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.doesnt_exist')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get "/api/v2/admin/currencies/#{coin.code}", token: level_3_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/currencies' do
|
||||
it 'list of currencies' do
|
||||
api_get '/api/v2/admin/currencies', token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.count
|
||||
end
|
||||
|
||||
it 'list of coins' do
|
||||
api_get '/api/v2/admin/currencies', params: { type: 'coin' }, token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.coins.size
|
||||
end
|
||||
|
||||
it 'list of fiats' do
|
||||
api_get '/api/v2/admin/currencies', params: { type: 'fiat' }, token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body, symbolize_names: true)
|
||||
expect(result.size).to eq Currency.fiats.size
|
||||
expect(result.dig(0, :code)).to eq 'usd'
|
||||
end
|
||||
|
||||
it 'list of deposit enabled currencies' do
|
||||
api_get '/api/v2/admin/currencies', params: { deposit_enabled: true }, token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.deposit_enabled.count
|
||||
end
|
||||
|
||||
it 'list of deposit disabled currencies' do
|
||||
api_get '/api/v2/admin/currencies', params: { deposit_enabled: false }, token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.where(deposit_enabled: false).count
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid deposit_enabled type' do
|
||||
api_get '/api/v2/admin/currencies', params: { deposit_enabled: 'invalid' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.non_boolean_deposit_enabled')
|
||||
end
|
||||
|
||||
it 'list of withdrawal enabled currencies' do
|
||||
api_get '/api/v2/admin/currencies', params: { withdrawal_enabled: true }, token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.withdrawal_enabled.count
|
||||
end
|
||||
|
||||
it 'list of withdrawal disabled currencies' do
|
||||
api_get '/api/v2/admin/currencies', params: { withdrawal_enabled: false }, token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.where(withdrawal_enabled: false).count
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid withdrawal_enabled type' do
|
||||
api_get '/api/v2/admin/currencies', params: { withdrawal_enabled: 'invalid' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.non_boolean_withdrawal_enabled')
|
||||
end
|
||||
|
||||
it 'list of visible currencies' do
|
||||
api_get '/api/v2/admin/currencies', params: { visible: true }, token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.visible.count
|
||||
end
|
||||
|
||||
it 'list of not visible currencies' do
|
||||
api_get '/api/v2/admin/currencies', params: { visible: false }, token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.where(visible: false).count
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid visible type' do
|
||||
api_get '/api/v2/admin/currencies', params: { visible: 'invalid' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.non_boolean_visible')
|
||||
end
|
||||
|
||||
it 'list of visible coins' do
|
||||
api_get '/api/v2/admin/currencies', params: { visible: true, type: 'coin' }, token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.coins.select { |c| c['visible'] == true }.count
|
||||
end
|
||||
|
||||
it 'list of not visible coins' do
|
||||
api_get '/api/v2/admin/currencies', params: { visible: false, type: 'coin' }, token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.coins.where(visible: false).count
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid type' do
|
||||
api_get '/api/v2/admin/currencies', params: { type: 'invalid' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
end
|
||||
|
||||
it 'returns currencies by ascending order' do
|
||||
api_get '/api/v2/admin/currencies', params: { ordering: 'asc', order_by: 'code'}, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.first['code']).to eq 'btc'
|
||||
end
|
||||
|
||||
it 'returns paginated currencies' do
|
||||
api_get '/api/v2/admin/currencies', params: { limit: 3, page: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response.headers.fetch('Total')).to eq '6'
|
||||
expect(result.size).to eq 3
|
||||
expect(result.first['code']).to eq 'usd'
|
||||
|
||||
api_get '/api/v2/admin/currencies', params: { limit: 3, page: 2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response.headers.fetch('Total')).to eq '6'
|
||||
expect(result.size).to eq 3
|
||||
expect(result.first['code']).to eq 'eth'
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get '/api/v2/admin/currencies', token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/currencies/new' do
|
||||
it 'create coin' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', blockchain_key: 'btc-testnet' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['type']).to eq 'coin'
|
||||
end
|
||||
|
||||
it 'create token' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', blockchain_key: 'btc-testnet', parent_id: 'btc' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['type']).to eq 'coin'
|
||||
expect(result['parent_id']).to eq 'btc'
|
||||
end
|
||||
|
||||
it 'create fiat' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', type: 'fiat' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['type']).to eq 'fiat'
|
||||
end
|
||||
|
||||
it 'validate blockchain_key param' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', blockchain_key: 'test-blockchain' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.blockchain_key_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validate type param' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', blockchain_key: 'test-blockchain' , type: 'test'}, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.invalid_type')
|
||||
end
|
||||
|
||||
it 'validate visible param' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', type: 'fiat', visible: '123'}, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.non_boolean_visible')
|
||||
end
|
||||
|
||||
it 'validate parent_id param' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', type: 'coin', parent_id: 'trst'}, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.parent_id_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validate deposit_enabled param' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: Currency.first.id, deposit_enabled: '123' }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.non_boolean_deposit_enabled')
|
||||
end
|
||||
|
||||
it 'validate withdrawal_enabled param' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: Currency.first.id, withdrawal_enabled: '123' }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.non_boolean_withdrawal_enabled')
|
||||
end
|
||||
|
||||
it 'validate options param' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', type: 'fiat', options: 'test'}, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.non_json_options')
|
||||
end
|
||||
|
||||
it 'verifies subunits >= 0' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', blockchain_key: 'btc-testnet', subunits: -1 }, token: token
|
||||
|
||||
expect(response).to include_api_error 'admin.currency.invalid_subunits'
|
||||
expect(response).not_to be_successful
|
||||
end
|
||||
|
||||
it 'verifies subunits <= 18' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', blockchain_key: 'btc-testnet', subunits: 19 }, token: token
|
||||
|
||||
expect(response).to include_api_error 'admin.currency.invalid_subunits'
|
||||
expect(response).not_to be_successful
|
||||
end
|
||||
|
||||
it 'creates 1_000_000_000_000_000_000 base_factor' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', blockchain_key: 'btc-testnet', subunits: 18 }, token: token
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(response).to be_successful
|
||||
expect(result['base_factor']).to eq 1_000_000_000_000_000_000
|
||||
expect(result['subunits']).to eq 18
|
||||
end
|
||||
|
||||
it 'return error while putting base_factor and subunit params' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', blockchain_key: 'btc-testnet', subunits: 18, base_factor: 1 }, token: token
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(result['errors']).to eq(['admin.currency.one_of_base_factor_subunits_fields'])
|
||||
end
|
||||
|
||||
it 'creates currency with 1000 base_factor' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', blockchain_key: 'btc-testnet', base_factor: 1000 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['base_factor']).to eq 1000
|
||||
expect(result['subunits']).to eq 3
|
||||
end
|
||||
|
||||
it 'checked required params' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.missing_code')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/currencies/new', params: { code: 'test', blockchain_key: 'btc-testnet' }, token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/currencies/update' do
|
||||
context 'permissions' do
|
||||
let(:support) { create(:member, :admin, :level_3, role: :support, email: 'example@gmail.com', uid: 'ID73BF61C8H1') }
|
||||
let(:support_token) { jwt_for(support) }
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.find_by(type: 'fiat').code, precision: 1 }, token: support_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
it 'updates fiat' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.find_by(type: 'fiat').code, name: 'Test' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['name']).to eq 'Test'
|
||||
end
|
||||
end
|
||||
|
||||
it 'update fiat' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.find_by(type: 'fiat').code, min_collection_amount: 1.2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['min_collection_amount']).to eq '1.2'
|
||||
end
|
||||
|
||||
it 'update coin' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.find_by(type: 'coin').code, min_collection_amount: 1.2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['min_collection_amount']).to eq '1.2'
|
||||
end
|
||||
|
||||
it 'validate blockchain_key param' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.find_by(type: 'coin').code, blockchain_key: 'test' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.blockchain_key_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validate parent_id param' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.find_by(type: 'coin').code, parent_id: 'trst' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.parent_id_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validate position param' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.find_by(type: 'coin').code, position: 0 }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.invalid_position')
|
||||
end
|
||||
|
||||
it 'validate visible param' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.first.id, visible: '123' }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.non_boolean_visible')
|
||||
end
|
||||
|
||||
it 'validate deposit_enabled param' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.first.id, deposit_enabled: '123' }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.non_boolean_deposit_enabled')
|
||||
end
|
||||
|
||||
it 'validate withdrawal_enabled param' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.first.id, withdrawal_enabled: '123' }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.non_boolean_withdrawal_enabled')
|
||||
end
|
||||
|
||||
it 'validates negative precision' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.first.id, precision: -1 }, token: token
|
||||
}.not_to change { Currency.first }
|
||||
|
||||
expect(response).not_to be_successful
|
||||
expect(response.status).to eq 422
|
||||
end
|
||||
|
||||
it 'validate options param' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.first.id, options: 'test' }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.non_json_options')
|
||||
end
|
||||
|
||||
it 'checked required params' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.currency.missing_code')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/currencies/update', params: { code: Currency.first.id }, token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
293
spec/api/v2/admin/deposits_spec.rb
Normal file
293
spec/api/v2/admin/deposits_spec.rb
Normal file
@@ -0,0 +1,293 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Deposits, 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) }
|
||||
let!(:fiat_deposits) do
|
||||
[
|
||||
create(:deposit_usd, amount: 10.0),
|
||||
create(:deposit_usd, amount: 9.0),
|
||||
create(:deposit_usd, amount: 100.0, member: level_3_member),
|
||||
]
|
||||
end
|
||||
let!(:coin_deposits) do
|
||||
[
|
||||
create(:deposit_btc, amount: 102.0),
|
||||
create(:deposit_btc, amount: 11.0, member: level_3_member),
|
||||
create(:deposit_btc, amount: 12.0, member: level_3_member),
|
||||
]
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/deposits' do
|
||||
let(:url) { '/api/v2/admin/deposits' }
|
||||
|
||||
it 'get all deposits' do
|
||||
api_get url, token: token
|
||||
|
||||
actual = JSON.parse(response.body)
|
||||
expected = coin_deposits + fiat_deposits
|
||||
|
||||
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 = (coin_deposits + fiat_deposits).sort { |a, b| a.id <=> b.id }
|
||||
|
||||
expect(actual.map { |a| a['id'] }).to eq expected.map(&:id)
|
||||
end
|
||||
|
||||
it 'descending by amount' do
|
||||
api_get url, token: token, params: { order_by: 'amount', ordering: 'desc' }
|
||||
|
||||
actual = JSON.parse(response.body)
|
||||
expected = (coin_deposits + fiat_deposits).sort { |a, b| b.amount <=> a.amount }
|
||||
|
||||
expect(actual.map { |a| a['id'] }).to eq expected.map(&:id)
|
||||
end
|
||||
|
||||
it 'ordering by unexisting field' do
|
||||
api_get url, token: token, params: { order_by: 'cutiness', ordering: 'desc' }
|
||||
|
||||
actual = JSON.parse(response.body)
|
||||
expected = coin_deposits + fiat_deposits
|
||||
|
||||
expect(actual.map { |a| a['id'] }).to match_array 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 = (coin_deposits + fiat_deposits).select { |d| d.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 type' do
|
||||
api_get url, token: token, params: { type: 'coin' }
|
||||
|
||||
actual = JSON.parse(response.body)
|
||||
expected = coin_deposits
|
||||
|
||||
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 email' do
|
||||
api_get url, token: token, params: { email: level_3_member.email }
|
||||
|
||||
expected = (coin_deposits + fiat_deposits).select { |d| d.member.email == level_3_member.email }
|
||||
|
||||
expect(response_body.length).to eq expected.length
|
||||
expect(response_body.map { |a| a['state'] }).to match_array expected.map(&:aasm_state)
|
||||
expect(response_body.map { |a| a['id'] }).to match_array expected.map(&:id)
|
||||
expect(response_body.map { |a| a['currency'] }).to match_array expected.map(&:currency_id)
|
||||
expect(response_body.map { |a| a['member'] }).to all eq level_3_member.id
|
||||
expect(response_body.map { |a| a['type'] }).to match_array(expected.map { |d| d.currency.coin? ? 'coin' : 'fiat' })
|
||||
expect(response_body.map { |a| a['uid'] }).to match_array(expected.map { |d| d.member.uid })
|
||||
expect(response_body.map { |a| a['email'] }).to match_array(expected.map { |d| d.member.email })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/deposits/actions' do
|
||||
let(:url) { '/api/v2/admin/deposits/actions' }
|
||||
let(:fiat) { fiat_deposits.first }
|
||||
let!(:coin) { create(:deposit, :deposit_trst, aasm_state: :accepted) }
|
||||
|
||||
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.deposit.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.deposit.invalid_action')
|
||||
end
|
||||
|
||||
it 'does not pass coin action for fiat' do
|
||||
api_post url, token: token, params: { action: 'collect', id: fiat.id }
|
||||
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('admin.deposit.invalid_action')
|
||||
end
|
||||
end
|
||||
|
||||
context 'updates deposit' do
|
||||
let!(:coin) { create(:deposit, :deposit_trst) }
|
||||
|
||||
it 'accept fiat' do
|
||||
api_post url, token: token, params: { action: 'accept', id: fiat.id }
|
||||
expect(fiat.reload.aasm_state).to eq('accepted')
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'accept coin' do
|
||||
api_post url, token: token, params: { action: 'accept', id: coin.id }
|
||||
expect(coin.reload.aasm_state).to eq('accepted')
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'reject fiat' do
|
||||
api_post url, token: token, params: { action: 'reject', id: fiat.id }
|
||||
expect(response).to be_successful
|
||||
expect(fiat.reload.aasm_state).to eq('rejected')
|
||||
end
|
||||
end
|
||||
|
||||
context 'action :process' do
|
||||
it 'sends event to deposit_collection daemon' do
|
||||
api_post url, token: token, params: { action: 'process', id: coin.id }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(Deposit.find(response_body['id']).processing?).to be_truthy
|
||||
end
|
||||
|
||||
it 'sends event to deposit_collection daemon' do
|
||||
api_post url, token: token, params: { action: 'fee_process', fees: true, id: coin.id }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(Deposit.find(response_body['id']).fee_processing?).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/deposits/new' do
|
||||
let(:url) { '/api/v2/admin/deposits/new' }
|
||||
let(:fiat) { Currency.find(:usd) }
|
||||
let(:coin) { Currency.find(:btc) }
|
||||
|
||||
context 'validates params' do
|
||||
it 'returns error when user doesnt exist' do
|
||||
api_post url, token: token, params: { uid: SecureRandom.uuid, currency: fiat.code, amount: 12.2 }
|
||||
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('admin.deposit.user_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'returns error when currency doesnt exist' do
|
||||
api_post url, token: token, params: { uid: admin.uid, currency: coin.code, amount: 12.2 }
|
||||
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('admin.deposit.currency_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'returns error when amount is not decimal' do
|
||||
api_post url, token: token, params: { uid: admin.uid, currency: fiat.code, amount: 'amount' }
|
||||
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('admin.deposit.non_decimal_amount')
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates fiat deposit' do
|
||||
api_post url, token: token, params: { uid: admin.uid, currency: fiat.code, amount: '13.4' }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response.status).to eq 201
|
||||
expect(result['currency']).to eq fiat.id
|
||||
expect(result['member']).to eq admin.id
|
||||
expect(result['uid']).to eq admin.uid
|
||||
expect(result['email']).to eq admin.email
|
||||
expect(result['amount']).to eq '13.4'
|
||||
expect(result['type']).to eq 'fiat'
|
||||
expect(result['state']).to eq 'submitted'
|
||||
expect(result['transfer_type']).to eq 'fiat'
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post url, token: level_3_member_token, params: { uid: admin.uid, currency: fiat.code, amount: 12.1 }
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/deposit_address' do
|
||||
let(:url) { '/api/v2/admin/deposit_address' }
|
||||
|
||||
context 'failed' do
|
||||
let(:currency) { :eth }
|
||||
|
||||
it 'validates currency with address_format param' do
|
||||
api_post url, params: { currency: 'abc', uid: '' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.deposit.user_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validates currency' do
|
||||
api_post url, params: { currency: 'dildocoin', uid: level_3_member.uid }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.deposit.currency_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validates currency address format' do
|
||||
api_post url, params: { currency: 'eth', uid: level_3_member.uid, address_format: 'cash' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.deposit.doesnt_support_cash_address_format')
|
||||
end
|
||||
end
|
||||
|
||||
context 'successful' do
|
||||
context 'eth address' do
|
||||
let(:currency) { :eth }
|
||||
let(:wallet) { Wallet.deposit_wallet(currency) }
|
||||
before { level_3_member.payment_address(wallet.id).update!(address: '2N2wNXrdo4oEngp498XGnGCbru29MycHogR') }
|
||||
|
||||
it 'expose data about eth address' do
|
||||
api_post url, params: { currency: currency, uid: level_3_member.uid}, token: token
|
||||
expect(response.body).to eq '{"currencies":["eth"],"address":"2n2wnxrdo4oengp498xgngcbru29mychogr","state":"active"}'
|
||||
end
|
||||
|
||||
it 'pending user address state' do
|
||||
level_3_member.payment_address(wallet.id).update!(address: nil)
|
||||
api_post url, params: { currency: currency, uid: level_3_member.uid}, 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_post url, params: { currency: currency, uid: level_3_member.uid}, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.deposit.deposit_disabled')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
150
spec/api/v2/admin/engines_spec.rb
Normal file
150
spec/api/v2/admin/engines_spec.rb
Normal file
@@ -0,0 +1,150 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Engines, 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) }
|
||||
|
||||
describe 'GET /api/v2/admin/engines/:id' do
|
||||
let(:engine) { Engine.first }
|
||||
|
||||
it 'returns information about specified engine' do
|
||||
api_get "/api/v2/admin/engines/#{engine.id}", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('id')).to eq engine.id
|
||||
expect(result.fetch('name')).to eq engine.name
|
||||
expect(result.fetch('driver')).to eq engine.driver
|
||||
expect(result.fetch('state')).to eq engine.state
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid id' do
|
||||
api_get "/api/v2/admin/engines/#{Engine.last.id + 1}", token: token
|
||||
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get "/api/v2/admin/engines/#{engine.id}", token: level_3_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/engines' do
|
||||
it 'lists of engines' do
|
||||
api_get '/api/v2/admin/engines', token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq 2
|
||||
end
|
||||
|
||||
it 'returns engines by ascending order' do
|
||||
api_get '/api/v2/admin/engines', params: { ordering: 'asc' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.first['id']).to eq Engine.first.id
|
||||
end
|
||||
|
||||
it 'returns paginated engines' do
|
||||
api_get '/api/v2/admin/engines', params: { limit: 1, page: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '2'
|
||||
expect(result.size).to eq 1
|
||||
|
||||
api_get '/api/v2/admin/engines', params: { limit: 1, page: 2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '2'
|
||||
expect(result.size).to eq 1
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get '/api/v2/admin/engines', token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/engines/new' do
|
||||
let(:engine) { create(:engine) }
|
||||
let(:valid_params) do
|
||||
{
|
||||
name: 'new-engine',
|
||||
driver: 'new_driver',
|
||||
uid: 'UID123456',
|
||||
key: 'your_key',
|
||||
secret: 'your_secret',
|
||||
data: { some_data: 'some data' }
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates new engine' do
|
||||
api_post '/api/v2/admin/engines/new', token: token, params: valid_params
|
||||
result = JSON.parse(response.body)
|
||||
expect(response).to be_successful
|
||||
expect(result['name']).to eq 'new-engine'
|
||||
expect(result['data'].blank?).to eq true
|
||||
|
||||
api_post '/api/v2/admin/engines/new', token: token, params: valid_params
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.engine.duplicate_name')
|
||||
end
|
||||
|
||||
it 'checked required params' do
|
||||
api_post '/api/v2/admin/engines/new', params: {}, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.engine.missing_name')
|
||||
expect(response).to include_api_error('admin.engine.missing_driver')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/engines/new', params: valid_params, token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/engines/update' do
|
||||
it 'updates attributes' do
|
||||
api_post '/api/v2/admin/engines/update', params: { id: Engine.first.id, name: 'Second Engine', driver: 'second_driver' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['name']).to eq 'Second Engine'
|
||||
expect(result['driver']).to eq 'second_driver'
|
||||
end
|
||||
|
||||
it 'updates secret' do
|
||||
api_post '/api/v2/admin/engines/update', params: { id: Engine.first.id, secret: 'my_secret' }, token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(Engine.first.secret).to eq('my_secret')
|
||||
end
|
||||
|
||||
it 'checkes required params' do
|
||||
api_post '/api/v2/admin/engines/update', params: {}, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.engine.missing_id')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/engines/update', params: { id: Engine.first.id, name: :new }, token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
101
spec/api/v2/admin/internal_transfer_spec.rb
Normal file
101
spec/api/v2/admin/internal_transfer_spec.rb
Normal file
@@ -0,0 +1,101 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::InternalTransfers, type: :request do
|
||||
let(:endpoint) { '/api/v2/admin/internal_transfers' }
|
||||
let(:admin) { create(:member, :admin, :level_3, email: 'example@gmail.com', uid: 'ID73BF61C8H0', username: 'test') }
|
||||
let(:token) { jwt_for(admin) }
|
||||
let(:level_3_member) { create(:member, :level_3, uid: 'ID84BF61C8H0', username: 'member') }
|
||||
let(:level_3_member_token) { jwt_for(level_3_member) }
|
||||
|
||||
describe 'GET /api/v2/admin/internal_transfer' do
|
||||
let!(:internal_transfer_btc) { create_list(:internal_transfer_btc, 4, :with_deposit_liability, sender: admin, receiver: level_3_member) }
|
||||
let!(:internal_transfer_usd) { create_list(:internal_transfer_usd, 3, :with_deposit_liability, sender: level_3_member) }
|
||||
|
||||
it 'lists of internal transfers' do
|
||||
api_get endpoint, token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response_body.size).to eq 7
|
||||
end
|
||||
|
||||
it 'returns paginated internal transfers' do
|
||||
api_get endpoint, params: { limit: 1, page: 1 }, token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '7'
|
||||
expect(response_body.size).to eq 1
|
||||
|
||||
api_get endpoint, params: { limit: 1, page: 2 }, token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '7'
|
||||
expect(response_body.size).to eq 1
|
||||
end
|
||||
|
||||
it 'returns internal transfers by desc order' do
|
||||
api_get endpoint, params: { ordering: 'desc' }, token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response_body.first['id']).to eq InternalTransfer.last.id
|
||||
end
|
||||
|
||||
it 'returns internal transfers by ascending order' do
|
||||
api_get endpoint, params: { ordering: 'asc' }, token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response_body.first['id']).to eq InternalTransfer.first.id
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get endpoint, token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
context 'filtering' do
|
||||
it 'by currency' do
|
||||
api_get endpoint, token: token, params: { currency: 'btc' }
|
||||
|
||||
expect(response_body.count).to eq(InternalTransfer.where(currency_id: 'btc').count)
|
||||
end
|
||||
|
||||
it 'returns orders for specific sender by uid' do
|
||||
api_get endpoint, params: { sender: admin.uid }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['sender_uid']}.size).to eq 4
|
||||
expect(result.map{|r| r['sender_uid']}).to all eq admin.uid
|
||||
end
|
||||
|
||||
it 'returns orders for specific sender by username' do
|
||||
api_get endpoint, params: { sender: admin.username }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['sender_username']}.size).to eq 4
|
||||
expect(result.map{|r| r['sender_username']}).to all eq admin.username
|
||||
end
|
||||
|
||||
it 'returns orders for specific receiver by uid' do
|
||||
api_get endpoint, params: { receiver: level_3_member.uid }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['receiver_uid']}.size).to eq 4
|
||||
expect(result.map{|r| r['receiver_uid']}).to all eq level_3_member.uid
|
||||
end
|
||||
|
||||
it 'returns orders for specific receiver by username' do
|
||||
api_get endpoint, params: { receiver: level_3_member.username }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['receiver_username']}.size).to eq 4
|
||||
expect(result.map{|r| r['receiver_username']}).to all eq level_3_member.username
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
241
spec/api/v2/admin/markets_spec.rb
Normal file
241
spec/api/v2/admin/markets_spec.rb
Normal file
@@ -0,0 +1,241 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Markets, 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) }
|
||||
|
||||
describe 'GET /api/v2/admin/markets/:id' do
|
||||
let(:market) { Market.find_by(id: 'btcusd') }
|
||||
|
||||
it 'returns information about specified market' do
|
||||
api_get "/api/v2/admin/markets/#{market.id}", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('id')).to eq market.id
|
||||
expect(result.fetch('base_unit')).to eq market.base_currency
|
||||
expect(result.fetch('quote_unit')).to eq market.quote_currency
|
||||
expect(result.fetch('data')).to eq market.data
|
||||
end
|
||||
|
||||
it 'returns ordered by position currencies' do
|
||||
api_get "/api/v2/admin/markets/", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('position')).to eq Market.ordered.pluck(:position)
|
||||
end
|
||||
|
||||
context 'market name with dot' do
|
||||
let!(:currency) { create(:currency, :xagm_cx) }
|
||||
let!(:market) { create(:market, :xagm_cxusd) }
|
||||
|
||||
it 'returns information about specified market' do
|
||||
api_get "/api/v2/admin/markets/#{market.id}", token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('id')).to eq market.id
|
||||
expect(result.fetch('base_unit')).to eq market.base_currency
|
||||
expect(result.fetch('quote_unit')).to eq market.quote_currency
|
||||
expect(result.fetch('data')).to eq market.data
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid id' do
|
||||
api_get '/api/v2/admin/markets/120', token: token
|
||||
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get "/api/v2/admin/markets/#{market.id}", token: level_3_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/markets' do
|
||||
it 'lists of markets' do
|
||||
api_get '/api/v2/admin/markets', token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq 2
|
||||
end
|
||||
|
||||
it 'returns markets by ascending order' do
|
||||
api_get '/api/v2/admin/markets', params: { ordering: 'asc', order_by: 'quote_currency' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.first['quote_unit']).to eq 'eth'
|
||||
end
|
||||
|
||||
it 'returns paginated markets' do
|
||||
api_get '/api/v2/admin/markets', params: { limit: 1, page: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '2'
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['id']).to eq 'btcusd'
|
||||
|
||||
api_get '/api/v2/admin/markets', params: { limit: 1, page: 2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '2'
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['id']).to eq 'btceth'
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get '/api/v2/admin/markets', token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/markets/new' do
|
||||
let(:engine) { create(:engine) }
|
||||
let(:valid_params) do
|
||||
{
|
||||
base_currency: 'trst',
|
||||
quote_currency: 'btc',
|
||||
engine_id: engine.id,
|
||||
price_precision: 2,
|
||||
amount_precision: 2,
|
||||
min_price: 0.01,
|
||||
min_amount: 0.01,
|
||||
data: {
|
||||
upstream: {
|
||||
driver: :opendax
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates new market' do
|
||||
api_post '/api/v2/admin/markets/new', token: token, params: valid_params
|
||||
result = JSON.parse(response.body)
|
||||
expect(response).to be_successful
|
||||
expect(result['id']).to eq 'trstbtc'
|
||||
expect(result['engine_id']).to eq Market.last.engine_id
|
||||
expect(result['data']).to eq({ 'upstream' => { 'driver' => 'opendax' } })
|
||||
end
|
||||
|
||||
it 'create new market with engine name param' do
|
||||
api_post '/api/v2/admin/markets/new', token: token, params: valid_params.except(:engine_id).merge(engine_name: engine.name)
|
||||
result = JSON.parse(response.body)
|
||||
expect(response).to be_successful
|
||||
expect(result['id']).to eq 'trstbtc'
|
||||
expect(result['engine_id']).to eq Market.last.engine_id
|
||||
expect(result['data']).to eq({ 'upstream' => { 'driver' => 'opendax' } })
|
||||
end
|
||||
|
||||
it 'validate base_currency param' do
|
||||
api_post '/api/v2/admin/markets/new', token: token, params: valid_params.merge(base_currency: 'test')
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.market.currency_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validate quote_currency param' do
|
||||
api_post '/api/v2/admin/markets/new', token: token, params: valid_params.merge(quote_currency: 'test')
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.market.currency_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validate enabled param' do
|
||||
api_post '/api/v2/admin/markets/new', token: token, params: valid_params.merge(state: '123')
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.market.invalid_state')
|
||||
end
|
||||
|
||||
it 'validate engine name param' do
|
||||
api_post '/api/v2/admin/markets/new', token: token, params: valid_params.except(:engine_id).merge(engine_name: 'test')
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.market.engine_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'checked exactly_one_ofr params' do
|
||||
api_post '/api/v2/admin/markets/new', token: token, params: valid_params.merge(engine_name: 'test')
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.market.one_of_engine_id_engine_name_fields')
|
||||
end
|
||||
|
||||
it 'checked required params' do
|
||||
api_post '/api/v2/admin/markets/new', params: {}, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.market.missing_base_currency')
|
||||
expect(response).to include_api_error('admin.market.missing_quote_currency')
|
||||
expect(response).to include_api_error('admin.market.one_of_engine_id_engine_name_fields')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/markets/new', params: valid_params, token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/markets/update' do
|
||||
it 'updates attributes' do
|
||||
api_post '/api/v2/admin/markets/update', params: { id: Market.first.id, amount_precision: 3, price_precision: 5, min_amount: 0.1, min_price: 0.1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['amount_precision']).to eq 3
|
||||
expect(result['price_precision']).to eq 5
|
||||
expect(result['min_amount']).to eq '0.1'
|
||||
expect(result['min_price']).to eq '0.1'
|
||||
end
|
||||
|
||||
it 'updates data' do
|
||||
api_post '/api/v2/admin/markets/update', params: { id: Market.first.id, data: { 'upstream' => { 'driver' => 'opendax' } } }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['data']).to eq({ 'upstream' => { 'driver' => 'opendax' } })
|
||||
end
|
||||
|
||||
it 'validates data field' do
|
||||
api_post '/api/v2/admin/markets/update', params: { id: Market.first.id, data: 'data' }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.market.invalid_data')
|
||||
end
|
||||
|
||||
it 'validates position' do
|
||||
api_post '/api/v2/admin/markets/update', params: { id: Market.first.id, position: 0 }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.market.invalid_position')
|
||||
end
|
||||
|
||||
it 'checkes required params' do
|
||||
api_post '/api/v2/admin/markets/update', params: {}, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.market.missing_id')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/markets/update', params: { id: Market.first.id, state: :disabled }, token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
195
spec/api/v2/admin/members_spec.rb
Normal file
195
spec/api/v2/admin/members_spec.rb
Normal file
@@ -0,0 +1,195 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Members, type: :request do
|
||||
let(:uid) { 'ID00FEE1DEAD' }
|
||||
let(:email) { 'someone@mailbox.com' }
|
||||
let(:admin) { create(:member, :admin, :level_3, email: email, uid: uid) }
|
||||
let(:token) { jwt_for(admin) }
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:member_token) { jwt_for(member) }
|
||||
let!(:members) do
|
||||
[
|
||||
create(:member, role: 'admin', state: 'pending'),
|
||||
create(:member, role: 'admin', state: 'active'),
|
||||
create(:member, group: 'any'),
|
||||
]
|
||||
end
|
||||
|
||||
describe 'GET' do
|
||||
context 'authentication' do
|
||||
it 'requires token' do
|
||||
get '/api/v2/admin/members'
|
||||
expect(response.code).to eq '401'
|
||||
end
|
||||
|
||||
it 'validates permissions' do
|
||||
api_get'/api/v2/admin/members', token: member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
it 'authenticate admin' do
|
||||
api_get'/api/v2/admin/members', token: token
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context 'filtering' do
|
||||
it 'returns all members' do
|
||||
api_get'/api/v2/admin/members', token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.length).to eq(Member.count)
|
||||
end
|
||||
|
||||
it 'filters by role & state' do
|
||||
api_get'/api/v2/admin/members', token: token, params: { role: 'admin', state: 'active' }
|
||||
result = JSON.parse(response.body)
|
||||
expected = Member.where(role: 'admin', state: 'active').pluck(:id)
|
||||
|
||||
expect(result.map { |r| r['id'] }).to match_array expected
|
||||
end
|
||||
|
||||
it 'filters by group' do
|
||||
api_get'/api/v2/admin/members', token: token, params: { group: 'any' }
|
||||
result = JSON.parse(response.body)
|
||||
expected = Member.where(group: 'any').pluck(:id)
|
||||
|
||||
expect(result.map { |r| r['id'] }).to match_array expected
|
||||
end
|
||||
|
||||
it 'filters by uid' do
|
||||
api_get'/api/v2/admin/members', token: token, params: { uid: uid }
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.length).to eq 1
|
||||
expect(result.first['id']).to eq admin.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'accounts' do
|
||||
before { admin.touch_accounts }
|
||||
it 'returns accounts for all currencies' do
|
||||
api_get'/api/v2/admin/members', token: token, params: { uid: uid }
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.first['accounts'].count).to eq(Currency.count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Get by uid' do
|
||||
context 'authentication' do
|
||||
it 'requires token' do
|
||||
get "/api/v2/admin/members/#{member.uid}"
|
||||
expect(response.code).to eq '401'
|
||||
end
|
||||
|
||||
it 'validates permissions' do
|
||||
api_get "/api/v2/admin/members/#{member.uid}", token: member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
it 'authenticate admin' do
|
||||
api_get "/api/v2/admin/members/#{member.uid}", token: token
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context 'get user by uid' do
|
||||
let!(:account) { member.touch_accounts }
|
||||
let(:address) { Faker::Blockchain::Ethereum.address }
|
||||
let(:coin) { Currency.find(:btc) }
|
||||
|
||||
let!(:beneficiary) { create(:beneficiary,
|
||||
member: member,
|
||||
currency: coin,
|
||||
state: :active,
|
||||
data: generate(:coin_beneficiary_data).merge(address: address)) }
|
||||
|
||||
|
||||
it 'returns user entities' do
|
||||
api_get "/api/v2/admin/members/UID1234", token: token
|
||||
expect(response.code).to eq '404'
|
||||
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'returns user entities' do
|
||||
api_get "/api/v2/admin/members/#{member.uid}", token: token
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result['uid']).to eq(member.uid)
|
||||
expect(result['email']).to eq(member.email)
|
||||
expect(result['uid']).to eq(member.uid)
|
||||
expect(result['group']).to eq(member.group)
|
||||
expect(result['accounts'][0]['currency']).to eq(member.accounts[0].currency.id)
|
||||
expect(result['accounts'][0]['balance']).to eq(member.accounts[0].balance.to_s)
|
||||
expect(result['accounts'][0]['locked']).to eq(member.accounts[0].locked.to_s)
|
||||
expect(result['beneficiaries'][0]['currency']).to eq(member.beneficiaries[0].currency_id)
|
||||
expect(result['beneficiaries'][0]['data']['address']).to eq(member.beneficiaries[0].data['address'])
|
||||
end
|
||||
|
||||
context 'fiat beneficiary' do
|
||||
let(:fiat) { Currency.find(:usd) }
|
||||
|
||||
let!(:beneficiary) { create(:beneficiary,
|
||||
member: member,
|
||||
currency: fiat,
|
||||
state: :active,
|
||||
data: generate(:fiat_beneficiary_data)) }
|
||||
|
||||
it 'returns user entities' do
|
||||
api_get "/api/v2/admin/members/#{member.uid}", token: token
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result['uid']).to eq(member.uid)
|
||||
expect(result['email']).to eq(member.email)
|
||||
expect(result['uid']).to eq(member.uid)
|
||||
expect(result['group']).to eq(member.group)
|
||||
expect(result['accounts'][0]['currency']).to eq(member.accounts[0].currency.id)
|
||||
expect(result['accounts'][0]['balance']).to eq(member.accounts[0].balance.to_s)
|
||||
expect(result['accounts'][0]['locked']).to eq(member.accounts[0].locked.to_s)
|
||||
expect(result['beneficiaries'][0]['currency']).to eq(member.beneficiaries[0].currency_id)
|
||||
expect(result['beneficiaries'][0]['data']['address']).to eq(member.beneficiaries[0].data['address'])
|
||||
expect(result['beneficiaries'][0]['data']['account_number']).to eq(member.beneficiaries[0].data['account_number'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/members/groups' do
|
||||
it 'get list of all existing groups' do
|
||||
api_get '/api/v2/admin/members/groups', token: token
|
||||
expect(JSON.parse(response.body)).to match_array Member.groups.map &:to_s
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/members/groups' do
|
||||
it 'returns user with updated role' do
|
||||
api_put "/api/v2/admin/members/#{member.uid}", token: token, params: { group: 'vip-2' }
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-2')
|
||||
end
|
||||
|
||||
it 'returns user with updated group' do
|
||||
api_put "/api/v2/admin/members/#{member.uid}", token: token, params: { group: ' Vip-2 ' }
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-2')
|
||||
end
|
||||
|
||||
it 'returns status 404 and error' do
|
||||
api_put "/api/v2/admin/members/U1234", token: token, params: { group: 'vip-2' }
|
||||
expect(response).to have_http_status(404)
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'validate params' do
|
||||
api_put "/api/v2/admin/members/#{member.uid}", token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.member.missing_group')
|
||||
end
|
||||
end
|
||||
end
|
||||
175
spec/api/v2/admin/operations_spec.rb
Normal file
175
spec/api/v2/admin/operations_spec.rb
Normal file
@@ -0,0 +1,175 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Operations, type: :request do
|
||||
let(:uid) { 'ID00FEE1DEAD' }
|
||||
let(:email) { 'someone@mailbox.com' }
|
||||
let(:admin) { create(:member, :admin, :level_3, email: email, uid: uid) }
|
||||
let(:token) { jwt_for(admin) }
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:member_token) { jwt_for(member) }
|
||||
|
||||
describe 'GET' do
|
||||
context 'authentication' do
|
||||
it 'requires token' do
|
||||
get '/api/v2/admin/assets'
|
||||
expect(response.code).to eq '401'
|
||||
end
|
||||
|
||||
it 'validates permissions' do
|
||||
api_get'/api/v2/admin/expenses', token: member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
it 'authenticate admin' do
|
||||
api_get'/api/v2/admin/liabilities', token: token
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context 'assets' do
|
||||
let!(:assets) do
|
||||
[
|
||||
create(:asset, currency: Currency.find(:btc), debit: 80.0),
|
||||
create(:asset, currency: Currency.find(:btc), debit: 120.0),
|
||||
create(:asset, currency: Currency.find(:usd), debit: 220.0),
|
||||
]
|
||||
end
|
||||
|
||||
it 'entity present valid fields' do
|
||||
api_get '/api/v2/admin/assets', token: token
|
||||
result = JSON.parse(response.body)
|
||||
expected = %w[id rid currency reference_type credit debit created_at code account_kind]
|
||||
|
||||
expect(result.first.keys).to match_array expected
|
||||
end
|
||||
|
||||
it 'filters by currency' do
|
||||
api_get '/api/v2/admin/assets', token: token, params: { currency: 'usd' }
|
||||
result = JSON.parse(response.body)
|
||||
expected = assets.select { |a| a.currency_id == 'usd' }
|
||||
|
||||
expect(result.map { |a| a['id'] }).to match_array expected.map { |e| e.id }
|
||||
end
|
||||
|
||||
it 'orders by debit ascending' do
|
||||
api_get '/api/v2/admin/assets', token: token, params: { order_by: 'debit', ordering: 'asc' }
|
||||
result = JSON.parse(response.body)
|
||||
expected = assets.sort { |a, b| a.debit <=> b.debit }
|
||||
|
||||
expect(result.map { |a| a['id'] }).to match_array expected.map { |e| e.id }
|
||||
end
|
||||
end
|
||||
|
||||
context 'expenses' do
|
||||
let!(:expenses) do
|
||||
[
|
||||
create(:expense, created_at: 5.days.ago, reference_type: 'Deposit'),
|
||||
create(:expense, created_at: 2.days.ago, reference_type: 'Trade'),
|
||||
create(:expense, created_at: 2.days.ago, reference_type: 'Deposit'),
|
||||
]
|
||||
end
|
||||
|
||||
it 'entity presents valid fields' do
|
||||
api_get '/api/v2/admin/expenses', token: token
|
||||
result = JSON.parse(response.body)
|
||||
expected = %w[id rid currency reference_type credit debit created_at code account_kind]
|
||||
|
||||
expect(result.first.keys).to match_array expected
|
||||
end
|
||||
|
||||
it 'filters by reference type' do
|
||||
api_get '/api/v2/admin/expenses', token: token, params: { reference_type: 'Deposit' }
|
||||
result = JSON.parse(response.body)
|
||||
expected = expenses.select { |a| a.reference_type == 'Deposit' }
|
||||
|
||||
expect(result.map { |e| e['id'] }).to match_array expected.map { |e| e.id }
|
||||
end
|
||||
|
||||
it 'fileters by created_at_to' do
|
||||
api_get '/api/v2/admin/expenses', token: token, params: { range: 'created', to: 3.days.ago }
|
||||
result = JSON.parse(response.body)
|
||||
expected = expenses.select { |l| l.created_at < 3.days.ago }
|
||||
|
||||
expect(result.map { |a| a['id'] }).to match_array expected.map { |e| e.id }
|
||||
end
|
||||
|
||||
it 'filters by created_at_from' do
|
||||
api_get '/api/v2/admin/expenses', token: token, params: { range: 'created', from: 3.days.ago }
|
||||
result = JSON.parse(response.body)
|
||||
expected = expenses.select { |l| l.created_at >= 3.days.ago }
|
||||
|
||||
expect(result.map { |a| a['id'] }).to match_array expected.map { |e| e.id }
|
||||
end
|
||||
end
|
||||
|
||||
context 'revenues' do
|
||||
let!(:revenues) do
|
||||
[
|
||||
create(:revenue, code: 301, currency: Currency.find(:usd), reference_id: 1),
|
||||
create(:revenue, code: 302, currency: Currency.find(:btc), reference_id: 1),
|
||||
create(:revenue, code: 302, currency: Currency.find(:btc), reference_id: 2),
|
||||
]
|
||||
end
|
||||
|
||||
it 'entity presents valid fields' do
|
||||
api_get '/api/v2/admin/revenues', token: token
|
||||
result = JSON.parse(response.body)
|
||||
expected = %w[id rid currency reference_type credit debit created_at code account_kind]
|
||||
|
||||
expect(result.first.keys).to match_array expected
|
||||
end
|
||||
|
||||
it 'filters by code' do
|
||||
api_get '/api/v2/admin/revenues', token: token, params: { code: 302 }
|
||||
result = JSON.parse(response.body)
|
||||
expected = revenues.select { |a| a.code == 302 }
|
||||
|
||||
expect(result.map { |r| r['id'] }).to match_array expected.map { |e| e.id }
|
||||
end
|
||||
|
||||
it 'filters by reference id' do
|
||||
api_get '/api/v2/admin/revenues', token: token, params: { rid: 1 }
|
||||
result = JSON.parse(response.body)
|
||||
expected = revenues.select { |a| a.reference_id == 1 }
|
||||
|
||||
expect(result.map { |r| r['id'] }).to match_array expected.map { |e| e.id }
|
||||
end
|
||||
end
|
||||
|
||||
context 'liabilities' do
|
||||
let!(:liabilities) do
|
||||
[
|
||||
create(:liability, member: member, credit: 110.0),
|
||||
create(:liability, member: member, credit: 190.0),
|
||||
create(:liability, member: admin, credit: 80.0),
|
||||
]
|
||||
end
|
||||
|
||||
it 'entity presents valid fields' do
|
||||
api_get '/api/v2/admin/liabilities', token: token
|
||||
result = JSON.parse(response.body)
|
||||
expected = %w[id rid currency reference_type credit debit created_at code uid account_kind]
|
||||
|
||||
expect(result.first.keys).to match_array expected
|
||||
end
|
||||
|
||||
it 'filters by member uid' do
|
||||
api_get '/api/v2/admin/liabilities', token: token, params: { uid: member.uid }
|
||||
result = JSON.parse(response.body)
|
||||
expected = liabilities.select { |l| l.member.uid == member.uid }
|
||||
|
||||
expect(result.map { |a| a['id'] }).to match_array expected.map { |e| e.id }
|
||||
end
|
||||
|
||||
it 'orders by credit descending' do
|
||||
api_get '/api/v2/admin/liabilities', token: token, params: { order_by: 'credit', ordering: 'asc' }
|
||||
result = JSON.parse(response.body)
|
||||
expected = liabilities.sort { |a, b| b.credit <=> a.credit }
|
||||
|
||||
expect(result.map { |a| a['id'] }).to match_array expected.map { |e| e.id }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
262
spec/api/v2/admin/orders_spec.rb
Normal file
262
spec/api/v2/admin/orders_spec.rb
Normal file
@@ -0,0 +1,262 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Orders, 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) }
|
||||
|
||||
describe 'GET /api/v2/admin/orders' do
|
||||
before do
|
||||
# NOTE: We specify updated_at attribute for testing order of Order.
|
||||
create(:order_bid, :btcusd, price: '11'.to_d, origin_volume: '123.12', member: admin, updated_at: Time.at(1548224524), created_at: Time.at(1548234524))
|
||||
create(:order_bid, :btceth, price: '11'.to_d, origin_volume: '123.12', member: admin, updated_at: Time.at(1548234524), created_at: Time.at(1548254524))
|
||||
create(:order_bid, :btcusd, price: '12'.to_d, origin_volume: '123.12', member: admin, state: Order::CANCEL, updated_at: Time.at(1548244524), created_at: Time.at(1548254524))
|
||||
create(:order_ask, :btcusd, price: '13'.to_d, origin_volume: '123.12', member: admin, state: Order::WAIT, updated_at: Time.at(1548254524), created_at: Time.at(1548254524))
|
||||
create(:order_ask, :btcusd, price: '14'.to_d, origin_volume: '123.12', member: admin, state: Order::DONE, created_at: Time.at(1548254524))
|
||||
end
|
||||
|
||||
it 'csv export' do
|
||||
api_get'/api/v2/admin/orders', token: token, params: { format: :csv }
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'requires authentication' do
|
||||
get '/api/v2/admin/orders', params: { market: 'btcusd' }
|
||||
expect(response.code).to eq '401'
|
||||
end
|
||||
|
||||
it 'validates market param' do
|
||||
api_get '/api/v2/admin/orders', params: { market: 'usdusd' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.market.doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validates limit param' do
|
||||
api_get '/api/v2/admin/orders', params: { market: 'btcusd', limit: -1 }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.pagination.invalid_limit')
|
||||
end
|
||||
|
||||
it 'validates price param' do
|
||||
api_get '/api/v2/admin/orders', params: { market: 'btcusd', price: -1 }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.order.non_positive_price')
|
||||
end
|
||||
|
||||
it 'validates origin_volume param' do
|
||||
api_get '/api/v2/admin/orders', params: { market: 'btcusd', origin_volume: -1 }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.order.non_positive_origin_volume')
|
||||
end
|
||||
|
||||
it 'validates page param' do
|
||||
api_get '/api/v2/admin/orders', params: { market: 'btcusd', limit: 2, page: "page 2" }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.pagination.non_integer_page')
|
||||
end
|
||||
|
||||
it 'validates ord_type param' do
|
||||
api_get '/api/v2/admin/orders', params: { ord_type: 'test' }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.order.invalid_ord_type')
|
||||
end
|
||||
|
||||
it 'returns orders with state done' do
|
||||
api_get '/api/v2/admin/orders', params: { market: 'btcusd', state: Order::DONE }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['state']).to eq Order::DONE
|
||||
end
|
||||
|
||||
it 'returns all my orders for btcusd market' do
|
||||
api_get '/api/v2/admin/orders', params: { market: 'btcusd' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 4
|
||||
end
|
||||
|
||||
it 'returns orders with ord_type limit' do
|
||||
api_get '/api/v2/admin/orders', params: { ord_type: 'limit' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['ord_type']}).to all eq 'limit'
|
||||
end
|
||||
|
||||
it 'returns orders with type sell' do
|
||||
api_get '/api/v2/admin/orders', params: { type: 'sell' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['side']}).to all eq 'sell'
|
||||
end
|
||||
|
||||
it 'returns orders for specific price' do
|
||||
api_get '/api/v2/admin/orders', params: { price: '11'.to_d }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['price']}.size).to eq 2
|
||||
expect(result.map{|r| r['price']}).to all eq '11.0'
|
||||
end
|
||||
|
||||
it 'returns orders for specific origin_volume' do
|
||||
api_get '/api/v2/admin/orders', params: { origin_volume: '123.12' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['origin_volume']}.size).to eq 5
|
||||
expect(result.map{|r| r['origin_volume']}).to all eq '123.12'
|
||||
end
|
||||
|
||||
it 'returns orders for specific user by email' do
|
||||
api_get '/api/v2/admin/orders', params: { email: 'example@gmail.com' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['email']}.size).to eq 5
|
||||
expect(result.map{|r| r['email']}).to all eq 'example@gmail.com'
|
||||
end
|
||||
|
||||
it 'returns orders for specific user by uid' do
|
||||
api_get '/api/v2/admin/orders', params: { uid: 'ID73BF61C8H0' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['uid']}.size).to eq 5
|
||||
expect(result.map{|r| r['uid']}).to all eq 'ID73BF61C8H0'
|
||||
end
|
||||
|
||||
it 'returns paginated orders' do
|
||||
api_get '/api/v2/admin/orders', params: { market: 'btcusd', limit: 1, page: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['price']).to eq '11.0'
|
||||
|
||||
api_get '/api/v2/admin/orders', params: { market: 'btcusd', limit: 1, page: 2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['price']).to eq '12.0'
|
||||
end
|
||||
|
||||
it 'returns orders by ascending order' do
|
||||
api_get '/api/v2/admin/orders', params: { market: 'btcusd', ordering: 'asc', order_by: 'updated_at'}, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.first['price']).to eq '11.0'
|
||||
end
|
||||
|
||||
it 'returns orders for updated time range' do
|
||||
api_get '/api/v2/admin/orders', params: { range: 'updated', from: Time.at(1548224524).iso8601, to: Time.at(1548244524).iso8601 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 3
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get'/api/v2/admin/orders', token: level_3_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/orders/:id/cancel' do
|
||||
let!(:order) { create(:order_bid, :btcusd, price: '12.32'.to_d, volume: '3.14', origin_volume: '12.13', locked: '20.1082', origin_locked: '38.0882', member: level_3_member) }
|
||||
|
||||
before do
|
||||
level_3_member.get_account(:usd).update_attributes(locked: order.price * order.volume)
|
||||
end
|
||||
|
||||
it 'should cancel specified order' do
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: order.to_matching_attributes)
|
||||
expect do
|
||||
api_post "/api/v2/admin/orders/#{order.id}/cancel", token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['id']).to eq order.id
|
||||
end.not_to change(Order, :count)
|
||||
end
|
||||
|
||||
it 'return error in case of non existent order' do
|
||||
api_post '/api/v2/admin/orders/0/cancel', token: token
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/orders/0/cancel', token: level_3_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/orders/cancel' do
|
||||
before do
|
||||
create(:order_ask, :btcusd, price: '12.32', volume: '3.14', origin_volume: '12.13', member: level_3_member)
|
||||
create(:order_bid, :btcusd, price: '12.32', volume: '3.14', origin_volume: '12.13', member: level_3_member)
|
||||
create(:order_bid, :btceth, price: '12.32', volume: '3.14', origin_volume: '12.13', member: level_3_member)
|
||||
|
||||
level_3_member.get_account(:btc).update_attributes(locked: '5')
|
||||
level_3_member.get_account(:usd).update_attributes(locked: '50')
|
||||
end
|
||||
|
||||
it 'should cancel all my orders for specific market' do
|
||||
level_3_member.orders.where(market: 'btceth').each do |o|
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: o.to_matching_attributes)
|
||||
end
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/admin/orders/cancel', token: token, params: { market: 'btceth' }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 1
|
||||
end.not_to change(Order, :count)
|
||||
end
|
||||
|
||||
it 'should cancel all asks for specific market' do
|
||||
level_3_member.orders.where(type: 'OrderAsk', market_id: 'btcusd').each do |o|
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: o.to_matching_attributes)
|
||||
end
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/admin/orders/cancel', token: token, params: { market: 'btcusd', side: 'sell' }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['id']).to eq level_3_member.orders.where(type: 'OrderAsk').first.id
|
||||
end.not_to change(Order, :count)
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/orders/cancel', token: level_3_member_token, params: { market: 'btceth' }
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
it 'return error in case of invalid order type' do
|
||||
api_post '/api/v2/admin/orders/cancel', token: token, params: { market: 'btceth', side: 'ask' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.order.invalid_side')
|
||||
end
|
||||
|
||||
it 'return error in case of invalid market' do
|
||||
api_post '/api/v2/admin/orders/cancel', token: token, params: { market: 'testusd' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.order.market_doesnt_exist')
|
||||
end
|
||||
end
|
||||
end
|
||||
230
spec/api/v2/admin/trades_spec.rb
Normal file
230
spec/api/v2/admin/trades_spec.rb
Normal file
@@ -0,0 +1,230 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Trades, type: :request do
|
||||
let(:uid) { 'ID00FEE1DEAD' }
|
||||
let(:email) { 'someone@mailbox.com' }
|
||||
let(:admin) { create(:member, :admin, :level_3, email: email, uid: uid) }
|
||||
let(:token) { jwt_for(admin) }
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:member_token) { jwt_for(member) }
|
||||
|
||||
describe 'GET /api/v2/admin/trades' do
|
||||
let!(:trades) do
|
||||
[
|
||||
create(:trade, :btcusd, price: 12.0, amount: 2.0, created_at: 3.days.ago),
|
||||
create(:trade, :btcusd, price: 3.0, amount: 13.0, created_at: 5.days.ago),
|
||||
create(:trade, :btcusd, price: 25.0, amount: 5.0, created_at: 1.days.ago, maker: member),
|
||||
create(:trade, :btcusd, price: 6.0, amount: 5.0, created_at: 5.days.ago, taker: member),
|
||||
create(:trade, :btcusd, price: 5.0, amount: 6.0, created_at: 5.days.ago, taker: member),
|
||||
]
|
||||
end
|
||||
|
||||
it 'entity provides correct fields' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { limit: 5 }
|
||||
result = JSON.parse(response.body).first
|
||||
keys = %w[id amount price total maker_order_email taker_order_email created_at maker_uid taker_uid
|
||||
taker_type market maker_fee_currency maker_fee_amount taker_fee_currency taker_fee_amount]
|
||||
|
||||
expect(result.keys).to match_array keys
|
||||
expect(result.values).not_to include nil
|
||||
end
|
||||
|
||||
it 'csv export' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { format: :csv }
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
context 'authentication' do
|
||||
it 'requires token' do
|
||||
get '/api/v2/admin/trades'
|
||||
expect(response.code).to eq '401'
|
||||
end
|
||||
|
||||
it 'validates permissions' do
|
||||
api_get'/api/v2/admin/trades', token: member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
it 'authenticate admin' do
|
||||
api_get'/api/v2/admin/trades', token: token
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context 'pagination' do
|
||||
it 'with default values' do
|
||||
api_get'/api/v2/admin/trades', token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.length).to eq trades.length
|
||||
end
|
||||
|
||||
it 'validates limit' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { limit: 'meow' }
|
||||
expect(response).to include_api_error 'admin.pagination.non_integer_limit'
|
||||
end
|
||||
|
||||
it 'validates page' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { page: 'meow' }
|
||||
expect(response).to include_api_error 'admin.pagination.non_integer_page'
|
||||
end
|
||||
|
||||
it 'first 5 trades ordered by id' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { limit: 5 }
|
||||
result = JSON.parse(response.body)
|
||||
expected = trades[0...5]
|
||||
|
||||
expect(result.map { |t| t['id'] }).to match_array expected.map(&:id)
|
||||
end
|
||||
|
||||
it 'second 5 trades ordered by id' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { limit: 5, page: 2 }
|
||||
result = JSON.parse(response.body)
|
||||
expected = trades[5...10]
|
||||
|
||||
expect(result.map { |t| t['id'] }).to match_array expected.map(&:id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'ordering' do
|
||||
it 'validates ordering' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { ordering: 'straight' }
|
||||
|
||||
expect(response).not_to be_successful
|
||||
end
|
||||
|
||||
it 'orders by price ascending' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { order_by: 'price', ordering: 'asc' }
|
||||
result = JSON.parse(response.body)
|
||||
expected = trades.sort { |a, b| a.price <=> b.price }
|
||||
expect(result.map { |t| t['id'] }).to match_array expected.map(&:id)
|
||||
end
|
||||
|
||||
it 'orders by amount descending' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { order_by: 'amount', ordering: 'asc' }
|
||||
result = JSON.parse(response.body)
|
||||
expected = trades.sort { |a, b| b.amount <=> a.amount }
|
||||
|
||||
expect(result.map { |t| t['id'] }).to match_array expected.map(&:id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'filtering' do
|
||||
context 'with market' do
|
||||
it 'validates market param' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { market: 'btcbtc' }
|
||||
expect(response).to include_api_error "admin.market.doesnt_exist"
|
||||
end
|
||||
|
||||
it 'filters by market' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { market: 'btcusd' }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expected = trades.select { |t| t.market_id == 'btcusd' }
|
||||
|
||||
expect(result.map { |t| t['id'] }).to match_array expected.map(&:id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with uid' do
|
||||
it 'returns orders for specific user (both maker and taker sides)' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { uid: member.uid }
|
||||
result = JSON.parse(response.body)
|
||||
expected = member.trades
|
||||
|
||||
expect(result.map { |t| t['id'] }).to match_array expected.map(&:id)
|
||||
end
|
||||
|
||||
it 'return error when user does not exist' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { uid: 'ID00DEADBEEF' }
|
||||
expect(response).to include_api_error 'admin.user.doesnt_exist'
|
||||
end
|
||||
|
||||
it 'empty collection when user has no trades' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { uid: admin.uid }
|
||||
expect(JSON.parse(response.body)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with timestamps' do
|
||||
it 'validates created_at_from' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { from: 'yesterday' }
|
||||
expect(response).to include_api_error 'admin.filter.range_from_invalid'
|
||||
end
|
||||
|
||||
it 'validates created_at_to' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { to: 'today' }
|
||||
expect(response).to include_api_error 'admin.filter.range_to_invalid'
|
||||
end
|
||||
|
||||
it 'returns trades created after specidfied date' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { from: 4.days.ago }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expected = trades.select { |t| t.created_at >= 4.days.ago }
|
||||
|
||||
expect(result.map { |t| t['id'] }).to match_array expected.map(&:id)
|
||||
end
|
||||
|
||||
it 'return trades created before specidfied date' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { to: 2.days.ago }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expected = trades.select { |t| t.created_at < 2.days.ago }
|
||||
|
||||
expect(result.map { |t| t['id'] }).to match_array expected.map(&:id)
|
||||
end
|
||||
|
||||
it 'returns trades created after and before specidfied dates' do
|
||||
api_get'/api/v2/admin/trades', token: token, params: { from: 4.days.ago, to: 2.days.ago }
|
||||
result = JSON.parse(response.body)
|
||||
expected = trades.select { |t| t.created_at >= 4.days.ago && t.created_at < 2.days.ago }
|
||||
|
||||
expect(result.map { |t| t['id'] }).to match_array expected.map(&:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/trades/:id' do
|
||||
let(:maker) { create(:order_ask, :btcusd) }
|
||||
let(:taker) { create(:order_bid, :btcusd) }
|
||||
let(:trade) { create(:trade, :btcusd, price: 12.0, amount: 2.0, maker_order: maker, taker_order: taker) }
|
||||
|
||||
it 'entity provides correct fields' do
|
||||
api_get "/api/v2/admin/trades/#{trade.id}", token: token
|
||||
result = JSON.parse(response.body)
|
||||
keys = %w[id amount price total maker_order_email taker_order_email created_at maker_uid taker_uid taker_type
|
||||
market maker_fee_currency maker_fee maker_fee_amount taker_fee_currency taker_fee taker_fee_amount maker_order taker_order]
|
||||
|
||||
expect(result.keys).to match_array keys
|
||||
expect(result.values).not_to include nil
|
||||
end
|
||||
|
||||
it 'exposes correct orders' do
|
||||
api_get "/api/v2/admin/trades/#{trade.id}", token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result['maker_order']['id']).to eq maker.id
|
||||
expect(result['taker_order']['id']).to eq taker.id
|
||||
end
|
||||
|
||||
it 'fee calculation' do
|
||||
api_get "/api/v2/admin/trades/#{trade.id}", token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result['maker_fee_amount']).to eq((trade.total * maker.maker_fee).to_s)
|
||||
expect(result['taker_fee_amount']).to eq((trade.amount * taker.taker_fee).to_s)
|
||||
end
|
||||
|
||||
it 'fee currency' do
|
||||
api_get "/api/v2/admin/trades/#{trade.id}", token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result['maker_fee_currency']).to eq 'usd'
|
||||
expect(result['taker_fee_currency']).to eq 'btc'
|
||||
end
|
||||
end
|
||||
end
|
||||
266
spec/api/v2/admin/trading_fees_spec.rb
Normal file
266
spec/api/v2/admin/trading_fees_spec.rb
Normal file
@@ -0,0 +1,266 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::TradingFees, type: :request do
|
||||
|
||||
let(:admin) { create(:member, :admin, :level_3, email: 'example@gmail.com', uid: 'ID73BF61C8H1') }
|
||||
let(:token) { jwt_for(admin) }
|
||||
let(:level_3_member) { create(:member, :level_3) }
|
||||
let(:level_3_member_token) { jwt_for(level_3_member) }
|
||||
|
||||
describe 'GET /trading_fees' do
|
||||
before do
|
||||
create(:trading_fee, maker: 0.0005, taker: 0.001, market_id: :btcusd, group: 'vip-0')
|
||||
create(:trading_fee, maker: 0.0008, taker: 0.001, market_id: :any, group: 'vip-0')
|
||||
create(:trading_fee, maker: 0.001, taker: 0.0012, market_id: :btcusd, group: :any)
|
||||
end
|
||||
|
||||
it 'returns all trading fees' do
|
||||
api_get '/api/v2/admin/trading_fees', token: token
|
||||
|
||||
expect(response.status).to eq 200
|
||||
expect(JSON.parse(response.body).length).to eq TradingFee.count
|
||||
end
|
||||
|
||||
it 'pagination' do
|
||||
api_get '/api/v2/admin/trading_fees', token: token, params: { limit: 1 }
|
||||
expect(JSON.parse(response.body).length).to eq 1
|
||||
end
|
||||
|
||||
it 'filters by market_id' do
|
||||
api_get '/api/v2/admin/trading_fees', token: token, params: { market_id: 'btcusd' }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.map { |r| r['market_id'] }).to all eq 'btcusd'
|
||||
expect(result.length).to eq TradingFee.where(market_id: 'btcusd').count
|
||||
end
|
||||
|
||||
it 'filters by group' do
|
||||
api_get '/api/v2/admin/trading_fees', token: token, params: { group: 'vip-0' }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.map { |r| r['group'] }).to all eq 'vip-0'
|
||||
expect(result.length).to eq TradingFee.where(group: 'vip-0').count
|
||||
end
|
||||
|
||||
it 'capitalized fee group' do
|
||||
api_get '/api/v2/admin/trading_fees', token: token, params: { group: 'Vip-0' }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.map { |r| r['group'] }).to all eq 'vip-0'
|
||||
expect(result.length).to eq TradingFee.where(group: 'vip-0').count
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /trading_fees/new' do
|
||||
it 'creates a table with default group' do
|
||||
api_post '/api/v2/admin/trading_fees/new', token: token, params: { maker: 0.001, taker: 0.0015, market_id: 'btcusd' }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['maker']).to eq('0.001')
|
||||
expect(JSON.parse(response.body)['taker']).to eq('0.0015')
|
||||
expect(JSON.parse(response.body)['group']).to eq('any')
|
||||
expect(JSON.parse(response.body)['market_id']).to eq('btcusd')
|
||||
end
|
||||
|
||||
it 'creates a table with default market' do
|
||||
api_post '/api/v2/admin/trading_fees/new', token: token, params: { group: 'vip-1', maker: 0.001, taker: 0.0015 }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['maker']).to eq('0.001')
|
||||
expect(JSON.parse(response.body)['taker']).to eq('0.0015')
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-1')
|
||||
expect(JSON.parse(response.body)['market_id']).to eq('any')
|
||||
end
|
||||
|
||||
it 'returns created trading fee table' do
|
||||
api_post '/api/v2/admin/trading_fees/new', token: token, params: { group: 'vip-1', market_id: 'btcusd', maker: 0.001, taker: 0.0015 }
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['maker']).to eq('0.001')
|
||||
expect(JSON.parse(response.body)['taker']).to eq('0.0015')
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-1')
|
||||
expect(JSON.parse(response.body)['market_id']).to eq('btcusd')
|
||||
end
|
||||
|
||||
context 'returns created trading fee table without group' do
|
||||
it 'returns created trading fee table' do
|
||||
api_post '/api/v2/admin/trading_fees/new', token: token, params: { market_id: 'btcusd', maker: 0.001, taker: 0.0015 }
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['maker']).to eq('0.001')
|
||||
expect(JSON.parse(response.body)['taker']).to eq('0.0015')
|
||||
expect(JSON.parse(response.body)['group']).to eq('any')
|
||||
expect(JSON.parse(response.body)['market_id']).to eq('btcusd')
|
||||
end
|
||||
end
|
||||
|
||||
context 'returns created trading fee table without market_id' do
|
||||
it 'returns created trading fee table' do
|
||||
api_post '/api/v2/admin/trading_fees/new', token: token, params: { maker: 0.001, taker: 0.0015, group: 'vip-1' }
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['maker']).to eq('0.001')
|
||||
expect(JSON.parse(response.body)['taker']).to eq('0.0015')
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-1')
|
||||
expect(JSON.parse(response.body)['market_id']).to eq('any')
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid market_id' do
|
||||
it 'returns status 422 and error' do
|
||||
api_post '/api/v2/admin/trading_fees/new', token: token, params: { maker: 0.001, taker: 0.0015, market_id: 'uahusd' }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.trading_fee.market_doesnt_exist')
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty maker field' do
|
||||
it 'returns status 422 and error' do
|
||||
api_post '/api/v2/admin/trading_fees/new', token: token, params: { taker: 0.0015, group: 'vip-1', market_id: 'btcusd' }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.trading_fee.invalid_maker')
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty taker field' do
|
||||
it 'returns status 422 and error' do
|
||||
api_post '/api/v2/admin/trading_fees/new', token: token, params: { maker: 0.0015, group: 'vip-1', market_id: 'btcusd' }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.trading_fee.invalid_taker')
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid maker/taker type' do
|
||||
it 'returns status 422 and error' do
|
||||
api_post '/api/v2/admin/trading_fees/new', token: token, params: { taker: -0.1, maker: -0.15, group: 'vip-1', market_id: 'btcusd' }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.trading_fee.invalid_maker')
|
||||
expect(response).to include_api_error('admin.trading_fee.invalid_taker')
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid maker/taker fee' do
|
||||
it 'returns status 422 and error' do
|
||||
api_post '/api/v2/admin/trading_fees/new', token: token, params: { taker: 1, maker: 1, group: 'vip-1', market_id: 'btcusd' }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('Maker must be less than or equal to 0.5')
|
||||
expect(response).to include_api_error('Taker must be less than or equal to 0.5')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /trading_fees/update' do
|
||||
it 'returns updated trading fee table with new group' do
|
||||
api_post '/api/v2/admin/trading_fees/update', token: token, params: { group: 'vip-1', id: TradingFee.first.id }
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['maker']).to eq('0.0015')
|
||||
expect(JSON.parse(response.body)['taker']).to eq('0.0015')
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-1')
|
||||
expect(JSON.parse(response.body)['market_id']).to eq('any')
|
||||
end
|
||||
|
||||
it 'returns updated trading fee table with new group with capitalized letter' do
|
||||
api_post '/api/v2/admin/trading_fees/update', token: token, params: { group: 'Vip-1 ', id: TradingFee.first.id }
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['maker']).to eq('0.0015')
|
||||
expect(JSON.parse(response.body)['taker']).to eq('0.0015')
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-1')
|
||||
expect(JSON.parse(response.body)['market_id']).to eq('any')
|
||||
end
|
||||
|
||||
it 'returns updated trading fee table with new maker' do
|
||||
api_post '/api/v2/admin/trading_fees/update', token: token, params: { market_id: 'btcusd', id: TradingFee.first.id }
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['maker']).to eq('0.0015')
|
||||
expect(JSON.parse(response.body)['taker']).to eq('0.0015')
|
||||
expect(JSON.parse(response.body)['group']).to eq('any')
|
||||
expect(JSON.parse(response.body)['market_id']).to eq('btcusd')
|
||||
end
|
||||
|
||||
it 'returns updated trading fee table with new maker, taker fields' do
|
||||
api_post '/api/v2/admin/trading_fees/update', token: token, params: { maker: 0.1, taker: 0.1, id: TradingFee.first.id }
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['maker']).to eq('0.1')
|
||||
expect(JSON.parse(response.body)['taker']).to eq('0.1')
|
||||
expect(JSON.parse(response.body)['group']).to eq('any')
|
||||
expect(JSON.parse(response.body)['market_id']).to eq('any')
|
||||
end
|
||||
|
||||
context 'not found trading_fee table' do
|
||||
it 'returns status 404 and error' do
|
||||
api_post '/api/v2/admin/trading_fees/update', token: token, params: { id: TradingFee.last.id + 1 }
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty maker type' do
|
||||
it 'returns status 422 and error' do
|
||||
api_post '/api/v2/admin/trading_fees/update', token: token, params: { maker: -1, id: TradingFee.first.id }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.trading_fee.invalid_maker')
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty taker type' do
|
||||
it 'returns status 422 and error' do
|
||||
api_post '/api/v2/admin/trading_fees/update', token: token, params: { taker: -1, id: TradingFee.first.id }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.trading_fee.invalid_taker')
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid maker/taker type' do
|
||||
it 'returns status 422 and error' do
|
||||
api_post '/api/v2/admin/trading_fees/update', token: token, params: { market_id: 'uahusd', id: TradingFee.first.id }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.trading_fee.market_doesnt_exist')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /trading_fees/delete' do
|
||||
let!(:trading_fee) { create(:trading_fee) }
|
||||
|
||||
it 'requires id' do
|
||||
api_post '/api/v2/admin/trading_fees/delete', token: token
|
||||
expect(response).to include_api_error 'admin.tradingfee.missing_id'
|
||||
end
|
||||
|
||||
it 'deletes trading fee table' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/trading_fees/delete', token: token, params: { id: trading_fee.id }
|
||||
}.to change { TradingFee.count }.by(-1)
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
end
|
||||
|
||||
it 'returns deleted trading fee table' do
|
||||
api_post '/api/v2/admin/trading_fees/delete', token: token, params: { id: trading_fee.id }
|
||||
|
||||
expect(JSON.parse(response.body)['id']).to eq trading_fee.id
|
||||
end
|
||||
|
||||
it 'retuns 404 if record does not exist' do
|
||||
expect {
|
||||
api_post '/api/v2/admin/trading_fees/delete', token: token, params: { id: TradingFee.last.id + 42 }
|
||||
}.not_to change { TradingFee.count }
|
||||
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
end
|
||||
358
spec/api/v2/admin/wallets_spec.rb
Normal file
358
spec/api/v2/admin/wallets_spec.rb
Normal file
@@ -0,0 +1,358 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::Wallets, 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) }
|
||||
|
||||
describe 'GET /api/v2/admin/wallets/:id' do
|
||||
let(:wallet) { Wallet.find_by(blockchain_key: 'eth-rinkeby') }
|
||||
|
||||
it 'returns information about specified wallet' do
|
||||
api_get "/api/v2/admin/wallets/#{wallet.id}", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('id')).to eq wallet.id
|
||||
expect(result.fetch('currencies')).to eq wallet.currency_ids
|
||||
expect(result.fetch('address')).to eq wallet.address
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid id' do
|
||||
api_get '/api/v2/admin/wallets/120', token: token
|
||||
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get "/api/v2/admin/wallets/#{wallet.id}", token: level_3_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
it 'returns information about specified wallet' do
|
||||
api_get "/api/v2/admin/wallets/#{wallet.id}", token: token
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result).not_to include('settings')
|
||||
end
|
||||
|
||||
it 'returns NA balance if node not accessible' do
|
||||
wallet.update(balance: wallet.current_balance)
|
||||
api_get "/api/v2/admin/wallets/#{wallet.id}", token: token
|
||||
expect(response).to be_successful
|
||||
expect(response_body['balance']).to eq(wallet.current_balance)
|
||||
end
|
||||
|
||||
it 'returns wallet balance if node accessible' do
|
||||
wallet.update(balance: { 'eth' => '1'})
|
||||
|
||||
api_get "/api/v2/admin/wallets/#{wallet.id}", token: token
|
||||
expect(response).to be_successful
|
||||
expect(response_body['balance']).to eq({ 'eth' => '1' })
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/wallets' do
|
||||
it 'lists of wallets' do
|
||||
api_get '/api/v2/admin/wallets', token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Wallet.count
|
||||
end
|
||||
|
||||
it 'returns paginated wallets' do
|
||||
api_get '/api/v2/admin/wallets', params: { limit: 6, page: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response.headers.fetch('Total')).to eq Wallet.count.to_s
|
||||
expect(result.size).to eq 6
|
||||
expect(result.first['name']).to eq 'Ethereum Deposit Wallet'
|
||||
|
||||
api_get '/api/v2/admin/wallets', params: { limit: 6, page: 2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response.headers.fetch('Total')).to eq Wallet.count.to_s
|
||||
expect(result.size).to eq 2
|
||||
expect(result.first['name']).to eq 'Bitcoin Hot Wallet'
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get "/api/v2/admin/wallets", token: level_3_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
context 'filtering' do
|
||||
it 'filters by blockchain key' do
|
||||
api_get "/api/v2/admin/wallets", token: token, params: { blockchain_key: "eth-rinkeby" }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.length).not_to eq 0
|
||||
expect(result.map { |r| r["blockchain_key"]}).to all eq "eth-rinkeby"
|
||||
end
|
||||
|
||||
it 'filters by kind'do
|
||||
api_get "/api/v2/admin/wallets", token: token, params: { kind: "deposit" }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.length).not_to eq 0
|
||||
expect(result.map { |r| r["kind"]}).to all eq "deposit"
|
||||
end
|
||||
|
||||
context do
|
||||
let(:hot_wallet) { Wallet.joins(:currencies).find_by(blockchain_key: 'eth-rinkeby', kind: :hot, currencies: { id: :eth }) }
|
||||
|
||||
before do
|
||||
hot_wallet.currencies << Currency.find(:trst)
|
||||
end
|
||||
|
||||
it 'filters by currency' do
|
||||
api_get '/api/v2/admin/wallets', token: token, params: { currencies: 'eth' }
|
||||
|
||||
expect(response_body.length).not_to eq 0
|
||||
expect(response_body.pluck('currencies').map { |a| a.include?('eth') }.all?).to eq(true)
|
||||
count = Wallet.joins(:currencies).where(currencies: { id: :eth }).count
|
||||
expect(response_body.find { |c| c['id'] == hot_wallet.id }['currencies'].sort).to eq(%w[eth trst])
|
||||
expect(response_body.count).to eq(count)
|
||||
end
|
||||
|
||||
it 'filters by currency' do
|
||||
api_get '/api/v2/admin/wallets', token: token, params: { currencies: %w[eth trst] }
|
||||
|
||||
expect(response_body.length).not_to eq 0
|
||||
count = Wallet.joins(:currencies).where(currencies: { id: %i[eth trst] }).distinct.count
|
||||
expect(response_body.find { |c| c['id'] == hot_wallet.id }['currencies'].sort).to eq(%w[eth trst])
|
||||
expect(response_body.count).to eq(count)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/wallets/kinds' do
|
||||
it 'list kinds' do
|
||||
api_get '/api/v2/admin/wallets/kinds', token: token
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/wallets/gateways' do
|
||||
it 'list gateways' do
|
||||
api_get '/api/v2/admin/wallets/gateways', token: token
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/wallets/new' do
|
||||
it 'create wallet' do
|
||||
api_post '/api/v2/admin/wallets/new', params: { name: 'Test', kind: 'deposit', currencies: 'eth', address: 'blank', blockchain_key: 'btc-testnet', gateway: 'geth', settings: { uri: 'http://127.0.0.1:18332'}}, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['name']).to eq 'Test'
|
||||
end
|
||||
|
||||
it 'create wallet' do
|
||||
api_post '/api/v2/admin/wallets/new', params: { name: 'Test', kind: 'deposit', currencies: ['eth','trst'], address: 'blank', blockchain_key: 'btc-testnet', gateway: 'geth', settings: { uri: 'http://127.0.0.1:18332'}}, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['currencies']).to eq(['eth', 'trst'])
|
||||
expect(result['name']).to eq 'Test'
|
||||
end
|
||||
|
||||
it 'checked required params' do
|
||||
api_post '/api/v2/admin/wallets/new', params: { }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.wallet.missing_name')
|
||||
expect(response).to include_api_error('admin.wallet.missing_kind')
|
||||
expect(response).to include_api_error('admin.wallet.currencies_field_is_missing')
|
||||
expect(response).to include_api_error('admin.wallet.missing_blockchain_key')
|
||||
expect(response).to include_api_error('admin.wallet.missing_gateway')
|
||||
end
|
||||
|
||||
it 'validate status' do
|
||||
api_post '/api/v2/admin/wallets/new', params: { name: 'Test', kind: 'deposit', currencies: 'eth', address: 'blank', blockchain_key: 'btc-testnet', gateway: 'geth', settings: { uri: 'http://127.0.0.1:18332'}, status: 'disable' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.wallet.invalid_status')
|
||||
end
|
||||
|
||||
it 'validate gateway' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { name: 'Test', kind: 'deposit', currencies: 'eth', address: 'blank', blockchain_key: 'btc-testnet', settings: { uri: 'http://127.0.0.1:18332'}, gateway: 'test' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.wallet.gateway_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validate kind' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { name: 'Test', kind: 'test', currencies: 'eth', address: 'blank', blockchain_key: 'btc-testnet', settings: { uri: 'http://127.0.0.1:18332'}, gateway: 'geth' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.wallet.invalid_kind')
|
||||
end
|
||||
|
||||
it 'validate currency_id' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: 1, name: 'Test', kind: 'deposit', address: 'blank', blockchain_key: 'btc-testnet', gateway: 'geth', settings: { uri: 'http://127.0.0.1:18332'}, currencies: 'test' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.wallet.currency_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validate uri' do
|
||||
api_post '/api/v2/admin/wallets/new', params: { name: 'Test', kind: 'hot', currencies: 'eth', address: 'blank', blockchain_key: 'btc-testnet', settings: { uri: 'invalid_uri'}, gateway: 'geth' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.wallet.invalid_uri_setting')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/wallets/new', params: { name: 'Test', kind: 'deposit', currencies: 'eth', address: 'blank', blockchain_key: 'btc-testnet', gateway: 'geth', settings: { uri: 'http://127.0.0.1:18332'}}, token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/wallets/update' do
|
||||
it 'update wallet' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: Wallet.first.id, gateway: 'geth' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['gateway']).to eq 'geth'
|
||||
end
|
||||
|
||||
it 'update currency' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: Wallet.first.id, currencies: 'btc' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['currencies']).to eq ['btc']
|
||||
end
|
||||
|
||||
it 'update wallet with new secret' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: Wallet.first.id, currencies: 'btc', settings: { secret: 'new secret'} }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['currencies']).to eq ['btc']
|
||||
expect(Wallet.first.settings['uri']).to eq nil
|
||||
expect(Wallet.first.settings['secret']).to eq 'new secret'
|
||||
end
|
||||
|
||||
it 'update wallet with settings' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: Wallet.first.id, currencies: 'btc', settings: { secret: 'new secret', access_token: 'new token'} }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['currencies']).to eq ['btc']
|
||||
expect(Wallet.first.settings['uri']).to eq nil
|
||||
expect(Wallet.first.settings['access_token']).to eq 'new token'
|
||||
expect(Wallet.first.settings['secret']).to eq 'new secret'
|
||||
end
|
||||
|
||||
it 'validate blockchain_key' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: Wallet.first.id, blockchain_key: 'test' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.wallet.blockchain_key_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validate status' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: Wallet.first.id, status: 'disable' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.wallet.invalid_status')
|
||||
end
|
||||
|
||||
it 'validate gateway' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: Wallet.first.id, gateway: 'test' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.wallet.gateway_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validate kind' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: Wallet.first.id, kind: 'test' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.wallet.invalid_kind')
|
||||
end
|
||||
|
||||
it 'validate currency_id' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: Wallet.first.id, currencies: 'test ' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.wallet.currency_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'checked required params' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.wallet.missing_id')
|
||||
end
|
||||
|
||||
it 'validate uri' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: Wallet.first.id, name: 'Test', kind: 'hot', currencies: 'eth', address: 'blank', blockchain_key: 'btc-testnet', settings: { uri: 'invalid_uri'}, gateway: 'geth' }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.wallet.invalid_uri_setting')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/wallets/update', params: { id: Wallet.first.id, status: 'disabled' }, token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/wallets/currencies' do
|
||||
let(:wallet) { Wallet.joins(:currencies).find_by(currencies: { id: 'eth' }) }
|
||||
|
||||
it do
|
||||
api_post '/api/v2/admin/wallets/currencies', params: { id: wallet.id, currencies: 'trst' }, token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response_body['currencies'].include?('trst')).to be_truthy
|
||||
end
|
||||
|
||||
it do
|
||||
api_post '/api/v2/admin/wallets/currencies', params: { id: wallet.id, currencies: 'eth' }, token: token
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('Currency has already been taken')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/wallets/currencies' do
|
||||
let(:wallet) { Wallet.joins(:currencies).find_by(currencies: { id: 'eth' }) }
|
||||
|
||||
it do
|
||||
api_delete '/api/v2/admin/wallets/currencies', params: { id: wallet.id, currencies: 'eth' }, token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response_body['currencies'].include?('eth')).to be_falsey
|
||||
end
|
||||
|
||||
it do
|
||||
api_delete '/api/v2/admin/wallets/currencies', params: { id: wallet.id, currencies: 'trst' }, token: token
|
||||
|
||||
expect(response).to have_http_status 404
|
||||
end
|
||||
end
|
||||
end
|
||||
201
spec/api/v2/admin/whitelisted_smart_contracts_spec.rb
Normal file
201
spec/api/v2/admin/whitelisted_smart_contracts_spec.rb
Normal file
@@ -0,0 +1,201 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::WhitelistedSmartContracts, type: :request do
|
||||
let(:admin) { create(:member, :admin, :level_3, email: 'example15@gmail.com', uid: 'ID73BF61C8H1') }
|
||||
let(:token) { jwt_for(admin) }
|
||||
let(:level_3_member) { create(:member, :level_3) }
|
||||
let(:level_3_member_token) { jwt_for(level_3_member) }
|
||||
let(:test_file) { Rack::Test::UploadedFile.new(Rails.root.join('spec', 'resources', 'whitelisted_addresses', file_name), 'text/csv') }
|
||||
let(:file_name) { '1.csv' }
|
||||
|
||||
describe 'GET /api/v2/admin/whitelisted_smart_contract/:id' do
|
||||
let!(:addresses_1) { create(:whitelisted_smart_contract, :address_1) }
|
||||
let!(:addresses_2) { create(:whitelisted_smart_contract, :address_2) }
|
||||
let!(:addresses_3) { create(:whitelisted_smart_contract, :address_3) }
|
||||
let!(:addresses_4) { create(:whitelisted_smart_contract, :address_4) }
|
||||
let!(:addresses_5) { create(:whitelisted_smart_contract, :address_5) }
|
||||
|
||||
let(:whitelisted_address) { WhitelistedSmartContract.find(1) }
|
||||
|
||||
it 'returns information about specified WhitelistedSmartContract' do
|
||||
api_get "/api/v2/admin/whitelisted_smart_contract/#{whitelisted_address.id}", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('id')).to eq whitelisted_address.id
|
||||
expect(result.fetch('address')).to eq whitelisted_address.address
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid id' do
|
||||
api_get '/api/v2/admin/whitelisted_smart_contract/120', token: token
|
||||
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get "/api/v2/admin/whitelisted_smart_contract/#{whitelisted_address.id}", token: level_3_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/admin/whitelisted_smart_contracts' do
|
||||
let!(:addresses_1) { create(:whitelisted_smart_contract, :address_1) }
|
||||
let!(:addresses_2) { create(:whitelisted_smart_contract, :address_2) }
|
||||
let!(:addresses_3) { create(:whitelisted_smart_contract, :address_3) }
|
||||
let!(:addresses_4) { create(:whitelisted_smart_contract, :address_4) }
|
||||
let!(:addresses_5) { create(:whitelisted_smart_contract, :address_5) }
|
||||
|
||||
it 'lists of whitelisted_smart_contracts' do
|
||||
api_get '/api/v2/admin/whitelisted_smart_contracts', token: token
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq WhitelistedSmartContract.count
|
||||
end
|
||||
|
||||
it 'returns paginated whitelisted_smart_contracts' do
|
||||
api_get '/api/v2/admin/whitelisted_smart_contracts', token: token, params: { limit: 4, page: 1 }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response.headers.fetch('Total')).to eq WhitelistedSmartContract.count.to_s
|
||||
expect(result.size).to eq 4
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_get "/api/v2/admin/whitelisted_smart_contracts", token: level_3_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
context 'filtering' do
|
||||
it 'filters by blockchain key' do
|
||||
api_get "/api/v2/admin/whitelisted_smart_contracts", token: token, params: { blockchain_key: "eth-rinkeby" }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.length).not_to eq 0
|
||||
expect(result.map { |r| r["blockchain_key"]}).to all eq "eth-rinkeby"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/whitelisted_smart_contracts/csv' do
|
||||
it 'create whitelisted_smart_contracts from csv' do
|
||||
api_post '/api/v2/admin/whitelisted_smart_contracts/csv', token: token, params: { file: test_file }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/admin/whitelisted_smart_contracts' do
|
||||
it 'create whitelisted_smart_contracts' do
|
||||
api_post '/api/v2/admin/whitelisted_smart_contracts', token: token, params: { description: 'Test', address: 'blank', blockchain_key: 'eth-rinkeby' }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['description']).to eq 'Test'
|
||||
end
|
||||
|
||||
it 'checked required params' do
|
||||
api_post '/api/v2/admin/whitelisted_smart_contracts', token: token, params: { }
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.whitelistedsmartcontract.missing_address')
|
||||
expect(response).to include_api_error('admin.whitelistedsmartcontract.missing_blockchain_key')
|
||||
end
|
||||
|
||||
it 'validate state' do
|
||||
api_post '/api/v2/admin/whitelisted_smart_contracts', token: token, params: { description: 'Test', address: 'blank', blockchain_key: 'eth-rinkeby', state: 'invalid' }
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.whitelistedsmartcontract.invalid_state')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_post '/api/v2/admin/whitelisted_smart_contracts', params: { description: 'Test', address: 'blank', blockchain_key: 'eth-rinkeby'}, token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /api/v2/admin/whitelisted_smart_contracts' do
|
||||
let!(:addresses_1) { create(:whitelisted_smart_contract, :address_1) }
|
||||
|
||||
it 'update WhitelistedSmartContract' do
|
||||
api_put '/api/v2/admin/whitelisted_smart_contracts', token: token, params: { id: WhitelistedSmartContract.first.id, address: 'test' }
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['address']).to eq 'test'
|
||||
end
|
||||
|
||||
it 'update WhitelistedSmartContract with new description' do
|
||||
api_put '/api/v2/admin/whitelisted_smart_contracts', token: token, params: { id: WhitelistedSmartContract.first.id, description: 'test'}
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(WhitelistedSmartContract.first.description).to eq 'test'
|
||||
end
|
||||
|
||||
it 'update WhitelistedSmartContract with new state' do
|
||||
api_put '/api/v2/admin/whitelisted_smart_contracts', token: token, params: { id: WhitelistedSmartContract.first.id, state: 'disabled' }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(WhitelistedSmartContract.first.state).to eq 'disabled'
|
||||
end
|
||||
|
||||
it 'validate blockchain_key' do
|
||||
api_put '/api/v2/admin/whitelisted_smart_contracts', token: token, params: { id: WhitelistedSmartContract.first.id, blockchain_key: 'test' }
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.whitelistedsmartcontract.blockchain_key_doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validate status' do
|
||||
api_put '/api/v2/admin/whitelisted_smart_contracts', token: token, params: { id: WhitelistedSmartContract.first.id, state: 'disable' }
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('admin.whitelistedsmartcontract.invalid_state')
|
||||
end
|
||||
|
||||
it 'checked required params' do
|
||||
api_put '/api/v2/admin/whitelisted_smart_contracts', token: token, params: { }
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('admin.whitelistedsmartcontract.missing_id')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_put '/api/v2/admin/whitelisted_smart_contracts', params: { id: WhitelistedSmartContract.first.id, status: 'disabled' }, token: level_3_member_token
|
||||
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('admin.ability.not_permitted')
|
||||
end
|
||||
|
||||
it 'return error in case of not permitted ability' do
|
||||
api_put '/api/v2/admin/whitelisted_smart_contracts', params: { id: WhitelistedSmartContract.last.id + 1, status: 'disabled' }, token: token
|
||||
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
context 'rails validations' do
|
||||
let!(:addresses_1) { create(:whitelisted_smart_contract, :address_1) }
|
||||
let!(:addresses_2) { create(:whitelisted_smart_contract, :address_2) }
|
||||
|
||||
it 'returns error' do
|
||||
api_put '/api/v2/admin/whitelisted_smart_contracts', token: token, params: { id: WhitelistedSmartContract.first.id, address: addresses_2.address }
|
||||
|
||||
expect(response).to include_api_error 'Address has already been taken'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
248
spec/api/v2/admin/withdraw_limts_spec.rb
Normal file
248
spec/api/v2/admin/withdraw_limts_spec.rb
Normal file
@@ -0,0 +1,248 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Admin::WithdrawLimits, 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) }
|
||||
|
||||
describe 'GET /withdraw_limits' do
|
||||
before do
|
||||
create(:withdraw_limit, limit_24_hour: 100, limit_1_month: 1000, kyc_level: 2, group: 'vip-0')
|
||||
create(:withdraw_limit, limit_24_hour: 50, limit_1_month: 500, kyc_level: 1, group: 'vip-0')
|
||||
create(:withdraw_limit, limit_24_hour: 50, limit_1_month: 500, kyc_level: 1, group: :any)
|
||||
end
|
||||
|
||||
it 'returns all withdraw limits' do
|
||||
api_get '/api/v2/admin/withdraw_limits', token: token
|
||||
|
||||
expect(response.status).to eq 200
|
||||
expect(JSON.parse(response.body).length).to eq WithdrawLimit.count
|
||||
end
|
||||
|
||||
it 'pagination' do
|
||||
api_get '/api/v2/admin/withdraw_limits', token: token, params: { limit: 1 }
|
||||
expect(JSON.parse(response.body).length).to eq 1
|
||||
end
|
||||
|
||||
it 'filters by group' do
|
||||
api_get '/api/v2/admin/withdraw_limits', token: token, params: { group: 'vip-0' }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.map { |r| r['group'] }).to all eq 'vip-0'
|
||||
expect(result.length).to eq WithdrawLimit.where(group: 'vip-0').count
|
||||
end
|
||||
|
||||
it 'filters by kyc_level' do
|
||||
api_get '/api/v2/admin/withdraw_limits', token: token, params: { kyc_level: '1' }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.map { |r| r['kyc_level'] }).to all eq '1'
|
||||
expect(result.length).to eq WithdrawLimit.where(kyc_level: '1').count
|
||||
end
|
||||
|
||||
it 'capitalized group' do
|
||||
api_get '/api/v2/admin/withdraw_limits', token: token, params: { group: 'Vip-0' }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.map { |r| r['group'] }).to all eq 'vip-0'
|
||||
expect(result.length).to eq WithdrawLimit.where(group: 'vip-0').count
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /withdraw_limits' do
|
||||
it 'creates a table with default group' do
|
||||
api_post '/api/v2/admin/withdraw_limits', token: token, params: { kyc_level: 1, limit_24_hour: 100, limit_1_month: 1000 }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['limit_24_hour']).to eq('100.0')
|
||||
expect(JSON.parse(response.body)['limit_1_month']).to eq('1000.0')
|
||||
expect(JSON.parse(response.body)['group']).to eq('any')
|
||||
expect(JSON.parse(response.body)['kyc_level']).to eq('1')
|
||||
end
|
||||
|
||||
it 'creates a table with default kyc_level' do
|
||||
api_post '/api/v2/admin/withdraw_limits', token: token, params: { group: 'vip-1', limit_24_hour: 100, limit_1_month: 1000 }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['limit_24_hour']).to eq('100.0')
|
||||
expect(JSON.parse(response.body)['limit_1_month']).to eq('1000.0')
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-1')
|
||||
expect(JSON.parse(response.body)['kyc_level']).to eq('any')
|
||||
end
|
||||
|
||||
it 'returns created withdraw limit table' do
|
||||
api_post '/api/v2/admin/withdraw_limits', token: token, params: { kyc_level: 4, group: 'vip-1', limit_24_hour: 100, limit_1_month: 1000 }
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['limit_24_hour']).to eq('100.0')
|
||||
expect(JSON.parse(response.body)['limit_1_month']).to eq('1000.0')
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-1')
|
||||
expect(JSON.parse(response.body)['kyc_level']).to eq('4')
|
||||
end
|
||||
|
||||
context 'returns created withdraw limit table without group' do
|
||||
it 'returns created withdraw limit table' do
|
||||
api_post '/api/v2/admin/withdraw_limits', token: token, params: { kyc_level: 1, limit_24_hour: 100, limit_1_month: 1000 }
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['limit_24_hour']).to eq('100.0')
|
||||
expect(JSON.parse(response.body)['limit_1_month']).to eq('1000.0')
|
||||
expect(JSON.parse(response.body)['group']).to eq('any')
|
||||
expect(JSON.parse(response.body)['kyc_level']).to eq('1')
|
||||
end
|
||||
end
|
||||
|
||||
context 'returns created withdraw limit table without kyc_level' do
|
||||
it 'returns created withdraw limit table' do
|
||||
api_post '/api/v2/admin/withdraw_limits', token: token, params: { limit_24_hour: 100, limit_1_month: 1000, group: 'vip-1' }
|
||||
|
||||
expect(response).to have_http_status(201)
|
||||
expect(JSON.parse(response.body)['limit_24_hour']).to eq('100.0')
|
||||
expect(JSON.parse(response.body)['limit_1_month']).to eq('1000.0')
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-1')
|
||||
expect(JSON.parse(response.body)['kyc_level']).to eq('any')
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty limit_24_hour field' do
|
||||
it 'returns status 422 and error' do
|
||||
api_post '/api/v2/admin/withdraw_limits', token: token, params: { limit_1_month: 1000, group: 'vip-1' }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.withdraw_limit.invalid_limit_24_hour')
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty limit_1_month field' do
|
||||
it 'returns status 422 and error' do
|
||||
api_post '/api/v2/admin/withdraw_limits', token: token, params: { limit_24_hour: 1000, group: 'vip-1' }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.withdraw_limit.invalid_limit_1_month')
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid limit_24_hour/limit_1_month value' do
|
||||
it 'returns status 422 and error' do
|
||||
api_post '/api/v2/admin/withdraw_limits', token: token, params: { limit_1_month: -1, limit_24_hour: -15, group: 'vip-1' }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.withdraw_limit.invalid_limit_24_hour')
|
||||
expect(response).to include_api_error('admin.withdraw_limit.invalid_limit_1_month')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /withdraw_limits' do
|
||||
it 'returns updated withdraw limit table with new kyc_level' do
|
||||
api_put '/api/v2/admin/withdraw_limits', token: token, params: { kyc_level: '3', id: WithdrawLimit.first.id }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body)['limit_24_hour']).to eq('9999.0')
|
||||
expect(JSON.parse(response.body)['limit_1_month']).to eq('999999.0')
|
||||
expect(JSON.parse(response.body)['group']).to eq('any')
|
||||
expect(JSON.parse(response.body)['kyc_level']).to eq('3')
|
||||
end
|
||||
|
||||
it 'returns updated withdraw limit table with new group' do
|
||||
api_put '/api/v2/admin/withdraw_limits', token: token, params: { group: 'vip-1', id: WithdrawLimit.first.id }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body)['limit_24_hour']).to eq('9999.0')
|
||||
expect(JSON.parse(response.body)['limit_1_month']).to eq('999999.0')
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-1')
|
||||
expect(JSON.parse(response.body)['kyc_level']).to eq('any')
|
||||
end
|
||||
|
||||
it 'returns updated withdraw limit table with new group with capitalized letter' do
|
||||
api_put '/api/v2/admin/withdraw_limits', token: token, params: { group: 'Vip-1 ', id: WithdrawLimit.first.id }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body)['limit_24_hour']).to eq('9999.0')
|
||||
expect(JSON.parse(response.body)['limit_1_month']).to eq('999999.0')
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-1')
|
||||
expect(JSON.parse(response.body)['kyc_level']).to eq('any')
|
||||
end
|
||||
|
||||
it 'returns updated withdraw limit table with new limit_24_hour' do
|
||||
api_put '/api/v2/admin/withdraw_limits', token: token, params: { id: WithdrawLimit.first.id }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body)['limit_24_hour']).to eq('9999.0')
|
||||
expect(JSON.parse(response.body)['limit_1_month']).to eq('999999.0')
|
||||
expect(JSON.parse(response.body)['group']).to eq('any')
|
||||
expect(JSON.parse(response.body)['kyc_level']).to eq('any')
|
||||
end
|
||||
|
||||
it 'returns updated withdraw limit table with new limit_24_hour, limit_1_month fields' do
|
||||
api_put '/api/v2/admin/withdraw_limits', token: token, params: { limit_24_hour: 10, limit_1_month: 100, id: WithdrawLimit.first.id }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body)['limit_24_hour']).to eq('10.0')
|
||||
expect(JSON.parse(response.body)['limit_1_month']).to eq('100.0')
|
||||
expect(JSON.parse(response.body)['group']).to eq('any')
|
||||
expect(JSON.parse(response.body)['kyc_level']).to eq('any')
|
||||
end
|
||||
|
||||
context 'not found withdraw_limit table' do
|
||||
it 'returns status 404 and error' do
|
||||
api_put '/api/v2/admin/withdraw_limits', token: token, params: { id: WithdrawLimit.last.id + 1 }
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty limit_24_hour type' do
|
||||
it 'returns status 422 and error' do
|
||||
api_put '/api/v2/admin/withdraw_limits', token: token, params: { limit_24_hour: -1, id: WithdrawLimit.first.id }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.withdraw_limit.invalid_limit_24_hour')
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty limit_1_month type' do
|
||||
it 'returns status 422 and error' do
|
||||
api_put '/api/v2/admin/withdraw_limits', token: token, params: { limit_1_month: -1, id: WithdrawLimit.first.id }
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('admin.withdraw_limit.invalid_limit_1_month')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /withdraw_limits' do
|
||||
let!(:withdraw_limit) { create(:withdraw_limit, kyc_level: 1) }
|
||||
|
||||
it 'id has invalid type' do
|
||||
api_delete '/api/v2/admin/withdraw_limits/id', token: token
|
||||
expect(response).to include_api_error 'admin.withdraw_limit.non_integer_id'
|
||||
end
|
||||
|
||||
it 'deletes withdraw limit table' do
|
||||
expect {
|
||||
api_delete "/api/v2/admin/withdraw_limits/#{withdraw_limit.id}", token: token
|
||||
}.to change { WithdrawLimit.count }.by(-1)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns deleted withdraw limit table' do
|
||||
api_delete "/api/v2/admin/withdraw_limits/#{withdraw_limit.id}", token: token
|
||||
|
||||
expect(JSON.parse(response.body)['id']).to eq withdraw_limit.id
|
||||
end
|
||||
|
||||
it 'retuns 404 if record does not exist' do
|
||||
expect {
|
||||
api_delete "/api/v2/admin/withdraw_limits/#{WithdrawLimit.last.id + 42}", token: token
|
||||
}.not_to change { WithdrawLimit.count }
|
||||
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
end
|
||||
323
spec/api/v2/admin/withdraws_spec.rb
Normal file
323
spec/api/v2/admin/withdraws_spec.rb
Normal file
@@ -0,0 +1,323 @@
|
||||
# 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
|
||||
247
spec/api/v2/coin_gecko/historical_trades.rb
Normal file
247
spec/api/v2/coin_gecko/historical_trades.rb
Normal file
@@ -0,0 +1,247 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::CoinGecko::HistoricalTrades, type: :request do
|
||||
describe 'GET /api/v2/coingecko/historical_trades' do
|
||||
before(:each) { delete_measurments('trades') }
|
||||
after(:each) { delete_measurments('trades') }
|
||||
|
||||
context 'there is no market pair' do
|
||||
it 'should return error' do
|
||||
get '/api/v2/coingecko/historical_trades', params: { ticker_id: 'TEST_TEST' }
|
||||
|
||||
expect(response).to have_http_status 404
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
end
|
||||
|
||||
context 'there is no trades in influx' do
|
||||
let(:expected_response) do
|
||||
{
|
||||
'buy' => [],
|
||||
'sell' => []
|
||||
}
|
||||
end
|
||||
|
||||
it 'should return recent trades' do
|
||||
get '/api/v2/coingecko/historical_trades', params: { ticker_id: 'BTC_USD' }
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result).to eq expected_response
|
||||
end
|
||||
end
|
||||
|
||||
context 'there are trades in influx' do
|
||||
let!(:trade1) { create(:trade, :btcusd, price: '5.0'.to_d, amount: '1.1'.to_d, total: '5.5'.to_d, created_at: Time.now) }
|
||||
let!(:trade2) { create(:trade, :btcusd, price: '6.0'.to_d, amount: '0.9'.to_d, total: '5.4'.to_d, created_at: Time.now + 1.month) }
|
||||
|
||||
before do
|
||||
trade1.write_to_influx
|
||||
trade2.write_to_influx
|
||||
end
|
||||
|
||||
context 'return all trades' do
|
||||
let(:expected_response) do
|
||||
{
|
||||
'buy' => [
|
||||
{'base_volume' => 0.9,
|
||||
'price' => 6,
|
||||
'target_volume' => 5.4,
|
||||
'trade_id' => trade2.id,
|
||||
'trade_timestamp' => trade2.created_at.to_i * 1000,
|
||||
'type' => 'buy'},
|
||||
|
||||
{'base_volume' => 1.1,
|
||||
'price' => 5,
|
||||
'target_volume' => 5.5,
|
||||
'trade_id' => trade1.id,
|
||||
'trade_timestamp' => trade1.created_at.to_i * 1000,
|
||||
'type' => 'buy'}
|
||||
],
|
||||
'sell' => []
|
||||
}
|
||||
end
|
||||
|
||||
it do
|
||||
get '/api/v2/coingecko/historical_trades', params: { ticker_id: 'BTC_USD'}
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response_body).to eq expected_response
|
||||
end
|
||||
end
|
||||
|
||||
context 'return filtered trades' do
|
||||
let(:expected_response) do
|
||||
{
|
||||
'buy' => [
|
||||
{'base_volume' => 0.9,
|
||||
'price' => 6,
|
||||
'target_volume' => 5.4,
|
||||
'trade_id' => trade2.id,
|
||||
'trade_timestamp' => trade2.created_at.to_i * 1000,
|
||||
'type' => 'buy'},
|
||||
|
||||
{'base_volume' => 1.1,
|
||||
'price' => 5,
|
||||
'target_volume' => 5.5,
|
||||
'trade_id' => trade1.id,
|
||||
'trade_timestamp' => trade1.created_at.to_i * 1000,
|
||||
'type' => 'buy'}
|
||||
],
|
||||
'sell' => []
|
||||
}
|
||||
end
|
||||
|
||||
context 'by taker_type' do
|
||||
it 'buy' do
|
||||
get '/api/v2/coingecko/historical_trades', params: { ticker_id: 'BTC_USD', type: 'buy'}
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response_body).to eq expected_response
|
||||
end
|
||||
|
||||
it 'sell' do
|
||||
get '/api/v2/coingecko/historical_trades', params: { ticker_id: 'BTC_USD', type: 'sell'}
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response_body).to eq({'buy'=> [], 'sell' => []})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'by start time' do
|
||||
|
||||
let(:expected_response) do
|
||||
{
|
||||
'buy' => [
|
||||
{'base_volume' => 0.9,
|
||||
'price' => 6,
|
||||
'target_volume' => 5.4,
|
||||
'trade_id' => trade2.id,
|
||||
'trade_timestamp' => trade2.created_at.to_i * 1000,
|
||||
'type' => 'buy'}
|
||||
],
|
||||
'sell' => []
|
||||
}
|
||||
end
|
||||
|
||||
it do
|
||||
get '/api/v2/coingecko/historical_trades', params: { ticker_id: 'BTC_USD', start_time: Time.now + 15.days}
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response_body).to eq expected_response
|
||||
end
|
||||
end
|
||||
|
||||
context 'by end time' do
|
||||
|
||||
let(:expected_response) do
|
||||
{
|
||||
'buy' => [
|
||||
{'base_volume' => 1.1,
|
||||
'price' => 5,
|
||||
'target_volume' => 5.5,
|
||||
'trade_id' => trade1.id,
|
||||
'trade_timestamp' => trade1.created_at.to_i * 1000,
|
||||
'type' => 'buy'}
|
||||
],
|
||||
'sell' => []
|
||||
}
|
||||
end
|
||||
|
||||
it do
|
||||
get '/api/v2/coingecko/historical_trades', params: { ticker_id: 'BTC_USD', end_time: Time.now + 15.days}
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response_body).to eq expected_response
|
||||
end
|
||||
end
|
||||
|
||||
context 'by limit' do
|
||||
context 'without specified limit' do
|
||||
let(:expected_response) do
|
||||
{
|
||||
'buy' => [
|
||||
{'base_volume' => 0.9,
|
||||
'price' => 6,
|
||||
'target_volume' => 5.4,
|
||||
'trade_id' => trade2.id,
|
||||
'trade_timestamp' => trade2.created_at.to_i * 1000,
|
||||
'type' => 'buy'},
|
||||
|
||||
{'base_volume' => 1.1,
|
||||
'price' => 5,
|
||||
'target_volume' => 5.5,
|
||||
'trade_id' => trade1.id,
|
||||
'trade_timestamp' => trade1.created_at.to_i * 1000,
|
||||
'type' => 'buy'}
|
||||
],
|
||||
'sell' => []
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns all trades' do
|
||||
get '/api/v2/coingecko/historical_trades', params: { ticker_id: 'BTC_USD'}
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response_body).to eq expected_response
|
||||
end
|
||||
end
|
||||
|
||||
context 'with specified limit' do
|
||||
let(:expected_response) do
|
||||
{
|
||||
'buy' => [
|
||||
{'base_volume' => 0.9,
|
||||
'price' => 6,
|
||||
'target_volume' => 5.4,
|
||||
'trade_id' => trade2.id,
|
||||
'trade_timestamp' => trade2.created_at.to_i * 1000,
|
||||
'type' => 'buy'}
|
||||
],
|
||||
'sell' => []
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns specific number of trades' do
|
||||
get '/api/v2/coingecko/historical_trades', params: { ticker_id: 'BTC_USD', limit: 1}
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response_body).to eq expected_response
|
||||
end
|
||||
end
|
||||
|
||||
context 'with limit==0' do
|
||||
let(:expected_response) do
|
||||
{
|
||||
'buy' => [
|
||||
{'base_volume' => 0.9,
|
||||
'price' => 6,
|
||||
'target_volume' => 5.4,
|
||||
'trade_id' => trade2.id,
|
||||
'trade_timestamp' => trade2.created_at.to_i * 1000,
|
||||
'type' => 'buy'},
|
||||
|
||||
{'base_volume' => 1.1,
|
||||
'price' => 5,
|
||||
'target_volume' => 5.5,
|
||||
'trade_id' => trade1.id,
|
||||
'trade_timestamp' => trade1.created_at.to_i * 1000,
|
||||
'type' => 'buy'}
|
||||
],
|
||||
'sell' => []
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns all trades' do
|
||||
get '/api/v2/coingecko/historical_trades', params: { ticker_id: 'BTC_USD', limit: 0}
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response_body).to eq expected_response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
98
spec/api/v2/coin_gecko/orderbook_spec.rb
Normal file
98
spec/api/v2/coin_gecko/orderbook_spec.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::CoinGecko::Orderbook, type: :request do
|
||||
describe 'GET /api/v2/coingecko/orderbook' do
|
||||
before do
|
||||
create_list(:order_bid, 5, :btcusd)
|
||||
create_list(:order_bid, 5, :btcusd, price: 2)
|
||||
create_list(:order_ask, 5, :btcusd)
|
||||
create_list(:order_ask, 5, :btcusd, price: 3)
|
||||
end
|
||||
|
||||
let(:asks) { [["1.0", "5.0"], ["3.0", "5.0"]] }
|
||||
let(:bids) { [["2.0", "5.0"], ["1.0", "5.0"]] }
|
||||
|
||||
context 'valid market param' do
|
||||
it 'sorts asks and bids from highest to lowest' do
|
||||
get "/api/v2/coingecko/orderbook", params: { ticker_id: "BTC_USD"}
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 2
|
||||
expect(result['bids'].size).to eq 2
|
||||
expect(result['asks']).to eq asks
|
||||
expect(result['bids']).to eq bids
|
||||
end
|
||||
|
||||
context 'with depth param' do
|
||||
before do
|
||||
create_list(:order_bid, 5, :btcusd)
|
||||
create_list(:order_bid, 5, :btcusd, price: 4.1)
|
||||
create_list(:order_ask, 5, :btcusd)
|
||||
create_list(:order_ask, 5, :btcusd, price: 12.2)
|
||||
end
|
||||
|
||||
it 'get asks and bids with depth param' do
|
||||
get '/api/v2/coingecko/orderbook', params: { ticker_id: "BTC_USD", depth: 2 }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 1
|
||||
expect(result['bids'].size).to eq 1
|
||||
end
|
||||
|
||||
it 'get asks and bids with depth param' do
|
||||
get '/api/v2/coingecko/orderbook', params: { ticker_id: "BTC_USD", depth: 4 }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 2
|
||||
expect(result['bids'].size).to eq 2
|
||||
end
|
||||
|
||||
it 'get asks and bids with depth param' do
|
||||
get '/api/v2/coingecko/orderbook', params: { ticker_id: "BTC_USD", depth: 1 }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 0
|
||||
expect(result['bids'].size).to eq 0
|
||||
end
|
||||
|
||||
it 'get asks and bids with depth param' do
|
||||
get '/api/v2/coingecko/orderbook', params: { ticker_id: "BTC_USD", depth: 3 }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 1
|
||||
expect(result['bids'].size).to eq 1
|
||||
end
|
||||
|
||||
it 'get asks and bids with depth param for all orderbook' do
|
||||
get '/api/v2/coingecko/orderbook', params: { ticker_id: "BTC_USD", depth: 0 }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 3
|
||||
expect(result['bids'].size).to eq 3
|
||||
end
|
||||
|
||||
context 'invalid depth params' do
|
||||
it 'shoud return error' do
|
||||
get '/api/v2/coingecko/orderbook', params: { ticker_id: "BTC_USD", depth: 'test' }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('coingecko.market_depth.non_integer_depth')
|
||||
end
|
||||
|
||||
it 'shoud return error' do
|
||||
get '/api/v2/coingecko/orderbook', params: { ticker_id: "BTC_USD", depth: 2000 }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('coingecko.market_depth.invalid_depth')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid market param' do
|
||||
it 'validates market param' do
|
||||
get '/api/v2/coingecko/orderbook', params: { ticker_id: "usdusd" }
|
||||
expect(response).to have_http_status 404
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
42
spec/api/v2/coin_gecko/pairs_spec.rb
Normal file
42
spec/api/v2/coin_gecko/pairs_spec.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::CoinGecko::Pairs, type: :request do
|
||||
describe 'GET /api/v2/coingecko/pairs' do
|
||||
before(:each) { clear_redis }
|
||||
|
||||
let!(:market) do
|
||||
::Market.enabled.ordered.sample
|
||||
end
|
||||
|
||||
let!(:expected_response) {
|
||||
{
|
||||
"ticker_id" => market.underscore_name,
|
||||
"base" => market[:base_unit].upcase,
|
||||
"target" => market[:quote_unit].upcase
|
||||
}
|
||||
}
|
||||
|
||||
it 'lists visible currencies' do
|
||||
get '/api/v2/coingecko/pairs'
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq Market.enabled.size
|
||||
expect(result).to include(expected_response)
|
||||
end
|
||||
end
|
||||
|
||||
context 'There is no markets' do
|
||||
before { DatabaseCleaner.clean }
|
||||
|
||||
it 'should return summary' do
|
||||
get '/api/v2/coingecko/pairs'
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result).to eq []
|
||||
end
|
||||
end
|
||||
end
|
||||
107
spec/api/v2/coin_gecko/tickers_spec.rb
Normal file
107
spec/api/v2/coin_gecko/tickers_spec.rb
Normal file
@@ -0,0 +1,107 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::CoinGecko::Tickers, type: :request do
|
||||
describe 'GET /api/v2/coingecko/tickers' do
|
||||
before(:each) { delete_measurments('trades') }
|
||||
after(:each) { delete_measurments('trades') }
|
||||
|
||||
before do
|
||||
create_list(:order_bid, 5, :btcusd)
|
||||
create_list(:order_ask, 5, :btcusd)
|
||||
end
|
||||
|
||||
context 'no trades executed yet' do
|
||||
let(:expected_btcusd_ticker) do
|
||||
{
|
||||
'ticker_id' => 'BTC_USD',
|
||||
'base_currency' => 'BTC',
|
||||
'target_currency' => 'USD',
|
||||
'last_price' => '0.0',
|
||||
'target_volume' => '0.0', 'base_volume' => '0.0',
|
||||
'bid' => '1.0', 'ask' => '1.0',
|
||||
'high' => '0.0', 'low' => '0.0'
|
||||
}
|
||||
end
|
||||
|
||||
let(:expected_btceth_ticker) do
|
||||
{
|
||||
'ticker_id' => 'BTC_ETH',
|
||||
'base_currency' => 'BTC',
|
||||
'target_currency' => 'ETH',
|
||||
'last_price' => '0.0',
|
||||
'target_volume' => '0.0', 'base_volume' => '0.0',
|
||||
'bid' => '0.0', 'ask' => '0.0',
|
||||
'high' => '0.0', 'low' => '0.0'
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns tickers of all markets' do
|
||||
get '/api/v2/coingecko/tickers'
|
||||
expect(response).to be_successful
|
||||
|
||||
btc_usd_ticker = response_body.find {|ticker| ticker['ticker_id'] == 'BTC_USD'}
|
||||
btc_eth_ticker = response_body.find {|ticker| ticker['ticker_id'] == 'BTC_ETH'}
|
||||
expect(btc_usd_ticker).to eq expected_btcusd_ticker
|
||||
expect(btc_eth_ticker).to eq expected_btceth_ticker
|
||||
end
|
||||
end
|
||||
|
||||
context 'single trade was executed' do
|
||||
let!(:trade) { create(:trade, :btcusd, price: '5.0'.to_d, amount: '1.1'.to_d, total: '5.5'.to_d)}
|
||||
|
||||
let(:expected_ticker) do
|
||||
{
|
||||
'ticker_id' => 'BTC_USD',
|
||||
'base_currency' => 'BTC',
|
||||
'target_currency' => 'USD',
|
||||
'last_price' => '5.0',
|
||||
'target_volume' => '5.5', 'base_volume' => '1.1',
|
||||
'bid' => '1.0', 'ask' => '1.0',
|
||||
'high' => '5.0', 'low' => '5.0'
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
trade.write_to_influx
|
||||
end
|
||||
|
||||
it 'returns tickers of all markets' do
|
||||
get '/api/v2/coingecko/tickers'
|
||||
expect(response).to be_successful
|
||||
|
||||
btc_usd_ticker = response_body.find {|ticker| ticker['ticker_id'] == 'BTC_USD'}
|
||||
expect(btc_usd_ticker).to eq expected_ticker
|
||||
end
|
||||
end
|
||||
|
||||
context 'multiple trades were executed' do
|
||||
let!(:trade1) { create(:trade, :btcusd, price: '5.0'.to_d, amount: '1.1'.to_d, total: '5.5'.to_d)}
|
||||
let!(:trade2) { create(:trade, :btcusd, price: '6.0'.to_d, amount: '0.9'.to_d, total: '5.4'.to_d)}
|
||||
|
||||
let(:expected_ticker) do
|
||||
{
|
||||
'ticker_id' => 'BTC_USD',
|
||||
'base_currency' => 'BTC',
|
||||
'target_currency' => 'USD',
|
||||
'last_price' => '6.0',
|
||||
'target_volume' => '10.9', 'base_volume' => '2.0',
|
||||
'bid' => '1.0', 'ask' => '1.0',
|
||||
'high' => '6.0', 'low' => '5.0'
|
||||
}
|
||||
end
|
||||
before do
|
||||
trade1.write_to_influx
|
||||
trade2.write_to_influx
|
||||
end
|
||||
|
||||
it 'returns tickers of all markets' do
|
||||
get '/api/v2/coingecko/tickers'
|
||||
expect(response).to be_successful
|
||||
|
||||
btc_usd_ticker = response_body.find {|ticker| ticker['ticker_id'] == 'BTC_USD'}
|
||||
expect(btc_usd_ticker).to eq expected_ticker
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
113
spec/api/v2/coin_market_cap/assets_spec.rb
Normal file
113
spec/api/v2/coin_market_cap/assets_spec.rb
Normal file
@@ -0,0 +1,113 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::CoinMarketCap::Assets, type: :request do
|
||||
describe 'GET /api/v2/coinmarketcap/assets' do
|
||||
before(:each) { clear_redis }
|
||||
|
||||
context 'There are currencies' do
|
||||
context 'with unified id' do
|
||||
before do
|
||||
Currency.coins.each do |currency|
|
||||
stub_request(:get, "https://pro-api.coinmarketcap.com/v1/cryptocurrency/map?CMC_PRO_API_KEY=UNIFIED-CRYPTOASSET-INDEX&"\
|
||||
"listing_status=active&"\
|
||||
"symbol=#{currency.id}")
|
||||
.to_return(body:
|
||||
{
|
||||
'status'=>
|
||||
{
|
||||
'error_code'=>0,
|
||||
'error_message'=>nil,
|
||||
'elapsed'=>12,
|
||||
'credit_count'=>1,
|
||||
'notice'=>nil
|
||||
},
|
||||
'data'=>
|
||||
[
|
||||
{
|
||||
'id'=>1
|
||||
}
|
||||
]
|
||||
}.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return crypto assets' do
|
||||
get '/api/v2/coinmarketcap/assets'
|
||||
expect(response).to be_successful
|
||||
expect(response_body['BTC'].keys).to match_array %w[name unified_cryptoasset_id can_withdraw can_deposit min_withdraw]
|
||||
expect(response_body['BTC']['name']).to eq 'Bitcoin'
|
||||
expect(response_body['BTC']['unified_cryptoasset_id']).to eq 1
|
||||
expect(response_body['BTC']['can_withdraw']).to eq true
|
||||
expect(response_body['BTC']['can_deposit']).to eq true
|
||||
expect(response_body['BTC']['min_withdraw']).to eq '0.0'
|
||||
end
|
||||
end
|
||||
|
||||
context 'without unified id' do
|
||||
before do
|
||||
Currency.coins.each do |currency|
|
||||
stub_request(:get, "https://pro-api.coinmarketcap.com/v1/cryptocurrency/map?CMC_PRO_API_KEY=UNIFIED-CRYPTOASSET-INDEX&"\
|
||||
"listing_status=active&"\
|
||||
"symbol=#{currency.id}")
|
||||
.to_return(status: 400, body:
|
||||
{
|
||||
'status'=>
|
||||
{
|
||||
'timestamp'=>'2020-09-25T08:43:56.778Z',
|
||||
'error_code'=>400,
|
||||
'error_message'=>'Invalid value for \'symbol\': \'TESTTEST\'',
|
||||
'elapsed'=>0,
|
||||
'credit_count'=>0,
|
||||
'notice'=>nil
|
||||
}
|
||||
}.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return crypto assets' do
|
||||
get '/api/v2/coinmarketcap/assets'
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response_body['BTC'].keys).to match_array %w[name can_withdraw can_deposit min_withdraw]
|
||||
expect(response_body['BTC']['name']).to eq 'Bitcoin'
|
||||
expect(response_body['BTC']['can_withdraw']).to eq true
|
||||
expect(response_body['BTC']['can_deposit']).to eq true
|
||||
expect(response_body['BTC']['min_withdraw']).to eq '0.0'
|
||||
end
|
||||
|
||||
context 'with 500 error from Faraday' do
|
||||
before do
|
||||
Currency.coins.each do |currency|
|
||||
stub_request(:get, "https://pro-api.coinmarketcap.com/v1/cryptocurrency/map?CMC_PRO_API_KEY=UNIFIED-CRYPTOASSET-INDEX&"\
|
||||
"listing_status=active&"\
|
||||
"symbol=#{currency.id}")
|
||||
.to_raise(Faraday::Error)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return crypto assets' do
|
||||
get '/api/v2/coinmarketcap/assets'
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response_body['BTC'].keys).to match_array %w[name can_withdraw can_deposit min_withdraw]
|
||||
expect(response_body['BTC']['name']).to eq 'Bitcoin'
|
||||
expect(response_body['BTC']['can_withdraw']).to eq true
|
||||
expect(response_body['BTC']['can_deposit']).to eq true
|
||||
expect(response_body['BTC']['min_withdraw']).to eq '0.0'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'There is no currencies' do
|
||||
before { DatabaseCleaner.clean }
|
||||
|
||||
it 'should return assets' do
|
||||
get '/api/v2/coinmarketcap/assets'
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response_body).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
98
spec/api/v2/coin_market_cap/orderbook_spec.rb
Normal file
98
spec/api/v2/coin_market_cap/orderbook_spec.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::CoinMarketCap::Orderbook, type: :request do
|
||||
describe 'GET /api/v2/coinmarketcap/orderbook/:market_pair' do
|
||||
before do
|
||||
create_list(:order_bid, 5, :btcusd)
|
||||
create_list(:order_bid, 5, :btcusd, price: 2)
|
||||
create_list(:order_ask, 5, :btcusd)
|
||||
create_list(:order_ask, 5, :btcusd, price: 3)
|
||||
end
|
||||
|
||||
let(:asks) { [["1.0", "5.0"], ["3.0", "5.0"]] }
|
||||
let(:bids) { [["2.0", "5.0"], ["1.0", "5.0"]] }
|
||||
|
||||
context 'valid market param' do
|
||||
it 'sorts asks and bids from highest to lowest' do
|
||||
get "/api/v2/coinmarketcap/orderbook/BTC_USD"
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 2
|
||||
expect(result['bids'].size).to eq 2
|
||||
expect(result['asks']).to eq asks
|
||||
expect(result['bids']).to eq bids
|
||||
end
|
||||
|
||||
context 'with depth param' do
|
||||
before do
|
||||
create_list(:order_bid, 5, :btcusd)
|
||||
create_list(:order_bid, 5, :btcusd, price: 4.1)
|
||||
create_list(:order_ask, 5, :btcusd)
|
||||
create_list(:order_ask, 5, :btcusd, price: 12.2)
|
||||
end
|
||||
|
||||
it 'get asks and bids with depth param' do
|
||||
get '/api/v2/coinmarketcap/orderbook/BTC_USD', params: { depth: 2 }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 1
|
||||
expect(result['bids'].size).to eq 1
|
||||
end
|
||||
|
||||
it 'get asks and bids with depth param' do
|
||||
get '/api/v2/coinmarketcap/orderbook/BTC_USD', params: { depth: 4 }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 2
|
||||
expect(result['bids'].size).to eq 2
|
||||
end
|
||||
|
||||
it 'get asks and bids with depth param' do
|
||||
get '/api/v2/coinmarketcap/orderbook/BTC_USD', params: { depth: 1 }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 0
|
||||
expect(result['bids'].size).to eq 0
|
||||
end
|
||||
|
||||
it 'get asks and bids with depth param' do
|
||||
get '/api/v2/coinmarketcap/orderbook/BTC_USD', params: { depth: 3 }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 1
|
||||
expect(result['bids'].size).to eq 1
|
||||
end
|
||||
|
||||
it 'get asks and bids with depth param for all orderbook' do
|
||||
get '/api/v2/coinmarketcap/orderbook/BTC_USD', params: { depth: 0 }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 3
|
||||
expect(result['bids'].size).to eq 3
|
||||
end
|
||||
|
||||
context 'invalid depth params' do
|
||||
it 'shoud return error' do
|
||||
get '/api/v2/coinmarketcap/orderbook/BTC_USD', params: { depth: 'test' }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('coinmarketcap.market_depth.non_integer_depth')
|
||||
end
|
||||
|
||||
it 'shoud return error' do
|
||||
get '/api/v2/coinmarketcap/orderbook/BTC_USD', params: { depth: 2000 }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('coinmarketcap.market_depth.invalid_depth')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid market param' do
|
||||
it 'validates market param' do
|
||||
api_get "/api/v2/coinmarketcap/orderbook/usdusd"
|
||||
expect(response).to have_http_status 404
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
70
spec/api/v2/coin_market_cap/summary_spec.rb
Normal file
70
spec/api/v2/coin_market_cap/summary_spec.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::CoinMarketCap::Summary, type: :request do
|
||||
describe 'GET /api/v2/coinmarketcap/summary' do
|
||||
before(:each) { clear_redis }
|
||||
after(:each) { delete_measurments('trades') }
|
||||
|
||||
context 'There is no trades in influx' do
|
||||
it 'should return summary' do
|
||||
get '/api/v2/coinmarketcap/summary'
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.count).to eq Market.all.count
|
||||
expect(result.first['trading_pairs']).to eq 'BTC_USD'
|
||||
expect(result.first['base_currency']).to eq 'BTC'
|
||||
expect(result.first['quote_currency']).to eq 'USD'
|
||||
expect(result.first['last_price']).to eq '0.0'
|
||||
expect(result.first['lowest_ask']).to eq '0.0'
|
||||
expect(result.first['highest_bid']).to eq '0.0'
|
||||
expect(result.first['base_volume']).to eq '0.0'
|
||||
expect(result.first['quote_volume']).to eq '0.0'
|
||||
expect(result.first['price_change_percent_24h']).to eq '0.0'
|
||||
expect(result.first['highest_price_24h']).to eq '0.0'
|
||||
expect(result.first['lowest_price_24h']).to eq '0.0'
|
||||
end
|
||||
end
|
||||
|
||||
context 'There are trades in influx' do
|
||||
let!(:trade1) { create(:trade, :btcusd, price: '5.0'.to_d, amount: '1.1'.to_d, total: '5.5'.to_d)}
|
||||
let!(:trade2) { create(:trade, :btcusd, price: '6.0'.to_d, amount: '0.9'.to_d, total: '5.4'.to_d)}
|
||||
|
||||
before do
|
||||
trade1.write_to_influx
|
||||
trade2.write_to_influx
|
||||
end
|
||||
|
||||
it 'should return summary' do
|
||||
get '/api/v2/coinmarketcap/summary'
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.count).to eq Market.all.count
|
||||
expect(result.first['trading_pairs']).to eq 'BTC_USD'
|
||||
expect(result.first['base_currency']).to eq 'BTC'
|
||||
expect(result.first['quote_currency']).to eq 'USD'
|
||||
expect(result.first['last_price']).to eq '6.0'
|
||||
expect(result.first['lowest_ask']).to eq '1.0'
|
||||
expect(result.first['highest_bid']).to eq '1.0'
|
||||
expect(result.first['base_volume']).to eq '2.0'
|
||||
expect(result.first['quote_volume']).to eq '10.9'
|
||||
expect(result.first['price_change_percent_24h']).to eq '0.2'
|
||||
expect(result.first['highest_price_24h']).to eq '6.0'
|
||||
expect(result.first['lowest_price_24h']).to eq '5.0'
|
||||
end
|
||||
end
|
||||
|
||||
context 'There is no markets' do
|
||||
before { DatabaseCleaner.clean }
|
||||
|
||||
it 'should return summary' do
|
||||
get '/api/v2/coinmarketcap/summary'
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result).to eq []
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
127
spec/api/v2/coin_market_cap/ticker_spec.rb
Normal file
127
spec/api/v2/coin_market_cap/ticker_spec.rb
Normal file
@@ -0,0 +1,127 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::CoinMarketCap::Ticker, type: :request do
|
||||
describe 'GET /api/v2/coinmarketcap/ticker' do
|
||||
before do
|
||||
create_list(:order_bid, 5, :btcusd)
|
||||
create_list(:order_ask, 5, :btcusd)
|
||||
end
|
||||
|
||||
before(:each) { clear_redis }
|
||||
|
||||
context 'with unified id' do
|
||||
before(:each) { delete_measurments('trades') }
|
||||
after(:each) { delete_measurments('trades') }
|
||||
|
||||
before do
|
||||
Currency.ordered.coins.each.with_index(1) do |currency, index|
|
||||
stub_request(:get, "https://pro-api.coinmarketcap.com/v1/cryptocurrency/map?CMC_PRO_API_KEY=UNIFIED-CRYPTOASSET-INDEX&"\
|
||||
"listing_status=active&"\
|
||||
"symbol=#{currency.id}")
|
||||
.to_return(body:
|
||||
{
|
||||
'status'=>
|
||||
{
|
||||
'error_code'=>0,
|
||||
'error_message'=>nil,
|
||||
'elapsed'=>12,
|
||||
'credit_count'=>1,
|
||||
'notice'=>nil
|
||||
},
|
||||
'data'=>
|
||||
[
|
||||
{
|
||||
'id'=>index
|
||||
}
|
||||
]
|
||||
}.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
context 'no trades executed yet' do
|
||||
let(:expected_btc_usd_ticker) do
|
||||
{
|
||||
'base_id' => 1, 'last_price' => '0.0',
|
||||
'quote_volume' => '0.0', 'base_volume' => '0.0',
|
||||
'isFrozen' => 0 }
|
||||
end
|
||||
|
||||
let(:expected_btc_eth_ticker) do
|
||||
{
|
||||
'base_id' => 1, 'quote_id' => 2, 'last_price' => '0.0',
|
||||
'quote_volume' => '0.0', 'base_volume' => '0.0',
|
||||
'isFrozen' => 0 }
|
||||
end
|
||||
|
||||
it 'returns ticker of all markets' do
|
||||
get '/api/v2/coinmarketcap/ticker'
|
||||
expect(response).to be_successful
|
||||
|
||||
# crypto/fiat market
|
||||
expect(response_body['BTC_USD'].keys).to match_array %w[base_id last_price base_volume quote_volume isFrozen]
|
||||
expect(response_body['BTC_USD']).to include(expected_btc_usd_ticker)
|
||||
# crypto/crypto market
|
||||
expect(response_body['BTC_ETH'].keys).to match_array %w[base_id quote_id last_price base_volume quote_volume isFrozen]
|
||||
expect(response_body['BTC_ETH']).to include(expected_btc_eth_ticker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'single trade was executed' do
|
||||
let!(:trade) { create(:trade, :btcusd, price: '5.0'.to_d, amount: '1.1'.to_d, total: '5.5'.to_d)}
|
||||
|
||||
let(:expected_btc_usd_ticker) do
|
||||
{
|
||||
'base_id' => 1,
|
||||
'last_price' => '5.0', 'quote_volume' => '5.5',
|
||||
'base_volume' => '1.1', 'isFrozen' => 0
|
||||
}
|
||||
end
|
||||
|
||||
let(:expected_btc_usd_frozen_ticker) do
|
||||
{
|
||||
'base_id' => 1, 'last_price' => '5.0',
|
||||
'quote_volume' => '5.5', 'base_volume' => '1.1', 'isFrozen' => 1
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
trade.write_to_influx
|
||||
end
|
||||
|
||||
it 'returns market tickers' do
|
||||
get '/api/v2/coinmarketcap/ticker'
|
||||
|
||||
expect(response).to be_successful
|
||||
# crypto/fiat market
|
||||
expect(response_body['BTC_USD'].keys).to match_array %w[base_id last_price base_volume quote_volume isFrozen]
|
||||
expect(response_body['BTC_USD']).to include(expected_btc_usd_ticker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'multiple trades were executed' do
|
||||
let!(:trade1) { create(:trade, :btcusd, price: '5.0'.to_d, amount: '1.1'.to_d, total: '5.5'.to_d)}
|
||||
let!(:trade2) { create(:trade, :btcusd, price: '6.0'.to_d, amount: '0.9'.to_d, total: '5.4'.to_d)}
|
||||
|
||||
let(:expected_btc_usd_ticker) do
|
||||
{ 'base_id' => 1, 'last_price' => '6.0',
|
||||
'quote_volume' => '10.9', 'base_volume' => '2.0',
|
||||
'isFrozen' => 0 }
|
||||
end
|
||||
|
||||
before do
|
||||
trade1.write_to_influx
|
||||
trade2.write_to_influx
|
||||
end
|
||||
|
||||
it 'returns market tickers' do
|
||||
get '/api/v2/coinmarketcap/ticker'
|
||||
expect(response).to be_successful
|
||||
|
||||
# crypto/fiat market
|
||||
expect(response_body['BTC_USD'].keys).to match_array %w[base_id last_price base_volume quote_volume isFrozen]
|
||||
expect(response_body['BTC_USD']).to include(expected_btc_usd_ticker)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
51
spec/api/v2/coin_market_cap/trades_spec.rb
Normal file
51
spec/api/v2/coin_market_cap/trades_spec.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::CoinMarketCap::Trades, type: :request do
|
||||
describe 'GET /api/v2/coinmarketcap/trades/:market_pair' do
|
||||
before(:each) { delete_measurments('trades') }
|
||||
after(:each) { delete_measurments('trades') }
|
||||
|
||||
context 'there is no market pair' do
|
||||
it 'should return error' do
|
||||
get '/api/v2/coinmarketcap/trades/TEST_TEST'
|
||||
|
||||
expect(response).to have_http_status 404
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
end
|
||||
|
||||
context 'there is no trades in influx' do
|
||||
it 'should return recent trades' do
|
||||
get '/api/v2/coinmarketcap/trades/BTC_USD'
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result).to eq []
|
||||
end
|
||||
end
|
||||
|
||||
context 'there are trades in influx' do
|
||||
let!(:trade1) { create(:trade, :btcusd, price: '5.0'.to_d, amount: '1.1'.to_d, total: '5.5'.to_d)}
|
||||
let!(:trade2) { create(:trade, :btcusd, price: '6.0'.to_d, amount: '0.9'.to_d, total: '5.4'.to_d)}
|
||||
|
||||
before do
|
||||
trade1.write_to_influx
|
||||
trade2.write_to_influx
|
||||
end
|
||||
|
||||
it 'should return recent trades' do
|
||||
get '/api/v2/coinmarketcap/trades/BTC_USD'
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.count).to eq 2
|
||||
expect(result.first.keys).to match_array %w[trade_id price base_volume quote_volume timestamp type]
|
||||
expect(result.first['trade_id']).to eq trade2.id
|
||||
expect(result.first['price']).to eq trade2.price
|
||||
expect(result.first['base_volume']).to eq trade2.amount
|
||||
expect(result.first['quote_volume']).to eq trade2.total
|
||||
expect(result.first['type']).to eq trade2.taker_type
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
133
spec/api/v2/cors/cors_spec.rb
Normal file
133
spec/api/v2/cors/cors_spec.rb
Normal file
@@ -0,0 +1,133 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rack/cors'
|
||||
|
||||
describe Rack::Cors, type: :request do
|
||||
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:frontend_url) { 'https://frontend.io' }
|
||||
let(:local_url) { 'http://localhost:3000' }
|
||||
let(:token) { jwt_for(member) }
|
||||
|
||||
let(:app) {
|
||||
Rack::Builder.new do
|
||||
use Rack::Cors do
|
||||
allow do
|
||||
origins CORS::Validations.validate_origins(ENV['API_CORS_ORIGINS'])
|
||||
resource '/api/*',
|
||||
methods: %i[get post delete put patch options head],
|
||||
headers: :any,
|
||||
credentials: ENV.true?('API_CORS_ALLOW_CREDENTIALS'),
|
||||
max_age: CORS::Validations.validate_max_age(ENV['API_CORS_MAX_AGE'])
|
||||
end
|
||||
end
|
||||
run Peatio::Application
|
||||
end
|
||||
}
|
||||
|
||||
def check_cors(response, origin, allow_crendentails, max_age = '3600')
|
||||
expect(response.headers['Access-Control-Allow-Origin']).to eq(origin)
|
||||
expect(response.headers['Access-Control-Allow-Methods']).to eq('GET, POST, DELETE, PUT, PATCH, OPTIONS, HEAD')
|
||||
expect(response.headers['Access-Control-Allow-Credentials']).to eq(allow_crendentails)
|
||||
expect(response.headers['Access-Control-Max-Age']).to eq(max_age)
|
||||
end
|
||||
|
||||
def without_cors(response)
|
||||
expect(response.headers['Access-Control-Allow-Origin']).to eq(nil)
|
||||
expect(response.headers['Access-Control-Allow-Methods']).to eq(nil)
|
||||
expect(response.headers['Access-Control-Allow-Credentials']).to eq(nil)
|
||||
expect(response.headers['Access-Control-Max-Age']).to eq(nil)
|
||||
end
|
||||
|
||||
context 'set API_CORS_ORIGINS as "*"' do
|
||||
let(:origin) { '*' }
|
||||
let(:allow_crendentails) { nil }
|
||||
let(:max_age) { '3600' }
|
||||
|
||||
before do
|
||||
ENV['API_CORS_ORIGINS'] = origin
|
||||
ENV['API_CORS_ALLOW_CREDENTIALS'] = allow_crendentails
|
||||
ENV['API_CORS_MAX_AGE'] = max_age
|
||||
end
|
||||
|
||||
after do
|
||||
ENV['API_CORS_ORIGINS'] = nil
|
||||
ENV['API_CORS_ALLOW_CREDENTIALS'] = nil
|
||||
ENV['API_CORS_MAX_AGE'] = nil
|
||||
end
|
||||
|
||||
it 'sends CORS headers when requesting using GET from frontend url' do
|
||||
api_get '/api/v2/account/balances', token: token, headers: { 'Origin' => frontend_url }
|
||||
expect(response).to be_successful
|
||||
check_cors(response, '*', allow_crendentails, max_age)
|
||||
end
|
||||
|
||||
it 'sends CORS headers when requesting using GET from localhost' do
|
||||
api_get '/api/v2/account/balances', token: token, headers: { 'Origin' => local_url }
|
||||
expect(response).to be_successful
|
||||
check_cors(response, '*', allow_crendentails, max_age)
|
||||
end
|
||||
end
|
||||
|
||||
context 'set multiple API_CORS_ORIGINS for frontend and localhost' do
|
||||
let(:allow_crendentails) { 'true' }
|
||||
let(:max_age) { '6200' }
|
||||
|
||||
before do
|
||||
ENV['API_CORS_ORIGINS'] = "#{frontend_url},#{local_url}"
|
||||
ENV['API_CORS_ALLOW_CREDENTIALS'] = allow_crendentails
|
||||
ENV['API_CORS_MAX_AGE'] = max_age
|
||||
end
|
||||
|
||||
after do
|
||||
ENV['API_CORS_ORIGINS'] = nil
|
||||
ENV['API_CORS_ALLOW_CREDENTIALS'] = nil
|
||||
ENV['API_CORS_MAX_AGE'] = nil
|
||||
end
|
||||
|
||||
it 'sends CORS headers when requesting using GET from frontend url' do
|
||||
api_get '/api/v2/account/balances', token: token, headers: { 'Origin' => frontend_url }
|
||||
expect(response).to be_successful
|
||||
check_cors(response, frontend_url, allow_crendentails, max_age)
|
||||
end
|
||||
|
||||
it 'sends CORS headers when requesting using GET from localhost' do
|
||||
api_get '/api/v2/account/balances', token: token, headers: { 'Origin' => local_url }
|
||||
expect(response).to be_successful
|
||||
check_cors(response, local_url, allow_crendentails, max_age)
|
||||
end
|
||||
|
||||
it 'doesn\'t sends CORS headers when requesting using GET from unkown domain' do
|
||||
api_get '/api/v2/account/balances', token: token, headers: { 'Origin' => 'http://domain.com' }
|
||||
expect(response).to be_successful
|
||||
without_cors(response)
|
||||
end
|
||||
end
|
||||
|
||||
context 'send invalid request' do
|
||||
let(:allow_crendentails) { 'true' }
|
||||
|
||||
before do
|
||||
ENV['API_CORS_ORIGINS'] = "#{frontend_url},#{local_url}"
|
||||
ENV['API_CORS_ALLOW_CREDENTIALS'] = allow_crendentails
|
||||
end
|
||||
|
||||
after do
|
||||
ENV['API_CORS_ORIGINS'] = nil
|
||||
ENV['API_CORS_ALLOW_CREDENTIALS'] = nil
|
||||
end
|
||||
|
||||
it 'sends CORS headers ever when user is not authenticated' do
|
||||
api_get '/api/v2/account/balances', headers: { 'Origin' => local_url }
|
||||
expect(response).to have_http_status 401
|
||||
check_cors(response, local_url, allow_crendentails)
|
||||
end
|
||||
|
||||
it 'sends CORS headers when invalid parameter supplied' do
|
||||
api_get '/api/v2/account/balances/somecoin', token: token, headers: { 'Origin' => local_url }
|
||||
expect(response).to have_http_status 422
|
||||
check_cors(response, local_url, allow_crendentails)
|
||||
end
|
||||
end
|
||||
end
|
||||
66
spec/api/v2/cors/validations_spec.rb
Normal file
66
spec/api/v2/cors/validations_spec.rb
Normal file
@@ -0,0 +1,66 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe CORS::Validations do
|
||||
describe 'validate origins' do
|
||||
subject { CORS::Validations.validate_origins(ENV['API_CORS_ORIGINS']) }
|
||||
|
||||
context 'set API_CORS_ORIGINS as "*"' do
|
||||
before { ENV['API_CORS_ORIGINS'] = '*' }
|
||||
|
||||
it { is_expected.to eq('*') }
|
||||
|
||||
after { ENV['API_CORS_ORIGINS'] = nil }
|
||||
end
|
||||
|
||||
context 'set mulitple API_CORS_ORIGINS with "*"' do
|
||||
before { ENV['API_CORS_ORIGINS'] = 'https://localhost,*,https://domain.com' }
|
||||
|
||||
it { is_expected.to eq('*') }
|
||||
|
||||
after { ENV['API_CORS_ORIGINS'] = nil }
|
||||
end
|
||||
|
||||
context 'set multiple API_CORS_ORIGINS' do
|
||||
before { ENV['API_CORS_ORIGINS'] = 'https://localhost,https://domain.com' }
|
||||
|
||||
it { is_expected.to eq(['https://localhost','https://domain.com']) }
|
||||
|
||||
after { ENV['API_CORS_ORIGINS'] = nil }
|
||||
end
|
||||
|
||||
context 'set invalid domain into API_CORS_ORIGINS' do
|
||||
before { ENV['API_CORS_ORIGINS'] = 'htt:://localhost' }
|
||||
|
||||
it { expect { subject }.to raise_error(CORS::Validations::Error) }
|
||||
|
||||
after { ENV['API_CORS_MAX_AGE'] = nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validate max age' do
|
||||
subject { CORS::Validations.validate_max_age(ENV['API_CORS_MAX_AGE']) }
|
||||
|
||||
context 'set API_CORS_MAX_AGE as "6200"' do
|
||||
before { ENV['API_CORS_MAX_AGE'] = '6200' }
|
||||
|
||||
it { is_expected.to eq('6200') }
|
||||
|
||||
after { ENV['API_CORS_MAX_AGE'] = nil }
|
||||
end
|
||||
|
||||
context 'set API_CORS_MAX_AGE as "6200.1"' do
|
||||
before { ENV['API_CORS_MAX_AGE'] = '6200.1' }
|
||||
|
||||
it { is_expected.to eq('3600') }
|
||||
|
||||
after { ENV['API_CORS_MAX_AGE'] = nil }
|
||||
end
|
||||
|
||||
context 'doesn\'t set API_CORS_MAX_AGE"' do
|
||||
before { ENV['API_CORS_MAX_AGE'] = nil }
|
||||
|
||||
it { is_expected.to eq('3600') }
|
||||
end
|
||||
end
|
||||
end
|
||||
14
spec/api/v2/entities/account_spec.rb
Normal file
14
spec/api/v2/entities/account_spec.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Entities::Account do
|
||||
let(:account) { create_account(:btc, balance: 100) }
|
||||
|
||||
subject { OpenStruct.new API::V2::Entities::Account.represent(account).serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.currency).to eq 'btc'
|
||||
expect(subject.balance).to eq '100.0'
|
||||
expect(subject.locked).to eq '0.0'
|
||||
end
|
||||
end
|
||||
13
spec/api/v2/entities/member_spec.rb
Normal file
13
spec/api/v2/entities/member_spec.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Entities::Member do
|
||||
let(:member) { create(:member, :level_3) }
|
||||
|
||||
subject { OpenStruct.new API::V2::Entities::Member.represent(member).serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.uid).to eq member.uid
|
||||
expect(subject.email).to eq member.email
|
||||
end
|
||||
end
|
||||
52
spec/api/v2/entities/order_spec.rb
Normal file
52
spec/api/v2/entities/order_spec.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Entities::Order do
|
||||
let(:order) do
|
||||
create(
|
||||
:order_ask,
|
||||
:btcusd,
|
||||
price: '12.32'.to_d,
|
||||
volume: '3.1418',
|
||||
origin_volume: '12.13'
|
||||
)
|
||||
end
|
||||
|
||||
context 'default exposure' do
|
||||
subject { OpenStruct.new API::V2::Entities::Order.represent(order, {}).serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.id).to eq order.id
|
||||
|
||||
expect(subject.price).to eq order.price
|
||||
expect(subject.avg_price).to eq ::Trade::ZERO
|
||||
|
||||
expect(subject.origin_volume).to eq order.origin_volume
|
||||
expect(subject.remaining_volume).to eq order.volume
|
||||
expect(subject.executed_volume).to eq(order.origin_volume - order.volume)
|
||||
|
||||
expect(subject.state).to eq order.state
|
||||
expect(subject.market).to eq order.market_id
|
||||
|
||||
expect(subject.side).to eq 'sell'
|
||||
|
||||
expect(subject.maker_fee).to eq order.maker_fee
|
||||
expect(subject.taker_fee).to eq order.taker_fee
|
||||
|
||||
expect(subject.trades).to be_nil
|
||||
expect(subject.trades_count).to be_zero
|
||||
|
||||
expect(subject.created_at).to eq order.created_at.iso8601
|
||||
end
|
||||
end
|
||||
|
||||
context 'full exposure' do
|
||||
it 'should expose related trades' do
|
||||
create(:trade, :btcusd, maker_order: order, amount: '8.0', price: '12')
|
||||
create(:trade, :btcusd, maker_order: order, amount: '0.99', price: '12.56')
|
||||
|
||||
json = API::V2::Entities::Order.represent(order, type: :full).serializable_hash
|
||||
expect(json[:trades].size).to eq 2
|
||||
end
|
||||
end
|
||||
end
|
||||
38
spec/api/v2/entities/trade_spec.rb
Normal file
38
spec/api/v2/entities/trade_spec.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Entities::Trade do
|
||||
let(:trade) do
|
||||
create :trade, :btcusd, maker_order: create(:order_ask, :btcusd), taker_order: create(:order_bid, :btcusd)
|
||||
end
|
||||
|
||||
subject { OpenStruct.new API::V2::Entities::Trade.represent(trade, side: 'sell').serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.id).to eq trade.id
|
||||
expect(subject.order_id).to be_nil
|
||||
expect(subject.price).to eq trade.price
|
||||
expect(subject.amount).to eq trade.amount
|
||||
expect(subject.total).to eq trade.total
|
||||
expect(subject.market).to eq trade.market_id
|
||||
expect(subject.side).to eq 'sell'
|
||||
expect(subject.created_at).to eq trade.created_at.iso8601
|
||||
end
|
||||
|
||||
context 'sell order maker' do
|
||||
it { expect(subject.taker_type).to eq 'buy' }
|
||||
end
|
||||
|
||||
context 'buy order maker' do
|
||||
let(:trade) do
|
||||
create :trade, :btcusd, maker_order: create(:order_bid, :btcusd), taker_order: create(:order_ask, :btcusd)
|
||||
end
|
||||
|
||||
it { expect(subject.taker_type).to eq 'sell' }
|
||||
end
|
||||
|
||||
context 'empty side' do
|
||||
subject { OpenStruct.new API::V2::Entities::Trade.represent(trade).serializable_hash }
|
||||
it { expect(subject.respond_to?(:side)).to be_falsey }
|
||||
end
|
||||
end
|
||||
59
spec/api/v2/helpers_spec.rb
Normal file
59
spec/api/v2/helpers_spec.rb
Normal file
@@ -0,0 +1,59 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
class AuthTest < Grape::API
|
||||
get('/auth_test') do
|
||||
authenticate!
|
||||
end
|
||||
end
|
||||
|
||||
class Mount
|
||||
mount AuthTest
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe API::V2::Helpers, type: :request do
|
||||
context '#authentic?' do
|
||||
let!(:member) { create(:member, :level_3) }
|
||||
let!(:token) { jwt_for(member) }
|
||||
|
||||
context 'Authenticate using headers' do
|
||||
it 'should response successfully' do
|
||||
api_get '/api/v2/auth_test', foo: 'bar', hello: 'world', token: token
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'should not return authorization header' do
|
||||
api_get '/api/v2/auth_test', foo: 'bar', hello: 'world', token: token
|
||||
expect(response.headers).not_to include('Authorization')
|
||||
end
|
||||
|
||||
it 'should set current user' do
|
||||
api_get '/api/v2/auth_test', foo: 'bar', hello: 'world', token: token
|
||||
expect(response.body).to eq member.reload.to_json
|
||||
end
|
||||
|
||||
it 'should fail authorization' do
|
||||
get '/api/v2/auth_test'
|
||||
|
||||
expect(response.code).to eq '401'
|
||||
expect(response).to include_api_error('jwt.decode_and_verify')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#authentic_include_username?' do
|
||||
let!(:member) { create(:member, username: 'foobar') }
|
||||
let!(:token) { jwt_for(member, { username: 'foobar' }) }
|
||||
|
||||
context 'Authenticate using headers' do
|
||||
it 'should set current user' do
|
||||
api_get '/api/v2/auth_test', token: token
|
||||
expect(response.body).to eq member.reload.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
77
spec/api/v2/management/accounts_spec.rb
Normal file
77
spec/api/v2/management/accounts_spec.rb
Normal file
@@ -0,0 +1,77 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Deposits, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
read_accounts: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] }
|
||||
}
|
||||
end
|
||||
|
||||
describe 'get balance' do
|
||||
def request
|
||||
post_json '/api/v2/management/accounts/balance', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { { uid: member.uid, currency: 'usd'} }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:member) { create(:member, :barong) }
|
||||
|
||||
before do
|
||||
deposit = create(:deposit_usd, member: member)
|
||||
deposit.accept
|
||||
end
|
||||
|
||||
it 'returns the correct status code' do
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'contains the correct response data' do
|
||||
request
|
||||
expect(JSON.parse(response.body)).to include(
|
||||
'balance' => member.get_account(data[:currency]).balance.to_s,
|
||||
'locked' => member.get_account(data[:currency]).locked.to_s
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'get balances' do
|
||||
def request
|
||||
post_json '/api/v2/management/accounts/balances', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { { currency: 'usd'} }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let!(:members) { create_list(:member, 12, :barong) }
|
||||
let!(:deposits) do
|
||||
members.each do |member|
|
||||
create(:deposit_usd, member: member).accept
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns the correct status code' do
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'paginates' do
|
||||
balances = Account.order(id: :asc).pluck(:balance)
|
||||
data.merge!(page: 1, limit: 4)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).map { |x| x.fetch('balance').to_f }).to eq balances[0...4].map(&:to_f)
|
||||
data.merge!(page: 3, limit: 4)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).map { |x| x.fetch('balance').to_f }).to eq balances[8...12].map(&:to_f)
|
||||
end
|
||||
|
||||
it 'contains the correct response data' do
|
||||
request
|
||||
expect(JSON.parse(response.body).map { |x| x.fetch('balance').to_f }).to eq Account.order(id: :asc).pluck(:balance).map(&:to_f)
|
||||
end
|
||||
end
|
||||
end
|
||||
374
spec/api/v2/management/beneficiaries_spec.rb
Normal file
374
spec/api/v2/management/beneficiaries_spec.rb
Normal file
@@ -0,0 +1,374 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Beneficiaries, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
read_beneficiaries: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
write_beneficiaries: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] }
|
||||
}
|
||||
end
|
||||
|
||||
describe 'beneficiary list' do
|
||||
def request
|
||||
post_json '/api/v2/management/beneficiaries/list', multisig_jwt_management_api_v1({ data: beneficiary_data }, *signers)
|
||||
end
|
||||
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:beneficiary_data) do
|
||||
{
|
||||
uid: member.uid,
|
||||
}
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
context 'missing required params' do
|
||||
it do
|
||||
beneficiary_data.except!(:uid)
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(JSON.parse(response.body)['error']).to match(/uid is missing/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without currency and state' do
|
||||
it do
|
||||
request
|
||||
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
|
||||
beneficiary_data.merge!(currency: :uah)
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.currency.doesnt_exist/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'existing currency' do
|
||||
let!(:btc_beneficiaries_for_member) do
|
||||
create_list(:beneficiary, 3, member: member)
|
||||
end
|
||||
|
||||
it do
|
||||
beneficiary_data.merge!(currency: :btc)
|
||||
request
|
||||
expect(response.status).to eq 200
|
||||
expect(response_body.all? { |b| b['currency'] == 'btc' }).to be_truthy
|
||||
end
|
||||
|
||||
context 'fiat currency' do
|
||||
let!(:usd_beneficiary_for_member) { create(:beneficiary, currency: Currency.find('usd'), member: member) }
|
||||
|
||||
it do
|
||||
beneficiary_data.merge!(currency: :usd)
|
||||
request
|
||||
expect(response.status).to eq 200
|
||||
expect(response_body.all? { |b| b['currency'] == 'usd' }).to be_truthy
|
||||
expect(usd_beneficiary_for_member.id).to eq response_body.first['id']
|
||||
expect(usd_beneficiary_for_member.data[:account_number]).to eq response_body.first['data']['account_number']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid state' do
|
||||
it do
|
||||
beneficiary_data.merge!(state: :invalid)
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.beneficiary.invalid_state/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'existing state' do
|
||||
it do
|
||||
beneficiary_data.merge!(state: :pending)
|
||||
request
|
||||
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
|
||||
beneficiary_data.merge!(currency: :btc, state: :active)
|
||||
request
|
||||
expect(response.status).to eq 200
|
||||
expect(response_body.all? { |b| b['currency'] == 'btc' && b['state'] == 'active' }).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'create beneficiary' do
|
||||
def request
|
||||
post_json '/api/v2/management/beneficiaries', multisig_jwt_management_api_v1({ data: beneficiary_data }, *signers)
|
||||
end
|
||||
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:beneficiary_data) do
|
||||
{
|
||||
currency: :btc,
|
||||
name: 'Personal Bitcoin wallet',
|
||||
description: 'Multisignature Bitcoin Wallet',
|
||||
uid: member.uid,
|
||||
state: 'active',
|
||||
data: {
|
||||
address: Faker::Blockchain::Bitcoin.address
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'invalid params' do
|
||||
context 'missing required params' do
|
||||
%i[currency name data uid].each do |rp|
|
||||
context rp do
|
||||
it do
|
||||
beneficiary_data.except!(rp)
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(JSON.parse(response.body)['error']).to match(/#{rp} is missing/i)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'currency doesn\'t exist' do
|
||||
it do
|
||||
beneficiary_data.merge!(currency: :uah)
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.currency.doesnt_exist/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'name is too long' do
|
||||
it do
|
||||
beneficiary_data.merge!(name: Faker::Lorem.sentence(500))
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.beneficiary.too_long_name/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid state' do
|
||||
it do
|
||||
beneficiary_data.merge!(state: 'test')
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.beneficiary.invalid_state/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'description is too long' do
|
||||
it do
|
||||
beneficiary_data.merge!(description: Faker::Lorem.sentence(500))
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.beneficiary.too_long_description/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'data has invalid type' do
|
||||
it do
|
||||
beneficiary_data.merge!(data: 'data')
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.beneficiary.non_json_data/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'crypto beneficiary' do
|
||||
context 'nil address in data' do
|
||||
it do
|
||||
beneficiary_data[:data][:address] = nil
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.beneficiary.missing_address_in_data/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'data without address' do
|
||||
it do
|
||||
beneficiary_data[:data].delete(:address)
|
||||
beneficiary_data[:data][:memo] = :memo
|
||||
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.beneficiary.missing_address_in_data/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'disabled withdrawal for currency' do
|
||||
let(:currency) { Currency.find(:btc) }
|
||||
before do
|
||||
currency.update(withdrawal_enabled: false)
|
||||
end
|
||||
it do
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.currency.withdrawal_disabled/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid character in address' do
|
||||
before do
|
||||
beneficiary_data[:data][:address] = "'" + Faker::Blockchain::Bitcoin.address
|
||||
end
|
||||
it do
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.beneficiary.failed_to_create/i)
|
||||
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
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.beneficiary.duplicate_address/i)
|
||||
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
|
||||
request
|
||||
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
|
||||
request
|
||||
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
|
||||
request
|
||||
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
|
||||
|
||||
context 'fiat beneficiary' do
|
||||
let(:beneficiary_data) do
|
||||
{
|
||||
currency: :usd,
|
||||
uid: member.uid,
|
||||
name: Faker::Bank.name,
|
||||
description: Faker::Company.catch_phrase,
|
||||
data: generate(:fiat_beneficiary_data)
|
||||
}
|
||||
end
|
||||
|
||||
context 'nil address in data' do
|
||||
it do
|
||||
beneficiary_data[:data].except!(:address)
|
||||
request
|
||||
expect(response.status).to eq 201
|
||||
expect(response_body['data']).to eq beneficiary_data[:data].with_indifferent_access
|
||||
end
|
||||
end
|
||||
|
||||
context 'nil data' do
|
||||
it do
|
||||
beneficiary_data[:data] = nil
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(JSON.parse(response.body)['error']).to match(/data is empty/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'duplicated address' do
|
||||
context 'same currency' do
|
||||
before do
|
||||
create(:beneficiary,
|
||||
member: member,
|
||||
currency_id: beneficiary_data[:currency],
|
||||
data: beneficiary_data[:data])
|
||||
end
|
||||
|
||||
it do
|
||||
request
|
||||
expect(response.status).to eq 201
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'valid params' do
|
||||
it 'creates beneficiary for member' do
|
||||
expect do
|
||||
request
|
||||
end.to change{ member.beneficiaries.count }.by(1)
|
||||
end
|
||||
|
||||
it 'creates beneficiary with active state' do
|
||||
request
|
||||
expect(response.status).to eq 201
|
||||
id = response_body['id']
|
||||
expect(Beneficiary.find_by!(id: id).state).to eq 'active'
|
||||
expect(Beneficiary.find_by!(id: id).data).to eq response_body['data']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
203
spec/api/v2/management/currencies_spec.rb
Normal file
203
spec/api/v2/management/currencies_spec.rb
Normal file
@@ -0,0 +1,203 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Currencies, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
read_currencies: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
write_currencies: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] }
|
||||
}
|
||||
end
|
||||
|
||||
describe 'get currency by code' do
|
||||
def request
|
||||
post_json "/api/v2/management/currencies/#{currency.code}", multisig_jwt_management_api_v1({ data: {} }, *signers)
|
||||
end
|
||||
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:currency) { Currency.find(:usd) }
|
||||
|
||||
it 'returns currency by code' do
|
||||
request
|
||||
expect(JSON.parse(response.body).fetch('id')).to eq currency.code
|
||||
end
|
||||
|
||||
context 'currency code with dot' do
|
||||
let!(:currency) { create(:currency, :xagm_cx) }
|
||||
|
||||
it 'returns currency by code' do
|
||||
request
|
||||
expect(JSON.parse(response.body).fetch('id')).to eq currency.code
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'update currency' do
|
||||
def request
|
||||
put_json '/api/v2/management/currencies/update', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { {} }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:currency) { Currency.find(:btc) }
|
||||
|
||||
it 'should validate deposit_fee param' do
|
||||
data.merge!(id: currency.id, deposit_fee: -10.0)
|
||||
request
|
||||
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/management.currency.invalid_deposit_fee/i)
|
||||
end
|
||||
|
||||
it 'should validate min_deposit_amount param' do
|
||||
data.merge!(id: currency.id, min_deposit_amount: -123.0)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/management.currency.invalid_min_deposit_amount/i)
|
||||
end
|
||||
|
||||
it 'should validate min_collection_amount param' do
|
||||
data.merge!(id: currency.id, min_collection_amount: -100.0)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/management.currency.invalid_min_collection_amount/i)
|
||||
end
|
||||
|
||||
it 'should validate withdraw_fee param' do
|
||||
data.merge!(id: currency.id, withdraw_fee: -100.0)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/management.currency.invalid_withdraw_fee/i)
|
||||
end
|
||||
|
||||
it 'should validate min_withdraw_amount param' do
|
||||
data.merge!(id: currency.id, min_withdraw_amount: -1)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/management.currency.invalid_min_withdraw_amount/i)
|
||||
end
|
||||
|
||||
it 'should validate withdraw_limit_24h param' do
|
||||
data.merge!(id: currency.id, withdraw_limit_24h: -1)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/management.currency.invalid_withdraw_limit_24h/i)
|
||||
end
|
||||
|
||||
it 'should validate withdraw_limit_72h param' do
|
||||
data.merge!(id: currency.id, withdraw_limit_72h: -1)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/management.currency.invalid_withdraw_limit_72h/i)
|
||||
end
|
||||
|
||||
it 'should validate options param' do
|
||||
data.merge!(id: currency.id, options: 'blah-blah')
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/non_json_options/i)
|
||||
end
|
||||
|
||||
it 'should validate visible param' do
|
||||
data.merge!(id: currency.id, visible: 'blah-blah')
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/management.currency.non_boolean_visible/i)
|
||||
end
|
||||
|
||||
it 'should validate position param' do
|
||||
data.merge!(id: currency.id, position: 0)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/management.currency.invalid_position/i)
|
||||
end
|
||||
|
||||
it 'should validate deposit_enabled param' do
|
||||
data.merge!(id: currency.id, deposit_enabled: 'blah-blah')
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/management.currency.non_boolean_deposit_enabled/i)
|
||||
end
|
||||
|
||||
it 'should validate withdrawal_enabled param' do
|
||||
data.merge!(id: currency.id, withdrawal_enabled: 'blah-blah')
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/management.currency.non_boolean_withdrawal_enabled/i)
|
||||
end
|
||||
|
||||
it 'should check required params' do
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/id is missing/i)
|
||||
end
|
||||
|
||||
it 'should update currency' do
|
||||
data.merge!(id: currency.id, visible: 'true', withdraw_fee: '0.1')
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 200
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('id')).to eq currency.id
|
||||
expect(result.fetch('visible')).to eq true
|
||||
expect(result.fetch('withdraw_fee')).to eq '0.1'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/management/currencies/list' do
|
||||
def request
|
||||
post_json "/api/v2/management/currencies/list", multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:data) { {} }
|
||||
|
||||
it 'lists visible currencies' do
|
||||
request
|
||||
expect(response).to have_http_status 200
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.count
|
||||
end
|
||||
|
||||
it 'lists visible coins' do
|
||||
data.merge!(type: 'coin')
|
||||
request
|
||||
expect(response).to have_http_status 200
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.coins.size
|
||||
end
|
||||
|
||||
it 'lists visible fiats' do
|
||||
data.merge!(type: 'fiat')
|
||||
request
|
||||
expect(response).to have_http_status 200
|
||||
|
||||
result = JSON.parse(response.body, symbolize_names: true)
|
||||
expect(result.size).to eq Currency.fiats.size
|
||||
expect(result.dig(0, :id)).to eq 'usd'
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid type' do
|
||||
data.merge!(type: 'invalid')
|
||||
request
|
||||
expect(response).to have_http_status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
269
spec/api/v2/management/deposits_spec.rb
Normal file
269
spec/api/v2/management/deposits_spec.rb
Normal file
@@ -0,0 +1,269 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Deposits, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
read_deposits: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
write_deposits: { permitted_signers: %i[alex jeff james], mandatory_signers: %i[alex jeff] }
|
||||
}
|
||||
end
|
||||
|
||||
describe 'list deposits' do
|
||||
def request
|
||||
post_json '/api/v2/management/deposits', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { {} }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:members) { create_list(:member, 2, :barong) }
|
||||
|
||||
before do
|
||||
Deposit::STATES.tap do |states|
|
||||
(states.count * 2).times do
|
||||
create(:deposit_btc, member: members.sample, aasm_state: states.sample)
|
||||
create(:deposit_usd, member: members.sample, aasm_state: states.sample)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns deposits' do
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).map { |x| x.fetch('tid') }).to eq Deposit.order(id: :desc).pluck(:tid)
|
||||
end
|
||||
|
||||
it 'paginates' do
|
||||
ids = Deposit.order(id: :desc).pluck(:tid)
|
||||
data.merge!(page: 1, limit: 4)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).map { |x| x.fetch('tid') }).to eq ids[0...4]
|
||||
data.merge!(page: 3, limit: 4)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).map { |x| x.fetch('tid') }).to eq ids[8...12]
|
||||
end
|
||||
|
||||
it 'filters by state' do
|
||||
Deposit::STATES.each do |state|
|
||||
data.merge!(state: state)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).count).to eq Deposit.where(aasm_state: state).count
|
||||
end
|
||||
end
|
||||
|
||||
it 'filters by member' do
|
||||
member = members.last
|
||||
data.merge!(uid: member.uid)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).count).to eq member.deposits.count
|
||||
end
|
||||
|
||||
it 'filters by currency' do
|
||||
data.merge!(currency: :usd)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).count).to eq Deposit.where(currency_id: :usd).count
|
||||
end
|
||||
end
|
||||
|
||||
describe 'create deposits' do
|
||||
let(:member) { create(:member, :barong) }
|
||||
let(:currency) { Currency.find(:usd) }
|
||||
let!(:account) { member.get_account(:usd) }
|
||||
let(:amount) { 750.77 }
|
||||
let :data do
|
||||
{ uid: member.uid,
|
||||
currency: currency.code,
|
||||
amount: amount.to_s }
|
||||
end
|
||||
let(:signers) { %i[alex jeff] }
|
||||
|
||||
def request
|
||||
post_json '/api/v2/management/deposits/new', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
it 'creates new fiat deposit with state «submitted»' do
|
||||
request
|
||||
expect(response.status).to eq 201
|
||||
record = Deposit.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.amount).to eq 750.77
|
||||
expect(record.aasm_state).to eq 'submitted'
|
||||
expect(record.account).to eq member.get_account(currency)
|
||||
expect(response_body['transfer_type']).to eq 'fiat'
|
||||
end
|
||||
|
||||
it 'can create fiat deposit and immediately accept it' do
|
||||
data.merge!(state: :accepted)
|
||||
expect { request }.to change { member.get_account(currency).balance }.by amount
|
||||
end
|
||||
|
||||
it 'denies access unless enough signatures are supplied' do
|
||||
data.merge!(state: :accepted)
|
||||
signers.clear.concat %i[james jeff]
|
||||
expect { request }.not_to(change { member.get_account(currency).balance })
|
||||
expect(response.status).to eq 401
|
||||
end
|
||||
|
||||
it 'validates member' do
|
||||
data.delete(:uid)
|
||||
request
|
||||
expect(response.body).to match(/uid is missing/i)
|
||||
data[:uid] = '1234567890'
|
||||
request
|
||||
expect(response.body).to match(/member must exist/i)
|
||||
end
|
||||
|
||||
it 'validates currency' do
|
||||
data.delete(:currency)
|
||||
request
|
||||
expect(response.body).to match(/currency is missing/i)
|
||||
data[:currency] = 'btc'
|
||||
request
|
||||
expect(response.body).to match(/currency does not have a valid value/i)
|
||||
end
|
||||
|
||||
it 'validates amount' do
|
||||
data.delete(:amount)
|
||||
request
|
||||
expect(response.body).to match(/amount is missing/i)
|
||||
data[:amount] = '-340.50'
|
||||
request
|
||||
expect(response.body).to match(/amount must be greater than 0/i)
|
||||
end
|
||||
|
||||
it 'validates state' do
|
||||
data[:state] = 'submitted'
|
||||
request
|
||||
expect(response.body).to match(/state does not have a valid value/i)
|
||||
end
|
||||
|
||||
context 'when coin instead of fiat is supplied' do
|
||||
let(:currency) { Currency.find(:btc) }
|
||||
it 'doesn\'t work' do
|
||||
request
|
||||
expect(response.body).to match(/currency does not have a valid value/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'extremely precise values' do
|
||||
it 'keeps precision for amount' do
|
||||
data.merge!(amount: '0.0000000123456789')
|
||||
request
|
||||
expect(response).to have_http_status(201)
|
||||
expect(Deposit.last.amount.to_s).to eq data[:amount]
|
||||
end
|
||||
end
|
||||
|
||||
context 'disabled currency' do
|
||||
before do
|
||||
currency.update(deposit_enabled: false)
|
||||
end
|
||||
|
||||
it 'returns error for enabled disabled deposit' do
|
||||
request
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('management.currency.deposit_disabled')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'get deposit' do
|
||||
def request
|
||||
post_json '/api/v2/management/deposits/get', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:data) { { tid: record.tid } }
|
||||
let(:record) { create(:deposit_btc, member: member) }
|
||||
let(:member) { create(:member, :barong) }
|
||||
|
||||
it 'returns deposit by TID' do
|
||||
request
|
||||
expect(JSON.parse(response.body).fetch('tid')).to eq record.tid
|
||||
end
|
||||
end
|
||||
|
||||
describe 'update deposit' do
|
||||
def request
|
||||
put_json '/api/v2/management/deposits/state', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:currency) { Currency.find(:usd) }
|
||||
let(:member) { create(:member, :barong) }
|
||||
let(:amount) { '500.90' }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:data) { { tid: record.tid } }
|
||||
let(:account) { member.get_account(currency) }
|
||||
let(:record) { Deposits::Fiat.create!(member: member, amount: amount, currency: currency) }
|
||||
|
||||
context 'coin deposit' do
|
||||
let(:record) { create(:deposit_btc) }
|
||||
|
||||
it 'works only with fiat deposits' do
|
||||
data.merge!(state: :accepted)
|
||||
request
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
it 'cancels deposit' do
|
||||
data.merge!(state: :canceled)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(Deposit.find_by_tid!(JSON.parse(response.body).fetch('tid')).aasm_state).to eq 'canceled'
|
||||
account.reload
|
||||
expect(account.balance).to eq 0
|
||||
expect(account.locked).to eq 0
|
||||
end
|
||||
|
||||
it 'accepts deposit' do
|
||||
data.merge!(state: :accepted)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(Deposit.find_by_tid!(JSON.parse(response.body).fetch('tid')).aasm_state).to eq 'accepted'
|
||||
account.reload
|
||||
expect(account.balance).to eq amount.to_d
|
||||
expect(account.locked).to eq 0
|
||||
end
|
||||
|
||||
it 'doesn\'t cancel deposit twice' do
|
||||
record.cancel!
|
||||
expect(record.aasm_state).to eq 'canceled'
|
||||
data.merge!(state: :canceled)
|
||||
expect { request }.not_to(change { record.reload.aasm_state })
|
||||
expect(response).to have_http_status(422)
|
||||
expect(record.account.balance).to eq 0
|
||||
expect(record.account.locked).to eq 0
|
||||
end
|
||||
|
||||
it 'doesn\'t accept deposit twice' do
|
||||
record.accept!
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
data.merge!(state: :accepted)
|
||||
expect { request }.not_to(change { record.reload.aasm_state })
|
||||
expect(response).to have_http_status(422)
|
||||
expect(record.account.balance).to eq amount.to_d
|
||||
expect(record.account.locked).to eq 0
|
||||
end
|
||||
|
||||
it 'validates state' do
|
||||
data.merge!(state: :rejected)
|
||||
request
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response.body).to match(/state does not have a valid value/i)
|
||||
end
|
||||
|
||||
it 'denies access unless enough signatures are supplied' do
|
||||
data.merge!(state: :accepted)
|
||||
signers.clear.concat %i[james]
|
||||
expect { request }.not_to(change { member.get_account(:usd).balance })
|
||||
expect(response.status).to eq 401
|
||||
end
|
||||
end
|
||||
end
|
||||
191
spec/api/v2/management/engines_spec.rb
Normal file
191
spec/api/v2/management/engines_spec.rb
Normal file
@@ -0,0 +1,191 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Engines, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
read_engines: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
write_engines: { permitted_signers: %i[alex jeff james], mandatory_signers: %i[alex jeff] }
|
||||
}
|
||||
end
|
||||
|
||||
describe 'POST /engines/get' do
|
||||
def request
|
||||
post_json "/api/v2/management/engines/get", multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:data) { params.merge(scope: :read_engines) }
|
||||
|
||||
let(:params) do
|
||||
engines_params
|
||||
end
|
||||
|
||||
context do
|
||||
let(:engines_params) { {} }
|
||||
|
||||
it 'lists of engines' do
|
||||
request
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
context do
|
||||
let(:engines_params) do
|
||||
{
|
||||
ordering: 'asc'
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns engines by ascending order' do
|
||||
request
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.first['id']).to eq Engine.first.id
|
||||
end
|
||||
end
|
||||
|
||||
context do
|
||||
let(:engines_params) do
|
||||
{
|
||||
limit: 1,
|
||||
page: 1
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns paginated engines' do
|
||||
request
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '2'
|
||||
expect(result.size).to eq 1
|
||||
|
||||
params[:page] = 2
|
||||
request
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response.headers.fetch('Total')).to eq '2'
|
||||
expect(result.size).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /engines' do
|
||||
def request
|
||||
post_json "/api/v2/management/engines", multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:data) { params.merge(scope: :write_engines) }
|
||||
|
||||
let(:params) do
|
||||
engines_params
|
||||
end
|
||||
|
||||
context do
|
||||
let(:engines_params) do
|
||||
{
|
||||
name: 'new-engine',
|
||||
driver: 'new_driver',
|
||||
uid: 'UID123456',
|
||||
key: 'your_key',
|
||||
secret: 'your_secret',
|
||||
data: { some_data: 'some data' }
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates new engine' do
|
||||
request
|
||||
result = JSON.parse(response.body)
|
||||
expect(response).to be_successful
|
||||
expect(result['name']).to eq 'new-engine'
|
||||
expect(result['data'].blank?).to eq true
|
||||
|
||||
request
|
||||
expect(response).to have_http_status 422
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['error']).to include('management.engine.duplicate_name')
|
||||
end
|
||||
end
|
||||
|
||||
context do
|
||||
let(:engines_params) { {} }
|
||||
it 'checked required params' do
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['error']).to include('name is missing, driver is missing')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /engines/update' do
|
||||
def request
|
||||
post_json "/api/v2/management/engines/update", multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:data) { params.merge(scope: :write_engines) }
|
||||
|
||||
let(:params) do
|
||||
engines_params
|
||||
end
|
||||
|
||||
let!(:engine) { create(:engine) }
|
||||
|
||||
context do
|
||||
let(:engines_params) do
|
||||
{
|
||||
id: engine.id,
|
||||
name: 'Second Engine',
|
||||
driver: 'second_driver'
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates attributes' do
|
||||
request
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result['name']).to eq 'Second Engine'
|
||||
expect(result['driver']).to eq 'second_driver'
|
||||
end
|
||||
end
|
||||
|
||||
context do
|
||||
let(:engines_params) do
|
||||
{
|
||||
id: engine.id,
|
||||
name: 'Second Engine',
|
||||
secret: 'my_secret'
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates secret' do
|
||||
request
|
||||
expect(response).to be_successful
|
||||
engine.reload
|
||||
expect(engine.secret).to eq('my_secret')
|
||||
end
|
||||
end
|
||||
|
||||
context do
|
||||
let(:engines_params) { {} }
|
||||
it 'checkes required params' do
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['error']).to include('id is missing')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
15
spec/api/v2/management/entities/balance_spec.rb
Normal file
15
spec/api/v2/management/entities/balance_spec.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Entities::Balance do
|
||||
let(:member) { create(:member, :barong) }
|
||||
let(:record) { member.get_account(:usd) }
|
||||
before { record.update!(balance: 1000.85, locked: 330.55) }
|
||||
subject { OpenStruct.new API::V2::Management::Entities::Balance.represent(record).serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.uid).to eq record.member.uid
|
||||
expect(subject.balance).to eq '1000.85'
|
||||
expect(subject.locked).to eq '330.55'
|
||||
end
|
||||
end
|
||||
42
spec/api/v2/management/entities/deposit_spec.rb
Normal file
42
spec/api/v2/management/entities/deposit_spec.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Entities::Deposit do
|
||||
context 'fiat' do
|
||||
let(:record) { create(:deposit_usd, member: create(:member, :barong)) }
|
||||
|
||||
subject { OpenStruct.new API::V2::Management::Entities::Deposit.represent(record).serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.tid).to eq record.tid
|
||||
expect(subject.currency).to eq 'usd'
|
||||
expect(subject.uid).to eq record.member.uid
|
||||
expect(subject.type).to eq 'fiat'
|
||||
expect(subject.amount).to eq record.amount.to_s
|
||||
expect(subject.state).to eq record.aasm_state
|
||||
expect(subject.created_at).to eq record.created_at.iso8601
|
||||
expect(subject.completed_at).to eq record.completed_at&.iso8601
|
||||
expect(subject.respond_to?(:blockchain_txid)).to be_falsey
|
||||
expect(subject.respond_to?(:confirmations)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'coin' do
|
||||
let(:record) { create(:deposit_btc, member: create(:member, :barong)) }
|
||||
|
||||
subject { OpenStruct.new API::V2::Management::Entities::Deposit.represent(record).serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.tid).to eq record.tid
|
||||
expect(subject.currency).to eq 'btc'
|
||||
expect(subject.uid).to eq record.member.uid
|
||||
expect(subject.type).to eq 'coin'
|
||||
expect(subject.amount).to eq record.amount.to_s
|
||||
expect(subject.state).to eq record.aasm_state
|
||||
expect(subject.created_at).to eq record.created_at.iso8601
|
||||
expect(subject.completed_at).to eq record.completed_at&.iso8601
|
||||
expect(subject.blockchain_txid).to eq record.txid
|
||||
expect(subject.blockchain_confirmations).to eq record.confirmations
|
||||
end
|
||||
end
|
||||
end
|
||||
64
spec/api/v2/management/entities/operation_spec.rb
Normal file
64
spec/api/v2/management/entities/operation_spec.rb
Normal file
@@ -0,0 +1,64 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Entities::Operation do
|
||||
Operations::Account::PLATFORM_TYPES.each do |op_type|
|
||||
context op_type do
|
||||
let(:record) { create(op_type) }
|
||||
|
||||
subject { OpenStruct.new API::V2::Management::Entities::Operation.represent(record).serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.code).to eq record.code
|
||||
expect(subject.currency).to eq record.currency_id
|
||||
expect(subject.created_at).to eq record.created_at.iso8601
|
||||
end
|
||||
|
||||
context 'credit' do
|
||||
it do
|
||||
expect(subject.credit).to eq record.credit
|
||||
expect(subject.respond_to?(:debit)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'debit' do
|
||||
let(:record) { create(:asset, :debit) }
|
||||
it do
|
||||
expect(subject.debit).to eq record.debit
|
||||
expect(subject.respond_to?(:credit)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Operations::Account::MEMBER_TYPES.each do |op_type|
|
||||
context op_type do
|
||||
let(:record) { create(op_type, :with_member) }
|
||||
|
||||
subject { OpenStruct.new API::V2::Management::Entities::Operation.represent(record).serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.code).to eq record.code
|
||||
expect(subject.currency).to eq record.currency_id
|
||||
expect(subject.uid).to eq record.member.uid
|
||||
expect(subject.created_at).to eq record.created_at.iso8601
|
||||
end
|
||||
|
||||
context 'credit' do
|
||||
it do
|
||||
expect(subject.credit).to eq record.credit
|
||||
expect(subject.respond_to?(:debit)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'debit' do
|
||||
let(:record) { create(:asset, :debit) }
|
||||
|
||||
it do
|
||||
expect(subject.debit).to eq record.debit
|
||||
expect(subject.respond_to?(:credit)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
spec/api/v2/management/entities/trade_spec.rb
Normal file
43
spec/api/v2/management/entities/trade_spec.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Entities::Trade do
|
||||
let(:trade) do
|
||||
create :trade, :btcusd, maker_order: create(:order_ask, :btcusd), taker_order: create(:order_bid, :btcusd)
|
||||
end
|
||||
|
||||
subject { OpenStruct.new API::V2::Management::Entities::Trade.represent(trade, side: 'sell').serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.id).to eq trade.id
|
||||
expect(subject.order_id).to be_nil
|
||||
expect(subject.price).to eq trade.price
|
||||
expect(subject.amount).to eq trade.amount
|
||||
expect(subject.total).to eq trade.total
|
||||
expect(subject.market).to eq trade.market_id
|
||||
expect(subject.side).to eq 'sell'
|
||||
expect(subject.created_at).to eq trade.created_at.iso8601
|
||||
expect(subject.maker_order_id).to eq trade.maker_order_id
|
||||
expect(subject.maker_order_id).to eq trade.maker_order_id
|
||||
expect(subject.maker_member_uid).to eq trade.maker.uid
|
||||
expect(subject.taker_member_uid).to eq trade.taker.uid
|
||||
end
|
||||
|
||||
|
||||
context 'sell order maker' do
|
||||
it { expect(subject.taker_type).to eq 'buy' }
|
||||
end
|
||||
|
||||
context 'buy order maker' do
|
||||
let(:trade) do
|
||||
create :trade, :btcusd, maker_order: create(:order_bid, :btcusd), taker_order: create(:order_ask, :btcusd)
|
||||
end
|
||||
|
||||
it { expect(subject.taker_type).to eq 'sell' }
|
||||
end
|
||||
|
||||
context 'empty side' do
|
||||
subject { OpenStruct.new API::V2::Management::Entities::Trade.represent(trade).serializable_hash }
|
||||
it { expect(subject.respond_to?(:side)).to be_falsey }
|
||||
end
|
||||
end
|
||||
69
spec/api/v2/management/entities/transfer_spec.rb
Normal file
69
spec/api/v2/management/entities/transfer_spec.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Entities::Transfer do
|
||||
let(:record) { create(:transfer) }
|
||||
subject { OpenStruct.new API::V2::Management::Entities::Transfer.represent(record.reload).serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.key).to eq record.key
|
||||
expect(subject.category).to eq record.category
|
||||
expect(subject.description).to eq record.description
|
||||
end
|
||||
|
||||
context 'with operations' do
|
||||
let(:record) { create(:transfer_with_operations) }
|
||||
it do
|
||||
::Operations::Account::TYPES.map(&:pluralize).each do |op_t|
|
||||
expect(subject.respond_to?(op_t)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
it do
|
||||
record_assets = API::V2::Management::Entities::Operation
|
||||
.represent(record.reload.assets)
|
||||
expect(subject.assets.to_json).to eq record_assets.to_json
|
||||
end
|
||||
|
||||
it do
|
||||
record_expenses = API::V2::Management::Entities::Operation
|
||||
.represent(record.reload.expenses)
|
||||
expect(subject.expenses.to_json).to eq record_expenses.to_json
|
||||
end
|
||||
|
||||
it do
|
||||
record_liabilities = API::V2::Management::Entities::Operation
|
||||
.represent(record.reload.liabilities)
|
||||
expect(subject.liabilities.to_json).to eq record_liabilities.to_json
|
||||
end
|
||||
|
||||
it do
|
||||
record_revenues = API::V2::Management::Entities::Operation
|
||||
.represent(record.reload.revenues)
|
||||
expect(subject.revenues.to_json).to eq record_revenues.to_json
|
||||
end
|
||||
end
|
||||
|
||||
context 'with single operation type' do
|
||||
let(:record) { create(:transfer, :with_liabilities) }
|
||||
|
||||
it do
|
||||
expect(subject.respond_to?(:liabilities)).to be_truthy
|
||||
end
|
||||
|
||||
it do
|
||||
# TYPES - 'liabilities' = PLATFORM_TYPES
|
||||
::Operations::Account::PLATFORM_TYPES.map(&:pluralize).each do |op_t|
|
||||
expect(subject.respond_to?(op_t.to_sym)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without operations' do
|
||||
it do
|
||||
::Operations::Account::TYPES.map(&:pluralize).each do |op_t|
|
||||
expect(subject.respond_to?(op_t)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
48
spec/api/v2/management/entities/withdraw_spec.rb
Normal file
48
spec/api/v2/management/entities/withdraw_spec.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Entities::Withdraw do
|
||||
context 'fiat' do
|
||||
let(:rid) { Faker::Bank.iban }
|
||||
let(:member) { create(:member, :barong) }
|
||||
let(:record) { create(:usd_withdraw, :with_deposit_liability, member: member, rid: rid) }
|
||||
|
||||
subject { OpenStruct.new API::V2::Management::Entities::Withdraw.represent(record).serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.tid).to eq record.tid
|
||||
expect(subject.rid).to eq rid
|
||||
expect(subject.currency).to eq 'usd'
|
||||
expect(subject.uid).to eq record.member.uid
|
||||
expect(subject.type).to eq 'fiat'
|
||||
expect(subject.amount).to eq record.amount.to_s
|
||||
expect(subject.note).to eq record.note
|
||||
expect(subject.fee).to eq record.fee.to_s
|
||||
expect(subject.respond_to?(:txid)).to be_falsey
|
||||
expect(subject.state).to eq record.aasm_state
|
||||
expect(subject.created_at).to eq record.created_at.iso8601
|
||||
end
|
||||
end
|
||||
|
||||
context 'coin' do
|
||||
let(:rid) { Faker::Blockchain::Bitcoin.address }
|
||||
let(:member) { create(:member, :barong) }
|
||||
let(:record) { create(:btc_withdraw, :with_deposit_liability, member: member, rid: rid) }
|
||||
|
||||
subject { OpenStruct.new API::V2::Management::Entities::Withdraw.represent(record).serializable_hash }
|
||||
|
||||
it do
|
||||
expect(subject.tid).to eq record.tid
|
||||
expect(subject.rid).to eq rid
|
||||
expect(subject.currency).to eq 'btc'
|
||||
expect(subject.uid).to eq record.member.uid
|
||||
expect(subject.type).to eq 'coin'
|
||||
expect(subject.amount).to eq record.amount.to_s
|
||||
expect(subject.note).to eq record.note
|
||||
expect(subject.fee).to eq record.fee.to_s
|
||||
expect(subject.blockchain_txid).to eq record.txid
|
||||
expect(subject.state).to eq record.aasm_state
|
||||
expect(subject.created_at).to eq record.created_at.iso8601
|
||||
end
|
||||
end
|
||||
end
|
||||
8
spec/api/v2/management/error_spec.rb
Normal file
8
spec/api/v2/management/error_spec.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
describe API::V2::Management::Exceptions::Base do
|
||||
it do
|
||||
expect(API::V2::Management::Exceptions::Base.new(message: 'Wrong argument.').inspect).to eq \
|
||||
'#<API::V2::Management::Exceptions::Base: Wrong argument.>'
|
||||
expect(API::V2::Management::Exceptions::Base.new(message: 'Wrong argument.', debug_message: 'Debug message.').inspect).to eq \
|
||||
'#<API::V2::Management::Exceptions::Base: Wrong argument. (Debug message.)>'
|
||||
end
|
||||
end
|
||||
120
spec/api/v2/management/jwt_authentication_middleware_spec.rb
Normal file
120
spec/api/v2/management/jwt_authentication_middleware_spec.rb
Normal file
@@ -0,0 +1,120 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::JWTAuthenticationMiddleware, type: :request do
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:config) { management_api_v1_security_configuration }
|
||||
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
config.merge! \
|
||||
scopes: {
|
||||
tools: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] }
|
||||
}
|
||||
end
|
||||
|
||||
it 'works in standard conditions' do
|
||||
post_json '/api/v2/management/timestamp', multisig_jwt_management_api_v1({}, :alex)
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'allows only POST, PUT, and DELETE' do
|
||||
get '/api/v2/management/timestamp'
|
||||
expect(response).to have_http_status(405)
|
||||
|
||||
patch '/api/v2/management/timestamp'
|
||||
expect(response).to have_http_status(405)
|
||||
|
||||
head '/api/v2/management/timestamp'
|
||||
expect(response).to have_http_status(405)
|
||||
end
|
||||
|
||||
it 'doesn\'t allow query parameters' do
|
||||
post '/api/v2/management/timestamp?foo=baz&baz=qux'
|
||||
expect(response).to have_http_status(400)
|
||||
expect(response.body).to match(/query parameters/i)
|
||||
end
|
||||
|
||||
it 'requires JSON in the request body' do
|
||||
post '/api/v2/management/timestamp', params: { foo: 'baz', baz: 'qux' }
|
||||
expect(response).to have_http_status(400)
|
||||
expect(response.body).to match(/only json/i)
|
||||
end
|
||||
|
||||
it 'denies access when not enough signatures are supplied' do
|
||||
post_json '/api/v2/management/timestamp', multisig_jwt_management_api_v1({})
|
||||
expect(response).to have_http_status(401)
|
||||
expect(response.body).to match(/not enough signatures/i)
|
||||
end
|
||||
|
||||
it 'denies access when token is expired' do
|
||||
config[:jwt][:verify_expiration] = true
|
||||
post_json '/api/v2/management/timestamp', multisig_jwt_management_api_v1({ exp: 1.minute.ago.to_i }, :alex)
|
||||
expect(response).to have_http_status(401)
|
||||
expect(response.body).to match(/failed to verify jwt/i)
|
||||
end
|
||||
|
||||
context 'valid issuer' do
|
||||
before { config[:jwt][:verify_iss] = true }
|
||||
before { config[:jwt].merge!(iss: 'qux') }
|
||||
it 'validates issuer' do
|
||||
post_json '/api/v2/management/timestamp', multisig_jwt_management_api_v1({ iss: 'qux' }, :alex)
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid issuer' do
|
||||
before { config[:jwt][:verify_iss] = true }
|
||||
before { config[:jwt].merge!(iss: 'qux') }
|
||||
it 'validates issuer' do
|
||||
post_json '/api/v2/management/timestamp', multisig_jwt_management_api_v1({ iss: 'hacker' }, :alex)
|
||||
expect(response).to have_http_status(401)
|
||||
expect(response.body).to match(/failed to verify jwt/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'valid audience' do
|
||||
before { config[:jwt][:verify_aud] = true }
|
||||
before { config[:jwt].merge!(aud: 'qux') }
|
||||
it 'validates audience' do
|
||||
post_json '/api/v2/management/timestamp', multisig_jwt_management_api_v1({ aud: 'qux' }, :alex)
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid audience' do
|
||||
before { config[:jwt][:verify_aud] = true }
|
||||
before { config[:jwt].merge!(aud: 'qux') }
|
||||
it 'validates audience' do
|
||||
post_json '/api/v2/management/timestamp', multisig_jwt_management_api_v1({ aud: 'hacker' }, :alex)
|
||||
expect(response).to have_http_status(401)
|
||||
expect(response.body).to match(/failed to verify jwt/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'missing JWT ID' do
|
||||
before { config[:jwt][:verify_jti] = true }
|
||||
it 'requires JTI' do
|
||||
post_json '/api/v2/management/timestamp', multisig_jwt_management_api_v1({}, :alex)
|
||||
expect(response).to have_http_status(401)
|
||||
expect(response.body).to match(/failed to verify jwt/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'issued at in future' do
|
||||
before { config[:jwt][:verify_iat] = true }
|
||||
it 'denies access' do
|
||||
post_json '/api/v2/management/timestamp', multisig_jwt_management_api_v1({ iat: 200.seconds.from_now.to_i }, :alex)
|
||||
expect(response).to have_http_status(401)
|
||||
expect(response.body).to match(/failed to verify jwt/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'issued at before future' do
|
||||
before { config[:jwt][:verify_iat] = true }
|
||||
it 'allows access' do
|
||||
post_json '/api/v2/management/timestamp', multisig_jwt_management_api_v1({ iat: 3.seconds.ago.to_i }, :alex)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
124
spec/api/v2/management/markets_spec.rb
Normal file
124
spec/api/v2/management/markets_spec.rb
Normal file
@@ -0,0 +1,124 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Markets, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
write_markets: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
read_markets: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
}
|
||||
end
|
||||
|
||||
describe 'update market' do
|
||||
def request
|
||||
put_json '/api/v2/management/markets/update', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { {} }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:market) { Market.find(:btcusd) }
|
||||
|
||||
it 'should validate min_price param' do
|
||||
data.merge!(id: market.id, min_price: -10.0)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/min_price does not have a valid value/i)
|
||||
end
|
||||
|
||||
it 'should validate min_amount param' do
|
||||
data.merge!(id: market.id, min_amount: -123.0)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/min_amount does not have a valid value/i)
|
||||
end
|
||||
|
||||
it 'should validate amount_precision param' do
|
||||
data.merge!(id: market.id, amount_precision: -100.0)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/amount_precision does not have a valid value/i)
|
||||
end
|
||||
|
||||
it 'should validate price_precision param' do
|
||||
data.merge!(id: market.id, price_precision: -100.0)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/price_precision does not have a valid value/i)
|
||||
end
|
||||
|
||||
it 'should validate max_price param' do
|
||||
data.merge!(id: market.id, max_price: -1)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/max_price does not have a valid value/i)
|
||||
end
|
||||
|
||||
it 'should validate position param' do
|
||||
data.merge!(id: market.id, position: -100.0)
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/position does not have a valid value/i)
|
||||
end
|
||||
|
||||
it 'should validate state param' do
|
||||
data.merge!(id: market.id, state: 'blah-blah')
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/state does not have a valid value/i)
|
||||
end
|
||||
|
||||
it 'should check required params' do
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/id is missing/i)
|
||||
end
|
||||
|
||||
it 'should update market' do
|
||||
data.merge!(id: market.id, state: 'disabled', min_amount: '0.1')
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 200
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('id')).to eq market.id
|
||||
expect(result.fetch('state')).to eq 'disabled'
|
||||
expect(result.fetch('min_amount')).to eq '0.1'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'fetch markets list' do
|
||||
def request
|
||||
post_json '/api/v2/management/markets/list', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
|
||||
let(:data) { {} }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
|
||||
let(:expected_keys) do
|
||||
%w[id name base_unit quote_unit min_price max_price
|
||||
min_amount amount_precision price_precision state position created_at updated_at]
|
||||
end
|
||||
|
||||
it 'lists enabled markets' do
|
||||
request
|
||||
expect(response).to have_http_status 200
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq Market.count
|
||||
result.each do |market|
|
||||
expect(market.keys).to eq expected_keys
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
93
spec/api/v2/management/members_spec.rb
Normal file
93
spec/api/v2/management/members_spec.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Members, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
write_members: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex jeff] },
|
||||
}
|
||||
end
|
||||
|
||||
describe 'create member' do
|
||||
def request
|
||||
post_json '/api/v2/management/members', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { build(:member).slice(:uid, :email, :level, :role, :group, :state) }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
|
||||
it 'returns member' do
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body)).to include( data )
|
||||
end
|
||||
|
||||
context 'invalid params' do
|
||||
context 'email' do
|
||||
it 'returns status 422 and error' do
|
||||
data[:email] = 'fake_email'
|
||||
|
||||
request
|
||||
expect(response).to have_http_status(422)
|
||||
expect(JSON.parse(response.body)['errors']).to eq("Validation failed: Email is invalid")
|
||||
end
|
||||
end
|
||||
|
||||
context 'level' do
|
||||
it 'returns status 422 and error' do
|
||||
data[:level] = 'fake_level'
|
||||
|
||||
request
|
||||
expect(response).to have_http_status(422)
|
||||
expect(JSON.parse(response.body)['error']).to eq("level is invalid")
|
||||
end
|
||||
end
|
||||
|
||||
context 'role' do
|
||||
it 'returns status 422 and error' do
|
||||
data[:role] = 'fake_role'
|
||||
|
||||
request
|
||||
expect(response).to have_http_status(422)
|
||||
expect(JSON.parse(response.body)['errors']).to eq("Validation failed: Role is not included in the list")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'set user group' do
|
||||
def request
|
||||
post_json '/api/v2/management/members/group', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { {uid: member.uid, group: 'vip-1'} }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:member) { create(:member, :barong) }
|
||||
|
||||
it 'returns user with updated role' do
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body)['group']).to eq('vip-1')
|
||||
end
|
||||
|
||||
context 'invalid uid' do
|
||||
let(:data) { { uid: 'fake_uid', group: 'vip-1' } }
|
||||
it 'returns status 404 and error' do
|
||||
request
|
||||
expect(response).to have_http_status(404)
|
||||
expect(JSON.parse(response.body)['error']).to eq("Couldn't find record.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid record' do
|
||||
let(:data) { { uid: member.uid, group: 'vip-12222222222222222222222222222' } }
|
||||
it 'returns status 422 and error' do
|
||||
request
|
||||
expect(response).to have_http_status(422)
|
||||
expect(JSON.parse(response.body)['errors']).to eq("Validation failed: Group is too long (maximum is 32 characters)")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
361
spec/api/v2/management/operations_spec.rb
Normal file
361
spec/api/v2/management/operations_spec.rb
Normal file
@@ -0,0 +1,361 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Operations, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
read_operations: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
write_operations: { permitted_signers: %i[alex jeff james], mandatory_signers: %i[alex jeff] }
|
||||
}
|
||||
end
|
||||
|
||||
describe 'list operations' do
|
||||
def request(op_type)
|
||||
post_json "/api/v2/management/#{op_type}", multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
def optional_request(op_type, data)
|
||||
post_json "/api/v2/management/#{op_type}", multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
Operations::Account::PLATFORM_TYPES.each do |op_type|
|
||||
context op_type do
|
||||
let(:data) { {} }
|
||||
let(:signers) { %i[alex] }
|
||||
let(:operations_number) { 15 }
|
||||
let!(:operations) { create_list(op_type, operations_number) }
|
||||
|
||||
before do
|
||||
request(op_type.to_s.pluralize)
|
||||
end
|
||||
|
||||
it { expect(response).to have_http_status(200) }
|
||||
|
||||
context 'filter by currency' do
|
||||
let(:data) { { currency: :btc } }
|
||||
it { expect(response).to have_http_status(200) }
|
||||
|
||||
it 'returns operations by currency' do
|
||||
operations = "operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
.where(currency_id: :btc)
|
||||
expect(JSON.parse(response.body).count).to eq operations.count
|
||||
expect(JSON.parse(response.body).map { |h| h['currency'] }).to\
|
||||
eq operations.pluck(:currency_id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'pagination' do
|
||||
let(:data) { { page: 2, limit: 8 } }
|
||||
|
||||
it 'returns second page of operations' do
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).count).to eq 7
|
||||
credits = "operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
.order(id: :desc)
|
||||
.pluck(:credit)
|
||||
|
||||
# Consider that credit sequence is unique.
|
||||
expect(JSON.parse(response.body).map { |h| h['credit'].to_d }).to eq credits[8..15]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Operations::Account::MEMBER_TYPES.each do |op_type|
|
||||
context op_type do
|
||||
let(:data) { {} }
|
||||
let(:signers) { %i[alex] }
|
||||
let(:operations_number) { 15 }
|
||||
let!(:operations) { create_list(op_type, operations_number, :with_member) }
|
||||
|
||||
before do
|
||||
request(op_type.to_s.pluralize)
|
||||
end
|
||||
|
||||
it { expect(response).to have_http_status(200) }
|
||||
|
||||
context 'filter by currency' do
|
||||
let(:data) { { currency: :btc } }
|
||||
|
||||
it 'returns operations by currency' do
|
||||
expect(response).to have_http_status(200)
|
||||
operations = "operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
.where(currency_id: :btc)
|
||||
expect(JSON.parse(response.body).count).to eq operations.count
|
||||
expect(JSON.parse(response.body).map { |h| h['currency'] }).to\
|
||||
eq operations.pluck(:currency_id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'filter by uid' do
|
||||
let(:member) { create(:member, :barong) }
|
||||
let!(:member_operations) do
|
||||
create_list(op_type, operations_number, member_id: member.id)
|
||||
end
|
||||
let(:data) { { uid: member.uid } }
|
||||
|
||||
it 'returns operations by member UID' do
|
||||
expect(response).to have_http_status(200)
|
||||
request(op_type.to_s.pluralize)
|
||||
operations = "operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
.where(member: member)
|
||||
expect(JSON.parse(response.body).count).to eq operations.count
|
||||
expect(JSON.parse(response.body).map { |h| h['uid'] }).to\
|
||||
eq [member.uid] * operations_number
|
||||
end
|
||||
end
|
||||
|
||||
context 'filter by reference type' do
|
||||
let(:deposit_data) { { reference_type: 'deposit' } }
|
||||
let(:trade_data) { { reference_type: 'trade' } }
|
||||
let(:order_data) { { reference_type: 'order' } }
|
||||
|
||||
it { expect(response).to have_http_status(200) }
|
||||
|
||||
def equal_amount!(response, optional_field, op_type)
|
||||
operations = "operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
.where(optional_field)
|
||||
expect(JSON.parse(response.body).count).to eq operations.count
|
||||
end
|
||||
|
||||
it 'returns operations by reference type deposit' do
|
||||
optional_request(op_type.to_s.pluralize, deposit_data)
|
||||
equal_amount!(response, deposit_data, op_type)
|
||||
end
|
||||
|
||||
it 'returns operations by reference type trade' do
|
||||
optional_request(op_type.to_s.pluralize, trade_data)
|
||||
equal_amount!(response, trade_data, op_type)
|
||||
end
|
||||
|
||||
it 'returns operations by reference type order' do
|
||||
optional_request(op_type.to_s.pluralize, order_data)
|
||||
equal_amount!(response, order_data, op_type)
|
||||
end
|
||||
end
|
||||
|
||||
context 'time range' do
|
||||
let(:time_from) { 2.days.ago }
|
||||
let(:time_to) { 1.day.ago }
|
||||
|
||||
let(:data) { { time_from: time_from.to_i, time_to: time_to.to_i } }
|
||||
|
||||
it { expect(response).to have_http_status(200) }
|
||||
|
||||
it 'returns operations between 48h and 24h ago' do
|
||||
request(op_type.to_s.pluralize)
|
||||
operations = "operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
.where('created_at >= ?', time_from)
|
||||
.where('created_at < ?', time_to)
|
||||
expect(JSON.parse(response.body).count).to eq operations.count
|
||||
end
|
||||
end
|
||||
|
||||
context 'pagination' do
|
||||
let(:data) { { page: 2, limit: 8 } }
|
||||
|
||||
it { expect(response).to have_http_status(200) }
|
||||
|
||||
it 'returns second page of operations' do
|
||||
expect(JSON.parse(response.body).count).to eq 7
|
||||
credits = "operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
.order(id: :desc)
|
||||
.pluck(:credit)
|
||||
|
||||
# Consider that credit sequence is unique.
|
||||
expect(JSON.parse(response.body).map{ |h| h['credit'].to_d }).to eq credits[8..15]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'create operation' do
|
||||
def request(op_type)
|
||||
post_json "/api/v2/management/#{op_type}/new", multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
Operations::Account::PLATFORM_TYPES.each do |op_type|
|
||||
context op_type do
|
||||
let(:currency) { Currency.coins.sample }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:data) do
|
||||
{ currency: currency.code,
|
||||
code: Operations::Account.find_by(type: op_type, currency_type: currency.type).code}
|
||||
end
|
||||
|
||||
context 'credit' do
|
||||
let(:amount) { '0.2515' }
|
||||
before do
|
||||
data[:credit] = amount
|
||||
request(op_type.to_s.pluralize)
|
||||
end
|
||||
|
||||
it 'returns operation' do
|
||||
expect(response).to have_http_status 200
|
||||
expect(JSON.parse(response.body)['currency']).to eq currency.code.to_s
|
||||
expect(JSON.parse(response.body)['credit'].to_d).to eq amount.to_d
|
||||
expect(JSON.parse(response.body)['code']).to \
|
||||
eq Operations::Account.find_by(type: op_type,
|
||||
kind: :main,
|
||||
currency_type: currency.type).code
|
||||
end
|
||||
|
||||
it 'saves operation' do
|
||||
op_klass = "operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
expect { request(op_type.to_s.pluralize) }.to \
|
||||
change(op_klass, :count).by(1)
|
||||
end
|
||||
|
||||
context 'wrong account code' do
|
||||
before do
|
||||
data[:code] = (::Operations::Account.pluck(:code) - [data[:code]]).sample
|
||||
request(op_type.to_s.pluralize)
|
||||
end
|
||||
|
||||
it { expect(response).to have_http_status 422 }
|
||||
end
|
||||
end
|
||||
|
||||
context 'debit' do
|
||||
let(:amount) { '0.1545' }
|
||||
before do
|
||||
# Create credit operation to avoid negative balance.
|
||||
create(op_type, credit: amount)
|
||||
data[:debit] = amount
|
||||
request(op_type.to_s.pluralize)
|
||||
end
|
||||
|
||||
it 'returns operation' do
|
||||
expect(response).to have_http_status 200
|
||||
expect(JSON.parse(response.body)['currency']).to eq currency.code.to_s
|
||||
expect(JSON.parse(response.body)['debit'].to_d).to eq amount.to_d
|
||||
expect(JSON.parse(response.body)['code']).to \
|
||||
eq Operations::Account.find_by(type: op_type,
|
||||
kind: :main,
|
||||
currency_type: currency.type).code
|
||||
end
|
||||
|
||||
it 'saves operation' do
|
||||
op_klass = "operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
expect { request(op_type.to_s.pluralize) }.to \
|
||||
change(op_klass, :count).by(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Operations::Account::MEMBER_TYPES.each do |op_type|
|
||||
context op_type do
|
||||
let(:currency) { Currency.coins.sample }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:member) { create(:member, :barong) }
|
||||
let(:data) do
|
||||
{ currency: currency.code,
|
||||
code: Operations::Account.find_by(type: op_type, currency_type: currency.type, kind: :main).code,
|
||||
uid: member.uid }
|
||||
end
|
||||
|
||||
context 'credit' do
|
||||
let(:amount) { '0.2515' }
|
||||
before do
|
||||
data[:credit] = amount
|
||||
request(op_type.to_s.pluralize)
|
||||
end
|
||||
|
||||
it 'returns operation' do
|
||||
expect(response).to have_http_status 200
|
||||
expect(JSON.parse(response.body)['uid']).to eq member.uid
|
||||
expect(JSON.parse(response.body)['currency']).to eq currency.code.to_s
|
||||
expect(JSON.parse(response.body)['credit'].to_d).to eq amount.to_d
|
||||
expect(JSON.parse(response.body)['code']).to \
|
||||
eq Operations::Account.find_by(type: op_type,
|
||||
kind: :main,
|
||||
currency_type: currency.type).code
|
||||
end
|
||||
|
||||
it 'saves operation' do
|
||||
op_klass = "operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
expect { request(op_type.to_s.pluralize) }.to \
|
||||
change(op_klass, :count).by(1)
|
||||
end
|
||||
|
||||
it 'updates legacy balance' do
|
||||
currency_id = JSON.parse(response.body)['currency']
|
||||
expect(member.get_account(currency_id).balance).to \
|
||||
eq JSON.parse(response.body)['credit'].to_d
|
||||
end
|
||||
|
||||
context 'wrong account code' do
|
||||
before do
|
||||
data[:code] = 999
|
||||
request(op_type.to_s.pluralize)
|
||||
end
|
||||
|
||||
it { expect(response).to have_http_status 422 }
|
||||
end
|
||||
end
|
||||
|
||||
context 'debit' do
|
||||
let(:amount) { '0.1545' }
|
||||
before do
|
||||
# Create credit operation to avoid negative balance.
|
||||
create(op_type, :with_member, credit: amount,
|
||||
member: member, currency: currency)
|
||||
data[:debit] = amount
|
||||
request(op_type.to_s.pluralize)
|
||||
end
|
||||
|
||||
it 'returns operation' do
|
||||
expect(response).to have_http_status 200
|
||||
expect(JSON.parse(response.body)['uid']).to eq member.uid
|
||||
expect(JSON.parse(response.body)['currency']).to eq currency.code.to_s
|
||||
expect(JSON.parse(response.body)['debit'].to_d).to eq amount.to_d
|
||||
expect(JSON.parse(response.body)['code']).to \
|
||||
eq Operations::Account.find_by(type: op_type,
|
||||
kind: :main,
|
||||
currency_type: currency.type).code
|
||||
end
|
||||
|
||||
it 'saves operation' do
|
||||
# Create one more credit operation to avoid negative balance.
|
||||
# So we can create one more debit operation.
|
||||
create(op_type, :with_member, credit: amount, member: member, currency: currency)
|
||||
op_klass = "operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
expect { request(op_type.to_s.pluralize) }.to \
|
||||
change(op_klass, :count).by(1)
|
||||
end
|
||||
|
||||
it 'updates legacy balance' do
|
||||
currency_id = JSON.parse(response.body)['currency']
|
||||
expect(member.get_account(currency_id).balance).to \
|
||||
eq 0.to_d
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
237
spec/api/v2/management/orders_spec.rb
Normal file
237
spec/api/v2/management/orders_spec.rb
Normal file
@@ -0,0 +1,237 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Orders, type: :request do
|
||||
let(:member1) { create(:member, :level_3) }
|
||||
let(:member2) { create(:member, :level_3) }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let!(:finex_engine) { create(:engine, driver: 'finex-spot') }
|
||||
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
read_orders: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
write_orders: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] }
|
||||
}
|
||||
|
||||
Market.find('btceth').update!(engine: finex_engine)
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/management/orders' do
|
||||
before do
|
||||
create(:order_bid, :btcusd, member: member1, state: Order::CANCEL)
|
||||
create(:order_ask, :btcusd, member: member1, state: Order::WAIT)
|
||||
create(:order_ask, :btceth, member: member1, state: Order::DONE)
|
||||
create(:order_bid, :btcusd, member: member2, state: Order::CANCEL)
|
||||
create(:order_ask, :btcusd, member: member2, state: Order::WAIT)
|
||||
create(:order_ask, :btceth, member: member2, state: Order::DONE)
|
||||
end
|
||||
|
||||
def request
|
||||
post_json '/api/v2/management/orders', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { {} }
|
||||
|
||||
it 'returns all orders on the platform' do
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 200
|
||||
expect(response_body.count).to eq(Order.count)
|
||||
end
|
||||
|
||||
context 'by member' do
|
||||
let(:data) do
|
||||
{
|
||||
uid: member1.uid
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns only member orders' do
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 200
|
||||
expect(response_body.pluck('member_id').uniq).to eq([member1.id])
|
||||
end
|
||||
end
|
||||
|
||||
context 'by member, market, state and order type' do
|
||||
let(:data) do
|
||||
{
|
||||
uid: member1.uid,
|
||||
market: 'btcusd',
|
||||
state: 'wait',
|
||||
ord_type: 'limit'
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns only member orders on specific market with specific state and order type' do
|
||||
request
|
||||
|
||||
expect(response).to have_http_status 200
|
||||
expect(response_body.pluck('member_id').uniq).to eq([member1.id])
|
||||
expect(response_body.pluck('state').uniq).to eq(['wait'])
|
||||
expect(response_body.pluck('market').uniq).to eq(['btcusd'])
|
||||
expect(response_body.pluck('ord_type').uniq).to eq(['limit'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid params' do
|
||||
context 'member_uid' do
|
||||
it 'returns status 422 and error' do
|
||||
data[:uid] = 'invalid_uid'
|
||||
request
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'market' do
|
||||
it 'returns status 422 and error' do
|
||||
data[:market] = 'invalid_market'
|
||||
request
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'state' do
|
||||
it 'returns status 422 and error' do
|
||||
data[:state] = 'invalid_state'
|
||||
request
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'ord_type' do
|
||||
it 'returns status 422 and error' do
|
||||
data[:ord_type] = 'invalid_ord_type'
|
||||
request
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/management/orders/:id/cancel' do
|
||||
let!(:peatio_order) { create(:order_ask, :btcusd, member: member1, state: Order::WAIT) }
|
||||
let!(:third_party_order) { create(:order_ask, :btceth, member: member1, state: Order::WAIT) }
|
||||
|
||||
def request(order_id)
|
||||
post_json "/api/v2/management/orders/#{order_id}/cancel", multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { {} }
|
||||
|
||||
it 'cancels an order on peatio market' do
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: peatio_order.to_matching_attributes)
|
||||
AMQP::Queue.expects(:publish).with(finex_engine.driver, data: peatio_order.as_json_for_third_party, type: 3).never
|
||||
request(peatio_order.id)
|
||||
expect(response).to have_http_status 200
|
||||
end
|
||||
|
||||
context 'third party order cancel' do
|
||||
it 'cancel an order on third party market' do
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: third_party_order.to_matching_attributes).never
|
||||
AMQP::Queue.expects(:publish).with(finex_engine.driver, data: third_party_order.as_json_for_third_party, type: 3)
|
||||
|
||||
request(third_party_order.id)
|
||||
expect(response).to have_http_status 200
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid params' do
|
||||
it 'returns status 404 and error' do
|
||||
request(0)
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/management/orders/cancel' do
|
||||
let!(:member1_peatio_order) { create(:order_ask, :btcusd, member: member1, state: Order::WAIT) }
|
||||
let!(:member2_peatio_order) { create(:order_ask, :btcusd, member: member2, state: Order::WAIT) }
|
||||
let!(:member1_third_party_order) { create(:order_ask, :btceth, member: member1, state: Order::WAIT) }
|
||||
let!(:member2_third_party_order) { create(:order_ask, :btceth, member: member2, state: Order::WAIT) }
|
||||
|
||||
def request
|
||||
post_json '/api/v2/management/orders/cancel', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { {} }
|
||||
|
||||
context 'peatio order cancel' do
|
||||
|
||||
it 'cancels the orders on peatio market' do
|
||||
data[:market] = 'btcusd'
|
||||
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: member1_peatio_order.to_matching_attributes)
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: member2_peatio_order.to_matching_attributes)
|
||||
AMQP::Queue.expects(:publish).with(finex_engine.driver, data: { market_id: 'btcusd' }, type: 4).never
|
||||
|
||||
request
|
||||
expect(response).to have_http_status 204
|
||||
end
|
||||
|
||||
it 'cancels the orders on peatio market' do
|
||||
data[:market] = 'btcusd'
|
||||
data[:uid] = member1.uid
|
||||
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: member1_peatio_order.to_matching_attributes)
|
||||
AMQP::Queue.expects(:publish).with(finex_engine.driver, data: { market_id: 'btcusd' }, type: 4).never
|
||||
|
||||
request
|
||||
expect(response).to have_http_status 204
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'third party orders cancel' do
|
||||
it 'cancels the orders on third party market' do
|
||||
data[:market] = 'btceth'
|
||||
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: member1_peatio_order.to_matching_attributes).never
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: member2_peatio_order.to_matching_attributes).never
|
||||
AMQP::Queue.expects(:publish).with(finex_engine.driver, data: { market_id: 'btceth' }, type: 4)
|
||||
|
||||
request
|
||||
expect(response).to have_http_status 204
|
||||
end
|
||||
|
||||
it 'cancels the orders on third party market' do
|
||||
data[:market] = 'btceth'
|
||||
data[:uid] = member1.uid
|
||||
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: member1_peatio_order.to_matching_attributes).never
|
||||
AMQP::Queue.expects(:publish).with(finex_engine.driver, data: { market_id: 'btceth', member_uid: member1.uid }, type: 4)
|
||||
|
||||
request
|
||||
expect(response).to have_http_status 204
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid params' do
|
||||
context 'invalid market' do
|
||||
it 'returns status 422 and error' do
|
||||
data[:market] = 'btcbtc'
|
||||
request
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid uid' do
|
||||
it 'returns status 422 and error' do
|
||||
data[:market] = 'btceth'
|
||||
data[:uid] = 'invalid_uid'
|
||||
request
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
113
spec/api/v2/management/payment_address_spec.rb
Normal file
113
spec/api/v2/management/payment_address_spec.rb
Normal file
@@ -0,0 +1,113 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::PaymentAddress, type: :request do
|
||||
let(:member1) { create(:member, :level_3) }
|
||||
let(:member2) { create(:member, :level_3) }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
write_payment_addresses: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
}
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/management/deposit_address/new' do
|
||||
let(:data) { { currency: 'eth', uid: member1.uid } }
|
||||
let(:address) { 'qwerty' }
|
||||
|
||||
def request
|
||||
post_json '/api/v2/management/deposit_address/new', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
before do
|
||||
WalletService.any_instance.stubs(:create_address!).returns({ address: address, secret: 'qwerty' })
|
||||
end
|
||||
|
||||
it 'generates new address' do
|
||||
request
|
||||
expect(response_body).to eq({"address"=>address, "currencies"=>[data[:currency]], "remote"=>false, "state"=>"active", "uid"=>data[:uid]})
|
||||
expect(response).to have_http_status 200
|
||||
end
|
||||
|
||||
context 'generates new address for btc' do
|
||||
it do
|
||||
data[:currency] = 'btc'
|
||||
request
|
||||
expect(response_body).to eq({"address"=>address, "currencies"=>[data[:currency]], "remote"=>false, "state"=>"active", "uid"=>data[:uid]})
|
||||
expect(response).to have_http_status 200
|
||||
end
|
||||
end
|
||||
|
||||
context 'generates new address with specified remote value' do
|
||||
it do
|
||||
data[:remote] = true
|
||||
request
|
||||
expect(response_body).to eq({"address"=>address, "currencies"=>[data[:currency]], "remote"=>true, "state"=>"active", "uid"=>data[:uid]})
|
||||
expect(response).to have_http_status 200
|
||||
end
|
||||
end
|
||||
|
||||
context 'missing required params' do
|
||||
context 'uid' do
|
||||
it do
|
||||
data.delete(:uid)
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/uid is missing/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'currency' do
|
||||
it do
|
||||
data.delete(:currency)
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/currency is missing/i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'non-existing params applied' do
|
||||
context 'uid' do
|
||||
it do
|
||||
data[:uid] = '123456'
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.payment_address.uid_doesnt_exist/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'currency' do
|
||||
it do
|
||||
data[:currency] = 'uah'
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.payment_address.currency_doesnt_exist/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'remote' do
|
||||
it do
|
||||
data[:remote] = 'remote'
|
||||
request
|
||||
expect(response.status).to eq 422
|
||||
expect(response.body).to match(/management.payment_address.non_boolean_remote/i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'wallet service raised an error' do
|
||||
before do
|
||||
WalletService.any_instance.stubs(:create_address!).raises(StandardError.new)
|
||||
end
|
||||
|
||||
it do
|
||||
request
|
||||
expect(response.body).to match(/management.payment_address.failed_to_generate/i)
|
||||
expect(response).to have_http_status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
29
spec/api/v2/management/tools_spec.rb
Normal file
29
spec/api/v2/management/tools_spec.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Tools, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
tools: { permitted_signers: %i[alex jeff], mandatory_signers: %i[jeff] }
|
||||
}
|
||||
end
|
||||
|
||||
describe 'management/timestamp' do
|
||||
let(:data) { {} }
|
||||
let(:signers) { %i[jeff] }
|
||||
|
||||
def request
|
||||
post_json '/api/v2/management/timestamp',
|
||||
multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
it 'returns current time in seconds' do
|
||||
now = Time.now
|
||||
request
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body).fetch('timestamp')).to be_between(now.iso8601, (now + 1).iso8601)
|
||||
end
|
||||
end
|
||||
end
|
||||
102
spec/api/v2/management/trades_spec.rb
Normal file
102
spec/api/v2/management/trades_spec.rb
Normal file
@@ -0,0 +1,102 @@
|
||||
describe API::V2::Management::Trades, type: :request do
|
||||
let(:member) do
|
||||
create(:member, :level_3).tap do |m|
|
||||
m.get_account(:btc).update_attributes(balance: 12.13, locked: 3.14)
|
||||
m.get_account(:usd).update_attributes(balance: 2014.47, locked: 0)
|
||||
end
|
||||
end
|
||||
|
||||
let(:second_member) do
|
||||
create(:member, :level_3).tap do |m|
|
||||
m.get_account(:btc).update_attributes(balance: 12.13, locked: 3.14)
|
||||
m.get_account(:usd).update_attributes(balance: 2014.47, locked: 0)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
let(:btcusd_ask) do
|
||||
create(
|
||||
:order_ask,
|
||||
:btcusd,
|
||||
price: '12.32'.to_d,
|
||||
volume: '123.12345678',
|
||||
member: member
|
||||
)
|
||||
end
|
||||
|
||||
let(:btceth_ask) do
|
||||
create(
|
||||
:order_ask,
|
||||
:btceth,
|
||||
price: '12.326'.to_d,
|
||||
volume: '123.1234',
|
||||
member: second_member
|
||||
)
|
||||
end
|
||||
|
||||
let(:btcusd_bid) do
|
||||
create(
|
||||
:order_bid,
|
||||
:btcusd,
|
||||
price: '12.32'.to_d,
|
||||
volume: '123.12345678',
|
||||
member: member
|
||||
)
|
||||
end
|
||||
|
||||
let(:btceth_bid) do
|
||||
create(
|
||||
:order_bid,
|
||||
:btceth,
|
||||
price: '12.326'.to_d,
|
||||
volume: '123.1234',
|
||||
member: second_member
|
||||
)
|
||||
end
|
||||
|
||||
let!(:btcusd_ask_trade) { create(:trade, :btcusd, maker_order: btcusd_ask, created_at: 2.days.ago) }
|
||||
let!(:btceth_ask_trade) { create(:trade, :btceth, maker_order: btceth_ask, created_at: 2.days.ago) }
|
||||
let!(:btcusd_bid_trade) { create(:trade, :btcusd, taker_order: btcusd_bid, created_at: 23.hours.ago) }
|
||||
let!(:btceth_bid_trade) { create(:trade, :btceth, taker_order: btceth_bid, created_at: 23.hours.ago) }
|
||||
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
read_trades: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
}
|
||||
end
|
||||
|
||||
def request
|
||||
post_json '/api/v2/management/trades', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { {} }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
|
||||
it 'returns all recent trades' do
|
||||
request
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.count).to eq 4
|
||||
end
|
||||
|
||||
it 'returns trades by uid of user' do
|
||||
data.merge!(uid: member.uid)
|
||||
request
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.count).to eq 2
|
||||
end
|
||||
|
||||
it 'returns trades by market' do
|
||||
data.merge!(market: 'btcusd')
|
||||
request
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.count).to eq 2
|
||||
end
|
||||
end
|
||||
72
spec/api/v2/management/trading_fees_spec.rb
Normal file
72
spec/api/v2/management/trading_fees_spec.rb
Normal file
@@ -0,0 +1,72 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::TradingFees, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
read_trading_fees: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
}
|
||||
end
|
||||
|
||||
describe '/fee_schedule/trading_fees' do
|
||||
def request
|
||||
post_json '/api/v2/management/fee_schedule/trading_fees', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) {}
|
||||
let(:signers) { %i[alex jeff] }
|
||||
|
||||
before do
|
||||
create(:trading_fee, maker: 0.0005, taker: 0.001, market_id: :btcusd, group: 'vip-0')
|
||||
create(:trading_fee, maker: 0.0008, taker: 0.001, market_id: :any, group: 'vip-0')
|
||||
create(:trading_fee, maker: 0.001, taker: 0.0012, market_id: :btcusd, group: :any)
|
||||
end
|
||||
|
||||
it 'returns all trading fees tables' do
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.headers.fetch('Total')).to eq('4')
|
||||
end
|
||||
|
||||
context 'group: vip-0, market: btcusd' do
|
||||
let(:data) { { group: 'vip-0', market_id: 'btcusd' } }
|
||||
it 'returns trading fee with btcusd market_id and vip-0 group' do
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).first['maker']).to eq('0.0005')
|
||||
expect(JSON.parse(response.body).first['taker']).to eq('0.001')
|
||||
expect(JSON.parse(response.body).first['group']).to eq('vip-0')
|
||||
expect(JSON.parse(response.body).first['market_id']).to eq('btcusd')
|
||||
expect(response.headers.fetch('Total')).to eq('1')
|
||||
end
|
||||
end
|
||||
|
||||
context 'group: any, market: btcusd' do
|
||||
let(:data) { { group: 'any', market_id: 'btcusd' } }
|
||||
it 'returns trading fee with btcusd market_id and `any` group' do
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).first['maker']).to eq('0.001')
|
||||
expect(JSON.parse(response.body).first['taker']).to eq('0.0012')
|
||||
expect(JSON.parse(response.body).first['group']).to eq('any')
|
||||
expect(JSON.parse(response.body).first['market_id']).to eq('btcusd')
|
||||
expect(response.headers.fetch('Total')).to eq('1')
|
||||
end
|
||||
end
|
||||
|
||||
context 'group: vip-0, market: any' do
|
||||
let(:data) { { group: 'vip-0', market_id: 'any' } }
|
||||
it 'returns trading fee with btcusd market_id and `any` group' do
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).first['maker']).to eq('0.0008')
|
||||
expect(JSON.parse(response.body).first['taker']).to eq('0.001')
|
||||
expect(JSON.parse(response.body).first['group']).to eq('vip-0')
|
||||
expect(JSON.parse(response.body).first['market_id']).to eq('any')
|
||||
expect(response.headers.fetch('Total')).to eq('1')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
485
spec/api/v2/management/transfers_spec.rb
Normal file
485
spec/api/v2/management/transfers_spec.rb
Normal file
@@ -0,0 +1,485 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Transfers, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
read_transfers: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
write_transfers: { permitted_signers: %i[alex jeff james], mandatory_signers: %i[alex jeff] }
|
||||
}
|
||||
end
|
||||
|
||||
describe 'create operation' do
|
||||
def request
|
||||
post_json '/api/v2/management/transfers/new', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:currency) { Currency.coins.sample }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:data) do
|
||||
{ key: generate(:transfer_key),
|
||||
category: Transfer::CATEGORIES.sample,
|
||||
description: "Referral program payoffs (#{Time.now.to_date})",
|
||||
operations: operations }
|
||||
end
|
||||
|
||||
let(:valid_operation) do
|
||||
{ currency: :btc,
|
||||
amount: '0.0001',
|
||||
account_src: {
|
||||
code: 102
|
||||
},
|
||||
account_dst: {
|
||||
code: 102
|
||||
} }
|
||||
end
|
||||
|
||||
context 'automatically creates an account, if transfer will be send' do
|
||||
let!(:sender_member) { create(:member, :level_3) }
|
||||
let!(:receiver_member) { create(:member, :level_3) }
|
||||
let!(:deposit) { create(:deposit_btc, member: sender_member, amount: 1) }
|
||||
|
||||
let(:operation) do
|
||||
{
|
||||
currency: :btc,
|
||||
amount: '0.5',
|
||||
account_src: {
|
||||
code: 202,
|
||||
uid: sender_member.uid
|
||||
},
|
||||
account_dst: {
|
||||
code: 202,
|
||||
uid: receiver_member.uid
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:operations) { [operation] }
|
||||
|
||||
before do
|
||||
deposit.accept!
|
||||
deposit.process!
|
||||
deposit.dispatch!
|
||||
end
|
||||
|
||||
it do
|
||||
expect(receiver_member.accounts.count).to eq(0)
|
||||
|
||||
request
|
||||
expect(response).to have_http_status(201)
|
||||
expect(receiver_member.accounts.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty key' do
|
||||
let(:operations) { [valid_operation] }
|
||||
|
||||
before do
|
||||
data.delete(:key)
|
||||
request
|
||||
end
|
||||
|
||||
it do
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response.body).to match(/key is missing/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty category' do
|
||||
let(:operations) {[valid_operation]}
|
||||
|
||||
before do
|
||||
data.delete(:category)
|
||||
request
|
||||
end
|
||||
|
||||
it do
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response.body).to match(/category is missing/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty description' do
|
||||
let(:operations) {[valid_operation]}
|
||||
|
||||
before do
|
||||
data.delete(:description)
|
||||
request
|
||||
end
|
||||
|
||||
it { expect(response).to have_http_status(201) }
|
||||
end
|
||||
|
||||
context 'empty operations' do
|
||||
let(:operations) {[]}
|
||||
|
||||
before { request }
|
||||
|
||||
it do
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response.body).to match(/operations is empty/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid account code' do
|
||||
let(:operations) do
|
||||
valid_operation[:account_src][:code] = 999
|
||||
[valid_operation]
|
||||
end
|
||||
before { request }
|
||||
|
||||
it do
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response.body).to match(/does not have a valid value/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid currency' do
|
||||
let(:operations) do
|
||||
valid_operation[:currency] = :neo
|
||||
[valid_operation]
|
||||
end
|
||||
before { request }
|
||||
|
||||
it do
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response.body).to match(/does not have a valid value/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid amount' do
|
||||
let(:operations) do
|
||||
valid_operation[:amount] = -1
|
||||
[valid_operation]
|
||||
end
|
||||
before { request }
|
||||
|
||||
it do
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response.body).to match(/does not have a valid value/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'existing transfer key' do
|
||||
let(:operations) {[valid_operation]}
|
||||
before do
|
||||
t = create(:transfer)
|
||||
data[:key] = t.key
|
||||
request
|
||||
end
|
||||
|
||||
it do
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/key has already been taken/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'debit liability on account with insufficient balance' do
|
||||
let!(:sender_member) { create(:member, :level_3) }
|
||||
let!(:receiver_member) { create(:member, :level_3) }
|
||||
|
||||
let!(:deposit) { create(:deposit_btc, member: sender_member, amount: 1) }
|
||||
let(:operation) do
|
||||
{
|
||||
currency: :btc,
|
||||
amount: '1.1',
|
||||
account_src: {
|
||||
code: 202,
|
||||
uid: sender_member.uid
|
||||
},
|
||||
account_dst: {
|
||||
code: 202,
|
||||
uid: receiver_member.uid
|
||||
}
|
||||
}
|
||||
end
|
||||
let(:operations) {[operation]}
|
||||
|
||||
before { request }
|
||||
|
||||
it do
|
||||
expect(response).to have_http_status 422
|
||||
expect(response.body).to match(/account balance is insufficient/i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'referral program story' do
|
||||
# In case of referral program some fees received by platform
|
||||
# during trading are returned to the referrer once per 4h for example.
|
||||
# We debit Revenue account balance and credit member Liabilities.
|
||||
|
||||
before do
|
||||
# Credit Revenue accounts.
|
||||
create(:revenue, currency_id: base_unit,
|
||||
code: coin_revenues_code, credit: 10)
|
||||
create(:revenue, currency_id: quote_unit,
|
||||
code: fiat_revenues_code, credit: 100)
|
||||
end
|
||||
|
||||
let(:referrer1) { create(:member, :barong) }
|
||||
let(:referrer2) { create(:member, :barong) }
|
||||
let(:referrer3) { create(:member, :barong) }
|
||||
|
||||
let(:base_unit) { Currency.coins.ids.sample }
|
||||
let(:quote_unit) { Currency.fiats.ids.sample }
|
||||
|
||||
let(:coin_liabilities_code) { 202 }
|
||||
let(:fiat_liabilities_code) { 201 }
|
||||
|
||||
let(:coin_revenues_code) { 302 }
|
||||
let(:fiat_revenues_code) { 301 }
|
||||
|
||||
# Consider we have BTC/USD market
|
||||
# Return all BTC/USD fees for 4h in single batch.
|
||||
# Balance changes:
|
||||
# Liability:
|
||||
# referrer1:
|
||||
# base_unit: 0.0001 + 0.0003
|
||||
# quote_unit: 0
|
||||
# referrer2:
|
||||
# base_unit: 0.00015
|
||||
# quote_unit: 0.05
|
||||
# referrer3:
|
||||
# base_unit: 0
|
||||
# quote_unit: 0.075
|
||||
# Revenue:
|
||||
# base_unit: -(0.0001 + 0.0003 + 0.00015)
|
||||
# quote_unit -(0.05 + 0.075)
|
||||
let(:operations) do
|
||||
[
|
||||
{
|
||||
currency: base_unit,
|
||||
amount: '0.0001',
|
||||
account_src: {
|
||||
code: coin_revenues_code
|
||||
},
|
||||
account_dst: {
|
||||
code: coin_liabilities_code,
|
||||
uid: referrer1.uid
|
||||
}
|
||||
},
|
||||
{
|
||||
currency: base_unit,
|
||||
amount: '0.00015',
|
||||
account_src: {
|
||||
code: coin_revenues_code
|
||||
},
|
||||
account_dst: {
|
||||
code: coin_liabilities_code,
|
||||
uid: referrer2.uid
|
||||
}
|
||||
},
|
||||
{
|
||||
currency: base_unit,
|
||||
amount: '0.0003',
|
||||
account_src: {
|
||||
code: coin_revenues_code
|
||||
},
|
||||
account_dst: {
|
||||
code: coin_liabilities_code,
|
||||
uid: referrer1.uid
|
||||
}
|
||||
},
|
||||
{
|
||||
currency: quote_unit,
|
||||
amount: '0.075',
|
||||
account_src: {
|
||||
code: fiat_revenues_code
|
||||
},
|
||||
account_dst: {
|
||||
code: fiat_liabilities_code,
|
||||
uid: referrer3.uid
|
||||
}
|
||||
},
|
||||
{
|
||||
currency: quote_unit,
|
||||
amount: '0.05',
|
||||
account_src: {
|
||||
code: fiat_revenues_code
|
||||
},
|
||||
account_dst: {
|
||||
code: fiat_liabilities_code,
|
||||
uid: referrer2.uid
|
||||
}
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
it do
|
||||
request
|
||||
expect(response).to have_http_status 201
|
||||
end
|
||||
|
||||
it 'returns transfer with operations' do
|
||||
request
|
||||
expect(JSON.parse(response.body)['key']).to eq data[:key]
|
||||
expect(JSON.parse(response.body)['category']).to eq data[:category]
|
||||
expect(JSON.parse(response.body)['description']).to eq data[:description]
|
||||
expect(JSON.parse(response.body)['liabilities'].size).to eq operations.size
|
||||
expect(JSON.parse(response.body)['revenues'].size).to eq operations.size
|
||||
end
|
||||
|
||||
it 'saves liabilities' do
|
||||
expect { request }.to change(::Operations::Liability, :count).by(operations.size)
|
||||
end
|
||||
|
||||
it 'saves revenues' do
|
||||
expect { request }.to change(::Operations::Revenue, :count).by(operations.size)
|
||||
end
|
||||
|
||||
it 'updates legacy balances' do
|
||||
expect { request }.to change{ referrer1.get_account(base_unit).balance }.by(0.0001 + 0.0003).and \
|
||||
change{ referrer2.get_account(base_unit).balance }.by(0.00015).and \
|
||||
change{ referrer2.get_account(quote_unit).balance }.by(0.05).and \
|
||||
change{ referrer3.get_account(quote_unit).balance }.by(0.075)
|
||||
end
|
||||
|
||||
context 'wrong account code' do
|
||||
let(:operations) do
|
||||
[
|
||||
{
|
||||
currency: base_unit,
|
||||
amount: '0.0001',
|
||||
account_src: {
|
||||
code: fiat_revenues_code # Wrong code because base_unit is coin.
|
||||
},
|
||||
account_dst: {
|
||||
code: coin_liabilities_code,
|
||||
uid: referrer2.uid
|
||||
}
|
||||
},
|
||||
{
|
||||
currency: quote_unit,
|
||||
amount: '0.05',
|
||||
account_src: {
|
||||
code: fiat_revenues_code
|
||||
},
|
||||
account_dst: {
|
||||
code: coin_liabilities_code, # Wrong code because quote_unit is fiat.
|
||||
uid: referrer2.uid
|
||||
}
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
it do
|
||||
request
|
||||
expect(response).to have_http_status 422
|
||||
end
|
||||
|
||||
it 'doesn\'t save transfer' do
|
||||
expect { request }.to_not change(Transfer, :count)
|
||||
end
|
||||
|
||||
it 'doesn\'t save liabilities' do
|
||||
expect { request }.to_not change(::Operations::Liability, :count)
|
||||
end
|
||||
|
||||
it 'doesn\'t save revenues' do
|
||||
expect { request }.to_not change(::Operations::Revenue, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'token distribution story' do
|
||||
# In token distribution story we credit member token balance
|
||||
# once member is signed in for the first time.
|
||||
before do
|
||||
# Add token-distribution Liabilities account.
|
||||
Rails.configuration.x.chart_of_accounts << coin_distribution_account
|
||||
create(:operations_account, coin_distribution_account)
|
||||
end
|
||||
|
||||
before do
|
||||
# 1. Credit main Assets account.
|
||||
# 2. Credit token-distribution Liabilities account.
|
||||
# So we keep Balance Sheet equal Income Statement.
|
||||
create(:asset, currency_id: coin,
|
||||
code: coin_assets_code, credit: 1000)
|
||||
create(:liability, currency_id: coin,
|
||||
code: coin_distribution_account_code, credit: 1000, member_id: nil)
|
||||
end
|
||||
|
||||
let(:member1) { create(:member, :barong) }
|
||||
let(:member2) { create(:member, :barong) }
|
||||
|
||||
let(:coin) { :trst }
|
||||
|
||||
let(:coin_assets_code) { 102 }
|
||||
|
||||
let(:coin_liabilities_code) { 202 }
|
||||
let(:coin_distribution_account_code) do
|
||||
coin_distribution_account[:code]
|
||||
end
|
||||
let(:coin_distribution_account) do
|
||||
{ code: 292,
|
||||
type: :liability,
|
||||
kind: 'token-distribution',
|
||||
currency_type: :coin,
|
||||
description: 'Token Distributions Liabilities Account',
|
||||
scope: :platform
|
||||
}
|
||||
end
|
||||
|
||||
# Balance changes:
|
||||
# Liability-main:
|
||||
# member1:
|
||||
# coin: 10
|
||||
# member2:
|
||||
# coin: 5
|
||||
# Liability-token-distribution:
|
||||
# coin: -15
|
||||
let(:operations) do
|
||||
[
|
||||
{
|
||||
currency: coin,
|
||||
amount: 10,
|
||||
account_src: {
|
||||
code: coin_distribution_account_code
|
||||
},
|
||||
account_dst: {
|
||||
code: coin_liabilities_code,
|
||||
uid: member1.uid
|
||||
}
|
||||
},
|
||||
{
|
||||
currency: coin,
|
||||
amount: 5,
|
||||
account_src: {
|
||||
code: coin_distribution_account_code
|
||||
},
|
||||
account_dst: {
|
||||
code: coin_liabilities_code,
|
||||
uid: member2.uid
|
||||
}
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
it do
|
||||
request
|
||||
expect(response).to have_http_status 201
|
||||
end
|
||||
|
||||
it 'returns transfer with liabilities' do
|
||||
request
|
||||
expect(JSON.parse(response.body)['key']).to eq data[:key]
|
||||
expect(JSON.parse(response.body)['category']).to eq data[:category]
|
||||
expect(JSON.parse(response.body)['description']).to eq data[:description]
|
||||
# Two liability operation for each token-distribution operation.
|
||||
expect(JSON.parse(response.body)['liabilities'].size).to eq operations.size * 2
|
||||
end
|
||||
|
||||
it 'saves liabilities' do
|
||||
expect { request }.to change(::Operations::Liability, :count).by(operations.size * 2)
|
||||
end
|
||||
|
||||
it 'updates legacy balance' do
|
||||
expect { request }.to change{ member1.get_account(coin).balance }.by(10).and \
|
||||
change{ member2.get_account(coin).balance }.by(5)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
468
spec/api/v2/management/withdraws_spec.rb
Normal file
468
spec/api/v2/management/withdraws_spec.rb
Normal file
@@ -0,0 +1,468 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Management::Withdraws, type: :request do
|
||||
before do
|
||||
defaults_for_management_api_v1_security_configuration!
|
||||
management_api_v1_security_configuration.merge! \
|
||||
scopes: {
|
||||
read_withdraws: { permitted_signers: %i[alex jeff], mandatory_signers: %i[alex] },
|
||||
write_withdraws: { permitted_signers: %i[alex jeff james], mandatory_signers: %i[alex jeff] }
|
||||
}
|
||||
end
|
||||
|
||||
describe 'list withdraws' do
|
||||
def request
|
||||
post_json '/api/v2/management/withdraws', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:data) { {} }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:members) { create_list(:member, 2, :barong) }
|
||||
|
||||
before do
|
||||
Withdraw::STATES.tap do |states|
|
||||
(states.count * 2).times do
|
||||
create(:btc_withdraw, :with_deposit_liability, sum: 1, member: members.sample, aasm_state: states.sample, rid: Faker::Blockchain::Bitcoin.address)
|
||||
create(:usd_withdraw, :with_deposit_liability, sum: 1, member: members.sample, aasm_state: states.sample, rid: Faker::Bank.iban)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns withdraws' do
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).map { |x| x.fetch('tid') }).to eq Withdraw.order(id: :desc).pluck(:tid)
|
||||
end
|
||||
|
||||
it 'filters by member' do
|
||||
member = members.last
|
||||
data.merge!(uid: member.uid)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).count).to eq member.withdraws.count
|
||||
end
|
||||
|
||||
it 'filters by currency' do
|
||||
data.merge!(currency: :usd)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).count).to eq Withdraw.where(currency_id: :usd).count
|
||||
end
|
||||
|
||||
it 'filters by state' do
|
||||
Withdraw::STATES.each do |state|
|
||||
data.merge!(state: state)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).count).to eq Withdraw.where(aasm_state: state).count
|
||||
end
|
||||
end
|
||||
|
||||
it 'paginates' do
|
||||
ids = Withdraw.order(id: :desc).pluck(:tid)
|
||||
data.merge!(page: 1, limit: 4)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).map { |x| x.fetch('tid') }).to eq ids[0...4]
|
||||
data.merge!(page: 3, limit: 4)
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
expect(JSON.parse(response.body).map { |x| x.fetch('tid') }).to eq ids[8...12]
|
||||
end
|
||||
end
|
||||
|
||||
describe 'create withdraw' do
|
||||
def request
|
||||
post_json '/api/v2/management/withdraws/new', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:member) { create(:member, :barong) }
|
||||
let(:currency) { Currency.find(:btc) }
|
||||
let(:amount) { 0.1575 }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let :data do
|
||||
{ uid: member.uid,
|
||||
currency: currency.code,
|
||||
amount: amount.to_s,
|
||||
rid: Faker::Blockchain::Bitcoin.address }
|
||||
end
|
||||
let(:account) { member.get_account(currency) }
|
||||
let(:balance) { 1.2 }
|
||||
before { account.plus_funds(balance) }
|
||||
|
||||
context 'crypto withdraw' do
|
||||
it 'creates new withdraw and immediately submits it' do
|
||||
request
|
||||
expect(response).to have_http_status(201)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.sum).to eq 0.1575
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(record.rid).to eq data[:rid]
|
||||
expect(record.account).to eq account
|
||||
expect(record.account.balance).to eq (1.2 - amount)
|
||||
expect(record.account.locked).to eq amount
|
||||
expect(response_body['transfer_type']).to eq 'crypto'
|
||||
end
|
||||
|
||||
context 'disabled currency' do
|
||||
before do
|
||||
currency.update(withdrawal_enabled: false)
|
||||
end
|
||||
|
||||
it 'returns error for disabled withdrawal' do
|
||||
request
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('management.currency.withdrawal_disabled')
|
||||
end
|
||||
end
|
||||
|
||||
context 'withdrawal with beneficiary' do
|
||||
let(:beneficiary) { create(:beneficiary, state: :active, currency: currency) }
|
||||
let(:data) do
|
||||
{ uid: member.uid,
|
||||
currency: currency.code,
|
||||
amount: amount.to_s,
|
||||
beneficiary_id: beneficiary.id }
|
||||
end
|
||||
|
||||
it 'creates new withdraw and immediately submits it' do
|
||||
request
|
||||
expect(response).to have_http_status(201)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.sum).to eq 0.1575
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(record.rid).to eq beneficiary.rid
|
||||
expect(record.account).to eq account
|
||||
expect(record.account.balance).to eq (1.2 - amount)
|
||||
expect(record.account.locked).to eq amount
|
||||
end
|
||||
|
||||
context 'pending beneficiary' do
|
||||
before do
|
||||
beneficiary.update(state: :pending)
|
||||
end
|
||||
|
||||
it 'returns error for pending beneficiary' do
|
||||
request
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('management.beneficiary.invalid_state_for_withdrawal')
|
||||
end
|
||||
end
|
||||
|
||||
context 'withdrawal with note' do
|
||||
let(:member) { create(:member, :barong) }
|
||||
let(:currency) { Currency.find(:btc) }
|
||||
let(:amount) { 0.1575 }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let :data do
|
||||
{ uid: member.uid,
|
||||
currency: currency.code,
|
||||
amount: amount.to_s,
|
||||
rid: Faker::Blockchain::Bitcoin.address,
|
||||
note: 'Withdraw money'
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns new withdraw with correct note' do
|
||||
request
|
||||
expect(JSON.parse(response.body)['note']).to eq 'Withdraw money'
|
||||
end
|
||||
end
|
||||
|
||||
context 'withdrawal with transfer_type' do
|
||||
let(:member) { create(:member, :barong) }
|
||||
let(:currency) { Currency.find(:btc) }
|
||||
let(:amount) { 0.1575 }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let :data do
|
||||
{ uid: member.uid,
|
||||
currency: currency.code,
|
||||
amount: amount.to_s,
|
||||
rid: Faker::Blockchain::Bitcoin.address,
|
||||
transfer_type: 'card'
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns new withdraw with correct transfer_type' do
|
||||
request
|
||||
expect(JSON.parse(response.body)['transfer_type']).to eq 'card'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'action: :process' do
|
||||
it 'creates new withdraw and immediately submits it' do
|
||||
data.merge!(action: 'process')
|
||||
request
|
||||
expect(response).to have_http_status(201)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.sum).to eq 0.1575
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(record.rid).to eq data[:rid]
|
||||
expect(record.account).to eq account
|
||||
expect(record.account.balance).to eq (1.2 - amount)
|
||||
expect(record.account.locked).to eq amount
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid withdraw' do
|
||||
before do
|
||||
data[:action] = :process
|
||||
data[:amount] = '1000'
|
||||
end
|
||||
it 'validates enough balance' do
|
||||
request
|
||||
expect(response).to have_http_status(422)
|
||||
expect(JSON.parse(response.body)).to eq("errors"=>["Account balance is insufficient"])
|
||||
end
|
||||
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
|
||||
currency.update!(precision: 16)
|
||||
data.merge!(amount: '0.0000000123456789')
|
||||
request
|
||||
expect(response).to have_http_status(201)
|
||||
expect(Withdraw.last.sum.to_s).to eq data[:amount]
|
||||
end
|
||||
end
|
||||
|
||||
context 'fiat withdraw' do
|
||||
let(:currency) { Currency.find(:usd) }
|
||||
let(:amount) { 5 }
|
||||
let(:balance) { 20 }
|
||||
|
||||
it 'creates new withdraw with state set to «submitted»' do
|
||||
request
|
||||
expect(response).to have_http_status(201)
|
||||
expect(account.reload.balance).to eq(15)
|
||||
expect(account.reload.locked).to eq 5
|
||||
expect(Withdraw.last.aasm_state).to eq 'accepted'
|
||||
end
|
||||
|
||||
context 'action: :process' do
|
||||
it 'creates new withdraw with state set to «submitted»' do
|
||||
data.merge!(action: :process)
|
||||
request
|
||||
expect(response).to have_http_status(201)
|
||||
expect(account.reload.balance).to eq(15)
|
||||
expect(account.reload.locked).to eq 0
|
||||
expect(Withdraw.last.aasm_state).to eq 'succeed'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'get withdraw' do
|
||||
def request
|
||||
post_json '/api/v2/management/withdraws/get', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:data) { { tid: record.tid } }
|
||||
let(:record) { create(:btc_withdraw, :with_deposit_liability, member: member) }
|
||||
let(:member) { create(:member, :barong) }
|
||||
|
||||
it 'returns withdraw by TID' do
|
||||
request
|
||||
expect(JSON.parse(response.body).fetch('tid')).to eq record.tid
|
||||
end
|
||||
end
|
||||
|
||||
describe 'update withdraw' do
|
||||
def request
|
||||
put_json '/api/v2/management/withdraws/action', multisig_jwt_management_api_v1({ data: data }, *signers)
|
||||
end
|
||||
|
||||
let(:currency) { Currency.find(:usd) }
|
||||
let(:member) { create(:member, :barong) }
|
||||
let(:amount) { 160.79 }
|
||||
let(:signers) { %i[alex jeff] }
|
||||
let(:data) { { tid: record.tid } }
|
||||
let(:account) { member.get_account(currency) }
|
||||
let(:record) { "Withdraws::#{currency.type.camelize}".constantize.create!(member: member, sum: amount, rid: Faker::Bank.iban, currency: currency) }
|
||||
let(:balance) { 800.77 }
|
||||
before { account.plus_funds(balance) }
|
||||
|
||||
context 'crypto withdraws' do
|
||||
let(:currency) { Currency.find(:btc) }
|
||||
|
||||
context 'action: :process' do
|
||||
before { data[:action] = :process }
|
||||
|
||||
it 'processes prepared withdraws' do
|
||||
expect(record.aasm_state).to eq 'prepared'
|
||||
expect(account.reload.balance).to eq balance
|
||||
expect(account.reload.locked).to eq 0
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(record.account.balance).to eq (balance - amount)
|
||||
expect(record.account.locked).to eq amount
|
||||
end
|
||||
|
||||
it 'processes submitted withdraws' do
|
||||
record.accept!
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(account.reload.balance).to eq (balance - amount)
|
||||
expect(account.reload.locked).to eq amount
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(record.account.balance).to eq (balance - amount)
|
||||
expect(record.account.locked).to eq amount
|
||||
end
|
||||
|
||||
it 'processes accepted withdraws' do
|
||||
record.accept!
|
||||
record.accept!
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(account.reload.balance).to eq (balance - amount)
|
||||
expect(account.reload.locked).to eq amount
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(record.account.balance).to eq (balance - amount)
|
||||
expect(record.account.locked).to eq amount
|
||||
end
|
||||
end
|
||||
|
||||
context 'action: :cancel' do
|
||||
before { data[:action] = :cancel }
|
||||
|
||||
it 'cancels prepared withdraws' do
|
||||
expect(record.aasm_state).to eq 'prepared'
|
||||
expect(account.reload.balance).to eq balance
|
||||
expect(account.reload.locked).to eq 0
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'canceled'
|
||||
expect(record.account.balance).to eq balance
|
||||
expect(record.account.locked).to eq 0
|
||||
end
|
||||
|
||||
it 'cancels submitted withdraws' do
|
||||
record.accept!
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(account.reload.balance).to eq (balance - amount)
|
||||
expect(account.reload.locked).to eq amount
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'canceled'
|
||||
expect(record.account.balance).to eq balance
|
||||
expect(record.account.locked).to eq 0
|
||||
end
|
||||
|
||||
it 'cancels accepted withdraws' do
|
||||
record.accept!
|
||||
record.accept!
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(account.reload.balance).to eq (balance - amount)
|
||||
expect(account.reload.locked).to eq amount
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'canceled'
|
||||
expect(record.account.balance).to eq balance
|
||||
expect(record.account.locked).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'fiat withdraws' do
|
||||
context 'action: :process' do
|
||||
before { data[:action] = :process }
|
||||
|
||||
it 'processes prepared withdraws' do
|
||||
expect(record.aasm_state).to eq 'prepared'
|
||||
expect(account.reload.balance).to eq balance
|
||||
expect(account.reload.locked).to eq 0
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'succeed'
|
||||
expect(record.account.balance).to eq (balance - amount)
|
||||
expect(record.account.locked).to eq 0
|
||||
end
|
||||
|
||||
it 'processes accepted withdraws' do
|
||||
record.accept!
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(account.reload.balance).to eq (balance - amount)
|
||||
expect(account.reload.locked).to eq amount
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'succeed'
|
||||
expect(record.account.balance).to eq (balance - amount)
|
||||
expect(record.account.locked).to eq 0
|
||||
end
|
||||
|
||||
it 'processes accepted withdraws' do
|
||||
record.accept!
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(account.reload.balance).to eq (balance - amount)
|
||||
expect(account.reload.locked).to eq amount
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'succeed'
|
||||
expect(record.account.balance).to eq (balance - amount)
|
||||
expect(record.account.locked).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'action: :cancel' do
|
||||
before { data[:action] = :cancel }
|
||||
|
||||
it 'cancels prepared withdraws' do
|
||||
expect(record.aasm_state).to eq 'prepared'
|
||||
expect(account.reload.balance).to eq balance
|
||||
expect(account.reload.locked).to eq 0
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'canceled'
|
||||
expect(record.account.balance).to eq balance
|
||||
expect(record.account.locked).to eq 0
|
||||
end
|
||||
|
||||
it 'cancels accepted withdraws' do
|
||||
record.accept!
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(account.reload.balance).to eq (balance - amount)
|
||||
expect(account.reload.locked).to eq amount
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'canceled'
|
||||
expect(record.account.balance).to eq balance
|
||||
expect(record.account.locked).to eq 0
|
||||
end
|
||||
|
||||
it 'cancels accepted withdraws' do
|
||||
record.accept!
|
||||
record.accept!
|
||||
expect(record.aasm_state).to eq 'accepted'
|
||||
expect(account.reload.balance).to eq (balance - amount)
|
||||
expect(account.reload.locked).to eq amount
|
||||
request
|
||||
expect(response).to have_http_status(200)
|
||||
record = Withdraw.find_by_tid!(JSON.parse(response.body).fetch('tid'))
|
||||
expect(record.aasm_state).to eq 'canceled'
|
||||
expect(record.account.balance).to eq balance
|
||||
expect(record.account.locked).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
634
spec/api/v2/market/orders_spec.rb
Normal file
634
spec/api/v2/market/orders_spec.rb
Normal file
@@ -0,0 +1,634 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Market::Orders, type: :request do
|
||||
let(:member) { create(:member, :level_3) }
|
||||
let(:level_0_member) { create(:member, :level_0) }
|
||||
let(:token) { jwt_for(member) }
|
||||
let(:level_0_member_token) { jwt_for(level_0_member) }
|
||||
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns({'member'=>{'read'=>['Order'],'create'=>['Order'],'update'=>['Order']}})
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/market/orders' do
|
||||
before do
|
||||
# NOTE: We specify updated_at attribute for testing order of Order.
|
||||
create(:order_bid, :btcusd, price: '11'.to_d, volume: '123.12345678', member: member, created_at: 1.day.ago, updated_at: Time.now + 5)
|
||||
create(:order_bid, :btceth, price: '11'.to_d, volume: '123.1234', member: member)
|
||||
create(:order_bid, :btcusd, price: '12'.to_d, volume: '123.12345678', created_at: 1.day.ago, member: member, state: Order::CANCEL)
|
||||
create(:order_ask, :btcusd, price: '13'.to_d, volume: '123.12345678', created_at: 2.hours.ago, member: member, state: Order::WAIT, updated_at: Time.now + 10)
|
||||
create(:order_ask, :btcusd, price: '14'.to_d, volume: '123.12345678', created_at: 6.hours.ago, member: member, state: Order::DONE)
|
||||
end
|
||||
|
||||
it 'requires authentication' do
|
||||
get '/api/v2/market/orders', params: { market: 'btcusd' }
|
||||
expect(response.code).to eq '401'
|
||||
end
|
||||
|
||||
it 'validates market param' do
|
||||
api_get '/api/v2/market/orders', params: { market: 'usdusd' }, token: token
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('market.market.doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validates state param' do
|
||||
api_get '/api/v2/market/orders', params: { market: 'btcusd', state: 'test' }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.invalid_state')
|
||||
end
|
||||
|
||||
it 'validates limit param' do
|
||||
api_get '/api/v2/market/orders', params: { market: 'btcusd', limit: -1 }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.invalid_limit')
|
||||
end
|
||||
|
||||
it 'validates ord_type param' do
|
||||
api_get '/api/v2/market/orders', params: { ord_type: 'test' }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.invalid_ord_type')
|
||||
end
|
||||
|
||||
it 'validates type param' do
|
||||
api_get '/api/v2/market/orders', params: { type: 'test' }, token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.invalid_type')
|
||||
end
|
||||
|
||||
it 'returns all order history' do
|
||||
api_get '/api/v2/market/orders', token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 5
|
||||
end
|
||||
|
||||
it 'returns all my orders for btcusd market' do
|
||||
api_get '/api/v2/market/orders', params: { market: 'btcusd' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 4
|
||||
end
|
||||
|
||||
it 'returns orders for several markets' do
|
||||
api_get '/api/v2/market/orders', params: { market: ['btcusd', 'btceth'] }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 5
|
||||
end
|
||||
|
||||
it 'returns orders with state done' do
|
||||
api_get '/api/v2/market/orders', params: { market: 'btcusd', state: Order::DONE }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['state']).to eq Order::DONE
|
||||
end
|
||||
|
||||
it 'returns orders with state done and wait' do
|
||||
api_get '/api/v2/market/orders', params: { market: 'btcusd', state: [Order::DONE, Order::WAIT] }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
count = member.orders.where(state: [Order::DONE, Order::WAIT], market_id: 'btcusd').count
|
||||
expect(result.size).to eq count
|
||||
end
|
||||
|
||||
it 'returns paginated orders' do
|
||||
api_get '/api/v2/market/orders', params: { market: 'btcusd', limit: 1, page: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.first['price']).to eq '13.0'
|
||||
|
||||
api_get '/api/v2/market/orders', params: { market: 'btcusd', limit: 1, page: 2 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.first['price']).to eq '11.0'
|
||||
end
|
||||
|
||||
it 'returns sorted orders' do
|
||||
api_get '/api/v2/market/orders', params: { market: 'btcusd', order_by: 'asc' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
first_order_updated_at = Time.iso8601(result.first['updated_at'])
|
||||
second_order_updated_at = Time.iso8601(result.second['updated_at'])
|
||||
expect(first_order_updated_at).to be <= second_order_updated_at
|
||||
|
||||
api_get '/api/v2/market/orders', params: { market: 'btcusd', order_by: 'desc' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
first_order_updated_at = Time.iso8601(result.first['updated_at'])
|
||||
second_order_updated_at = Time.iso8601(result.second['updated_at'])
|
||||
expect(first_order_updated_at).to be >= second_order_updated_at
|
||||
end
|
||||
|
||||
it 'returns orders with ord_type limit' do
|
||||
api_get '/api/v2/market/orders', params: { ord_type: 'limit' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['ord_type']}.uniq.size).to eq 1
|
||||
expect(result.map{|r| r['ord_type']}.uniq.first).to eq 'limit'
|
||||
end
|
||||
|
||||
it 'returns orders with type sell' do
|
||||
api_get '/api/v2/market/orders', params: { type: 'sell' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.map{|r| r['side']}.uniq.size).to eq 1
|
||||
expect(result.map{|r| r['side']}.uniq.first).to eq 'sell'
|
||||
end
|
||||
|
||||
it 'returns orders with base unit btc' do
|
||||
api_get '/api/v2/market/orders', params: { base_unit: 'btc' }, token: token
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 5
|
||||
end
|
||||
|
||||
it 'returns orders with quote unit eth' do
|
||||
api_get '/api/v2/market/orders', params: { base_unit: 'btc' }, token: token
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 5
|
||||
end
|
||||
|
||||
it 'returns orders with timestamp filter' do
|
||||
api_get '/api/v2/market/orders', params: { time_from: 7.hours.ago.to_i, time_to: 5.hours.ago.to_i }, token: token
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 1
|
||||
end
|
||||
|
||||
it 'returns orders with timestamp filter' do
|
||||
api_get '/api/v2/market/orders', params: { time_from: 3.hours.ago.to_i }, token: token
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 2
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get '/api/v2/market/orders', params: { time_from: 3.hours.ago.to_i }, token: token
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
it 'denies access to unverified member' do
|
||||
api_get '/api/v2/market/orders', token: level_0_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('market.trade.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/market/orders/:id' do
|
||||
let(:order) { create(:order_bid, :btcusd, price: '12.32'.to_d, volume: '3.14', origin_volume: '12.13', member: member, trades_count: 1) }
|
||||
let!(:trade) { create(:trade, :btcusd, taker_order: order) }
|
||||
|
||||
it 'should get specified order by id' do
|
||||
api_get "/api/v2/market/orders/#{order.id}", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['id']).to eq order.id
|
||||
expect(result['executed_volume']).to eq '8.99'
|
||||
end
|
||||
|
||||
it 'should get specified order by uuid' do
|
||||
api_get "/api/v2/market/orders/#{order.uuid}", token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['uuid']).to eq order.uuid
|
||||
expect(result['executed_volume']).to eq '8.99'
|
||||
end
|
||||
|
||||
it 'should include related trades' do
|
||||
api_get "/api/v2/market/orders/#{order.id}", token: token
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['trades_count']).to eq 1
|
||||
expect(result['trades'].size).to eq 1
|
||||
expect(result['trades'].first['id']).to eq trade.id
|
||||
expect(result['trades'].first['side']).to eq 'buy'
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get "/api/v2/market/orders/#{order.id}", token: token
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
it 'should get 404 error when order doesn\'t exist' do
|
||||
api_get '/api/v2/market/orders/1234', token: token
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'should raise error' do
|
||||
api_get '/api/v2/market/orders/1234asd', token: token
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.invaild_id_or_uuid')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/market/orders' do
|
||||
it 'creates a sell order on peatio engine' do
|
||||
member.get_account(:btc).update_attributes(balance: 100)
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: '12.13', price: '2014' }
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['id']).to eq OrderAsk.last.id
|
||||
end.to change(OrderAsk, :count).by(1)
|
||||
end
|
||||
|
||||
it 'submit a sell order on third party engine' do
|
||||
member.get_account(:btc).update_attributes(balance: 100)
|
||||
Market.find('btcusd').engine.update(driver: "finex-spot")
|
||||
AMQP::Queue.expects(:publish)
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: '12.13', price: '2014' }
|
||||
expect(response).to be_successful
|
||||
expect(response_body['market']).to eq 'btcusd'
|
||||
end.not_to change(OrderAsk, :count)
|
||||
end
|
||||
|
||||
it 'creates a buy order' do
|
||||
member.get_account(:usd).update_attributes(balance: 100_000)
|
||||
AMQP::Queue.expects(:enqueue).with(:order_processor, is_a(Hash), is_a(Hash))
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'buy', volume: '12.13', price: '2014' }
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['id']).to eq OrderBid.last.id
|
||||
end.to change(OrderBid, :count).by(1)
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'buy', volume: '12.13', price: '2014' }
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
|
||||
it 'validates missing params' do
|
||||
member.get_account(:usd).update_attributes(balance: 100_000)
|
||||
api_post '/api/v2/market/orders', token: token
|
||||
expect(response).to have_http_status(422)
|
||||
expect(response).to include_api_error('market.order.missing_market')
|
||||
expect(response).to include_api_error('market.order.missing_side')
|
||||
expect(response).to include_api_error('market.order.missing_volume')
|
||||
expect(response).to include_api_error('market.order.missing_price')
|
||||
end
|
||||
|
||||
it 'validates volume positiveness' do
|
||||
old_count = OrderAsk.count
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: '-1.1', price: '2014' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.non_positive_volume')
|
||||
expect(OrderAsk.count).to eq old_count
|
||||
end
|
||||
|
||||
it 'validates volume to be a number' do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: 'test', price: '2014' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.non_decimal_volume')
|
||||
end
|
||||
|
||||
it 'validates volume greater than min_amount' do
|
||||
member.get_account(:btc).update_attributes(balance: 1)
|
||||
m = Market.find(:btcusd)
|
||||
m.update(min_amount: 1.0)
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: '0.1', price: '2014' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.invalid_volume_or_price')
|
||||
end
|
||||
|
||||
it 'validates price less than max_price' do
|
||||
member.get_account(:usd).update_attributes(balance: 1)
|
||||
m = Market.find(:btcusd)
|
||||
m.update(max_price: 1.0)
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'buy', volume: '0.1', price: '2' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.invalid_volume_or_price')
|
||||
end
|
||||
|
||||
it 'validates volume precision' do
|
||||
member.get_account(:usd).update_attributes(balance: 1)
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'buy', volume: '0.123456789', price: '0.1' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.invalid_volume_or_price')
|
||||
end
|
||||
|
||||
it 'validates price greater than min_price' do
|
||||
member.get_account(:usd).update_attributes(balance: 1)
|
||||
m = Market.find(:btcusd)
|
||||
m.update(min_price: 1.0)
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'buy', volume: '0.1', price: '0.2' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.invalid_volume_or_price')
|
||||
end
|
||||
|
||||
it 'validates price precision' do
|
||||
member.get_account(:usd).update_attributes(balance: 1)
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'buy', volume: '0.12', price: '0.123' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.invalid_volume_or_price')
|
||||
end
|
||||
|
||||
it 'validates enough funds' do
|
||||
old_count = OrderAsk.count
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: '12.13', price: '2014' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.account.insufficient_balance')
|
||||
expect(OrderAsk.count).to eq old_count
|
||||
end
|
||||
|
||||
it 'validates price positiveness' do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: '12.13', price: '-1.1' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.non_positive_price')
|
||||
end
|
||||
|
||||
it 'validates price to be a number' do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: '12.13', price: 'test' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.non_decimal_price')
|
||||
end
|
||||
|
||||
context 'market order' do
|
||||
it 'validates that market has sufficient volume' do
|
||||
member.get_account(:btc).update_attributes(balance: 20)
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: '12.13', ord_type: 'market' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.insufficient_market_liquidity')
|
||||
end
|
||||
|
||||
it 'validates that order has no price param' do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: '0.5', price: '0.5', ord_type: 'market' }
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.order.market_order_price')
|
||||
end
|
||||
|
||||
it 'creates sell order' do
|
||||
create(:order_bid, :btcusd, price: '10'.to_d, volume: '10', origin_volume: '10', member: member)
|
||||
|
||||
member.get_account(:btc).update_attributes(balance: 1)
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: '0.5', ord_type: 'market' }
|
||||
end.to change(OrderAsk, :count).by(1)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['id']).to eq OrderAsk.last.id
|
||||
end
|
||||
|
||||
context 'submit sell order on third party engine' do
|
||||
it do
|
||||
create(:order_bid, :btcusd, price: '10'.to_d, volume: '10', origin_volume: '10', member: member)
|
||||
|
||||
member.get_account(:btc).update_attributes(balance: 1)
|
||||
|
||||
Market.find('btcusd').engine.update(driver: "finex-spot")
|
||||
|
||||
AMQP::Queue.expects(:publish)
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'sell', volume: '0.5', ord_type: 'market' }
|
||||
end.not_to change(OrderAsk, :count)
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates buy order' do
|
||||
create(:order_ask, :btcusd, price: '10'.to_d, volume: '10', origin_volume: '10', member: member)
|
||||
|
||||
member.get_account(:usd).update_attributes(balance: 10)
|
||||
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'buy', volume: '0.5', ord_type: 'market' }
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'buy', volume: '0.5', ord_type: 'market' }
|
||||
end.to change(OrderBid, :count).by(1)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['id']).to eq OrderBid.last.id
|
||||
end
|
||||
|
||||
context '#compute_locked' do
|
||||
before do
|
||||
create(:order_ask, :btcusd, price: '10'.to_d, volume: '10', origin_volume: '10', member: member)
|
||||
member.get_account(:usd).update_attributes(balance: 10)
|
||||
end
|
||||
|
||||
it 'locks all balance' do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'buy', volume: '1', ord_type: 'market' }
|
||||
|
||||
expect(Order.find(response_body['id']).locked).to eq member.get_account(:usd).balance
|
||||
end
|
||||
|
||||
it 'locks with locking_buffer' do
|
||||
api_post '/api/v2/market/orders', token: token, params: { market: 'btcusd', side: 'buy', volume: '0.5', ord_type: 'market' }
|
||||
|
||||
# Price: 10, volume: 0.5, locking_buffer: 1.1
|
||||
expect(Order.find(response_body['id']).locked).to eq 5.5
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/market/orders/:id/cancel' do
|
||||
let!(:order) { create(:order_bid, :btcusd, price: '12.32'.to_d, volume: '3.14', origin_volume: '12.13', locked: '20.1082', origin_locked: '38.0882', member: member) }
|
||||
|
||||
context 'succesful' do
|
||||
before do
|
||||
member.get_account(:usd).update_attributes(locked: order.price * order.volume)
|
||||
end
|
||||
|
||||
it 'should cancel specified order by id' do
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: order.to_matching_attributes)
|
||||
|
||||
expect do
|
||||
api_post "/api/v2/market/orders/#{order.id}/cancel", token: token
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['id']).to eq order.id
|
||||
end.not_to change(Order, :count)
|
||||
end
|
||||
|
||||
it 'should cancel specified order by uuid' do
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: order.to_matching_attributes)
|
||||
|
||||
expect do
|
||||
api_post "/api/v2/market/orders/#{order.uuid}/cancel", token: token
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['uuid']).to eq order.uuid
|
||||
end.not_to change(Order, :count)
|
||||
end
|
||||
end
|
||||
|
||||
context 'third party order' do
|
||||
before do
|
||||
order.market.engine.update(driver: "finex-spot")
|
||||
end
|
||||
|
||||
it 'should cancel specified order by uuid' do
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: order.to_matching_attributes).never
|
||||
AMQP::Queue.expects(:publish).with(order.market.engine.driver, data: order.as_json_for_third_party, type: 3)
|
||||
|
||||
expect do
|
||||
api_post "/api/v2/market/orders/#{order.uuid}/cancel", token: token
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['uuid']).to eq order.uuid
|
||||
end.not_to change(Order, :count)
|
||||
end
|
||||
end
|
||||
|
||||
context 'failed' do
|
||||
it 'should return order not found error' do
|
||||
api_post '/api/v2/market/orders/0/cancel', token: token
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_post "/api/v2/market/orders/#{order.uuid}/cancel", token: token
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/market/orders/cancel' do
|
||||
before do
|
||||
create(:order_ask, :btcusd, price: '12.32', volume: '3.14', origin_volume: '12.13', member: member)
|
||||
create(:order_bid, :btcusd, price: '12.32', volume: '3.14', origin_volume: '12.13', member: member)
|
||||
create(:order_bid, :btceth, price: '12.32', volume: '3.14', origin_volume: '12.13', member: member)
|
||||
|
||||
member.get_account(:btc).update_attributes(locked: '5')
|
||||
member.get_account(:usd).update_attributes(locked: '50')
|
||||
end
|
||||
|
||||
it 'should cancel all my orders' do
|
||||
member.orders.each do |o|
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: o.to_matching_attributes)
|
||||
|
||||
end
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/market/orders/cancel', token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq 3
|
||||
end.not_to change(Order, :count)
|
||||
end
|
||||
|
||||
context 'third party order' do
|
||||
|
||||
before do
|
||||
Market.find('btcusd').engine.update(driver: "finex-spot")
|
||||
Market.find('btceth').engine.update(driver: "finex-spot")
|
||||
end
|
||||
|
||||
it 'should cancel all my orders on market with third party engine' do
|
||||
AMQP::Queue.expects(:enqueue).never
|
||||
|
||||
member.orders.each do |o|
|
||||
AMQP::Queue.expects(:publish).with(o.market.engine.driver, data: o.as_json_for_third_party, type: 3)
|
||||
end
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/market/orders/cancel', token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq 3
|
||||
end.not_to change(Order, :count)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should cancel all my orders for specific market' do
|
||||
member.orders.where(market: 'btceth').each do |o|
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: o.to_matching_attributes)
|
||||
end
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/market/orders/cancel', token: token, params: { market: 'btceth' }
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq 1
|
||||
end.not_to change(Order, :count)
|
||||
end
|
||||
|
||||
it 'should cancel all my asks' do
|
||||
member.orders.where(type: 'OrderAsk').each do |o|
|
||||
AMQP::Queue.expects(:enqueue).with(:matching, action: 'cancel', order: o.to_matching_attributes)
|
||||
end
|
||||
|
||||
expect do
|
||||
api_post '/api/v2/market/orders/cancel', token: token, params: { side: 'sell' }
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq 1
|
||||
expect(result.first['id']).to eq member.orders.where(type: 'OrderAsk').first.id
|
||||
end.not_to change(Order, :count)
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_post '/api/v2/market/orders/cancel', token: token
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
288
spec/api/v2/market/trades_spec.rb
Normal file
288
spec/api/v2/market/trades_spec.rb
Normal file
@@ -0,0 +1,288 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Market::Trades, type: :request do
|
||||
let(:member) do
|
||||
create(:member, :level_3).tap do |m|
|
||||
m.get_account(:btc).update_attributes(balance: 12.13, locked: 3.14)
|
||||
m.get_account(:usd).update_attributes(balance: 2014.47, locked: 0)
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns({'member'=>{'read'=>['Trade']}})
|
||||
end
|
||||
|
||||
let(:token) { jwt_for(member) }
|
||||
|
||||
let(:level_0_member) { create(:member, :level_0) }
|
||||
let(:level_0_member_token) { jwt_for(level_0_member) }
|
||||
|
||||
let(:btcusd_ask) do
|
||||
create(
|
||||
:order_ask,
|
||||
:btcusd,
|
||||
price: '12.32'.to_d,
|
||||
volume: '123.12345678',
|
||||
member: member
|
||||
)
|
||||
end
|
||||
|
||||
let(:btceth_ask) do
|
||||
create(
|
||||
:order_ask,
|
||||
:btceth,
|
||||
price: '12.32'.to_d,
|
||||
volume: '123.1234',
|
||||
member: member
|
||||
)
|
||||
end
|
||||
|
||||
let(:btcusd_bid) do
|
||||
create(
|
||||
:order_bid,
|
||||
:btcusd,
|
||||
price: '12.32'.to_d,
|
||||
volume: '123.12345678',
|
||||
member: member
|
||||
)
|
||||
end
|
||||
|
||||
let(:btceth_bid) do
|
||||
create(
|
||||
:order_bid,
|
||||
:btceth,
|
||||
price: '12.32'.to_d,
|
||||
volume: '123.1234',
|
||||
member: member
|
||||
)
|
||||
end
|
||||
|
||||
let(:btcusd_bid_maker) do
|
||||
create(
|
||||
:order_bid,
|
||||
:btcusd,
|
||||
price: '12.32'.to_d,
|
||||
volume: '123.12345678',
|
||||
member: member
|
||||
)
|
||||
end
|
||||
|
||||
let(:btceth_ask_taker) do
|
||||
create(
|
||||
:order_ask,
|
||||
:btceth,
|
||||
price: '12.32'.to_d,
|
||||
volume: '123.1234',
|
||||
member: member
|
||||
)
|
||||
end
|
||||
|
||||
let(:btceth_bid_taker) do
|
||||
create(
|
||||
:order_bid,
|
||||
:btceth,
|
||||
price: '12.32'.to_d,
|
||||
volume: '123.1234',
|
||||
member: member
|
||||
)
|
||||
end
|
||||
|
||||
let!(:btcusd_ask_trade) { create(:trade, :btcusd, maker_order: btcusd_ask, created_at: 2.days.ago) }
|
||||
let!(:btceth_ask_trade) { create(:trade, :btceth, maker_order: btceth_ask, created_at: 2.days.ago) }
|
||||
let!(:btcusd_bid_trade) { create(:trade, :btcusd, taker_order: btcusd_bid, created_at: 23.hours.ago) }
|
||||
let!(:btceth_bid_trade) { create(:trade, :btceth, taker_order: btceth_bid, taker: member, created_at: 23.hours.ago) }
|
||||
|
||||
describe 'GET /api/v2/market/trades' do
|
||||
it 'requires authentication' do
|
||||
get '/api/v2/market/trades', params: { market: 'btcusd' }
|
||||
expect(response.code).to eq '401'
|
||||
expect(response).to include_api_error('jwt.decode_and_verify')
|
||||
end
|
||||
|
||||
it 'returns all my recent trades' do
|
||||
api_get '/api/v2/market/trades', token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 4
|
||||
|
||||
expect(result.find { |t| t['id'] == btcusd_ask_trade.id }['side']).to eq 'sell'
|
||||
expect(result.find { |t| t['id'] == btcusd_ask_trade.id }['order_id']).to eq btcusd_ask.id
|
||||
expect(result.find { |t| t['id'] == btceth_ask_trade.id }['side']).to eq 'sell'
|
||||
expect(result.find { |t| t['id'] == btceth_ask_trade.id }['order_id']).to eq btceth_ask.id
|
||||
expect(result.find { |t| t['id'] == btcusd_bid_trade.id }['side']).to eq 'buy'
|
||||
expect(result.find { |t| t['id'] == btcusd_bid_trade.id }['order_id']).to eq btcusd_bid.id
|
||||
expect(result.find { |t| t['id'] == btceth_bid_trade.id }['side']).to eq 'buy'
|
||||
expect(result.find { |t| t['id'] == btceth_bid_trade.id }['order_id']).to eq btceth_bid.id
|
||||
end
|
||||
|
||||
it 'returns all my recent trades for btcusd market' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btcusd' }, token: token
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 2
|
||||
expect(result.find { |t| t['id'] == btcusd_ask_trade.id }['side']).to eq 'sell'
|
||||
expect(result.find { |t| t['id'] == btcusd_ask_trade.id }['order_id']).to eq btcusd_ask.id
|
||||
expect(result.find { |t| t['id'] == btcusd_bid_trade.id }['side']).to eq 'buy'
|
||||
expect(result.find { |t| t['id'] == btcusd_bid_trade.id }['order_id']).to eq btcusd_bid.id
|
||||
end
|
||||
|
||||
it 'returns trades for several markets' do
|
||||
api_get '/api/v2/market/trades', params: { market: ['btcusd', 'btceth'] }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 4
|
||||
end
|
||||
|
||||
it 'returns 1 trade' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btcusd', limit: 1 }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 1
|
||||
end
|
||||
|
||||
it 'returns trades for last 24h' do
|
||||
create(:trade, :btcusd, maker: member, created_at: 6.hours.ago)
|
||||
api_get '/api/v2/market/trades', params: { time_from: 1.day.ago.to_i }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 3
|
||||
end
|
||||
|
||||
it 'returns trades older than 1 day' do
|
||||
api_get '/api/v2/market/trades', params: { time_to: 1.day.ago.to_i }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 2
|
||||
end
|
||||
|
||||
it 'returns trades for specific hour' do
|
||||
create(:trade, :btcusd, maker: member, created_at: 6.hours.ago)
|
||||
api_get '/api/v2/market/trades', params: { time_from: 7.hours.ago.to_i, time_to: 5.hours.ago.to_i }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(result.size).to eq 1
|
||||
end
|
||||
|
||||
it 'returns limit out of range error' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btcusd', limit: 1024 }, token: token
|
||||
|
||||
expect(response.code).to eq '422'
|
||||
expect(response).to include_api_error('market.trade.invalid_limit')
|
||||
end
|
||||
|
||||
it 'denies access to unverified member' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btcusd' }, token: level_0_member_token
|
||||
expect(response.code).to eq '403'
|
||||
expect(response).to include_api_error('market.trade.not_permitted')
|
||||
end
|
||||
|
||||
it 'fee calculation for buy order' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btcusd' }, token: token
|
||||
result = JSON.parse(response.body).find { |t| t['side'] == 'buy' }
|
||||
|
||||
expect(result['order_id']).to eq btcusd_bid.id
|
||||
expect(result['fee_amount']).to eq((btcusd_bid.taker_fee * btcusd_bid_trade.amount).to_s)
|
||||
expect(result['fee']).to eq btcusd_bid.taker_fee.to_s
|
||||
end
|
||||
|
||||
it 'fee calculation for sell order' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btcusd' }, token: token
|
||||
result = JSON.parse(response.body).find { |t| t['side'] == 'sell' }
|
||||
|
||||
expect(result['order_id']).to eq btcusd_ask.id
|
||||
expect(result['fee_amount']).to eq((btcusd_ask.taker_fee * btcusd_ask_trade.total).to_s)
|
||||
expect(result['fee']).to eq btcusd_ask.taker_fee.to_s
|
||||
end
|
||||
|
||||
it 'fee currency for buy order' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btcusd' }, token: token
|
||||
result = JSON.parse(response.body).find { |t| t['side'] == 'buy' }
|
||||
|
||||
expect(result['order_id']).to eq btcusd_bid.id
|
||||
expect(result['fee_currency']).to eq 'btc'
|
||||
end
|
||||
|
||||
it 'fee currency for sell order' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btcusd' }, token: token
|
||||
result = JSON.parse(response.body).find { |t| t['side'] == 'sell' }
|
||||
|
||||
expect(result['order_id']).to eq btcusd_ask.id
|
||||
expect(result['fee_currency']).to eq 'usd'
|
||||
end
|
||||
|
||||
context 'type filtering' do
|
||||
context 'sell orders' do
|
||||
let!(:btceth_ask_trade_taker) { create(:trade, :btceth, taker_order: btceth_ask_taker, created_at: 2.hours.ago) }
|
||||
let!(:btcusd_bid_trade_maker) { create(:trade, :btcusd, maker_order: btcusd_bid_maker, created_at: 2.hours.ago) }
|
||||
|
||||
it 'with taker_id = user_id and taker_type = sell' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btceth', type: 'sell' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 2
|
||||
expect(result.find { |t| t['id'] == btceth_ask_trade.id }['side']).to eq 'sell'
|
||||
expect(result.find { |t| t['id'] == btceth_ask_trade.id }['order_id']).to eq btceth_ask.id
|
||||
expect(result.find { |t| t['id'] == btceth_ask_trade_taker.id }['side']).to eq 'sell'
|
||||
expect(result.find { |t| t['id'] == btceth_ask_trade_taker.id }['order_id']).to eq btceth_ask_taker.id
|
||||
end
|
||||
|
||||
it 'with maker_id = user_id and taker_type = buy' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btcusd', type: 'sell' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 2
|
||||
expect(result.find { |t| t['id'] == btcusd_ask_trade.id }['side']).to eq 'sell'
|
||||
expect(result.find { |t| t['id'] == btcusd_ask_trade.id }['order_id']).to eq btcusd_ask.id
|
||||
expect(result.find { |t| t['id'] == btcusd_bid_trade_maker.id }['side']).to eq 'buy'
|
||||
expect(result.find { |t| t['id'] == btcusd_bid_trade_maker.id }['order_id']).to eq btcusd_bid_maker.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'buy orders' do
|
||||
let!(:btceth_bid_trade_taker) { create(:trade, :btceth, taker_order: btceth_bid_taker, created_at: 2.hours.ago) }
|
||||
|
||||
it 'with taker_id = user_id and taker_type = buy' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btceth', type: 'buy' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 2
|
||||
expect(result.find { |t| t['id'] == btceth_bid_trade.id }['side']).to eq 'buy'
|
||||
expect(result.find { |t| t['id'] == btceth_bid_trade.id }['order_id']).to eq btceth_bid.id
|
||||
expect(result.find { |t| t['id'] == btceth_bid_trade_taker.id }['side']).to eq 'buy'
|
||||
expect(result.find { |t| t['id'] == btceth_bid_trade_taker.id }['order_id']).to eq btceth_bid_taker.id
|
||||
end
|
||||
|
||||
it 'with maker_id = user_id and taker_type = sell' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btcusd', type: 'buy' }, token: token
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq 1
|
||||
expect(result.find { |t| t['id'] == btcusd_bid_trade.id }['side']).to eq 'buy'
|
||||
expect(result.find { |t| t['id'] == btcusd_bid_trade.id }['order_id']).to eq btcusd_bid.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized' do
|
||||
before do
|
||||
Ability.stubs(:user_permissions).returns([])
|
||||
end
|
||||
|
||||
it 'renders unauthorized error' do
|
||||
api_get '/api/v2/market/trades', params: { market: 'btcusd' }, token: token
|
||||
expect(response).to have_http_status 403
|
||||
expect(response).to include_api_error('user.ability.not_permitted')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
50
spec/api/v2/mount_spec.rb
Normal file
50
spec/api/v2/mount_spec.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
class Mount
|
||||
# Use /public namespace for skipping rack-jwt authorization.
|
||||
namespace :public do
|
||||
get('/null') { '' }
|
||||
get('/record-not-found') { raise ActiveRecord::RecordNotFound }
|
||||
get('/auth-error') { raise Peatio::Auth::Error }
|
||||
get('/standard-error') { raise StandardError }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe API::V2::Mount, type: :request do
|
||||
let(:middlewares) { API::V2::Mount.middleware }
|
||||
it 'should use attack middleware' do
|
||||
expect(middlewares.drop(1)).to eq [[:use, Rack::Attack]]
|
||||
end
|
||||
|
||||
context 'handle exception on request processing' do
|
||||
it 'returns array with record.not_found error' do
|
||||
get '/api/v2/public/record-not-found'
|
||||
expect(response.code).to eq '404'
|
||||
expect(response).to include_api_error('record.not_found')
|
||||
end
|
||||
|
||||
it 'returns array with jwt.decode_and_verify error' do
|
||||
get '/api/v2/public/auth-error'
|
||||
expect(response.code).to eq '401'
|
||||
expect(response).to include_api_error('jwt.decode_and_verify')
|
||||
end
|
||||
|
||||
it 'returns array with server.internal_error error' do
|
||||
get '/api/v2/public/standard-error'
|
||||
expect(response.code).to eq '500'
|
||||
expect(response).to include_api_error('server.internal_error')
|
||||
end
|
||||
end
|
||||
|
||||
context 'handle exception on request routing' do
|
||||
it 'should render json error message' do
|
||||
get '/api/v2/public/non/exist'
|
||||
expect(response.code).to eq '404'
|
||||
end
|
||||
end
|
||||
end
|
||||
140
spec/api/v2/public/currencies_spec.rb
Normal file
140
spec/api/v2/public/currencies_spec.rb
Normal file
@@ -0,0 +1,140 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Public::Currencies, type: :request do
|
||||
|
||||
before(:each) { clear_redis }
|
||||
describe 'GET /api/v2/public/currencies/:id' do
|
||||
let(:fiat) { Currency.find(:usd) }
|
||||
let(:coin) { Currency.find(:btc) }
|
||||
|
||||
let(:expected_for_fiat) do
|
||||
%w[id type deposit_enabled withdrawal_enabled deposit_fee withdraw_fee withdraw_limit_24h withdraw_limit_72h base_factor precision]
|
||||
end
|
||||
let(:expected_for_coin) do
|
||||
expected_for_fiat.concat(%w[explorer_transaction explorer_address])
|
||||
end
|
||||
|
||||
it 'returns information about specified currency' do
|
||||
get "/api/v2/public/currencies/#{coin.id}"
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('id')).to eq coin.id
|
||||
end
|
||||
|
||||
context 'currency code with dot' do
|
||||
let!(:currency) { create(:currency, :xagm_cx) }
|
||||
|
||||
it 'returns information about specified currency' do
|
||||
get "/api/v2/public/currencies/#{currency.id}"
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.fetch('id')).to eq currency.id
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns correct keys for fiat' do
|
||||
get "/api/v2/public/currencies/#{fiat.id}"
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expected_for_fiat.each { |key| expect(result).to have_key key }
|
||||
|
||||
(expected_for_coin - expected_for_fiat).each do |key|
|
||||
expect(result).not_to have_key key
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns correct keys for coin' do
|
||||
get "/api/v2/public/currencies/#{coin.id}"
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expected_for_coin.each { |key| expect(result).to have_key key }
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid id' do
|
||||
get '/api/v2/public/currencies/invalid'
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('public.currency.doesnt_exist')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/public/currencies' do
|
||||
it 'lists visible currencies' do
|
||||
get '/api/v2/public/currencies'
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.visible.size
|
||||
end
|
||||
|
||||
it 'lists visible coins' do
|
||||
get '/api/v2/public/currencies', params: { type: 'coin' }
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.size).to eq Currency.coins.visible.size
|
||||
end
|
||||
|
||||
it 'lists visible fiats' do
|
||||
get '/api/v2/public/currencies', params: { type: 'fiat' }
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body, symbolize_names: true)
|
||||
expect(result.size).to eq Currency.fiats.visible.size
|
||||
expect(result.dig(0, :id)).to eq 'usd'
|
||||
end
|
||||
|
||||
it 'returns error in case of invalid type' do
|
||||
get '/api/v2/public/currencies', params: { type: 'invalid' }
|
||||
expect(response).to have_http_status 422
|
||||
end
|
||||
|
||||
context 'pagination' do
|
||||
it 'returns paginated currencies' do
|
||||
get '/api/v2/public/currencies', params: { limit: 2 }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response.headers.fetch('Total').to_i).to eq Currency.visible.count
|
||||
expect(result.size).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'search' do
|
||||
it 'searches by code' do
|
||||
get '/api/v2/public/currencies', params: { search: { code: 't' } }
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.pluck('id')).to contain_exactly('eth', 'btc', 'trst')
|
||||
end
|
||||
|
||||
it 'searches by name' do
|
||||
get '/api/v2/public/currencies', params: { search: { name: 'e' } }
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.pluck('name')).to contain_exactly('Ethereum', 'Evolution Land Global Token', 'WeTrust')
|
||||
end
|
||||
|
||||
it 'searches by code or name' do
|
||||
get '/api/v2/public/currencies', params: { search: { name: 'us', code: 'us' } }
|
||||
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.pluck('id')).to contain_exactly('usd', 'trst')
|
||||
expect(result.pluck('name')).to contain_exactly('US Dollar', 'WeTrust')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
760
spec/api/v2/public/markets_spec.rb
Normal file
760
spec/api/v2/public/markets_spec.rb
Normal file
@@ -0,0 +1,760 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Public::Markets, type: :request do
|
||||
|
||||
before(:each) { clear_redis }
|
||||
describe 'GET /api/v2/markets' do
|
||||
before { create(:market, :ethusd) }
|
||||
|
||||
let(:expected_keys) do
|
||||
%w[id name base_unit quote_unit min_price max_price
|
||||
min_amount amount_precision price_precision state]
|
||||
end
|
||||
|
||||
it 'lists enabled markets' do
|
||||
get '/api/v2/public/markets'
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq Market.enabled.size
|
||||
|
||||
result.each do |market|
|
||||
expect(market.keys).to contain_exactly(*expected_keys)
|
||||
end
|
||||
end
|
||||
|
||||
context 'api will return hidden markets' do
|
||||
before { create(:market, :btceur, state: :hidden) }
|
||||
it 'returns hidden market' do
|
||||
get '/api/v2/public/markets'
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.find { |currency| currency['id'] == 'btceur' }['state']).to eq('hidden')
|
||||
end
|
||||
end
|
||||
|
||||
context 'pagination' do
|
||||
it 'returns paginated markets' do
|
||||
get '/api/v2/public/markets', params: { limit: 2 }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(response).to be_successful
|
||||
|
||||
expect(response.headers.fetch('Total').to_i).to eq Market.enabled.size
|
||||
expect(result.size).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'filters' do
|
||||
context 'base_unit & quote_unit' do
|
||||
it 'filters by base_unit' do
|
||||
get '/api/v2/public/markets', params: { base_unit: :btc }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq Market.enabled.where(base_unit: :btc).size
|
||||
result.each do |market|
|
||||
expect(market['base_unit']).to eq 'btc'
|
||||
end
|
||||
end
|
||||
|
||||
it 'filters by quote_unit' do
|
||||
get '/api/v2/public/markets', params: { quote_unit: :usd }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq Market.enabled.where(quote_unit: :usd).size
|
||||
result.each do |market|
|
||||
expect(market['quote_unit']).to eq 'usd'
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not filter' do
|
||||
get '/api/v2/public/markets'
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.size).to eq Market.enabled.size
|
||||
end
|
||||
end
|
||||
|
||||
context 'base_code & quote_code' do
|
||||
it 'filters by base_code' do
|
||||
get '/api/v2/public/markets', params: { search: { base_code: "bt" } }
|
||||
# Since we have next markets list:
|
||||
# btcusd, btceth, ethusd
|
||||
# Since 2 of them has 'bt' in base_unit (btc).
|
||||
# We expect them to be returned in API response.
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.pluck('id')).to contain_exactly('btceth', 'btcusd')
|
||||
end
|
||||
|
||||
it 'filters by quote_code' do
|
||||
Currency.find(:eur).update(visible: true)
|
||||
create(:market, :btceur)
|
||||
# Since we have next markets list:
|
||||
# btceur, btcusd, btceth, ethusd
|
||||
# Since 2 of them has 'e' in quote_unit (eur, eth).
|
||||
# We expect them to be returned in API response.
|
||||
get '/api/v2/public/markets', params: { search: { quote_code: "e" } }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
expect(result.pluck('id')).to contain_exactly('btceth', 'btceur')
|
||||
end
|
||||
end
|
||||
|
||||
context 'quote_name' do
|
||||
before do
|
||||
Currency.find(:eur).update(visible: true)
|
||||
create(:market, :btceur)
|
||||
create(:market, :btctrst)
|
||||
end
|
||||
|
||||
it 'filters by name 1' do
|
||||
# Since we have next markets list:
|
||||
# btceur, btcusd, btceth, btctrst, ethusd
|
||||
# Since 3 of them has 'E' in quote name (Euro, Ethereum, We Trust).
|
||||
# We expect them to be returned in API response.
|
||||
get '/api/v2/public/markets', params: { search: { quote_name: 'E' } }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('id')).to contain_exactly('btceth', 'btceur', 'btctrst')
|
||||
end
|
||||
|
||||
it 'filters by name 2' do
|
||||
# Since we have next markets list:
|
||||
# btceur, btcusd, btceth, btctrst, ethusd
|
||||
# Since 3 of them has 'uS' in quote name (US Dollar, We Trust).
|
||||
# We expect them to be returned in API response.
|
||||
get '/api/v2/public/markets', params: { search: { quote_name: 'uS' } }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('id')).to contain_exactly('btcusd', 'btctrst', 'ethusd')
|
||||
end
|
||||
end
|
||||
|
||||
context 'complex filter' do
|
||||
before do
|
||||
Currency.find(:eur).update(visible: true)
|
||||
create(:market, :btceur)
|
||||
create(:market, :btctrst)
|
||||
end
|
||||
|
||||
it 'filters by base_unit & quote_name or quote_code' do
|
||||
# Since we have next markets list:
|
||||
# btceur, btcusd, btceth, btctrst, ethusd
|
||||
# 1. Filter by base_unit btc: btceur, btcusd, btceth, btctrst
|
||||
# 2. Filter by quote_code or quote_name 'et': btceth, btctrst (Ethereum, WeTrust)
|
||||
# We expect them to be returned in API response.
|
||||
get '/api/v2/public/markets', params: { base_unit: :btc, search: { quote_name: 'et', quote_code: 'et' } }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('id')).to contain_exactly('btceth', 'btctrst')
|
||||
end
|
||||
|
||||
it 'filters by base_unit & quote_code' do
|
||||
# Since we have next markets list:
|
||||
# btceur, btcusd, btceth, btctrst, ethusd
|
||||
# 1. Filter by base_unit btc: btceur, btcusd, btceth, btctrst
|
||||
# 2. Filter by quote_code 'et': btceth (eth)
|
||||
# We expect them to be returned in API response.
|
||||
get '/api/v2/public/markets', params: { base_unit: :btc, search: { quote_code: 'et' } }
|
||||
expect(response).to be_successful
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.pluck('id')).to contain_exactly('btceth')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/public/markets/:market/order_book' do
|
||||
before do
|
||||
create_list(:order_bid, 5, :btcusd)
|
||||
create_list(:order_ask, 5, :btcusd)
|
||||
end
|
||||
|
||||
let(:market) { :btcusd }
|
||||
|
||||
it 'returns ask and bid orders on specified market' do
|
||||
get "/api/v2/public/markets/#{market}/order-book"
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 5
|
||||
expect(result['bids'].size).to eq 5
|
||||
end
|
||||
|
||||
context 'market name with dot' do
|
||||
let!(:currency) { create(:currency, :xagm_cx) }
|
||||
let!(:market) { create(:market, :xagm_cxusd) }
|
||||
|
||||
it 'returns information about specified market' do
|
||||
get "/api/v2/public/markets/#{market.id}/order-book"
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 0
|
||||
expect(result['bids'].size).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns limited asks and bids' do
|
||||
get "/api/v2/public/markets/#{market}/order-book", params: { asks_limit: 1, bids_limit: 1 }
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 1
|
||||
expect(result['bids'].size).to eq 1
|
||||
end
|
||||
|
||||
it 'validates market param' do
|
||||
get "/api/v2/public/markets/somecoin/order-book", params: { asks_limit: 1, bids_limit: 1 }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('public.market.doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validates asks limit' do
|
||||
get "/api/v2/public/markets/somecoin/order-book", params: { asks_limit: 201, bids_limit: 1 }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('public.order_book.invalid_ask_limit')
|
||||
end
|
||||
|
||||
it 'validates bids limit' do
|
||||
get "/api/v2/public/markets/somecoin/order-book", params: { asks_limit: 1, bids_limit: 201 }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('public.order_book.invalid_bid_limit')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/markets/:market/depth' do
|
||||
before do
|
||||
create_list(:order_bid, 5, :btcusd)
|
||||
create_list(:order_bid, 5, :btcusd, price: 2)
|
||||
create_list(:order_ask, 5, :btcusd)
|
||||
create_list(:order_ask, 5, :btcusd, price: 3)
|
||||
end
|
||||
|
||||
let(:asks) { [["1.0", "5.0"], ["3.0", "5.0"]] }
|
||||
let(:bids) { [["2.0", "5.0"], ["1.0", "5.0"]] }
|
||||
|
||||
let(:market) { :btcusd }
|
||||
|
||||
context 'valid market param' do
|
||||
it 'sorts asks and bids from highest to lowest' do
|
||||
get "/api/v2/public/markets/#{market}/depth"
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks']).to eq asks
|
||||
expect(result['bids']).to eq bids
|
||||
end
|
||||
end
|
||||
|
||||
context 'market name with dot' do
|
||||
let!(:currency) { create(:currency, :xagm_cx) }
|
||||
let!(:market) { create(:market, :xagm_cxusd) }
|
||||
|
||||
it 'returns information about specified market' do
|
||||
get "/api/v2/public/markets/#{market.id}/depth"
|
||||
expect(response).to be_successful
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result['asks'].size).to eq 0
|
||||
expect(result['bids'].size).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid market param' do
|
||||
it 'validates market param' do
|
||||
api_get "/api/v2/public/markets/usdusd/depth"
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('public.market.doesnt_exist')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/public/markets/market/k-line' do
|
||||
let(:points) do
|
||||
# [timestamp, open_price, max_price, min_price, last_price, period_volume]
|
||||
[[1537370460, 0.7079, 0.2204, 0.9794, 0.5273, 0.0747],
|
||||
[1537370520, 0.6293, 0.5054, 0.2253, 0.1969, 0.7276],
|
||||
[1537370580, 0.0939, 0.1949, 0.0032, 0.8328, 0.5895],
|
||||
[1537370640, 0.6416, 0.0772, 0.7045, 0.7794, 0.6151],
|
||||
[1537370700, 0.0566, 0.6377, 0.3007, 0.6855, 0.6976],
|
||||
[1537370760, 0.7868, 0.6465, 0.3207, 0.6428, 0.1771],
|
||||
[1537370820, 0.3318, 0.2124, 0.3773, 0.4274, 0.3473],
|
||||
[1537370880, 0.0704, 0.4902, 0.5957, 0.5214, 0.3687],
|
||||
[1537370940, 0.6629, 0.6585, 0.0756, 0.4559, 0.8554],
|
||||
[1537371000, 0.6627, 0.6627, 0.2128, 0.0788, 0.2013],
|
||||
[1537371060, 0.5165, 0.0435, 0.5228, 0.6447, 0.9237],
|
||||
[1537371120, 0.9311, 0.8886, 0.1605, 0.3223, 0.0211],
|
||||
[1537371180, 0.0704, 0.0103, 0.0325, 0.3846, 0.8957],
|
||||
[1537371240, 0.1445, 0.6031, 0.9533, 0.0866, 0.4871],
|
||||
[1537371300, 0.0974, 0.1344, 0.1533, 0.9029, 0.2009],
|
||||
[1537371360, 0.2609, 0.9687, 0.0287, 0.4465, 0.7088],
|
||||
[1537371420, 0.5671, 0.0576, 0.6617, 0.1041, 0.4942],
|
||||
[1537371480, 0.8355, 0.5336, 0.7419, 0.7062, 0.9562],
|
||||
[1537371540, 0.1805, 0.3577, 0.2768, 0.3162, 0.0209],
|
||||
[1537371600, 0.7971, 0.1799, 0.8307, 0.5074, 0.0122],
|
||||
[1537371660, 0.9491, 0.7448, 0.2019, 0.4662, 0.7035],
|
||||
[1537371720, 0.8126, 0.3899, 0.8823, 0.8115, 0.6067],
|
||||
[1537371780, 0.2632, 0.6558, 0.7411, 0.3894, 0.1509],
|
||||
[1537371840, 0.4274, 0.8187, 0.6661, 0.4331, 0.6335],
|
||||
[1537371900, 0.1356, 0.1787, 0.3081, 0.9549, 0.0723],
|
||||
[1537371960, 0.1931, 0.9486, 0.2469, 0.2295, 0.9366],
|
||||
[1537372020, 0.8323, 0.8168, 0.8453, 0.1278, 0.7975],
|
||||
[1537372080, 0.5663, 0.1374, 0.0025, 0.0358, 0.6063],
|
||||
[1537372140, 0.9296, 0.5443, 0.2732, 0.6434, 0.9173],
|
||||
[1537372200, 0.7292, 0.0367, 0.3569, 0.7876, 0.6626],
|
||||
[1537372260, 0.9979, 0.2182, 0.5141, 0.8984, 0.4512],
|
||||
[1537372320, 0.4363, 0.4416, 0.2354, 0.6053, 0.7398],
|
||||
[1537372380, 0.1815, 0.4969, 0.4091, 0.0798, 0.8797]]
|
||||
end
|
||||
let(:point_period) { KLineService::POINT_PERIOD_IN_SECONDS }
|
||||
let(:points_default_limit) { 30 }
|
||||
let(:last_point) { points.last }
|
||||
let(:first_point) { points.first }
|
||||
|
||||
before { write_to_influx(points) }
|
||||
after { delete_measurments("candles_1m") }
|
||||
|
||||
def influx_data(point)
|
||||
{
|
||||
values:
|
||||
{
|
||||
open: point[1],
|
||||
high: point[2],
|
||||
low: point[3],
|
||||
close: point[4],
|
||||
volume: point[5],
|
||||
},
|
||||
tags:
|
||||
{
|
||||
market: 'btcusd'
|
||||
},
|
||||
timestamp: point[0]
|
||||
}
|
||||
end
|
||||
|
||||
def write_to_influx(points)
|
||||
points.each do |point|
|
||||
Peatio::InfluxDB.client(epoch: 's').write_point('candles_1m', influx_data(point), 's')
|
||||
end
|
||||
end
|
||||
|
||||
def load_k_line(query = {})
|
||||
api_get '/api/v2/public/markets/btcusd/k-line?' + query.to_query
|
||||
expect(response).to have_http_status 200
|
||||
end
|
||||
|
||||
def response_body
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
context 'data exists' do
|
||||
it 'without time limits' do
|
||||
load_k_line
|
||||
expect(JSON.parse(response.body)).to eq points[-points_default_limit..-1]
|
||||
end
|
||||
|
||||
context 'with time_from' do
|
||||
it 'smaller than first point timestamp' do
|
||||
load_k_line(time_from: first_point.first - 2 * point_period)
|
||||
expect(response_body).to eq points[0...points_default_limit]
|
||||
end
|
||||
|
||||
it 'bigger than last point timestamp' do
|
||||
load_k_line(time_from: last_point.first + 2 * point_period)
|
||||
expect(response_body).to eq []
|
||||
end
|
||||
|
||||
it 'in range of first and last timestamp' do
|
||||
time_from = first_point.first + 10 * point_period
|
||||
load_k_line(time_from: time_from)
|
||||
expect(response_body).to eq points[10..-1]
|
||||
# First point timestamp should be eq to time_from.
|
||||
expect(response_body.first.first).to eq time_from
|
||||
|
||||
time_from = first_point.first + 22 * point_period
|
||||
load_k_line(time_from: time_from)
|
||||
expect(response_body).to eq points[22..-1]
|
||||
# First point timestamp should be eq to time_from.
|
||||
expect(response_body.first.first).to eq time_from
|
||||
end
|
||||
end
|
||||
|
||||
context 'with time_to' do
|
||||
it 'smaller than first point timestamp' do
|
||||
load_k_line(time_to: first_point.first - 2 * point_period)
|
||||
expect(response_body).to eq []
|
||||
end
|
||||
|
||||
it 'bigger than last point timestamp' do
|
||||
load_k_line(time_to: last_point.first + 2 * point_period)
|
||||
# Returns (limit - 2) left points.
|
||||
expect(response_body).to eq points[-points_default_limit..]
|
||||
end
|
||||
|
||||
it 'in range of first and last timestamp' do
|
||||
load_k_line(time_to: first_point.first + 1 * point_period)
|
||||
expect(response_body).to eq points[0..1]
|
||||
|
||||
load_k_line(time_to: first_point.first + 20 * point_period)
|
||||
expect(response_body).to eq points[0..20]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with time_from and time_to' do
|
||||
|
||||
it 'time_to less than time_from' do
|
||||
time_from = first_point.first + 2 * point_period
|
||||
time_to = first_point.first - 2 * point_period
|
||||
|
||||
load_k_line(time_from: time_from, time_to: time_to)
|
||||
expect(response_body).to eq []
|
||||
end
|
||||
|
||||
it 'both less than first point timestamp' do
|
||||
time_from = first_point.first - 10 * point_period
|
||||
time_to = first_point.first - 4 * point_period
|
||||
|
||||
load_k_line(time_from: time_from, time_to: time_to)
|
||||
expect(response_body).to eq []
|
||||
end
|
||||
|
||||
it 'both bigger than last point timestamp' do
|
||||
time_from = last_point.first + 2 * point_period
|
||||
time_to = last_point.first + 12 * point_period
|
||||
|
||||
load_k_line(time_from: time_from, time_to: time_to)
|
||||
expect(response_body).to eq []
|
||||
end
|
||||
|
||||
it 'both in range of first and last timestamp' do
|
||||
time_from = first_point.first + 10 * point_period
|
||||
time_to = last_point.first - 10 * point_period
|
||||
|
||||
load_k_line(time_from: time_from, time_to: time_to)
|
||||
# Points timestamps should be in range time_from..time_to (limit is bigger).
|
||||
expect(response_body).to eq\
|
||||
points.select { |p| p.first >= time_from && p.first <= time_to }
|
||||
expect(response_body.first.first).to eq time_from
|
||||
expect(response_body.last.first).to eq time_to
|
||||
end
|
||||
end
|
||||
|
||||
context 'with limit' do
|
||||
it 'returns n last points' do
|
||||
limit = 5
|
||||
load_k_line(limit: limit)
|
||||
expect(response_body).to eq points[-limit..-1]
|
||||
|
||||
limit = 10
|
||||
load_k_line(limit: limit)
|
||||
expect(response_body).to eq points[-limit..-1]
|
||||
end
|
||||
|
||||
it 'returns all points if limit greater than points number' do
|
||||
limit = points.length + 1
|
||||
load_k_line(limit: limit)
|
||||
expect(response_body).to eq points
|
||||
end
|
||||
end
|
||||
|
||||
context 'with limits, time_from and time_to' do
|
||||
it 'ignores limit' do
|
||||
time_from = first_point.first + 1 * point_period
|
||||
time_to = last_point.first - 1 * point_period
|
||||
limit = 5
|
||||
load_k_line(time_from: time_from, time_to: time_to, limit: limit)
|
||||
|
||||
# All point in time_from..time_to including time_to (time_to - time_from) / 60 + 1.
|
||||
expect(response_body.count).to eq (time_to - time_from) / 60 + 1
|
||||
# Points timestamps should be in range time_from..time_to.
|
||||
expect(response_body).to eq\
|
||||
points.select { |p| p.first >= time_from && p.first <= time_to }
|
||||
expect(response_body.first.first).to eq time_from
|
||||
expect(response_body.last.first).to eq time_to
|
||||
end
|
||||
end
|
||||
|
||||
context 'with limits and time_from' do
|
||||
it 'returns n right points from time_from (adds limit to time_from)' do
|
||||
time_from = first_point.first + 5 * point_period
|
||||
limit = 10
|
||||
load_k_line(time_from: time_from, limit: limit)
|
||||
|
||||
expect(response_body.count).to eq limit
|
||||
# Points timestamps should be bigger than time_from and we select first 10.
|
||||
expect(response_body).to eq\
|
||||
points.select { |p| p.first >= time_from }[0...limit]
|
||||
expect(response_body.first.first).to eq time_from
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'data is missing' do
|
||||
before { delete_measurments("candles_1m") }
|
||||
|
||||
it 'without time_from' do
|
||||
load_k_line
|
||||
expect(JSON.parse(response.body)).to eq []
|
||||
end
|
||||
|
||||
it 'with time_from' do
|
||||
load_k_line(time_from: first_point.first)
|
||||
expect(JSON.parse(response.body)).to eq []
|
||||
end
|
||||
|
||||
it 'with time_from and time_to' do
|
||||
load_k_line(time_from: first_point.first, time_to: last_point.first)
|
||||
expect(JSON.parse(response.body)).to eq []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/markets/tickers' do
|
||||
after { delete_measurments("trades") }
|
||||
|
||||
context 'no trades executed yet' do
|
||||
let(:expected_ticker) do
|
||||
{ 'low' => '0.0', 'high' => '0.0',
|
||||
'open' => '0.0', 'last' => '0.0',
|
||||
'volume' => '0.0', 'vol' => '0.0', 'amount' => '0.0',
|
||||
'avg_price' => '0.0', 'price_change_percent' => '+0.00%' }
|
||||
end
|
||||
|
||||
it 'returns ticker of all markets' do
|
||||
get '/api/v2/public/markets/tickers'
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['btcusd']['at']).not_to be_nil
|
||||
expect(JSON.parse(response.body)['btcusd']['ticker']).to include(expected_ticker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'single trade was executed' do
|
||||
let!(:trade) { create(:trade, :btcusd, price: '5.0'.to_d, amount: '1.1'.to_d, total: '5.5'.to_d)}
|
||||
let(:expected_ticker) do
|
||||
{ 'low' => '5.0', 'high' => '5.0',
|
||||
'open' => '5.0', 'last' => '5.0',
|
||||
'volume' => '5.5', 'vol' => '5.5', 'amount' => '1.1',
|
||||
'avg_price' => '5.0', 'price_change_percent' => '+0.00%' }
|
||||
end
|
||||
before do
|
||||
trade.write_to_influx
|
||||
end
|
||||
|
||||
it 'returns market tickers' do
|
||||
get '/api/v2/public/markets/tickers'
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['btcusd']['at']).not_to be_nil
|
||||
expect(JSON.parse(response.body)['btcusd']['ticker']).to include(expected_ticker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'multiple trades were executed' do
|
||||
let!(:trade1) { create(:trade, :btcusd, price: '5.0'.to_d, amount: '1.1'.to_d, total: '5.5'.to_d)}
|
||||
let!(:trade2) { create(:trade, :btcusd, price: '6.0'.to_d, amount: '0.9'.to_d, total: '5.4'.to_d)}
|
||||
|
||||
let(:expected_ticker) do
|
||||
{ 'low' => '5.0', 'high' => '6.0',
|
||||
'open' => '5.0', 'last' => '6.0',
|
||||
'vol' => '10.9', 'volume' => '10.9', 'amount' => '2.0',
|
||||
'avg_price' => '5.45', 'price_change_percent' => '+20.00%' }
|
||||
end
|
||||
before do
|
||||
trade1.write_to_influx
|
||||
trade2.write_to_influx
|
||||
end
|
||||
|
||||
it 'returns market tickers' do
|
||||
get '/api/v2/public/markets/tickers'
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['btcusd']['at']).not_to be_nil
|
||||
expect(JSON.parse(response.body)['btcusd']['ticker']).to include(expected_ticker)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/public/markets/:market/tickers' do
|
||||
after { delete_measurments("trades") }
|
||||
context 'no trades executed yet' do
|
||||
let(:expected_ticker) do
|
||||
{ 'low' => '0.0', 'high' => '0.0',
|
||||
'open' => '0.0', 'last' => '0.0',
|
||||
'volume' => '0.0', 'vol' => '0.0', 'amount' => '0.0',
|
||||
'avg_price' => '0.0', 'price_change_percent' => '+0.00%' }
|
||||
end
|
||||
|
||||
it 'returns market tickers' do
|
||||
get '/api/v2/public/markets/btcusd/tickers'
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['ticker']).to include(expected_ticker)
|
||||
end
|
||||
|
||||
context 'market name with dot' do
|
||||
let!(:currency) { create(:currency, :xagm_cx) }
|
||||
let!(:market) { create(:market, :xagm_cxusd) }
|
||||
|
||||
it 'returns information about specified market' do
|
||||
get "/api/v2/public/markets/#{market.id}/tickers"
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['ticker']).to include(expected_ticker)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'single trade was executed' do
|
||||
let!(:trade) { create(:trade, :btcusd, price: '5.0'.to_d, amount: '1.1'.to_d, total: '5.5'.to_d)}
|
||||
let(:expected_ticker) do
|
||||
{ 'low' => '5.0', 'high' => '5.0',
|
||||
'open' => '5.0', 'last' => '5.0',
|
||||
'volume' => '5.5', 'vol' => '5.5', 'amount' => '1.1',
|
||||
'avg_price' => '5.0', 'price_change_percent' => '+0.00%' }
|
||||
end
|
||||
before do
|
||||
trade.write_to_influx
|
||||
end
|
||||
|
||||
it 'returns market tickers' do
|
||||
get '/api/v2/public/markets/btcusd/tickers'
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['ticker']).to include(expected_ticker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'multiple trades were executed' do
|
||||
let!(:trade1) { create(:trade, :btcusd, price: '5.0'.to_d, amount: '1.1'.to_d, total: '5.5'.to_d)}
|
||||
let!(:trade2) { create(:trade, :btcusd, price: '6.0'.to_d, amount: '0.9'.to_d, total: '5.4'.to_d)}
|
||||
|
||||
# open = 6.0 because it takes last by default.
|
||||
# to make it work correctly need to run k-line daemon.
|
||||
let(:expected_ticker) do
|
||||
{ 'low' => '5.0', 'high' => '6.0',
|
||||
'open' => '5.0', 'last' => '6.0',
|
||||
'vol' => '10.9', 'volume' => '10.9', 'amount' => '2.0',
|
||||
'avg_price' => '5.45', 'price_change_percent' => '+20.00%' }
|
||||
end
|
||||
before do
|
||||
trade1.write_to_influx
|
||||
trade2.write_to_influx
|
||||
end
|
||||
|
||||
it 'returns market tickers' do
|
||||
get '/api/v2/public/markets/btcusd/tickers'
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)['ticker']).to include(expected_ticker)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/public/markets/#{market}/trades' do
|
||||
|
||||
let(:member) do
|
||||
create(:member, :level_3).tap do |m|
|
||||
m.get_account(:btc).update_attributes(balance: 12.13, locked: 3.14)
|
||||
m.get_account(:usd).update_attributes(balance: 2014.47, locked: 0)
|
||||
end
|
||||
end
|
||||
|
||||
let(:ask) do
|
||||
create(
|
||||
:order_ask,
|
||||
:btcusd,
|
||||
price: '12.32'.to_d,
|
||||
volume: '123.12345678',
|
||||
member: member
|
||||
)
|
||||
end
|
||||
|
||||
let(:bid) do
|
||||
create(
|
||||
:order_bid,
|
||||
:btcusd,
|
||||
price: '12.32'.to_d,
|
||||
volume: '123.12345678',
|
||||
member: member
|
||||
)
|
||||
end
|
||||
|
||||
let(:market) { :btcusd }
|
||||
|
||||
let!(:ask_trade) { create(:trade, :btcusd, maker_order: ask, created_at: 2.days.ago) }
|
||||
let!(:bid_trade) { create(:trade, :btcusd, taker_order: bid, created_at: 1.day.ago) }
|
||||
|
||||
|
||||
after do
|
||||
delete_measurments('trades')
|
||||
end
|
||||
|
||||
before do
|
||||
ask_trade.write_to_influx
|
||||
bid_trade.write_to_influx
|
||||
end
|
||||
|
||||
it 'returns all recent trades' do
|
||||
get "/api/v2/public/markets/#{market}/trades"
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body).size).to eq 2
|
||||
end
|
||||
|
||||
context 'market name with dot' do
|
||||
let!(:currency) { create(:currency, :xagm_cx) }
|
||||
let!(:market) { create(:market, :xagm_cxusd) }
|
||||
|
||||
it 'returns information about specified market' do
|
||||
get "/api/v2/public/markets/#{market.id}/trades"
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body).size).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns 1 trade' do
|
||||
get "/api/v2/public/markets/#{market}/trades", params: {limit: 1}
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body).size).to eq 1
|
||||
end
|
||||
|
||||
it 'sorts trades in reverse creation order' do
|
||||
get "/api/v2/public/markets/#{market}/trades"
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body).first['id']).to eq bid_trade.id
|
||||
end
|
||||
|
||||
it 'gets trades by limit' do
|
||||
trade = create(:trade, :btcusd, taker_order: bid, created_at: 6.hours.ago)
|
||||
trade.write_to_influx
|
||||
|
||||
get "/api/v2/public/markets/#{market}/trades", params: { limit: 2, order_by: 'asc'}
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body).count).to eq 2
|
||||
|
||||
get "/api/v2/public/markets/#{market}/trades", params: { market: 'btcusd', limit: 3, order_by: 'asc' }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body).count).to eq 3
|
||||
end
|
||||
|
||||
it 'validates market param' do
|
||||
api_get "/api/v2/public/markets/usdusd/trades"
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('public.market.doesnt_exist')
|
||||
end
|
||||
|
||||
it 'validates limit param' do
|
||||
get "/api/v2/public/markets/#{market}/trades", params: { limit: 1001 }
|
||||
expect(response).to have_http_status 422
|
||||
expect(response).to include_api_error('public.trade.invalid_limit')
|
||||
end
|
||||
end
|
||||
end
|
||||
12
spec/api/v2/public/member_levels_spec.rb
Normal file
12
spec/api/v2/public/member_levels_spec.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Public::MemberLevels, type: :request do
|
||||
describe 'GET /member_levels' do
|
||||
it 'responds with 200 and returns correct data' do
|
||||
api_get '/api/v2/public/member-levels'
|
||||
expect(response).to be_successful
|
||||
expect(response.body).to eq '{"deposit":{"minimum_level":3},"withdraw":{"minimum_level":3},"trading":{"minimum_level":3}}'
|
||||
end
|
||||
end
|
||||
end
|
||||
39
spec/api/v2/public/tools_spec.rb
Normal file
39
spec/api/v2/public/tools_spec.rb
Normal file
@@ -0,0 +1,39 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Public::Tools, type: :request do
|
||||
describe '/timestamp' do
|
||||
it 'returns current time in seconds' do
|
||||
now = Time.now
|
||||
get '/api/v2/public/timestamp'
|
||||
expect(response).to be_successful
|
||||
expect(JSON.parse(response.body)).to be_between(now.iso8601, (now + 1).iso8601)
|
||||
end
|
||||
end
|
||||
|
||||
describe '/health' do
|
||||
it 'returns successful liveness probe' do
|
||||
get '/api/v2/public/health/alive'
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'returns failed liveness probe' do
|
||||
Market.stubs(:connected?).returns(false)
|
||||
|
||||
get '/api/v2/public/health/alive'
|
||||
expect(response).to have_http_status(503)
|
||||
end
|
||||
|
||||
it 'returns successful readiness probe' do
|
||||
get '/api/v2/public/health/ready'
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'returns failed readiness probe' do
|
||||
Bunny.stubs(:run).returns(false)
|
||||
|
||||
get '/api/v2/public/health/alive'
|
||||
expect(response).to have_http_status(503)
|
||||
end
|
||||
end
|
||||
end
|
||||
50
spec/api/v2/public/trading_fees_spec.rb
Normal file
50
spec/api/v2/public/trading_fees_spec.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Public::TradingFees, type: :request do
|
||||
|
||||
before(:each) { clear_redis }
|
||||
describe 'GET /trading_fees' do
|
||||
before do
|
||||
create(:trading_fee, maker: 0.0005, taker: 0.001, market_id: :btcusd, group: 'vip-0')
|
||||
create(:trading_fee, maker: 0.0008, taker: 0.001, market_id: :any, group: 'vip-0')
|
||||
create(:trading_fee, maker: 0.001, taker: 0.0012, market_id: :btcusd, group: :any)
|
||||
end
|
||||
|
||||
it 'returns all trading fees' do
|
||||
api_get '/api/v2/public/trading_fees'
|
||||
|
||||
expect(response.status).to eq 200
|
||||
expect(JSON.parse(response.body).length).to eq TradingFee.count
|
||||
end
|
||||
|
||||
it 'pagination' do
|
||||
api_get '/api/v2/public/trading_fees', params: { limit: 1 }
|
||||
expect(JSON.parse(response.body).length).to eq 1
|
||||
end
|
||||
|
||||
it 'filters by market_id' do
|
||||
api_get '/api/v2/public/trading_fees', params: { market_id: 'btcusd' }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.map { |r| r['market_id'] }).to all eq 'btcusd'
|
||||
expect(result.length).to eq TradingFee.where(market_id: 'btcusd').count
|
||||
end
|
||||
|
||||
it 'filters by group' do
|
||||
api_get '/api/v2/public/trading_fees', params: { group: 'vip-0' }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.map { |r| r['group'] }).to all eq 'vip-0'
|
||||
expect(result.length).to eq TradingFee.where(group: 'vip-0').count
|
||||
end
|
||||
|
||||
it 'capitalized fee group' do
|
||||
api_get '/api/v2/public/trading_fees', params: { group: 'Vip-0' }
|
||||
|
||||
result = JSON.parse(response.body)
|
||||
expect(result.map { |r| r['group'] }).to all eq 'vip-0'
|
||||
expect(result.length).to eq TradingFee.where(group: 'vip-0').count
|
||||
end
|
||||
end
|
||||
end
|
||||
193
spec/api/v2/public/webhooks.rb
Normal file
193
spec/api/v2/public/webhooks.rb
Normal file
@@ -0,0 +1,193 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe API::V2::Public::Webhooks, type: :request do
|
||||
describe 'GET /webhooks/:event' do
|
||||
|
||||
let(:member) { create(:member) }
|
||||
|
||||
let(:transaction) do
|
||||
Peatio::Transaction.new(
|
||||
currency_id: :eth,
|
||||
hash: '0xa049b0202ba078caa723c6b59594247b0c9f33e24878950f8537cedff9ea20ac',
|
||||
amount: 0.5,
|
||||
to_address: '0x1ef338196bd0207ba4852ba7a6847eed59331b84',
|
||||
block_number: 16880960,
|
||||
txout: 0,
|
||||
status: :success
|
||||
)
|
||||
end
|
||||
|
||||
let(:invlaid_transaction) do
|
||||
Peatio::Transaction.new(
|
||||
currency_id: :eth,
|
||||
hash: '0xa049b0202ba078caa723c6b59594247b0c9f33e24878950f8537cedff9ea20ac',
|
||||
amount: 0.5,
|
||||
to_address: '0x1ef338196bd0207ba4852ba7a6847eed59331b85',
|
||||
block_number: 16880960,
|
||||
txout: 0
|
||||
)
|
||||
end
|
||||
|
||||
let!(:wallet) { create(:wallet, :eth_deposit, name: 'Bitgo Deposit',
|
||||
gateway: :bitgo, settings:
|
||||
{ uri: 'http://localhost',
|
||||
secret: 'changeme',
|
||||
wallet_id: '5e4d43680f39a6710435b74edba4e2c2',
|
||||
access_token: 'changeme',
|
||||
testnet: false }) }
|
||||
|
||||
let(:request_body) {
|
||||
{ 'event' => 'deposit',
|
||||
'id' => '5e539be5e6715b2006c7bfa6278aa3f4',
|
||||
'type' => 'transfer',
|
||||
'wallet' => '5e4d43680f39a6710435b74edba4e2c2',
|
||||
'url' => 'http://localhost.com',
|
||||
'hash' => '0xa049b0202ba078caa723c6b59594247b0c9f33e24878950f8537cedff9ea20ac',
|
||||
'coin' => 'eth',
|
||||
'transfer' => '5e4e824894d4902c060f20c28b161fa8',
|
||||
'state' => 'new',
|
||||
'simulation' => 'true',
|
||||
'retries' => '0',
|
||||
'webhook' => '5e5399ddda65833f06cd53429bcbca83',
|
||||
'updatedAt' => '2020-02-24T09:48:21.795Z',
|
||||
'version' => '2' }
|
||||
}
|
||||
|
||||
|
||||
context 'nonexistent wallet' do
|
||||
let(:request_body) {
|
||||
{ 'event' => 'deposit',
|
||||
'id' => '5e539be5e6715b2006c7bfa6278aa3f4',
|
||||
'type' => 'transfer',
|
||||
'wallet' => 'changeme',
|
||||
'url' => 'http://localhost.com',
|
||||
'hash' => '0xa049b0202ba078caa723c6b59594247b0c9f33e24878950f8537cedff9ea20ac',
|
||||
'coin' => 'eth',
|
||||
'transfer' => '5e4e824894d4902c060f20c28b161fa8',
|
||||
'state' => 'new',
|
||||
'simulation' => 'true',
|
||||
'retries' => '0',
|
||||
'webhook' => '5e5399ddda65833f06cd53429bcbca83',
|
||||
'updatedAt' => '2020-02-24T09:48:21.795Z',
|
||||
'version' => '2' }
|
||||
}
|
||||
|
||||
it 'doesnt create deposit and return 200' do
|
||||
expect do
|
||||
api_post '/api/v2/public/webhooks/deposit', params: request_body
|
||||
|
||||
expect(response.status).to eq 200
|
||||
end.not_to change { Deposit.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'valid webhook callback' do
|
||||
|
||||
before do
|
||||
member.get_account(:eth).payment_addresses.create(currency_id: :eth, address: '0x1ef338196bd0207ba4852ba7a6847eed59331b84')
|
||||
WalletService.any_instance.stubs(:trigger_webhook_event).with(request_body).returns({ transfers: [transaction] })
|
||||
end
|
||||
|
||||
it 'creates new deposit' do
|
||||
api_post '/api/v2/public/webhooks/deposit', params: request_body
|
||||
|
||||
expect(response.status).to eq 200
|
||||
expect(Deposit.last.txid).to eq('0xa049b0202ba078caa723c6b59594247b0c9f33e24878950f8537cedff9ea20ac')
|
||||
end
|
||||
|
||||
context 'process second time' do
|
||||
|
||||
it 'doesnt create deposit for same transfer' do
|
||||
api_post '/api/v2/public/webhooks/deposit', params: request_body
|
||||
expect do
|
||||
api_post '/api/v2/public/webhooks/deposit', params: request_body
|
||||
|
||||
expect(response.status).to eq 200
|
||||
expect(Deposit.last.txid).to eq('0xa049b0202ba078caa723c6b59594247b0c9f33e24878950f8537cedff9ea20ac')
|
||||
end.not_to change { Deposit.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'process undefined transfer' do
|
||||
|
||||
before do
|
||||
WalletService.any_instance.stubs(:trigger_webhook_event).with(request_body).returns({ transfers: [invlaid_transaction] })
|
||||
end
|
||||
|
||||
it 'doesnt create deposit and return 200' do
|
||||
expect do
|
||||
api_post '/api/v2/public/webhooks/deposit', params: request_body
|
||||
|
||||
expect(response.status).to eq 200
|
||||
end.not_to change { Deposit.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'adapter raises error invalid' do
|
||||
|
||||
before do
|
||||
member.get_account(:eth).payment_addresses.create(currency_id: :eth, address: '0x1ef338196bd0207ba4852ba7a6847eed59331b84')
|
||||
WalletService.any_instance.stubs(:trigger_webhook_event).with(request_body).raises(Peatio::Wallet::ClientError.new('something went wrong'))
|
||||
end
|
||||
|
||||
it 'returns error' do
|
||||
api_post '/api/v2/public/webhooks/deposit', params: request_body
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('public.webhook.cannot_perfom_transfer')
|
||||
end
|
||||
end
|
||||
|
||||
context 'address confirmation event' do
|
||||
let(:request_body) {
|
||||
{ 'event' => 'deposit',
|
||||
'address' => '0xa049b0202ba078caa723c6b59594247b0c9f33e24878950f8537cedff9ea20ac',
|
||||
'type' => 'address_confirmation',
|
||||
'walletId' => '5e4e824894d4902c060f20c28b161fa8',
|
||||
'hash' => '0xa049b0202ba078caa723c6b59594247b0c9f33e24878950f8537cedff9ea20ac',
|
||||
}
|
||||
}
|
||||
|
||||
context 'valid webhook callback' do
|
||||
context 'update payment address' do
|
||||
before do
|
||||
member.get_account(:eth).payment_addresses.create(currency_id: :eth, address: nil, details: { address_id: 'address_id' })
|
||||
WalletService.any_instance.stubs(:trigger_webhook_event).with(request_body).returns({ address_id: 'address_id', currency_id: 'eth' })
|
||||
end
|
||||
|
||||
it 'should create address for member' do
|
||||
api_post '/api/v2/public/webhooks/deposit', params: request_body
|
||||
expect(response.status).to eq 200
|
||||
expect(member.get_account(:eth).payment_addresses[0].address).to eq request_body['address']
|
||||
end
|
||||
end
|
||||
|
||||
context 'skip payment address' do
|
||||
before do
|
||||
member.get_account(:eth).payment_addresses.create(currency_id: :eth, address: request_body['address'], details: { address_id: 'address_id' })
|
||||
WalletService.any_instance.stubs(:trigger_webhook_event).with(request_body).returns({ address_id: 'address_id', currency_id: 'eth' })
|
||||
end
|
||||
|
||||
it 'should not update address if address already exists' do
|
||||
api_post '/api/v2/public/webhooks/deposit', params: request_body
|
||||
expect(response.status).to eq 200
|
||||
expect(member.get_account(:eth).payment_addresses[0].created_at).to eq member.get_account(:eth).payment_addresses[0].updated_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'adapter raises error invalid' do
|
||||
before do
|
||||
member.get_account(:eth).payment_addresses.create(currency_id: :eth, address: nil, details: { address_id: 'address_id' })
|
||||
WalletService.any_instance.stubs(:trigger_webhook_event).with(request_body).raises(Peatio::Wallet::ClientError.new('something went wrong'))
|
||||
end
|
||||
|
||||
it 'returns error' do
|
||||
api_post '/api/v2/public/webhooks/deposit', params: request_body
|
||||
expect(response.status).to eq 422
|
||||
expect(response).to include_api_error('public.webhook.cannot_perfom_address_confirmation')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user