tastytrade 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/commands/plan.md +13 -0
- data/.claude/commands/release-pr.md +12 -0
- data/CHANGELOG.md +170 -0
- data/README.md +424 -3
- data/ROADMAP.md +17 -17
- data/lib/tastytrade/cli/history_formatter.rb +304 -0
- data/lib/tastytrade/cli/orders.rb +749 -0
- data/lib/tastytrade/cli/positions_formatter.rb +114 -0
- data/lib/tastytrade/cli.rb +701 -12
- data/lib/tastytrade/cli_helpers.rb +111 -14
- data/lib/tastytrade/client.rb +7 -0
- data/lib/tastytrade/file_store.rb +83 -0
- data/lib/tastytrade/instruments/equity.rb +42 -0
- data/lib/tastytrade/models/account.rb +160 -2
- data/lib/tastytrade/models/account_balance.rb +46 -0
- data/lib/tastytrade/models/buying_power_effect.rb +61 -0
- data/lib/tastytrade/models/live_order.rb +272 -0
- data/lib/tastytrade/models/order_response.rb +106 -0
- data/lib/tastytrade/models/order_status.rb +84 -0
- data/lib/tastytrade/models/trading_status.rb +200 -0
- data/lib/tastytrade/models/transaction.rb +151 -0
- data/lib/tastytrade/models.rb +6 -0
- data/lib/tastytrade/order.rb +191 -0
- data/lib/tastytrade/order_validator.rb +355 -0
- data/lib/tastytrade/session.rb +26 -1
- data/lib/tastytrade/session_manager.rb +43 -14
- data/lib/tastytrade/version.rb +1 -1
- data/lib/tastytrade.rb +43 -0
- data/spec/exe/tastytrade_spec.rb +1 -1
- data/spec/spec_helper.rb +72 -0
- data/spec/tastytrade/cli/positions_spec.rb +267 -0
- data/spec/tastytrade/cli_auth_spec.rb +5 -0
- data/spec/tastytrade/cli_env_login_spec.rb +199 -0
- data/spec/tastytrade/cli_helpers_spec.rb +3 -26
- data/spec/tastytrade/cli_orders_spec.rb +168 -0
- data/spec/tastytrade/cli_status_spec.rb +153 -164
- data/spec/tastytrade/file_store_spec.rb +126 -0
- data/spec/tastytrade/models/account_balance_spec.rb +103 -0
- data/spec/tastytrade/models/account_order_history_spec.rb +229 -0
- data/spec/tastytrade/models/account_order_management_spec.rb +271 -0
- data/spec/tastytrade/models/account_place_order_spec.rb +125 -0
- data/spec/tastytrade/models/account_spec.rb +86 -15
- data/spec/tastytrade/models/buying_power_effect_spec.rb +250 -0
- data/spec/tastytrade/models/live_order_json_spec.rb +144 -0
- data/spec/tastytrade/models/live_order_spec.rb +295 -0
- data/spec/tastytrade/models/order_response_spec.rb +96 -0
- data/spec/tastytrade/models/order_status_spec.rb +113 -0
- data/spec/tastytrade/models/trading_status_spec.rb +260 -0
- data/spec/tastytrade/models/transaction_spec.rb +236 -0
- data/spec/tastytrade/order_edge_cases_spec.rb +163 -0
- data/spec/tastytrade/order_spec.rb +201 -0
- data/spec/tastytrade/order_validator_spec.rb +347 -0
- data/spec/tastytrade/session_env_spec.rb +169 -0
- data/spec/tastytrade/session_manager_spec.rb +43 -33
- data/vcr_implementation_plan.md +403 -0
- data/vcr_implementation_research.md +330 -0
- metadata +50 -18
- data/lib/tastytrade/keyring_store.rb +0 -72
- data/spec/tastytrade/keyring_store_spec.rb +0 -168
@@ -0,0 +1,295 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Tastytrade::Models::LiveOrder do
|
6
|
+
let(:live_order_data) do
|
7
|
+
{
|
8
|
+
"id" => "12345",
|
9
|
+
"account-number" => "5WV12345",
|
10
|
+
"status" => "Live",
|
11
|
+
"cancellable" => true,
|
12
|
+
"editable" => true,
|
13
|
+
"edited" => false,
|
14
|
+
"time-in-force" => "Day",
|
15
|
+
"order-type" => "Limit",
|
16
|
+
"size" => 100,
|
17
|
+
"price" => "150.50",
|
18
|
+
"price-effect" => "Debit",
|
19
|
+
"underlying-symbol" => "AAPL",
|
20
|
+
"underlying-instrument-type" => "Equity",
|
21
|
+
"created-at" => "2024-01-15T09:30:00.000Z",
|
22
|
+
"updated-at" => "2024-01-15T09:30:05.000Z",
|
23
|
+
"received-at" => "2024-01-15T09:30:00.000Z",
|
24
|
+
"live-at" => "2024-01-15T09:30:05.000Z",
|
25
|
+
"legs" => [
|
26
|
+
{
|
27
|
+
"symbol" => "AAPL",
|
28
|
+
"instrument-type" => "Equity",
|
29
|
+
"action" => "Buy",
|
30
|
+
"quantity" => 100,
|
31
|
+
"remaining-quantity" => 100,
|
32
|
+
"fills" => []
|
33
|
+
}
|
34
|
+
]
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:filled_order_data) do
|
39
|
+
live_order_data.merge(
|
40
|
+
"status" => "Filled",
|
41
|
+
"cancellable" => false,
|
42
|
+
"editable" => false,
|
43
|
+
"filled-at" => "2024-01-15T09:35:00.000Z",
|
44
|
+
"terminal-at" => "2024-01-15T09:35:00.000Z",
|
45
|
+
"legs" => [
|
46
|
+
{
|
47
|
+
"symbol" => "AAPL",
|
48
|
+
"instrument-type" => "Equity",
|
49
|
+
"action" => "Buy",
|
50
|
+
"quantity" => 100,
|
51
|
+
"remaining-quantity" => 0,
|
52
|
+
"fill-quantity" => 100,
|
53
|
+
"fill-price" => "150.45",
|
54
|
+
"fills" => [
|
55
|
+
{
|
56
|
+
"ext-exec-id" => "exec123",
|
57
|
+
"fill-id" => "fill123",
|
58
|
+
"quantity" => 100,
|
59
|
+
"fill-price" => "150.45",
|
60
|
+
"filled-at" => "2024-01-15T09:35:00.000Z"
|
61
|
+
}
|
62
|
+
]
|
63
|
+
}
|
64
|
+
]
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
let(:partially_filled_order_data) do
|
69
|
+
live_order_data.merge(
|
70
|
+
"legs" => [
|
71
|
+
{
|
72
|
+
"symbol" => "AAPL",
|
73
|
+
"instrument-type" => "Equity",
|
74
|
+
"action" => "Buy",
|
75
|
+
"quantity" => 100,
|
76
|
+
"remaining-quantity" => 60,
|
77
|
+
"fill-quantity" => 40,
|
78
|
+
"fill-price" => "150.48",
|
79
|
+
"fills" => [
|
80
|
+
{
|
81
|
+
"ext-exec-id" => "exec124",
|
82
|
+
"fill-id" => "fill124",
|
83
|
+
"quantity" => 40,
|
84
|
+
"fill-price" => "150.48",
|
85
|
+
"filled-at" => "2024-01-15T09:32:00.000Z"
|
86
|
+
}
|
87
|
+
]
|
88
|
+
}
|
89
|
+
]
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#initialize" do
|
94
|
+
subject(:live_order) { described_class.new(live_order_data) }
|
95
|
+
|
96
|
+
it "parses basic attributes correctly" do
|
97
|
+
expect(live_order.id).to eq("12345")
|
98
|
+
expect(live_order.account_number).to eq("5WV12345")
|
99
|
+
expect(live_order.status).to eq("Live")
|
100
|
+
expect(live_order.cancellable).to be true
|
101
|
+
expect(live_order.editable).to be true
|
102
|
+
expect(live_order.edited).to be false
|
103
|
+
end
|
104
|
+
|
105
|
+
it "parses order details correctly" do
|
106
|
+
expect(live_order.time_in_force).to eq("Day")
|
107
|
+
expect(live_order.order_type).to eq("Limit")
|
108
|
+
expect(live_order.size).to eq(100)
|
109
|
+
expect(live_order.price).to eq(BigDecimal("150.50"))
|
110
|
+
expect(live_order.price_effect).to eq("Debit")
|
111
|
+
expect(live_order.underlying_symbol).to eq("AAPL")
|
112
|
+
expect(live_order.underlying_instrument_type).to eq("Equity")
|
113
|
+
end
|
114
|
+
|
115
|
+
it "parses timestamps correctly" do
|
116
|
+
expect(live_order.created_at).to be_a(Time)
|
117
|
+
expect(live_order.updated_at).to be_a(Time)
|
118
|
+
expect(live_order.received_at).to be_a(Time)
|
119
|
+
expect(live_order.live_at).to be_a(Time)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "parses legs correctly" do
|
123
|
+
expect(live_order.legs).to be_an(Array)
|
124
|
+
expect(live_order.legs.size).to eq(1)
|
125
|
+
leg = live_order.legs.first
|
126
|
+
expect(leg).to be_a(Tastytrade::Models::LiveOrderLeg)
|
127
|
+
expect(leg.symbol).to eq("AAPL")
|
128
|
+
expect(leg.action).to eq("Buy")
|
129
|
+
expect(leg.quantity).to eq(100)
|
130
|
+
expect(leg.remaining_quantity).to eq(100)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "status check methods" do
|
135
|
+
context "with a live order" do
|
136
|
+
subject(:live_order) { described_class.new(live_order_data) }
|
137
|
+
|
138
|
+
it "#cancellable? returns true" do
|
139
|
+
expect(live_order.cancellable?).to be true
|
140
|
+
end
|
141
|
+
|
142
|
+
it "#editable? returns true" do
|
143
|
+
expect(live_order.editable?).to be true
|
144
|
+
end
|
145
|
+
|
146
|
+
it "#terminal? returns false" do
|
147
|
+
expect(live_order.terminal?).to be false
|
148
|
+
end
|
149
|
+
|
150
|
+
it "#working? returns true" do
|
151
|
+
expect(live_order.working?).to be true
|
152
|
+
end
|
153
|
+
|
154
|
+
it "#filled? returns false" do
|
155
|
+
expect(live_order.filled?).to be false
|
156
|
+
end
|
157
|
+
|
158
|
+
it "#cancelled? returns false" do
|
159
|
+
expect(live_order.cancelled?).to be false
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "with a filled order" do
|
164
|
+
subject(:filled_order) { described_class.new(filled_order_data) }
|
165
|
+
|
166
|
+
it "#cancellable? returns false" do
|
167
|
+
expect(filled_order.cancellable?).to be false
|
168
|
+
end
|
169
|
+
|
170
|
+
it "#editable? returns false" do
|
171
|
+
expect(filled_order.editable?).to be false
|
172
|
+
end
|
173
|
+
|
174
|
+
it "#terminal? returns true" do
|
175
|
+
expect(filled_order.terminal?).to be true
|
176
|
+
end
|
177
|
+
|
178
|
+
it "#working? returns false" do
|
179
|
+
expect(filled_order.working?).to be false
|
180
|
+
end
|
181
|
+
|
182
|
+
it "#filled? returns true" do
|
183
|
+
expect(filled_order.filled?).to be true
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
describe "quantity methods" do
|
189
|
+
context "with an unfilled order" do
|
190
|
+
subject(:order) { described_class.new(live_order_data) }
|
191
|
+
|
192
|
+
it "#remaining_quantity returns total remaining" do
|
193
|
+
expect(order.remaining_quantity).to eq(100)
|
194
|
+
end
|
195
|
+
|
196
|
+
it "#filled_quantity returns 0" do
|
197
|
+
expect(order.filled_quantity).to eq(0)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context "with a partially filled order" do
|
202
|
+
subject(:order) { described_class.new(partially_filled_order_data) }
|
203
|
+
|
204
|
+
it "#remaining_quantity returns correct value" do
|
205
|
+
expect(order.remaining_quantity).to eq(60)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "#filled_quantity returns correct value" do
|
209
|
+
expect(order.filled_quantity).to eq(40)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context "with a filled order" do
|
214
|
+
subject(:order) { described_class.new(filled_order_data) }
|
215
|
+
|
216
|
+
it "#remaining_quantity returns 0" do
|
217
|
+
expect(order.remaining_quantity).to eq(0)
|
218
|
+
end
|
219
|
+
|
220
|
+
it "#filled_quantity returns total quantity" do
|
221
|
+
expect(order.filled_quantity).to eq(100)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
RSpec.describe Tastytrade::Models::LiveOrderLeg do
|
228
|
+
let(:unfilled_leg_data) do
|
229
|
+
{
|
230
|
+
"symbol" => "AAPL",
|
231
|
+
"instrument-type" => "Equity",
|
232
|
+
"action" => "Buy",
|
233
|
+
"quantity" => 100,
|
234
|
+
"remaining-quantity" => 100,
|
235
|
+
"fills" => []
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
let(:partially_filled_leg_data) do
|
240
|
+
{
|
241
|
+
"symbol" => "AAPL",
|
242
|
+
"instrument-type" => "Equity",
|
243
|
+
"action" => "Buy",
|
244
|
+
"quantity" => 100,
|
245
|
+
"remaining-quantity" => 60,
|
246
|
+
"fill-quantity" => 40,
|
247
|
+
"fill-price" => "150.48",
|
248
|
+
"fills" => [
|
249
|
+
{
|
250
|
+
"ext-exec-id" => "exec124",
|
251
|
+
"fill-id" => "fill124",
|
252
|
+
"quantity" => 40,
|
253
|
+
"fill-price" => "150.48",
|
254
|
+
"filled-at" => "2024-01-15T09:32:00.000Z"
|
255
|
+
}
|
256
|
+
]
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
260
|
+
describe "#filled_quantity" do
|
261
|
+
it "calculates correctly for unfilled leg" do
|
262
|
+
leg = described_class.new(unfilled_leg_data)
|
263
|
+
expect(leg.filled_quantity).to eq(0)
|
264
|
+
end
|
265
|
+
|
266
|
+
it "calculates correctly for partially filled leg" do
|
267
|
+
leg = described_class.new(partially_filled_leg_data)
|
268
|
+
expect(leg.filled_quantity).to eq(40)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe "#filled?" do
|
273
|
+
it "returns false for unfilled leg" do
|
274
|
+
leg = described_class.new(unfilled_leg_data)
|
275
|
+
expect(leg.filled?).to be false
|
276
|
+
end
|
277
|
+
|
278
|
+
it "returns false for partially filled leg" do
|
279
|
+
leg = described_class.new(partially_filled_leg_data)
|
280
|
+
expect(leg.filled?).to be false
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
describe "#partially_filled?" do
|
285
|
+
it "returns false for unfilled leg" do
|
286
|
+
leg = described_class.new(unfilled_leg_data)
|
287
|
+
expect(leg.partially_filled?).to be false
|
288
|
+
end
|
289
|
+
|
290
|
+
it "returns true for partially filled leg" do
|
291
|
+
leg = described_class.new(partially_filled_leg_data)
|
292
|
+
expect(leg.partially_filled?).to be true
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Tastytrade::Models::OrderResponse do
|
4
|
+
let(:order_response_data) do
|
5
|
+
{
|
6
|
+
"id" => "123456",
|
7
|
+
"account-number" => "5WX12345",
|
8
|
+
"status" => "Filled",
|
9
|
+
"buying-power-effect" => "-15050.00",
|
10
|
+
"fee-calculation" => {
|
11
|
+
"total-fees" => "0.65",
|
12
|
+
"regulatory-fees" => "0.01"
|
13
|
+
},
|
14
|
+
"price" => "150.50",
|
15
|
+
"price-effect" => "Debit",
|
16
|
+
"value" => "-15050.00",
|
17
|
+
"value-effect" => "Debit",
|
18
|
+
"time-in-force" => "Day",
|
19
|
+
"order-type" => "Limit",
|
20
|
+
"cancellable" => false,
|
21
|
+
"editable" => false,
|
22
|
+
"edited" => false,
|
23
|
+
"warnings" => [],
|
24
|
+
"errors" => [],
|
25
|
+
"legs" => [
|
26
|
+
{
|
27
|
+
"action" => "Buy to Open",
|
28
|
+
"symbol" => "AAPL",
|
29
|
+
"quantity" => 100,
|
30
|
+
"instrument-type" => "Equity",
|
31
|
+
"remaining-quantity" => 0,
|
32
|
+
"fills" => [],
|
33
|
+
"execution-price" => "150.50"
|
34
|
+
}
|
35
|
+
],
|
36
|
+
"updated-at" => "2023-01-01T10:00:00Z",
|
37
|
+
"created-at" => "2023-01-01T09:59:00Z"
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#initialize" do
|
42
|
+
it "parses all order response attributes" do
|
43
|
+
response = described_class.new(order_response_data)
|
44
|
+
|
45
|
+
expect(response.order_id).to eq("123456")
|
46
|
+
expect(response.account_number).to eq("5WX12345")
|
47
|
+
expect(response.status).to eq("Filled")
|
48
|
+
expect(response.buying_power_effect).to eq(BigDecimal("-15050.00"))
|
49
|
+
expect(response.fee_calculations).to be_a(Hash)
|
50
|
+
expect(response.price).to eq(BigDecimal("150.50"))
|
51
|
+
expect(response.price_effect).to eq("Debit")
|
52
|
+
expect(response.value).to eq(BigDecimal("-15050.00"))
|
53
|
+
expect(response.value_effect).to eq("Debit")
|
54
|
+
expect(response.time_in_force).to eq("Day")
|
55
|
+
expect(response.order_type).to eq("Limit")
|
56
|
+
expect(response.cancellable).to be false
|
57
|
+
expect(response.editable).to be false
|
58
|
+
expect(response.edited).to be false
|
59
|
+
expect(response.warnings).to be_empty
|
60
|
+
expect(response.errors).to be_empty
|
61
|
+
expect(response.legs).to be_an(Array)
|
62
|
+
expect(response.updated_at).to be_a(Time)
|
63
|
+
expect(response.created_at).to be_a(Time)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "handles missing optional fields" do
|
67
|
+
minimal_data = {
|
68
|
+
"id" => "123456",
|
69
|
+
"account-number" => "5WX12345",
|
70
|
+
"status" => "Pending"
|
71
|
+
}
|
72
|
+
|
73
|
+
response = described_class.new(minimal_data)
|
74
|
+
expect(response.order_id).to eq("123456")
|
75
|
+
expect(response.buying_power_effect).to be_nil
|
76
|
+
expect(response.price).to be_nil
|
77
|
+
expect(response.warnings).to be_empty
|
78
|
+
expect(response.legs).to be_empty
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "leg parsing" do
|
83
|
+
it "parses order legs correctly" do
|
84
|
+
response = described_class.new(order_response_data)
|
85
|
+
leg = response.legs.first
|
86
|
+
|
87
|
+
expect(leg).to be_a(Tastytrade::Models::OrderLegResponse)
|
88
|
+
expect(leg.action).to eq("Buy to Open")
|
89
|
+
expect(leg.symbol).to eq("AAPL")
|
90
|
+
expect(leg.quantity).to eq(100)
|
91
|
+
expect(leg.instrument_type).to eq("Equity")
|
92
|
+
expect(leg.remaining_quantity).to eq(0)
|
93
|
+
expect(leg.execution_price).to eq(BigDecimal("150.50"))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Tastytrade::Models::OrderStatus do
|
6
|
+
describe "constants" do
|
7
|
+
it "defines submission phase statuses" do
|
8
|
+
expect(described_class::RECEIVED).to eq("Received")
|
9
|
+
expect(described_class::ROUTED).to eq("Routed")
|
10
|
+
expect(described_class::IN_FLIGHT).to eq("In Flight")
|
11
|
+
expect(described_class::CONTINGENT).to eq("Contingent")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "defines working phase statuses" do
|
15
|
+
expect(described_class::LIVE).to eq("Live")
|
16
|
+
expect(described_class::CANCEL_REQUESTED).to eq("Cancel Requested")
|
17
|
+
expect(described_class::REPLACE_REQUESTED).to eq("Replace Requested")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "defines terminal phase statuses" do
|
21
|
+
expect(described_class::FILLED).to eq("Filled")
|
22
|
+
expect(described_class::CANCELLED).to eq("Cancelled")
|
23
|
+
expect(described_class::REJECTED).to eq("Rejected")
|
24
|
+
expect(described_class::EXPIRED).to eq("Expired")
|
25
|
+
expect(described_class::REMOVED).to eq("Removed")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".submission?" do
|
30
|
+
it "returns true for submission statuses" do
|
31
|
+
expect(described_class.submission?("Received")).to be true
|
32
|
+
expect(described_class.submission?("Routed")).to be true
|
33
|
+
expect(described_class.submission?("In Flight")).to be true
|
34
|
+
expect(described_class.submission?("Contingent")).to be true
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns false for non-submission statuses" do
|
38
|
+
expect(described_class.submission?("Live")).to be false
|
39
|
+
expect(described_class.submission?("Filled")).to be false
|
40
|
+
expect(described_class.submission?("Cancelled")).to be false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe ".working?" do
|
45
|
+
it "returns true for working statuses" do
|
46
|
+
expect(described_class.working?("Live")).to be true
|
47
|
+
expect(described_class.working?("Cancel Requested")).to be true
|
48
|
+
expect(described_class.working?("Replace Requested")).to be true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "returns false for non-working statuses" do
|
52
|
+
expect(described_class.working?("Received")).to be false
|
53
|
+
expect(described_class.working?("Filled")).to be false
|
54
|
+
expect(described_class.working?("Cancelled")).to be false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe ".terminal?" do
|
59
|
+
it "returns true for terminal statuses" do
|
60
|
+
expect(described_class.terminal?("Filled")).to be true
|
61
|
+
expect(described_class.terminal?("Cancelled")).to be true
|
62
|
+
expect(described_class.terminal?("Rejected")).to be true
|
63
|
+
expect(described_class.terminal?("Expired")).to be true
|
64
|
+
expect(described_class.terminal?("Removed")).to be true
|
65
|
+
end
|
66
|
+
|
67
|
+
it "returns false for non-terminal statuses" do
|
68
|
+
expect(described_class.terminal?("Live")).to be false
|
69
|
+
expect(described_class.terminal?("Received")).to be false
|
70
|
+
expect(described_class.terminal?("Routed")).to be false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe ".cancellable?" do
|
75
|
+
it "returns true only for Live status" do
|
76
|
+
expect(described_class.cancellable?("Live")).to be true
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns false for other statuses" do
|
80
|
+
expect(described_class.cancellable?("Received")).to be false
|
81
|
+
expect(described_class.cancellable?("Filled")).to be false
|
82
|
+
expect(described_class.cancellable?("Cancel Requested")).to be false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe ".editable?" do
|
87
|
+
it "returns true only for Live status" do
|
88
|
+
expect(described_class.editable?("Live")).to be true
|
89
|
+
end
|
90
|
+
|
91
|
+
it "returns false for other statuses" do
|
92
|
+
expect(described_class.editable?("Received")).to be false
|
93
|
+
expect(described_class.editable?("Filled")).to be false
|
94
|
+
expect(described_class.editable?("Replace Requested")).to be false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe ".valid?" do
|
99
|
+
it "returns true for all valid statuses" do
|
100
|
+
all_statuses = described_class::ALL_STATUSES
|
101
|
+
all_statuses.each do |status|
|
102
|
+
expect(described_class.valid?(status)).to be true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it "returns false for invalid statuses" do
|
107
|
+
expect(described_class.valid?("Invalid")).to be false
|
108
|
+
expect(described_class.valid?("Unknown")).to be false
|
109
|
+
expect(described_class.valid?("")).to be false
|
110
|
+
expect(described_class.valid?(nil)).to be false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|