straight-server 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +138 -0
- data/LICENSE.txt +21 -0
- data/README.md +219 -0
- data/Rakefile +27 -0
- data/VERSION +1 -0
- data/bin/straight-console +12 -0
- data/bin/straight-server +6 -0
- data/db/migrations/001_create_orders.rb +26 -0
- data/db/migrations/002_create_gateways.rb +28 -0
- data/examples/client/client.dart +67 -0
- data/examples/client/client.html +32 -0
- data/lib/straight-server/config.rb +11 -0
- data/lib/straight-server/gateway.rb +260 -0
- data/lib/straight-server/initializer.rb +78 -0
- data/lib/straight-server/logger.rb +18 -0
- data/lib/straight-server/order.rb +62 -0
- data/lib/straight-server/orders_controller.rb +86 -0
- data/lib/straight-server/server.rb +52 -0
- data/lib/straight-server/thread.rb +9 -0
- data/lib/straight-server.rb +25 -0
- data/spec/.straight/config.yml +34 -0
- data/spec/factories.rb +11 -0
- data/spec/lib/gateway_spec.rb +191 -0
- data/spec/lib/order_spec.rb +82 -0
- data/spec/lib/orders_controller_spec.rb +113 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/support/custom_matchers.rb +44 -0
- data/templates/config.yml +46 -0
- metadata +220 -0
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe StraightServer::Gateway do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@gateway = StraightServer::GatewayOnConfig.find_by_id(1)
|
7
|
+
@order_mock = double("order mock")
|
8
|
+
[:id, :gateway=, :save, :to_h, :id=].each { |m| allow(@order_mock).to receive(m) }
|
9
|
+
@order_for_keychain_id_args = { amount: 1, keychain_id: 1, currency: nil, btc_denomination: nil }
|
10
|
+
end
|
11
|
+
|
12
|
+
it "checks for signature when creating a new order" do
|
13
|
+
@gateway.last_keychain_id = 0
|
14
|
+
expect( -> { @gateway.create_order(amount: 1, signature: 'invalid', id: 1) }).to raise_exception(StraightServer::GatewayModule::InvalidSignature)
|
15
|
+
expect(@gateway).to receive(:order_for_keychain_id).with(@order_for_keychain_id_args).once.and_return(@order_mock)
|
16
|
+
@gateway.create_order(amount: 1, signature: hmac_sha1(1, 'secret'), id: 1)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "checks md5 signature only if that setting is set ON for a particular gateway" do
|
20
|
+
gateway1 = StraightServer::GatewayOnConfig.find_by_id(1)
|
21
|
+
gateway2 = StraightServer::GatewayOnConfig.find_by_id(2)
|
22
|
+
expect(gateway2).to receive(:order_for_keychain_id).with(@order_for_keychain_id_args).once.and_return(@order_mock)
|
23
|
+
expect( -> { gateway1.create_order(amount: 1, signature: 'invalid') }).to raise_exception
|
24
|
+
expect( -> { gateway2.create_order(amount: 1, signature: 'invalid') }).not_to raise_exception()
|
25
|
+
end
|
26
|
+
|
27
|
+
it "doesn't allow nil or empty order id if signature checks are enabled" do
|
28
|
+
expect( -> { @gateway.create_order(amount: 1, signature: 'invalid', id: nil) }).to raise_exception(StraightServer::GatewayModule::InvalidOrderId)
|
29
|
+
expect( -> { @gateway.create_order(amount: 1, signature: 'invalid', id: '') }).to raise_exception(StraightServer::GatewayModule::InvalidOrderId)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "sets order amount in satoshis calculated from another currency" do
|
33
|
+
@gateway = StraightServer::GatewayOnConfig.find_by_id(2)
|
34
|
+
allow(@gateway).to receive(:address_for_keychain_id).and_return('address')
|
35
|
+
allow(@gateway.exchange_rate_adapters.first).to receive(:rate_for).and_return(450.5412)
|
36
|
+
expect(@gateway.create_order(amount: 2252.706, currency: 'USD').amount).to eq(500000000)
|
37
|
+
end
|
38
|
+
|
39
|
+
context "callback url" do
|
40
|
+
|
41
|
+
before(:each) do
|
42
|
+
@gateway = StraightServer::GatewayOnConfig.find_by_id(2) # Gateway 2 doesn't require signatures
|
43
|
+
@response_mock = double("http response mock")
|
44
|
+
expect(@response_mock).to receive(:body).once.and_return('body')
|
45
|
+
@order = create(:order)
|
46
|
+
allow(@order).to receive(:status).and_return(1)
|
47
|
+
allow(@order).to receive(:tid).and_return('tid1')
|
48
|
+
end
|
49
|
+
|
50
|
+
it "sends a request to the callback_url" do
|
51
|
+
expect(@response_mock).to receive(:code).twice.and_return("200")
|
52
|
+
expect(Net::HTTP).to receive(:get_response).and_return(@response_mock)
|
53
|
+
@gateway.order_status_changed(@order)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "keeps sending request according to the callback schedule if there's an error" do
|
57
|
+
expect(@response_mock).to receive(:code).twice.and_return("404")
|
58
|
+
expect(@gateway).to receive(:sleep).exactly(10).times
|
59
|
+
expect(Net::HTTP).to receive(:get_response).exactly(11).times.and_return(@response_mock)
|
60
|
+
expect(URI).to receive(:parse).with('http://localhost:3001/payment-callback?' + @order.to_http_params).exactly(11).times
|
61
|
+
@gateway.order_status_changed(@order)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "signs the callback if gateway has a secret" do
|
65
|
+
@gateway = StraightServer::GatewayOnConfig.find_by_id(1) # Gateway 1 requires signatures
|
66
|
+
expect(@response_mock).to receive(:code).twice.and_return("200")
|
67
|
+
expect(URI).to receive(:parse).with('http://localhost:3000/payment-callback?' + @order.to_http_params + "&signature=#{hmac_sha1(hmac_sha1(@order.id, 'secret'), 'secret')}")
|
68
|
+
expect(Net::HTTP).to receive(:get_response).and_return(@response_mock)
|
69
|
+
@gateway.order_status_changed(@order)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "receives random data in :data params and sends it back in a callback request" do
|
73
|
+
@order.data = 'some random data'
|
74
|
+
expect(@gateway).to receive(:order_for_keychain_id).with(@order_for_keychain_id_args).once.and_return(@order)
|
75
|
+
@gateway.create_order(amount: 1, data: 'some random data')
|
76
|
+
expect(@response_mock).to receive(:code).twice.and_return("200")
|
77
|
+
expect(Net::HTTP).to receive(:get_response).and_return(@response_mock)
|
78
|
+
expect(URI).to receive(:parse).with('http://localhost:3001/payment-callback?' + @order.to_http_params + "&data=#{@order.data}")
|
79
|
+
@gateway.order_status_changed(@order)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "saves callback url response in the order's record in DB" do
|
83
|
+
allow(@response_mock).to receive(:code).and_return("200")
|
84
|
+
allow(Net::HTTP).to receive(:get_response).and_return(@response_mock)
|
85
|
+
@gateway.order_status_changed(@order)
|
86
|
+
expect(@order.callback_response).to eq({code: "200", body: "body"})
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "config based gateway" do
|
92
|
+
|
93
|
+
it "loads all the gateways from the config file and assigns correct attributes" do
|
94
|
+
gateway1 = StraightServer::GatewayOnConfig.find_by_id(1)
|
95
|
+
gateway2 = StraightServer::GatewayOnConfig.find_by_id(2)
|
96
|
+
expect(gateway1).to be_kind_of(StraightServer::GatewayOnConfig)
|
97
|
+
expect(gateway2).to be_kind_of(StraightServer::GatewayOnConfig)
|
98
|
+
|
99
|
+
expect(gateway1.pubkey).to eq('xpub-000')
|
100
|
+
expect(gateway1.confirmations_required).to eq(0)
|
101
|
+
expect(gateway1.order_class).to eq("StraightServer::Order")
|
102
|
+
expect(gateway1.name).to eq("default")
|
103
|
+
|
104
|
+
expect(gateway2.pubkey).to eq('xpub-001')
|
105
|
+
expect(gateway2.confirmations_required).to eq(0)
|
106
|
+
expect(gateway2.order_class).to eq("StraightServer::Order")
|
107
|
+
expect(gateway2.name).to eq("second_gateway")
|
108
|
+
end
|
109
|
+
|
110
|
+
it "saves and retrieves last_keychain_id from the file in the .straight dir" do
|
111
|
+
expect(File.read("#{ENV['HOME']}/.straight/default_last_keychain_id").to_i).to eq(0)
|
112
|
+
@gateway.increment_last_keychain_id!
|
113
|
+
expect(File.read("#{ENV['HOME']}/.straight/default_last_keychain_id").to_i).to eq(1)
|
114
|
+
|
115
|
+
expect(@gateway).to receive(:order_for_keychain_id).with(@order_for_keychain_id_args.merge({ keychain_id: 2})).once.and_return(@order_mock)
|
116
|
+
@gateway.create_order(amount: 1, signature: hmac_sha1(1, 'secret'), id: 1)
|
117
|
+
expect(File.read("#{ENV['HOME']}/.straight/default_last_keychain_id").to_i).to eq(2)
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "db based gateway" do
|
123
|
+
|
124
|
+
before(:each) do
|
125
|
+
@gateway = StraightServer::GatewayOnDB.create(
|
126
|
+
confirmations_required: 0,
|
127
|
+
pubkey: 'xpub-000',
|
128
|
+
order_class: 'StraightServer::Order',
|
129
|
+
secret: 'secret',
|
130
|
+
name: 'default',
|
131
|
+
check_signature: true,
|
132
|
+
exchange_rate_adapter_names: ['Bitpay', 'Coinbase', 'Bitstamp']
|
133
|
+
)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "saves and retrieves last_keychain_id from the db" do
|
137
|
+
expect(DB[:gateways][:name => 'default'][:last_keychain_id]).to eq(0)
|
138
|
+
@gateway.increment_last_keychain_id!
|
139
|
+
expect(DB[:gateways][:name => 'default'][:last_keychain_id]).to eq(1)
|
140
|
+
|
141
|
+
expect(@gateway).to receive(:order_for_keychain_id).with(@order_for_keychain_id_args.merge({ keychain_id: 2})).once.and_return(@order_mock)
|
142
|
+
@gateway.create_order(amount: 1, signature: hmac_sha1(1, 'secret'), id: 1)
|
143
|
+
expect(DB[:gateways][:name => 'default'][:last_keychain_id]).to eq(2)
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "handling websockets" do
|
149
|
+
|
150
|
+
before(:each) do
|
151
|
+
@gateway.instance_variable_set(:@websockets, {})
|
152
|
+
@ws = double("websocket mock")
|
153
|
+
allow(@ws).to receive(:on).with(:close)
|
154
|
+
allow(@order_mock).to receive(:id).and_return(1)
|
155
|
+
allow(@order_mock).to receive(:status).and_return(0)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "adds a new websocket for the order" do
|
159
|
+
@gateway.add_websocket_for_order(@ws, @order_mock)
|
160
|
+
expect(@gateway.instance_variable_get(:@websockets)).to eq({ 1 => @ws})
|
161
|
+
end
|
162
|
+
|
163
|
+
it "sends a message to the websocket when status of the order is changed and closes the connection" do
|
164
|
+
allow(@gateway).to receive(:send_callback_http_request) # ignoring the callback which sends an callback_url request
|
165
|
+
expect(@order_mock).to receive(:to_json).and_return("order json info")
|
166
|
+
expect(@ws).to receive(:send).with("order json info")
|
167
|
+
expect(@ws).to receive(:close)
|
168
|
+
@gateway.add_websocket_for_order(@ws, @order_mock)
|
169
|
+
@gateway.order_status_changed(@order_mock)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "doesn't allow to listen to orders with statuses other than 0 or 1" do
|
173
|
+
allow(@order_mock).to receive(:status).and_return(2)
|
174
|
+
expect( -> { @gateway.add_websocket_for_order(@ws, @order_mock) }).to raise_exception(StraightServer::Gateway::WebsocketForCompletedOrder)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "doesn't allow to create a second websocket for the same order" do
|
178
|
+
allow(@order_mock).to receive(:status).and_return(0)
|
179
|
+
@gateway.add_websocket_for_order(@ws, @order_mock)
|
180
|
+
expect( -> { @gateway.add_websocket_for_order(@ws, @order_mock) }).to raise_exception(StraightServer::Gateway::WebsocketExists)
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
def hmac_sha1(key, secret)
|
186
|
+
h = HMAC::SHA1.new('secret')
|
187
|
+
h << key.to_s
|
188
|
+
h.hexdigest
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe StraightServer::Order do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
# clean the database
|
7
|
+
DB.run("DELETE FROM orders")
|
8
|
+
@gateway = double("Straight Gateway mock")
|
9
|
+
allow(@gateway).to receive(:id).and_return(1)
|
10
|
+
@order = create(:order, gateway_id: @gateway.id)
|
11
|
+
allow(@gateway).to receive(:fetch_transactions_for).with(anything).and_return([])
|
12
|
+
allow(@gateway).to receive(:order_status_changed).with(anything)
|
13
|
+
allow(StraightServer::Gateway).to receive(:find_by_id).and_return(@gateway)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "prepares data as http params" do
|
17
|
+
allow(@order).to receive(:tid).and_return("tid1")
|
18
|
+
expect(@order.to_http_params).to eq("order_id=#{@order.id}&amount=10&status=#{@order.status}&address=#{@order.address}&tid=tid1")
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "DB interaction" do
|
22
|
+
|
23
|
+
it "saves a new order into the database" do
|
24
|
+
expect(DB[:orders][:keychain_id => @order.id]).not_to be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "updates an existing order" do
|
28
|
+
expect(DB[:orders][:keychain_id => @order.id][:status]).to eq(0)
|
29
|
+
@order.status = 1
|
30
|
+
@order.save
|
31
|
+
expect(DB[:orders][:keychain_id => @order.id][:status]).to eq(1)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "finds first order in the database by id" do
|
35
|
+
expect(StraightServer::Order.find(id: @order.id)).to equal_order(@order)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "finds first order in the database by keychain_id" do
|
39
|
+
expect(StraightServer::Order.find(keychain_id: @order.keychain_id)).to equal_order(@order)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "finds orders in the database by any conditions" do
|
43
|
+
order1 = create(:order, gateway_id: @gateway.id)
|
44
|
+
order2 = create(:order, gateway_id: @gateway.id)
|
45
|
+
|
46
|
+
expect(StraightServer::Order.where(keychain_id: order1.keychain_id).first).to equal_order(order1)
|
47
|
+
expect(StraightServer::Order.where(keychain_id: order2.keychain_id).first).to equal_order(order2)
|
48
|
+
expect(StraightServer::Order.where(keychain_id: order2.keychain_id+1).first).to be_nil
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "with validations" do
|
53
|
+
|
54
|
+
it "doesn't save order if the order with the same id exists" do
|
55
|
+
order = create(:order, gateway_id: @gateway.id)
|
56
|
+
expect( -> { create(:order, id: order.id, gateway_id: @gateway.id) }).to raise_error()
|
57
|
+
end
|
58
|
+
|
59
|
+
it "doesn't save order if the order with the same address exists" do
|
60
|
+
order = create(:order, gateway_id: @gateway.id)
|
61
|
+
expect( -> { create(:order, address: order.address) }).to raise_error()
|
62
|
+
end
|
63
|
+
|
64
|
+
it "doesn't save order if the order with the same keychain_id and gateway_id exists" do
|
65
|
+
order = create(:order, gateway_id: @gateway.id)
|
66
|
+
expect( -> { create(:order, keychain_id: order.id, gateway_id: order.gateway_id+1) }).not_to raise_error()
|
67
|
+
expect( -> { create(:order, keychain_id: order.id, gateway_id: order.gateway_id) }).to raise_error()
|
68
|
+
end
|
69
|
+
|
70
|
+
it "doesn't save order if the amount is invalid" do
|
71
|
+
expect( -> { create(:order, amount: 0) }).to raise_error()
|
72
|
+
end
|
73
|
+
|
74
|
+
it "doesn't save order if gateway_id is invalid" do
|
75
|
+
expect( -> { create(:order, gateway_id: 0) }).to raise_error()
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe StraightServer::OrdersController do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
DB.run("DELETE FROM orders")
|
7
|
+
@gateway = gateway = StraightServer::Gateway.find_by_id(2)
|
8
|
+
allow(gateway).to receive(:address_for_keychain_id).and_return("address#{gateway.last_keychain_id+1}")
|
9
|
+
allow(gateway).to receive(:fetch_transactions_for).with(anything).and_return([])
|
10
|
+
allow(gateway).to receive(:send_callback_http_request)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "create action" do
|
14
|
+
|
15
|
+
it "creates an order and renders its attrs in json" do
|
16
|
+
allow(StraightServer::Thread).to receive(:new) # ignore periodic status checks, we're not testing it here
|
17
|
+
send_request "POST", '/gateways/2/orders', amount: 10
|
18
|
+
expect(response).to render_json_with(status: 0, amount: 10, address: "address1", tid: nil, id: :anything)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "renders 409 error when an order cannot be created due to some validation errors" do
|
22
|
+
send_request "POST", '/gateways/2/orders', amount: 0
|
23
|
+
expect(response[0]).to eq(409)
|
24
|
+
expect(response[2]).to eq("Invalid order: amount is invalid")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "starts tracking the order status in a separate thread" do
|
28
|
+
order_mock = double("order mock")
|
29
|
+
expect(order_mock).to receive(:start_periodic_status_check)
|
30
|
+
allow(order_mock).to receive(:to_h).and_return({})
|
31
|
+
expect(@gateway).to receive(:create_order).and_return(order_mock)
|
32
|
+
send_request "POST", '/gateways/2/orders', amount: 10
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "show action" do
|
38
|
+
|
39
|
+
before(:each) do
|
40
|
+
@order_mock = double('order mock')
|
41
|
+
allow(@order_mock).to receive(:status).and_return(2)
|
42
|
+
allow(@order_mock).to receive(:to_json).and_return("order json mock")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "renders json info about an order if it is found" do
|
46
|
+
allow(@order_mock).to receive(:status_changed?).and_return(false)
|
47
|
+
expect(StraightServer::Order).to receive(:[]).with(1).and_return(@order_mock)
|
48
|
+
send_request "GET", '/gateways/2/orders/1'
|
49
|
+
expect(response).to eq([200, {}, "order json mock"])
|
50
|
+
end
|
51
|
+
|
52
|
+
it "saves an order if status is updated" do
|
53
|
+
allow(@order_mock).to receive(:status_changed?).and_return(true)
|
54
|
+
expect(@order_mock).to receive(:save)
|
55
|
+
expect(StraightServer::Order).to receive(:[]).with(1).and_return(@order_mock)
|
56
|
+
send_request "GET", '/gateways/2/orders/1'
|
57
|
+
expect(response).to eq([200, {}, "order json mock"])
|
58
|
+
end
|
59
|
+
|
60
|
+
it "renders 404 if order is not found" do
|
61
|
+
expect(StraightServer::Order).to receive(:[]).with(1).and_return(nil)
|
62
|
+
send_request "GET", '/gateways/2/orders/1'
|
63
|
+
expect(response).to eq([404, {}, "GET /gateways/2/orders/1 Not found"])
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "websocket action" do
|
69
|
+
|
70
|
+
before(:each) do
|
71
|
+
@gateway.instance_variable_set(:@websockets, {})
|
72
|
+
@ws_mock = double("websocket mock")
|
73
|
+
@order_mock = double("order mock")
|
74
|
+
allow(@ws_mock).to receive(:rack_response).and_return("ws rack response")
|
75
|
+
[:id, :gateway=, :save, :to_h, :id=].each { |m| allow(@order_mock).to receive(m) }
|
76
|
+
allow(@ws_mock).to receive(:on)
|
77
|
+
allow(Faye::WebSocket).to receive(:new).and_return(@ws_mock)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "returns a websocket connection" do
|
81
|
+
allow(@order_mock).to receive(:status).and_return(0)
|
82
|
+
allow(StraightServer::Order).to receive(:[]).with(1).and_return(@order_mock)
|
83
|
+
send_request "GET", '/gateways/2/orders/1/websocket'
|
84
|
+
expect(response).to eq("ws rack response")
|
85
|
+
end
|
86
|
+
|
87
|
+
it "returns 403 when socket already exists" do
|
88
|
+
allow(@order_mock).to receive(:status).and_return(0)
|
89
|
+
allow(StraightServer::Order).to receive(:[]).with(1).twice.and_return(@order_mock)
|
90
|
+
send_request "GET", '/gateways/2/orders/1/websocket'
|
91
|
+
send_request "GET", '/gateways/2/orders/1/websocket'
|
92
|
+
expect(response).to eq([403, {}, "Someone is already listening to that order"])
|
93
|
+
end
|
94
|
+
|
95
|
+
it "returns 403 when order has is completed (status > 1 )" do
|
96
|
+
allow(@order_mock).to receive(:status).and_return(2)
|
97
|
+
allow(StraightServer::Order).to receive(:[]).with(1).and_return(@order_mock)
|
98
|
+
send_request "GET", '/gateways/2/orders/1/websocket'
|
99
|
+
expect(response).to eq([403, {}, "You cannot listen to this order because it is completed (status > 1)"])
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
def send_request(method, path, params={})
|
105
|
+
env = Hashie::Mash.new({ 'REQUEST_METHOD' => method, 'REQUEST_PATH' => path, 'params' => params })
|
106
|
+
@controller = StraightServer::OrdersController.new(env)
|
107
|
+
end
|
108
|
+
|
109
|
+
def response
|
110
|
+
@controller.response
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# !!! The order in which we require files here is very important.
|
2
|
+
|
3
|
+
# 1. First, load dependencies and connect to the Database
|
4
|
+
require 'sequel'
|
5
|
+
require 'straight'
|
6
|
+
require 'fileutils' # This is required to cleanup the test .straight dir
|
7
|
+
require 'hashie'
|
8
|
+
|
9
|
+
Sequel.extension :migration
|
10
|
+
DB = Sequel.sqlite
|
11
|
+
|
12
|
+
# 2. Then we can run migrations BEFORE we load actual models
|
13
|
+
Sequel::Migrator.run(DB, File.expand_path('../', File.dirname(__FILE__)) + '/db/migrations/')
|
14
|
+
|
15
|
+
# 3. Load config and initializer so that we can read our test config file located in
|
16
|
+
# spec/.straight/config.yml
|
17
|
+
|
18
|
+
# 3.1 This tells initializer where to read the config file from
|
19
|
+
ENV['HOME'] = File.expand_path(File.dirname(__FILE__))
|
20
|
+
|
21
|
+
# 3.2 Actually load the initializer
|
22
|
+
require_relative "../lib/straight-server/config"
|
23
|
+
require_relative "../lib/straight-server/initializer"
|
24
|
+
include StraightServer::Initializer
|
25
|
+
|
26
|
+
read_config_file
|
27
|
+
|
28
|
+
# 4. Load the rest of the files, including models, which are now ready
|
29
|
+
# to be used as intended and will follow all the previous configuration.
|
30
|
+
require_relative '../lib/straight-server/order'
|
31
|
+
require_relative '../lib/straight-server/gateway'
|
32
|
+
require_relative '../lib/straight-server/orders_controller'
|
33
|
+
require_relative '../lib/straight-server'
|
34
|
+
|
35
|
+
require_relative 'support/custom_matchers'
|
36
|
+
|
37
|
+
require "factory_girl"
|
38
|
+
require_relative "factories"
|
39
|
+
|
40
|
+
class StraightServer::Order
|
41
|
+
alias :save! :save
|
42
|
+
end
|
43
|
+
|
44
|
+
class StraightServer::Thread
|
45
|
+
def self.new(&block)
|
46
|
+
block.call
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
RSpec.configure do |config|
|
51
|
+
|
52
|
+
config.include FactoryGirl::Syntax::Methods
|
53
|
+
|
54
|
+
config.before(:suite) do
|
55
|
+
StraightServer.db_connection = DB #use a memory DB
|
56
|
+
end
|
57
|
+
|
58
|
+
config.before(:each) do
|
59
|
+
DB[:orders].delete
|
60
|
+
logger_mock = double("logger mock")
|
61
|
+
[:debug, :info, :warn, :fatal, :unknown, :blank_lines].each do |e|
|
62
|
+
allow(logger_mock).to receive(e)
|
63
|
+
end
|
64
|
+
StraightServer.logger = logger_mock
|
65
|
+
StraightServer::GatewayOnConfig.class_variable_get(:@@gateways).each do |g|
|
66
|
+
g.last_keychain_id = 0
|
67
|
+
g.save
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
config.after(:all) do
|
72
|
+
["default_last_keychain_id", "second_gateway_last_keychain_id"].each do |f|
|
73
|
+
FileUtils.rm "#{ENV['HOME']}/.straight/#{f}" if File.exists?("#{ENV['HOME']}/.straight/#{f}")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
RSpec::Matchers.define :equal_order do |expected|
|
2
|
+
match do |actual|
|
3
|
+
true
|
4
|
+
actual.address == expected.address &&
|
5
|
+
actual.status == expected.status &&
|
6
|
+
actual.keychain_id == expected.keychain_id &&
|
7
|
+
actual.amount == expected.amount &&
|
8
|
+
actual.gateway_id == expected.gateway_id &&
|
9
|
+
actual.id == expected.id
|
10
|
+
end
|
11
|
+
|
12
|
+
diffable
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec::Matchers.define :render_json_with do |hash|
|
16
|
+
|
17
|
+
match do |r|
|
18
|
+
json_response = JSON.parse(r[2])
|
19
|
+
check_one_dimensional_hash(hash, json_response)
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_one_dimensional_hash(hash, json_response)
|
23
|
+
hash.each do |k,v|
|
24
|
+
if v == :anything
|
25
|
+
expect(json_response[k.to_s]).to_not be_nil
|
26
|
+
elsif v == nil
|
27
|
+
expect(json_response[k.to_s]).to be_nil
|
28
|
+
elsif v.kind_of?(Hash)
|
29
|
+
expect(json_response[k.to_s].kind_of?(Hash)).to be_truthy
|
30
|
+
check_one_dimensional_hash(v, json_response[k.to_s])
|
31
|
+
else
|
32
|
+
expect(json_response[k.to_s]).to eq(v)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
failure_message do |actual|
|
38
|
+
"expected that it had:\n\n\t\t#{hash},\n\nbut instead it had:\n\n\t\t#{JSON.parse(actual[2])}"
|
39
|
+
end
|
40
|
+
failure_message_when_negated do |actual|
|
41
|
+
"expected that it wouldn't render #{hash.inspect} but it did!"
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# If set to db, then use DB table to store gateways,
|
2
|
+
# useful when your run many gateways on the same server.
|
3
|
+
gateways_source: config
|
4
|
+
|
5
|
+
gateways:
|
6
|
+
default:
|
7
|
+
pubkey: xpub-xxx # <- TODO: change this to your BIP32 pubkey
|
8
|
+
confirmations_required: 0
|
9
|
+
order_class: "StraightServer::Order"
|
10
|
+
secret: 'secret'
|
11
|
+
check_signature: false
|
12
|
+
callback_url: 'http://localhost:3000/my_app/payment_callback'
|
13
|
+
default_currency: 'BTC'
|
14
|
+
|
15
|
+
# The order matters here, we check for prices with the first adapter,
|
16
|
+
# if it fails, move on to the next
|
17
|
+
exchange_rate_adapters:
|
18
|
+
- Bitpay
|
19
|
+
- Coinbase
|
20
|
+
- Bitstamp
|
21
|
+
|
22
|
+
logmaster:
|
23
|
+
log_level: INFO # Wise to change to WARN for production
|
24
|
+
file: straight.log
|
25
|
+
raise_exception: false
|
26
|
+
name: Straight server logger
|
27
|
+
|
28
|
+
# These options bellow send you email whenever a FATAL error occurs.
|
29
|
+
# You probably want to uncomment them for production. See https://github.com/snitko/logmaste
|
30
|
+
# for various email options.
|
31
|
+
#
|
32
|
+
#email_config:
|
33
|
+
#to: 'me@email.foo',
|
34
|
+
#from: "logmaster@yourapp.com"
|
35
|
+
|
36
|
+
db:
|
37
|
+
adapter: sqlite
|
38
|
+
name: straight.db # file is always located in ~/.straight
|
39
|
+
|
40
|
+
# No need to set these options for sqlite,
|
41
|
+
# but other DBs may require them.
|
42
|
+
#
|
43
|
+
#user: username
|
44
|
+
#password: password
|
45
|
+
#host: hostname
|
46
|
+
#port: 1234
|