straight-server 0.2.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/Gemfile +21 -16
- data/Gemfile.lock +44 -30
- data/Gemfile.travis +15 -16
- data/README.md +66 -47
- data/VERSION +1 -1
- data/db/migrations/011_add_callback_data_to_orders.rb +1 -1
- data/db/migrations/012_add_address_provider.rb +11 -0
- data/db/migrations/013_add_address_derivation_scheme.rb +11 -0
- data/db/migrations/014_pubkey_null_address_provider_not_null.rb +8 -0
- data/db/migrations/015_add_amount_paid_to_orders.rb +11 -0
- data/db/migrations/016_add_new_params_to_orders.rb +13 -0
- data/db/migrations/017_add_test_mode_to_gateways.rb +11 -0
- data/db/migrations/018_add_test_keychain_id_to_gateways.rb +11 -0
- data/db/migrations/019_add_test_pubkey_to_gateways.rb +11 -0
- data/db/migrations/020_add_test_mode_to_orders.rb +11 -0
- data/db/schema.rb +11 -1
- data/lib/straight-server.rb +11 -9
- data/lib/straight-server/config.rb +28 -18
- data/lib/straight-server/gateway.rb +167 -87
- data/lib/straight-server/initializer.rb +13 -7
- data/lib/straight-server/order.rb +39 -17
- data/lib/straight-server/orders_controller.rb +71 -21
- data/lib/straight-server/random_string.rb +3 -13
- data/lib/straight-server/server.rb +3 -4
- data/lib/straight-server/signature_validator.rb +69 -0
- data/lib/straight-server/thread.rb +19 -4
- data/lib/straight-server/throttler.rb +7 -13
- data/lib/tasks/db.rake +1 -1
- data/spec/.straight/config.yml +8 -3
- data/spec/.straight/default_test_last_keychain_id +1 -0
- data/spec/factories.rb +2 -1
- data/spec/lib/gateway_spec.rb +222 -94
- data/spec/lib/initializer_spec.rb +1 -1
- data/spec/lib/order_spec.rb +26 -7
- data/spec/lib/orders_controller_spec.rb +65 -6
- data/spec/lib/signature_validator_spec.rb +72 -0
- data/spec/lib/thread_spec.rb +16 -0
- data/spec/lib/throttle_spec.rb +2 -2
- data/spec/spec_helper.rb +17 -22
- data/straight-server.gemspec +31 -12
- data/templates/config.yml +19 -10
- metadata +52 -11
@@ -1,9 +1,24 @@
|
|
1
1
|
module StraightServer
|
2
|
-
|
3
2
|
class Thread
|
4
|
-
|
5
|
-
|
3
|
+
|
4
|
+
def self.new(label: nil, &block)
|
5
|
+
thread = ::Thread.new(&block)
|
6
|
+
thread[:label] = label
|
7
|
+
thread
|
6
8
|
end
|
7
|
-
end
|
8
9
|
|
10
|
+
INTERRUPTION_FLAG = lambda { |label| "#{Config[:'redis.prefix']}:interrupt_thread:#{label}" }
|
11
|
+
|
12
|
+
def self.interrupt(label:)
|
13
|
+
redis = StraightServer.redis_connection
|
14
|
+
redis.set INTERRUPTION_FLAG[label], Time.now.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.interrupted?(thread:)
|
18
|
+
redis = StraightServer.redis_connection
|
19
|
+
result = redis.get(key = INTERRUPTION_FLAG[thread[:label]])
|
20
|
+
redis.del key if result
|
21
|
+
!!result
|
22
|
+
end
|
23
|
+
end
|
9
24
|
end
|
@@ -2,20 +2,16 @@ module StraightServer
|
|
2
2
|
class Throttler
|
3
3
|
|
4
4
|
def initialize(gateway_id)
|
5
|
-
@id
|
6
|
-
@redis
|
7
|
-
@limit
|
8
|
-
|
9
|
-
|
10
|
-
@period = Config.throttle[:period].to_i # in seconds
|
11
|
-
@ip_ban_duration = Config.throttle[:ip_ban_duration].to_i # in seconds
|
12
|
-
end
|
5
|
+
@id = "gateway_#{gateway_id}"
|
6
|
+
@redis = StraightServer.redis_connection
|
7
|
+
@limit = Config[:'throttle.requests_limit'].to_i
|
8
|
+
@period = Config[:'throttle.period'].to_i # in seconds
|
9
|
+
@ip_ban_duration = Config[:'throttle.ip_ban_duration'].to_i # in seconds
|
13
10
|
end
|
14
11
|
|
15
12
|
# @param [String] ip address
|
16
13
|
# @return [Boolean|Nil] true if request should be rejected,
|
17
14
|
# false if request should be served,
|
18
|
-
# nil if redis is not available
|
19
15
|
def deny?(ip)
|
20
16
|
banned?(ip) || throttled?(ip)
|
21
17
|
end
|
@@ -24,7 +20,6 @@ module StraightServer
|
|
24
20
|
|
25
21
|
def throttled?(ip)
|
26
22
|
return false if @limit <= 0 || @period <= 0
|
27
|
-
return unless @redis
|
28
23
|
key = throttled_key(ip)
|
29
24
|
value = @redis.incr(key)
|
30
25
|
@redis.expire key, @period * 2
|
@@ -38,7 +33,6 @@ module StraightServer
|
|
38
33
|
|
39
34
|
def banned?(ip)
|
40
35
|
return false if @ip_ban_duration <= 0
|
41
|
-
return unless @redis
|
42
36
|
value = @redis.get(banned_key(ip)).to_i
|
43
37
|
if value > 0
|
44
38
|
Time.now.to_i <= value + @ip_ban_duration
|
@@ -53,11 +47,11 @@ module StraightServer
|
|
53
47
|
end
|
54
48
|
|
55
49
|
def throttled_key(ip)
|
56
|
-
"#{Config
|
50
|
+
"#{Config[:'redis.prefix']}:Throttle:#{@id}:#{@period}_#{@limit}:#{Time.now.to_i / @period}:#{ip}"
|
57
51
|
end
|
58
52
|
|
59
53
|
def banned_key(ip)
|
60
|
-
"#{Config
|
54
|
+
"#{Config[:'redis.prefix']}:BannedIP:#{ip}"
|
61
55
|
end
|
62
56
|
end
|
63
57
|
end
|
data/lib/tasks/db.rake
CHANGED
@@ -21,7 +21,7 @@ namespace :db do
|
|
21
21
|
desc "Rollbacks database migrations"
|
22
22
|
task :rollback, [:step] => :environment do |t, args|
|
23
23
|
target = args[:step] && (step = args[:step].to_i) > 0 ?
|
24
|
-
current_migration_version - step :
|
24
|
+
current_migration_version - step : current_migration_version - 1
|
25
25
|
|
26
26
|
Sequel::Migrator.run(StraightServer.db_connection, MIGRATIONS_ROOT, target: target)
|
27
27
|
dump_schema
|
data/spec/.straight/config.yml
CHANGED
@@ -7,9 +7,9 @@ expiration_overtime: 0
|
|
7
7
|
reuse_address_orders_threshold: 5
|
8
8
|
|
9
9
|
gateways:
|
10
|
-
|
11
10
|
default:
|
12
11
|
pubkey: 'xpub6Arp6y5VVQzq3LWTHz7gGsGKAdM697RwpWgauxmyCybncqoAYim6P63AasNKSy3VUAYXFj7tN2FZ9CM9W7yTfmerdtAPU4amuSNjEKyDeo6'
|
12
|
+
test_pubkey: 'tpubDCzMzH5R7dvZAN7jNyZRUXxuo8XdRmMd7gmzvHs9LYG4w2EBvEjQ1Drm8ZXv4uwxrtUh3MqCZQJaq56oPMghsbtFnoLi9JBfG7vRLXLH21r'
|
13
13
|
confirmations_required: 0
|
14
14
|
order_class: "StraightServer::Order"
|
15
15
|
secret: 'secret'
|
@@ -22,6 +22,7 @@ gateways:
|
|
22
22
|
- Coinbase
|
23
23
|
- Bitstamp
|
24
24
|
active: true
|
25
|
+
test_mode: true
|
25
26
|
second_gateway:
|
26
27
|
pubkey: 'xpub6AH1Ymkkrwk3TaMrVrXBCpcGajKc9a1dAJBTKr1i4GwYLgLk7WDvPtN1o1cAqS5DZ9CYzn3gZtT7BHEP4Qpsz24UELTncPY1Zsscsm3ajmX'
|
27
28
|
confirmations_required: 0
|
@@ -38,9 +39,13 @@ gateways:
|
|
38
39
|
active: true
|
39
40
|
|
40
41
|
blockchain_adapters:
|
42
|
+
- Insight
|
41
43
|
- BlockchainInfo
|
42
44
|
- Mycelium
|
43
45
|
|
46
|
+
insight_url: "https://insight.mycelium.com/api"
|
47
|
+
insight_test_url: "https://test-insight.bitpay.com"
|
48
|
+
|
44
49
|
db:
|
45
50
|
adapter: sqlite
|
46
51
|
name: straight.db
|
@@ -48,5 +53,5 @@ db:
|
|
48
53
|
redis:
|
49
54
|
host: localhost
|
50
55
|
port: 6379
|
51
|
-
db: null
|
52
|
-
password: null
|
56
|
+
# db: null
|
57
|
+
# password: null
|
@@ -0,0 +1 @@
|
|
1
|
+
0
|
data/spec/factories.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
FactoryGirl.define do
|
2
|
-
|
2
|
+
|
3
3
|
factory :order, class: StraightServer::Order do
|
4
4
|
sequence(:id) { |i| i }
|
5
5
|
sequence(:keychain_id) { |i| i }
|
6
6
|
sequence(:address) { |i| "address_#{i}" }
|
7
7
|
amount 10
|
8
8
|
gateway_id 1
|
9
|
+
to_create { |order| order.save }
|
9
10
|
end
|
10
11
|
|
11
12
|
end
|
data/spec/lib/gateway_spec.rb
CHANGED
@@ -6,34 +6,16 @@ RSpec.describe StraightServer::Gateway do
|
|
6
6
|
@gateway = StraightServer::GatewayOnConfig.find_by_id(1)
|
7
7
|
@order_mock = double("order mock")
|
8
8
|
allow(@order_mock).to receive(:old_status)
|
9
|
+
allow(@order_mock).to receive(:description=)
|
10
|
+
allow(@order_mock).to receive(:set_amount_paid)
|
9
11
|
allow(@order_mock).to receive(:reused).and_return(0)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
it "checks for signature when creating a new order" do
|
15
|
-
@gateway.last_keychain_id = 0
|
16
|
-
expect( -> { @gateway.create_order(amount: 1, signature: 'invalid', id: 1) }).to raise_exception(StraightServer::GatewayModule::InvalidSignature)
|
17
|
-
expect(@gateway).to receive(:order_for_keychain_id).with(@order_for_keychain_id_args).once.and_return(@order_mock)
|
18
|
-
@gateway.create_order(amount: 1, signature: hmac_sha256(1, 'secret'), keychain_id: 1)
|
19
|
-
end
|
20
|
-
|
21
|
-
it "checks md5 signature only if that setting is set ON for a particular gateway" do
|
22
|
-
gateway1 = StraightServer::GatewayOnConfig.find_by_id(1)
|
23
|
-
gateway2 = StraightServer::GatewayOnConfig.find_by_id(2)
|
24
|
-
expect(gateway2).to receive(:order_for_keychain_id).with(@order_for_keychain_id_args).once.and_return(@order_mock)
|
25
|
-
expect( -> { gateway1.create_order(amount: 1, signature: 'invalid') }).to raise_exception
|
26
|
-
expect( -> { gateway2.create_order(amount: 1, signature: 'invalid') }).not_to raise_exception()
|
27
|
-
end
|
28
|
-
|
29
|
-
it "doesn't allow nil or empty order id if signature checks are enabled" do
|
30
|
-
expect( -> { @gateway.create_order(amount: 1, signature: hmac_sha256(nil, 'secret'), id: nil) }).to raise_exception(StraightServer::GatewayModule::InvalidOrderId)
|
31
|
-
expect( -> { @gateway.create_order(amount: 1, signature: hmac_sha256('', 'secret'), id: '') }).to raise_exception(StraightServer::GatewayModule::InvalidOrderId)
|
12
|
+
allow(@order_mock).to receive(:test_mode)
|
13
|
+
[:id, :gateway=, :save, :to_h, :id=, :test_mode=, :test_mode].each { |m| allow(@order_mock).to receive(m) }
|
14
|
+
@new_order_args = { amount: 1, keychain_id: 1, currency: nil, btc_denomination: nil }
|
32
15
|
end
|
33
16
|
|
34
17
|
it "sets order amount in satoshis calculated from another currency" do
|
35
18
|
@gateway = StraightServer::GatewayOnConfig.find_by_id(2)
|
36
|
-
allow(@gateway).to receive(:address_for_keychain_id).and_return('address')
|
37
19
|
allow(@gateway.exchange_rate_adapters.first).to receive(:rate_for).and_return(450.5412)
|
38
20
|
expect(@gateway.create_order(amount: 2252.706, currency: 'USD').amount).to eq(500000000)
|
39
21
|
end
|
@@ -45,21 +27,38 @@ RSpec.describe StraightServer::Gateway do
|
|
45
27
|
end
|
46
28
|
|
47
29
|
it "loads blockchain adapters according to the config file" do
|
48
|
-
|
30
|
+
gateway = StraightServer::GatewayOnConfig.find_by_id(2)
|
31
|
+
expect(gateway.blockchain_adapters.map(&:class)).to eq([Straight::Blockchain::InsightAdapter,
|
32
|
+
Straight::Blockchain::BlockchainInfoAdapter,
|
33
|
+
Straight::Blockchain::MyceliumAdapter])
|
34
|
+
end
|
35
|
+
|
36
|
+
it "loads Insight adapter with given host url" do
|
37
|
+
gateway = StraightServer::GatewayOnConfig.find_by_id(2)
|
38
|
+
expect(gateway.blockchain_adapters.first.class).to eq(Straight::Blockchain::InsightAdapter)
|
49
39
|
end
|
50
40
|
|
51
41
|
it "updates last_keychain_id to the new value provided in keychain_id if it's larger than the last_keychain_id" do
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
42
|
+
gateway = StraightServer::GatewayOnConfig.find_by_id(2)
|
43
|
+
gateway.create_order(amount: 2252.706, currency: 'BTC', keychain_id: 100)
|
44
|
+
expect(gateway.last_keychain_id).to eq(100)
|
45
|
+
gateway.create_order(amount: 2252.706, currency: 'BTC', keychain_id: 150)
|
46
|
+
expect(gateway.last_keychain_id).to eq(150)
|
47
|
+
gateway.create_order(amount: 2252.706, currency: 'BTC', keychain_id: 50)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "only warns about an invalid Bitcoin address, but doesn't fail", one: true do
|
51
|
+
expect(StraightServer.logger).to receive(:warn)
|
52
|
+
allow_any_instance_of(Straight::Blockchain::MyceliumAdapter).to receive(:api_request).and_return(nil)
|
53
|
+
allow_any_instance_of(Straight::Blockchain::InsightAdapter).to receive(:api_request).and_return(nil)
|
54
|
+
# mainnet Bitcoin address, while we're on testnet
|
55
|
+
expect(@gateway.fetch_transactions_for('12X3JTpcGPS1GXmuJn9gT3gspP6YFsFT6W')).to eq([])
|
57
56
|
end
|
58
57
|
|
59
58
|
context "reusing addresses" do
|
60
59
|
|
61
60
|
# Config.reuse_address_orders_threshold for the test env is 5
|
62
|
-
|
61
|
+
|
63
62
|
before(:each) do
|
64
63
|
@gateway = StraightServer::GatewayOnConfig.find_by_id(2)
|
65
64
|
allow(@gateway).to receive(:order_status_changed).with(anything).and_return([])
|
@@ -79,7 +78,7 @@ RSpec.describe StraightServer::Gateway do
|
|
79
78
|
it "picks an expired order which address is going to be reused" do
|
80
79
|
expect(@gateway.find_reusable_order).to eq(@expired_orders_1.last)
|
81
80
|
end
|
82
|
-
|
81
|
+
|
83
82
|
it "picks an expired order which address is going to be reused only when this address received no transactions" do
|
84
83
|
allow(@gateway).to receive(:fetch_transactions_for).with(@expired_orders_1.last.address).and_return(['transaction'])
|
85
84
|
expect(@gateway.find_reusable_order).to eq(nil)
|
@@ -87,30 +86,30 @@ RSpec.describe StraightServer::Gateway do
|
|
87
86
|
|
88
87
|
it "creates a new order with a reused address" do
|
89
88
|
reused_order = @expired_orders_1.last
|
90
|
-
order = @gateway.create_order(amount: 2252.706, currency: '
|
89
|
+
order = @gateway.create_order(amount: 2252.706, currency: 'BTC')
|
91
90
|
expect(order.keychain_id).to eq(reused_order.keychain_id)
|
92
|
-
expect(order.address).to eq(@gateway.
|
91
|
+
expect(order.address).to eq(@gateway.address_provider.new_address(keychain_id: reused_order.keychain_id))
|
93
92
|
expect(order.reused).to eq(1)
|
94
93
|
end
|
95
94
|
|
96
95
|
it "doesn't increment last_keychain_id if order is reused" do
|
97
96
|
last_keychain_id = @gateway.last_keychain_id
|
98
|
-
order = @gateway.create_order(amount: 2252.706, currency: '
|
99
|
-
expect(@gateway.last_keychain_id).to eq(last_keychain_id)
|
97
|
+
order = @gateway.create_order(amount: 2252.706, currency: 'BTC')
|
98
|
+
expect(@gateway.last_keychain_id).to eq(last_keychain_id)
|
100
99
|
|
101
100
|
order.status = StraightServer::Order::STATUSES[:paid]
|
102
101
|
order.save
|
103
|
-
order_2 = @gateway.create_order(amount: 2252.706, currency: '
|
104
|
-
expect(@gateway.last_keychain_id).to eq(last_keychain_id+1)
|
102
|
+
order_2 = @gateway.create_order(amount: 2252.706, currency: 'BTC')
|
103
|
+
expect(@gateway.last_keychain_id).to eq(last_keychain_id+1)
|
105
104
|
end
|
106
105
|
|
107
106
|
it "after the reused order was paid, gives next order a new keychain_id" do
|
108
|
-
order = @gateway.create_order(amount: 2252.706, currency: '
|
107
|
+
order = @gateway.create_order(amount: 2252.706, currency: 'BTC')
|
109
108
|
order.status = StraightServer::Order::STATUSES[:expired]
|
110
109
|
order.save
|
111
|
-
expect(order.keychain_id).to eq(@expired_orders_1.last.keychain_id)
|
110
|
+
expect(order.keychain_id).to eq(@expired_orders_1.last.keychain_id)
|
112
111
|
|
113
|
-
order = @gateway.create_order(amount: 2252.706, currency: '
|
112
|
+
order = @gateway.create_order(amount: 2252.706, currency: 'BTC')
|
114
113
|
order.status = StraightServer::Order::STATUSES[:paid]
|
115
114
|
order.save
|
116
115
|
expect(@gateway.send(:find_expired_orders_row).map(&:id)).to be_empty
|
@@ -121,53 +120,47 @@ RSpec.describe StraightServer::Gateway do
|
|
121
120
|
context "callback url" do
|
122
121
|
|
123
122
|
before(:each) do
|
124
|
-
@gateway = StraightServer::GatewayOnConfig.find_by_id(2)
|
125
|
-
@
|
126
|
-
expect(@response_mock).to receive(:body).once.and_return('body')
|
127
|
-
@order = create(:order)
|
123
|
+
@gateway = StraightServer::GatewayOnConfig.find_by_id(2)
|
124
|
+
@order = build(:order, gateway: @gateway, address: 'address_1', keychain_id: 1, id: 1)
|
128
125
|
allow(@order).to receive(:status).and_return(1)
|
129
126
|
allow(@order).to receive(:tid).and_return('tid1')
|
130
127
|
end
|
131
128
|
|
132
|
-
it "sends a request to the callback_url" do
|
133
|
-
|
134
|
-
|
129
|
+
it "sends a request to the callback_url and saves response" do
|
130
|
+
stub_request(:get, 'http://localhost:3001/payment-callback?address=address_1&amount=10&amount_in_btc=0.0000001&amount_paid_in_btc=0.0&keychain_id=1&last_keychain_id=0&order_id=1&status=1&tid=tid1').
|
131
|
+
with(headers: {'X-Signature' => 'u7DrzCaVirO1gehac9Fvkh4bMfR5lTn8FI8lHOvZzuEvHEGJrdFHTm6k6Q+fpTuszXG0ftKBc4a1xclpZjpTHA=='}).
|
132
|
+
to_return(status: 200, body: 'okay')
|
133
|
+
expect(@gateway).to receive(:sleep).exactly(0).times
|
135
134
|
@gateway.order_status_changed(@order)
|
135
|
+
expect(@order.callback_response).to eq(code: '200', body: 'okay')
|
136
136
|
end
|
137
137
|
|
138
138
|
it "keeps sending request according to the callback schedule if there's an error" do
|
139
|
-
|
139
|
+
stub_request(:get, 'http://localhost:3001/payment-callback?address=address_1&amount=10&amount_in_btc=0.0000001&amount_paid_in_btc=0.0&keychain_id=1&last_keychain_id=0&order_id=1&status=1&tid=tid1').
|
140
|
+
with(headers: {'X-Signature' => 'u7DrzCaVirO1gehac9Fvkh4bMfR5lTn8FI8lHOvZzuEvHEGJrdFHTm6k6Q+fpTuszXG0ftKBc4a1xclpZjpTHA=='}).
|
141
|
+
to_return(status: 404, body: '')
|
140
142
|
expect(@gateway).to receive(:sleep).exactly(10).times
|
141
|
-
expect(Net::HTTP).to receive(:get_response).exactly(11).times.and_return(@response_mock)
|
142
|
-
expect(URI).to receive(:parse).with('http://localhost:3001/payment-callback?' + @order.to_http_params).exactly(11).times
|
143
|
-
@gateway.order_status_changed(@order)
|
144
|
-
end
|
145
|
-
|
146
|
-
it "signs the callback if gateway has a secret" do
|
147
|
-
@gateway = StraightServer::GatewayOnConfig.find_by_id(1) # Gateway 1 requires signatures
|
148
|
-
expect(@response_mock).to receive(:code).twice.and_return("200")
|
149
|
-
expect(URI).to receive(:parse).with('http://localhost:3000/payment-callback?' + @order.to_http_params + "&signature=#{hmac_sha256(@order.id, 'secret')}")
|
150
|
-
expect(Net::HTTP).to receive(:get_response).and_return(@response_mock)
|
151
143
|
@gateway.order_status_changed(@order)
|
152
144
|
end
|
153
145
|
|
154
146
|
it "receives random data in :data params and sends it back in a callback request" do
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
expect(
|
160
|
-
|
147
|
+
uri = "/payment-callback?order_id=1&amount=10&amount_in_btc=0.0000001&amount_paid_in_btc=0.0&status=1&address=address_1&tid=tid1&keychain_id=1&last_keychain_id=1&callback_data=so%3Fme+ran%26dom+data"
|
148
|
+
signature = 'Q6X8n1W4/oTZ9aULeAxZ9E/yPWvs8yiawvttupVor+naITtCqe5bUrRDuDYJYcSPZ7Z3l0T8CyqhYfgX6R+qdw=='
|
149
|
+
expect(StraightServer::SignatureValidator.signature(nonce: nil, body: nil, method: 'GET', request_uri: uri, secret: @gateway.secret)).to eq signature
|
150
|
+
stub_request(:get, "http://localhost:3001#{uri}").with(headers: {'X-Signature' => signature}).to_return(status: 200, body: '')
|
151
|
+
expect(@gateway).to receive(:new_order).with(@new_order_args).once.and_return(@order)
|
152
|
+
@gateway.create_order(amount: 1, callback_data: 'so?me ran&dom data')
|
161
153
|
@gateway.order_status_changed(@order)
|
162
154
|
end
|
163
155
|
|
164
|
-
it "
|
165
|
-
|
166
|
-
|
156
|
+
it "uses callback_url from order when making callback" do
|
157
|
+
uri = '/?with=params&order_id=1&amount=10&amount_in_btc=0.0000001&amount_paid_in_btc=0.0&status=1&address=address_1&tid=tid1&keychain_id=1&last_keychain_id=0'
|
158
|
+
signature = 'MtRdAHH6lxsdD8LaoKsKyw/RDzfsh/OimmsciXhaAGyFFcm5/7bBKPygKn+41DHBK65gNcO/abgxOBR1Se2FdA=='
|
159
|
+
expect(StraightServer::SignatureValidator.signature(nonce: nil, body: nil, method: 'GET', request_uri: uri, secret: @gateway.secret)).to eq signature
|
160
|
+
stub_request(:get, "http://new_url#{uri}").with(headers: {'X-Signature' => signature}).to_return(status: 200, body: '')
|
161
|
+
@order.callback_url = 'http://new_url?with=params'
|
167
162
|
@gateway.order_status_changed(@order)
|
168
|
-
expect(@order.callback_response).to eq({code: "200", body: "body"})
|
169
163
|
end
|
170
|
-
|
171
164
|
end
|
172
165
|
|
173
166
|
describe "order counters" do
|
@@ -180,8 +173,8 @@ RSpec.describe StraightServer::Gateway do
|
|
180
173
|
|
181
174
|
it "raises exception when trying to access counters but the feature is disabled" do
|
182
175
|
allow(StraightServer::Config).to receive(:count_orders).and_return(false)
|
183
|
-
expect( -> { @gateway.order_counters(reload: true) }).to raise_exception(StraightServer::Gateway::OrderCountersDisabled)
|
184
|
-
expect( -> { @gateway.increment_order_counter!(:new) }).to raise_exception(StraightServer::Gateway::OrderCountersDisabled)
|
176
|
+
expect( -> { @gateway.order_counters(reload: true) }).to raise_exception(StraightServer::Gateway::OrderCountersDisabled)
|
177
|
+
expect( -> { @gateway.increment_order_counter!(:new) }).to raise_exception(StraightServer::Gateway::OrderCountersDisabled)
|
185
178
|
end
|
186
179
|
|
187
180
|
it "updates gateway's order counters when an associated order status changes" do
|
@@ -189,19 +182,19 @@ RSpec.describe StraightServer::Gateway do
|
|
189
182
|
allow(@gateway).to receive(:send_callback_http_request)
|
190
183
|
allow(@gateway).to receive(:send_order_to_websocket_client)
|
191
184
|
|
192
|
-
expect(@gateway.order_counters(reload: true)).to eq({ new: 0, unconfirmed: 0, paid: 0, underpaid: 0, overpaid: 0, expired: 0 })
|
185
|
+
expect(@gateway.order_counters(reload: true)).to eq({ new: 0, unconfirmed: 0, paid: 0, underpaid: 0, overpaid: 0, expired: 0, canceled: 0 })
|
193
186
|
order = create(:order, gateway_id: @gateway.id)
|
194
|
-
expect(@gateway.order_counters(reload: true)).to eq({ new: 1, unconfirmed: 0, paid: 0, underpaid: 0, overpaid: 0, expired: 0 })
|
187
|
+
expect(@gateway.order_counters(reload: true)).to eq({ new: 1, unconfirmed: 0, paid: 0, underpaid: 0, overpaid: 0, expired: 0, canceled: 0 })
|
195
188
|
order.status = 2
|
196
|
-
expect(@gateway.order_counters(reload: true)).to eq({ new: 0, unconfirmed: 0, paid: 1, underpaid: 0, overpaid: 0, expired: 0 })
|
189
|
+
expect(@gateway.order_counters(reload: true)).to eq({ new: 0, unconfirmed: 0, paid: 1, underpaid: 0, overpaid: 0, expired: 0, canceled: 0 })
|
197
190
|
|
198
|
-
expect(@gateway.order_counters(reload: true)).to eq({ new: 0, unconfirmed: 0, paid: 1, underpaid: 0, overpaid: 0, expired: 0 })
|
191
|
+
expect(@gateway.order_counters(reload: true)).to eq({ new: 0, unconfirmed: 0, paid: 1, underpaid: 0, overpaid: 0, expired: 0, canceled: 0 })
|
199
192
|
order = create(:order, gateway_id: @gateway.id)
|
200
|
-
expect(@gateway.order_counters(reload: true)).to eq({ new: 1, unconfirmed: 0, paid: 1, underpaid: 0, overpaid: 0, expired: 0 })
|
193
|
+
expect(@gateway.order_counters(reload: true)).to eq({ new: 1, unconfirmed: 0, paid: 1, underpaid: 0, overpaid: 0, expired: 0, canceled: 0 })
|
201
194
|
order.status = 1
|
202
|
-
expect(@gateway.order_counters(reload: true)).to eq({ new: 0, unconfirmed: 1, paid: 1, underpaid: 0, overpaid: 0, expired: 0 })
|
195
|
+
expect(@gateway.order_counters(reload: true)).to eq({ new: 0, unconfirmed: 1, paid: 1, underpaid: 0, overpaid: 0, expired: 0, canceled: 0 })
|
203
196
|
order.status = 5
|
204
|
-
expect(@gateway.order_counters(reload: true)).to eq({ new: 0, unconfirmed: 0, paid: 1, underpaid: 0, overpaid: 0, expired: 1 })
|
197
|
+
expect(@gateway.order_counters(reload: true)).to eq({ new: 0, unconfirmed: 0, paid: 1, underpaid: 0, overpaid: 0, expired: 1, canceled: 0 })
|
205
198
|
end
|
206
199
|
|
207
200
|
it "doesn't increment orders on status update unless the option is turned on (but no exception raised)" do
|
@@ -210,7 +203,7 @@ RSpec.describe StraightServer::Gateway do
|
|
210
203
|
allow(@gateway).to receive(:send_callback_http_request)
|
211
204
|
allow(@gateway).to receive(:send_order_to_websocket_client)
|
212
205
|
order = create(:order, gateway_id: @gateway.id)
|
213
|
-
expect(StraightServer
|
206
|
+
expect(StraightServer.redis_connection.get("#{StraightServer::Config.redis[:prefix]}:gateway_#{@gateway.id}:new_orders_counter")).to be_nil
|
214
207
|
end
|
215
208
|
|
216
209
|
end
|
@@ -223,33 +216,79 @@ RSpec.describe StraightServer::Gateway do
|
|
223
216
|
expect(gateway1).to be_kind_of(StraightServer::GatewayOnConfig)
|
224
217
|
expect(gateway2).to be_kind_of(StraightServer::GatewayOnConfig)
|
225
218
|
|
226
|
-
expect(gateway1.pubkey).to eq('xpub6Arp6y5VVQzq3LWTHz7gGsGKAdM697RwpWgauxmyCybncqoAYim6P63AasNKSy3VUAYXFj7tN2FZ9CM9W7yTfmerdtAPU4amuSNjEKyDeo6')
|
227
|
-
expect(gateway1.confirmations_required).to eq(0)
|
228
|
-
expect(gateway1.order_class).to eq("StraightServer::Order")
|
229
|
-
expect(gateway1.name).to eq("default")
|
219
|
+
expect(gateway1.pubkey).to eq('xpub6Arp6y5VVQzq3LWTHz7gGsGKAdM697RwpWgauxmyCybncqoAYim6P63AasNKSy3VUAYXFj7tN2FZ9CM9W7yTfmerdtAPU4amuSNjEKyDeo6')
|
220
|
+
expect(gateway1.confirmations_required).to eq(0)
|
221
|
+
expect(gateway1.order_class).to eq("StraightServer::Order")
|
222
|
+
expect(gateway1.name).to eq("default")
|
230
223
|
|
231
|
-
expect(gateway2.pubkey).to eq('xpub6AH1Ymkkrwk3TaMrVrXBCpcGajKc9a1dAJBTKr1i4GwYLgLk7WDvPtN1o1cAqS5DZ9CYzn3gZtT7BHEP4Qpsz24UELTncPY1Zsscsm3ajmX')
|
232
|
-
expect(gateway2.confirmations_required).to eq(0)
|
233
|
-
expect(gateway2.order_class).to eq("StraightServer::Order")
|
234
|
-
expect(gateway2.name).to eq("second_gateway")
|
224
|
+
expect(gateway2.pubkey).to eq('xpub6AH1Ymkkrwk3TaMrVrXBCpcGajKc9a1dAJBTKr1i4GwYLgLk7WDvPtN1o1cAqS5DZ9CYzn3gZtT7BHEP4Qpsz24UELTncPY1Zsscsm3ajmX')
|
225
|
+
expect(gateway2.confirmations_required).to eq(0)
|
226
|
+
expect(gateway2.order_class).to eq("StraightServer::Order")
|
227
|
+
expect(gateway2.name).to eq("second_gateway")
|
235
228
|
end
|
236
229
|
|
237
230
|
it "saves and retrieves last_keychain_id from the file in the .straight dir" do
|
231
|
+
@gateway = StraightServer::GatewayOnConfig.find_by_id(2)
|
238
232
|
@gateway.check_signature = false
|
239
|
-
|
233
|
+
@gateway.save
|
234
|
+
expect(File.read("#{ENV['HOME']}/.straight/second_gateway_last_keychain_id").to_i).to eq(0)
|
240
235
|
@gateway.update_last_keychain_id
|
241
236
|
@gateway.save
|
242
|
-
expect(File.read("#{ENV['HOME']}/.straight/
|
237
|
+
expect(File.read("#{ENV['HOME']}/.straight/second_gateway_last_keychain_id").to_i).to eq(1)
|
243
238
|
|
244
|
-
expect(@gateway).to receive(:
|
239
|
+
expect(@gateway).to receive(:new_order).with(@new_order_args.merge({ keychain_id: 2})).once.and_return(@order_mock)
|
245
240
|
@gateway.create_order(amount: 1)
|
246
|
-
expect(File.read("#{ENV['HOME']}/.straight/
|
241
|
+
expect(File.read("#{ENV['HOME']}/.straight/second_gateway_last_keychain_id").to_i).to eq(2)
|
247
242
|
end
|
248
|
-
|
243
|
+
|
249
244
|
it "searches for Gateway using regular ids when find_by_hashed_id method is called" do
|
250
245
|
expect(StraightServer::GatewayOnConfig.find_by_hashed_id(1)).not_to be_nil
|
251
246
|
end
|
252
247
|
|
248
|
+
it "set test mode `on` based on config" do
|
249
|
+
expect(@gateway.test_mode).to be true
|
250
|
+
end
|
251
|
+
|
252
|
+
it "set test mode `off`" do
|
253
|
+
@gateway = StraightServer::GatewayOnConfig.find_by_id(2)
|
254
|
+
expect(@gateway.test_mode).to be false
|
255
|
+
end
|
256
|
+
|
257
|
+
it "using testnet when test mode is enabled" do
|
258
|
+
@gateway = StraightServer::GatewayOnConfig.find_by_id(1)
|
259
|
+
expect(@gateway.blockchain_adapters).to_not be nil
|
260
|
+
expect(@gateway.blockchain_adapters.map(&:class)).to eq(@gateway.test_blockchain_adapters.map(&:class))
|
261
|
+
end
|
262
|
+
|
263
|
+
it "using testnet adapter that url given in config" do
|
264
|
+
@gateway = StraightServer::GatewayOnConfig.find_by_id(1)
|
265
|
+
expect(@gateway.blockchain_adapters).to_not be nil
|
266
|
+
expect(@gateway.test_blockchain_adapters.first.class).to eq(Straight::Blockchain::InsightAdapter)
|
267
|
+
end
|
268
|
+
|
269
|
+
it "fallback to another adapter if on previous one not ready for testnet" do
|
270
|
+
@gateway = StraightServer::GatewayOnConfig.find_by_id(1)
|
271
|
+
Straight::Blockchain::InsightAdapter.class_eval("def self.test_url=(val) @@test_url=val end")
|
272
|
+
Straight::Blockchain::InsightAdapter.test_url = nil
|
273
|
+
expect(@gateway.test_blockchain_adapters.first.class).to eq(Straight::Blockchain::MyceliumAdapter)
|
274
|
+
end
|
275
|
+
|
276
|
+
it "disable test mode manually" do
|
277
|
+
@gateway = StraightServer::GatewayOnConfig.find_by_id(1)
|
278
|
+
@gateway.test_mode = false
|
279
|
+
expect(@gateway.blockchain_adapters).to_not eq([Straight::Blockchain::MyceliumAdapter.testnet_adapter])
|
280
|
+
end
|
281
|
+
|
282
|
+
it "save test_last_keychain_id file with approciate data" do
|
283
|
+
gateway = StraightServer::GatewayOnConfig.find_by_id(1)
|
284
|
+
gateway.check_signature = false
|
285
|
+
gateway.save
|
286
|
+
expect(File.read("#{ENV['HOME']}/.straight/default_test_last_keychain_id").to_i).to eq(0)
|
287
|
+
gateway.update_last_keychain_id
|
288
|
+
gateway.save
|
289
|
+
expect(File.read("#{ENV['HOME']}/.straight/default_test_last_keychain_id").to_i).to eq(1)
|
290
|
+
end
|
291
|
+
|
253
292
|
end
|
254
293
|
|
255
294
|
describe "db based gateway" do
|
@@ -268,16 +307,17 @@ RSpec.describe StraightServer::Gateway do
|
|
268
307
|
exchange_rate_adapter_names: ['Bitpay', 'Coinbase', 'Bitstamp']
|
269
308
|
)
|
270
309
|
end
|
271
|
-
|
310
|
+
|
272
311
|
it "saves and retrieves last_keychain_id from the db" do
|
273
312
|
@gateway.check_signature = false
|
274
313
|
@gateway.save
|
275
314
|
expect(DB[:gateways][:name => 'default'][:last_keychain_id]).to eq(0)
|
276
315
|
@gateway.update_last_keychain_id
|
277
316
|
@gateway.save
|
317
|
+
@gateway.refresh
|
278
318
|
expect(DB[:gateways][:name => 'default'][:last_keychain_id]).to eq(1)
|
279
319
|
|
280
|
-
expect(@gateway).to receive(:
|
320
|
+
expect(@gateway).to receive(:new_order).with(@new_order_args.merge({ keychain_id: 2})).once.and_return(@order_mock)
|
281
321
|
@gateway.create_order(amount: 1)
|
282
322
|
expect(DB[:gateways][:name => 'default'][:last_keychain_id]).to eq(2)
|
283
323
|
end
|
@@ -306,6 +346,94 @@ RSpec.describe StraightServer::Gateway do
|
|
306
346
|
expect(StraightServer::GatewayOnDB.find_by_hashed_id(hashed_id)).to eq(@gateway)
|
307
347
|
end
|
308
348
|
|
349
|
+
context "test mode" do
|
350
|
+
|
351
|
+
before(:each) do
|
352
|
+
@gateway.test_pubkey = "txpub"
|
353
|
+
end
|
354
|
+
|
355
|
+
it "not activate after created" do
|
356
|
+
@gateway.save
|
357
|
+
expect(@gateway.test_mode).to be false
|
358
|
+
end
|
359
|
+
|
360
|
+
it "not using testnet adapter by default" do
|
361
|
+
@gateway.save
|
362
|
+
expect(@gateway.blockchain_adapters.map(&:class)).to_not eq(@gateway.test_blockchain_adapters.map(&:class))
|
363
|
+
end
|
364
|
+
|
365
|
+
it "activated if mode is specified explicity" do
|
366
|
+
@gateway.test_mode = true
|
367
|
+
@gateway.save
|
368
|
+
@gateway.refresh
|
369
|
+
expect(@gateway.test_mode).to be true
|
370
|
+
expect(@gateway.blockchain_adapters.map(&:class)).to eq(@gateway.test_blockchain_adapters.map(&:class))
|
371
|
+
end
|
372
|
+
|
373
|
+
it "enabled and not saved" do
|
374
|
+
@gateway.save
|
375
|
+
@gateway.enable_test_mode!
|
376
|
+
expect(@gateway.test_mode).to be true
|
377
|
+
@gateway.refresh
|
378
|
+
expect(@gateway.test_mode).to be false
|
379
|
+
end
|
380
|
+
|
381
|
+
it "enabled and saved" do
|
382
|
+
@gateway.save
|
383
|
+
@gateway.enable_test_mode!
|
384
|
+
expect(@gateway.test_mode).to be true
|
385
|
+
@gateway.refresh
|
386
|
+
expect(@gateway.test_mode).to be false
|
387
|
+
end
|
388
|
+
|
389
|
+
it "enabled and saved" do
|
390
|
+
@gateway[:test_mode] = false
|
391
|
+
expect(@gateway.test_mode).to eq false
|
392
|
+
@gateway.enable_test_mode!
|
393
|
+
@gateway.save
|
394
|
+
@gateway.refresh
|
395
|
+
expect(@gateway.test_mode).to be true
|
396
|
+
end
|
397
|
+
|
398
|
+
it "field updates in mass assigment" do
|
399
|
+
@gateway.save
|
400
|
+
fields = {test_mode: true}
|
401
|
+
@gateway.update(fields)
|
402
|
+
@gateway.refresh
|
403
|
+
expect(@gateway.test_mode).to be true
|
404
|
+
end
|
405
|
+
|
406
|
+
it "update test_last_keychain_id" do
|
407
|
+
@gateway.check_signature = false
|
408
|
+
@gateway.enable_test_mode!
|
409
|
+
@gateway.save
|
410
|
+
@gateway.refresh
|
411
|
+
|
412
|
+
expect(@gateway).to receive(:new_order).with(@new_order_args).once.and_return(@order_mock)
|
413
|
+
@gateway.create_order(amount: 1)
|
414
|
+
expect(DB[:gateways][:name => 'default'][:test_last_keychain_id]).to eq(1)
|
415
|
+
end
|
416
|
+
|
417
|
+
it "validate that test public key is provided when saving with test mode flag" do
|
418
|
+
@gateway = StraightServer::GatewayOnDB.new(
|
419
|
+
confirmations_required: 0,
|
420
|
+
pubkey: 'xpub-000',
|
421
|
+
test_mode: true
|
422
|
+
)
|
423
|
+
expect(@gateway.valid?).to be false
|
424
|
+
expect(@gateway.errors[:test_pubkey]).to_not be_empty
|
425
|
+
end
|
426
|
+
|
427
|
+
it "doesn't require pubkey in test mode" do
|
428
|
+
@gateway = StraightServer::GatewayOnDB.new(
|
429
|
+
test_pubkey: 'xpub-000',
|
430
|
+
test_mode: true,
|
431
|
+
)
|
432
|
+
expect(@gateway.valid?).to be true
|
433
|
+
@gateway.test_mode = false
|
434
|
+
expect(@gateway.valid?).to be false
|
435
|
+
end
|
436
|
+
end
|
309
437
|
end
|
310
438
|
|
311
439
|
describe "handling websockets" do
|