straight-server 0.2.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|