Initial commit

This commit is contained in:
Yaser
2026-08-13 19:50:53 +03:30
commit 38084458fe
879 changed files with 95198 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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