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