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.
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