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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/Gemfile +21 -16
  4. data/Gemfile.lock +44 -30
  5. data/Gemfile.travis +15 -16
  6. data/README.md +66 -47
  7. data/VERSION +1 -1
  8. data/db/migrations/011_add_callback_data_to_orders.rb +1 -1
  9. data/db/migrations/012_add_address_provider.rb +11 -0
  10. data/db/migrations/013_add_address_derivation_scheme.rb +11 -0
  11. data/db/migrations/014_pubkey_null_address_provider_not_null.rb +8 -0
  12. data/db/migrations/015_add_amount_paid_to_orders.rb +11 -0
  13. data/db/migrations/016_add_new_params_to_orders.rb +13 -0
  14. data/db/migrations/017_add_test_mode_to_gateways.rb +11 -0
  15. data/db/migrations/018_add_test_keychain_id_to_gateways.rb +11 -0
  16. data/db/migrations/019_add_test_pubkey_to_gateways.rb +11 -0
  17. data/db/migrations/020_add_test_mode_to_orders.rb +11 -0
  18. data/db/schema.rb +11 -1
  19. data/lib/straight-server.rb +11 -9
  20. data/lib/straight-server/config.rb +28 -18
  21. data/lib/straight-server/gateway.rb +167 -87
  22. data/lib/straight-server/initializer.rb +13 -7
  23. data/lib/straight-server/order.rb +39 -17
  24. data/lib/straight-server/orders_controller.rb +71 -21
  25. data/lib/straight-server/random_string.rb +3 -13
  26. data/lib/straight-server/server.rb +3 -4
  27. data/lib/straight-server/signature_validator.rb +69 -0
  28. data/lib/straight-server/thread.rb +19 -4
  29. data/lib/straight-server/throttler.rb +7 -13
  30. data/lib/tasks/db.rake +1 -1
  31. data/spec/.straight/config.yml +8 -3
  32. data/spec/.straight/default_test_last_keychain_id +1 -0
  33. data/spec/factories.rb +2 -1
  34. data/spec/lib/gateway_spec.rb +222 -94
  35. data/spec/lib/initializer_spec.rb +1 -1
  36. data/spec/lib/order_spec.rb +26 -7
  37. data/spec/lib/orders_controller_spec.rb +65 -6
  38. data/spec/lib/signature_validator_spec.rb +72 -0
  39. data/spec/lib/thread_spec.rb +16 -0
  40. data/spec/lib/throttle_spec.rb +2 -2
  41. data/spec/spec_helper.rb +17 -22
  42. data/straight-server.gemspec +31 -12
  43. data/templates/config.yml +19 -10
  44. metadata +52 -11
@@ -1,9 +1,24 @@
1
1
  module StraightServer
2
-
3
2
  class Thread
4
- def self.new(&block)
5
- ::Thread.new(&block)
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 = "gateway_#{gateway_id}"
6
- @redis = Config.redis && Config.redis[:connection]
7
- @limit = @period = @ip_ban_duration = 0
8
- if Config.throttle
9
- @limit = Config.throttle[:requests_limit].to_i
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.redis[:prefix]}:Throttle:#{@id}:#{@period}_#{@limit}:#{Time.now.to_i / @period}:#{ip}"
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.redis[:prefix]}:BannedIP:#{ip}"
54
+ "#{Config[:'redis.prefix']}:BannedIP:#{ip}"
61
55
  end
62
56
  end
63
57
  end
@@ -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 : 0
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
@@ -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
@@ -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
@@ -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
- [:id, :gateway=, :save, :to_h, :id=].each { |m| allow(@order_mock).to receive(m) }
11
- @order_for_keychain_id_args = { amount: 1, keychain_id: 1, currency: nil, btc_denomination: nil }
12
- end
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
- expect(@gateway.blockchain_adapters.map(&:class)).to eq([Straight::Blockchain::BlockchainInfoAdapter, Straight::Blockchain::MyceliumAdapter])
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
- @gateway.create_order(amount: 2252.706, currency: 'USD', signature: hmac_sha256('100', 'secret'), keychain_id: 100)
53
- expect(@gateway.last_keychain_id).to eq(100)
54
- @gateway.create_order(amount: 2252.706, currency: 'USD', signature: hmac_sha256('150', 'secret'), keychain_id: 150)
55
- expect(@gateway.last_keychain_id).to eq(150)
56
- @gateway.create_order(amount: 2252.706, currency: 'USD', signature: hmac_sha256('50', 'secret'), keychain_id: 50)
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: 'USD')
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.address_for_keychain_id(reused_order.keychain_id))
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: 'USD')
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: 'USD')
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: 'USD')
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: 'USD')
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) # Gateway 2 doesn't require signatures
125
- @response_mock = double("http response mock")
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
- expect(@response_mock).to receive(:code).twice.and_return("200")
134
- expect(Net::HTTP).to receive(:get_response).and_return(@response_mock)
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
- expect(@response_mock).to receive(:code).twice.and_return("404")
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
- @order.data = 'some random data'
156
- expect(@gateway).to receive(:order_for_keychain_id).with(@order_for_keychain_id_args).once.and_return(@order)
157
- @gateway.create_order(amount: 1, callback_data: 'some random data')
158
- expect(@response_mock).to receive(:code).twice.and_return("200")
159
- expect(Net::HTTP).to receive(:get_response).and_return(@response_mock)
160
- expect(URI).to receive(:parse).with('http://localhost:3001/payment-callback?' + @order.to_http_params + "&callback_data=#{@order.data}")
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 "saves callback url response in the order's record in DB" do
165
- allow(@response_mock).to receive(:code).and_return("200")
166
- allow(Net::HTTP).to receive(:get_response).and_return(@response_mock)
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::Config.redis[:connection].get("#{StraightServer::Config.redis[:prefix]}:gateway_#{@gateway.id}:new_orders_counter")).to be_nil
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
- expect(File.read("#{ENV['HOME']}/.straight/default_last_keychain_id").to_i).to eq(0)
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/default_last_keychain_id").to_i).to eq(1)
237
+ expect(File.read("#{ENV['HOME']}/.straight/second_gateway_last_keychain_id").to_i).to eq(1)
243
238
 
244
- expect(@gateway).to receive(:order_for_keychain_id).with(@order_for_keychain_id_args.merge({ keychain_id: 2})).once.and_return(@order_mock)
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/default_last_keychain_id").to_i).to eq(2)
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(:order_for_keychain_id).with(@order_for_keychain_id_args.merge({ keychain_id: 2})).once.and_return(@order_mock)
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