bitget 0.6.8

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.
@@ -0,0 +1,1131 @@
1
+ require_relative '../helper'
2
+
3
+ describe Bitget::V2::Client do
4
+ let(:api_key){ENV.fetch('BITGET_API_KEY', '<API_KEY>')}
5
+ let(:api_secret){ENV.fetch('BITGET_API_SECRET', '<API_SECRET>')}
6
+ let(:api_passphrase){ENV.fetch('BITGET_API_PASSPHRASE', '<API_PASSPHRASE>')}
7
+
8
+ let(:client) do
9
+ Bitget::V2::Client.new(
10
+ api_key: api_key,
11
+ api_secret: api_secret,
12
+ api_passphrase: api_passphrase
13
+ )
14
+ end
15
+
16
+ describe "where a request is addressed" do
17
+ # Written out rather than built from API_HOST and .path_prefix, which would
18
+ # pass however wrong either of them was.
19
+ it "is the scheme, the host, the version prefix, and the path" do
20
+ _(client.send(:request_string, '/spot/public/coins')) \
21
+ .must_equal('https://api.bitget.com/api/v2/spot/public/coins')
22
+ end
23
+
24
+ it "takes its version prefix from the client, which is what makes V2 a class of its own" do
25
+ _(Bitget::V2::Client.path_prefix).must_equal('/api/v2')
26
+ end
27
+ end
28
+
29
+ # Market
30
+
31
+ describe "#spot_public_coins" do
32
+ context "when a coin is NOT supplied" do
33
+ it "retrieves a list of all coins" do
34
+ VCR.use_cassette('v2/spot/public/coins-when_coin_is_not_supplied') do
35
+ response = client.spot_public_coins
36
+ _(response['code']).must_equal('00000')
37
+ _(response['msg']).must_equal('success')
38
+ _(response).must_include('requestTime')
39
+ _(response).must_include('data')
40
+ _(response['data']).must_be_kind_of(Array)
41
+
42
+ coin = response['data'].first
43
+ _(coin).must_include('coinId')
44
+ _(coin).must_include('coin')
45
+ _(coin).must_include('transfer')
46
+ _(coin).must_include('areaCoin')
47
+ _(coin['areaCoin']).must_match(/^(yes|no)$/)
48
+ _(coin).must_include('chains')
49
+ _(coin['chains']).must_be_kind_of(Array)
50
+
51
+ chain = coin['chains'].first
52
+ _(chain).must_include('chain')
53
+ _(chain).must_include('needTag')
54
+ _(chain).must_include('withdrawable')
55
+ _(chain).must_include('rechargeable')
56
+ _(chain).must_include('withdrawFee')
57
+ _(chain).must_include('extraWithdrawFee')
58
+ _(chain).must_include('depositConfirm')
59
+ _(chain).must_include('withdrawConfirm')
60
+ _(chain).must_include('minDepositAmount')
61
+ _(chain).must_include('minWithdrawAmount')
62
+ _(chain).must_include('browserUrl')
63
+ _(chain).must_include('contractAddress')
64
+ _(chain).must_include('withdrawStep')
65
+ _(chain).must_include('withdrawMinScale')
66
+ _(chain).must_include('congestion')
67
+
68
+ assert_operator response['data'].count, :>, 1500
69
+ end
70
+ end
71
+ end
72
+
73
+ context "when a coin IS supplied" do
74
+ it "retrieves one coin" do
75
+ VCR.use_cassette('v2/spot/public/coins-when_coin_is_supplied') do
76
+ response = client.spot_public_coins(coin: 'BTC')
77
+ _(response['code']).must_equal('00000')
78
+ _(response['msg']).must_equal('success')
79
+ _(response).must_include('requestTime')
80
+ _(response).must_include('data')
81
+ _(response['data']).must_be_kind_of(Array)
82
+ _(response['data'].count).must_equal(1)
83
+
84
+ coin = response['data'].first
85
+ _(coin).must_include('coinId')
86
+ _(coin).must_include('coin')
87
+ _(coin['coin']).must_equal('BTC')
88
+ _(coin).must_include('transfer')
89
+ _(coin).must_include('areaCoin')
90
+ _(coin['areaCoin']).must_match(/^(yes|no)$/)
91
+ _(coin).must_include('chains')
92
+ _(coin['chains']).must_be_kind_of(Array)
93
+
94
+ chain = coin['chains'].first
95
+ _(chain).must_include('chain')
96
+ _(chain).must_include('needTag')
97
+ _(chain).must_include('withdrawable')
98
+ _(chain).must_include('rechargeable')
99
+ _(chain).must_include('withdrawFee')
100
+ _(chain).must_include('extraWithdrawFee')
101
+ _(chain).must_include('depositConfirm')
102
+ _(chain).must_include('withdrawConfirm')
103
+ _(chain).must_include('minDepositAmount')
104
+ _(chain).must_include('minWithdrawAmount')
105
+ _(chain).must_include('browserUrl')
106
+ _(chain).must_include('contractAddress')
107
+ _(chain).must_include('withdrawStep')
108
+ _(chain).must_include('withdrawMinScale')
109
+ _(chain).must_include('congestion')
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ describe "#spot_public_symbols" do
116
+ context "when a symbol is NOT supplied" do
117
+ it "retrieves a list of all symbols" do
118
+ VCR.use_cassette('v2/spot/public/symbols-when_symbol_is_not_supplied') do
119
+ response = client.spot_public_symbols
120
+ _(response['msg']).must_equal('success')
121
+ _(response).must_include('data')
122
+ _(response['data'].first).must_include('symbol')
123
+ _(response['data'].first).must_include('baseCoin')
124
+ _(response['data'].first).must_include('quoteCoin')
125
+ assert_operator response['data'].count, :>, 0
126
+ end
127
+ end
128
+ end
129
+
130
+ context "when a symbol IS supplied" do
131
+ it "retrieves one symbol" do
132
+ VCR.use_cassette('v2/spot/public/symbols-when_symbol_is_supplied') do
133
+ response = client.spot_public_symbols(symbol: 'BTCUSDT')
134
+ _(response['msg']).must_equal('success')
135
+ _(response).must_include('data')
136
+ _(response['data'].first).must_include('symbol')
137
+ _(response['data'].first).must_include('baseCoin')
138
+ _(response['data'].first).must_include('quoteCoin')
139
+ _(response['data'].count).must_equal(1)
140
+ _(response['data'].first['symbol']).must_equal('BTCUSDT')
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ describe "#spot_market_vip_fee_rate" do
147
+ it "retrieves VIP fee rate information" do
148
+ VCR.use_cassette('v2/spot/market/vip-fee-rate') do
149
+ response = client.spot_market_vip_fee_rate
150
+ _(response['code']).must_equal('00000')
151
+ _(response['msg']).must_equal('success')
152
+ _(response).must_include('requestTime')
153
+ _(response).must_include('data')
154
+ _(response['data']).must_be_kind_of(Array)
155
+
156
+ vip_level = response['data'].first
157
+ _(vip_level).must_include('level')
158
+ _(vip_level).must_include('dealAmount')
159
+ _(vip_level).must_include('assetAmount')
160
+ _(vip_level).must_include('takerFeeRate')
161
+ _(vip_level).must_include('makerFeeRate')
162
+ _(vip_level).must_include('btcWithdrawAmount')
163
+ _(vip_level).must_include('usdtWithdrawAmount')
164
+ end
165
+ end
166
+ end
167
+
168
+ describe "#spot_market_tickers" do
169
+ context "when a symbol is NOT supplied" do
170
+ it "retrieves all tickers" do
171
+ VCR.use_cassette('v2/spot/market/tickers-when_symbol_is_not_supplied') do
172
+ response = client.spot_market_tickers
173
+ _(response['code']).must_equal('00000')
174
+ _(response['msg']).must_equal('success')
175
+ _(response).must_include('requestTime')
176
+ _(response).must_include('data')
177
+ _(response['data']).must_be_kind_of(Array)
178
+
179
+ ticker = response['data'].first
180
+ _(ticker).must_include('symbol')
181
+ _(ticker).must_include('high24h')
182
+ _(ticker).must_include('open')
183
+ _(ticker).must_include('low24h')
184
+ _(ticker).must_include('lastPr')
185
+ _(ticker).must_include('quoteVolume')
186
+ _(ticker).must_include('baseVolume')
187
+ _(ticker).must_include('usdtVolume')
188
+ _(ticker).must_include('bidPr')
189
+ _(ticker).must_include('askPr')
190
+ _(ticker).must_include('bidSz')
191
+ _(ticker).must_include('askSz')
192
+ _(ticker).must_include('openUtc')
193
+ _(ticker).must_include('ts')
194
+ _(ticker).must_include('changeUtc24h')
195
+ _(ticker).must_include('change24h')
196
+ end
197
+ end
198
+ end
199
+
200
+ context "when a symbol is supplied" do
201
+ it "retrieves ticker for the specified symbol" do
202
+ VCR.use_cassette('v2/spot/market/tickers-when_symbol_is_supplied') do
203
+ response = client.spot_market_tickers(symbol: 'BTCUSDT')
204
+ _(response['code']).must_equal('00000')
205
+ _(response['msg']).must_equal('success')
206
+ _(response).must_include('requestTime')
207
+ _(response).must_include('data')
208
+ _(response['data']).must_be_kind_of(Array)
209
+ _(response['data'].count).must_equal(1)
210
+
211
+ ticker = response['data'].first
212
+ _(ticker['symbol']).must_equal('BTCUSDT')
213
+ _(ticker).must_include('high24h')
214
+ _(ticker).must_include('open')
215
+ _(ticker).must_include('low24h')
216
+ _(ticker).must_include('lastPr')
217
+ _(ticker).must_include('quoteVolume')
218
+ _(ticker).must_include('baseVolume')
219
+ _(ticker).must_include('usdtVolume')
220
+ _(ticker).must_include('bidPr')
221
+ _(ticker).must_include('askPr')
222
+ _(ticker).must_include('bidSz')
223
+ _(ticker).must_include('askSz')
224
+ _(ticker).must_include('openUtc')
225
+ _(ticker).must_include('ts')
226
+ _(ticker).must_include('changeUtc24h')
227
+ _(ticker).must_include('change24h')
228
+ end
229
+ end
230
+ end
231
+ end
232
+
233
+ describe "#spot_market_merge_depth" do
234
+ it "retrieves merge depth" do
235
+ VCR.use_cassette('v2/spot/market/merge-depth') do
236
+ response = client.spot_market_merge_depth(symbol: 'BTCUSDT')
237
+ _(response['msg']).must_equal('success')
238
+ _(response).must_include('data')
239
+ _(response['data']).must_include('asks')
240
+ _(response['data']).must_include('bids')
241
+ _(response['data']).must_include('ts')
242
+ end
243
+ end
244
+ end
245
+
246
+ describe "#spot_market_orderbook" do
247
+ it "retrieves orderbook" do
248
+ VCR.use_cassette('v2/spot/market/orderbook') do
249
+ response = client.spot_market_orderbook(symbol: 'BTCUSDT')
250
+ _(response['msg']).must_equal('success')
251
+ _(response).must_include('data')
252
+ _(response['data']).must_include('asks')
253
+ _(response['data']).must_include('bids')
254
+ _(response['data']).must_include('ts')
255
+ end
256
+ end
257
+ end
258
+
259
+ describe "#spot_market_candles" do
260
+ it "retrieves candles" do
261
+ VCR.use_cassette('v2/spot/market/candles') do
262
+ response = client.spot_market_candles(symbol: 'BTCUSDT', granularity: '1min')
263
+ _(response['msg']).must_equal('success')
264
+ _(response).must_include('data')
265
+ _(response['data'].first).must_be_kind_of(Array)
266
+ _(response['data'].first.length).must_equal(8) # [timestamp, open, high, low, close, base_volume, usdt_volume, quote_volume]
267
+ end
268
+ end
269
+ end
270
+
271
+ describe "#spot_market_history_candles" do
272
+ it "retrieves history candles" do
273
+ VCR.use_cassette('v2/spot/market/history-candles') do
274
+ response = client.spot_market_history_candles(symbol: 'BTCUSDT', granularity: '1min', end_time: 1690196141868)
275
+ _(response['msg']).must_equal('success')
276
+ _(response).must_include('data')
277
+ _(response['data'].first).must_be_kind_of(Array)
278
+ _(response['data'].first.length).must_equal(8) # [ts, open, high, low, close, base volume, USDT volume, quote volume]
279
+ end
280
+ end
281
+ end
282
+
283
+ describe "#spot_market_fills" do
284
+ it "retrieves recent trades" do
285
+ VCR.use_cassette('v2/spot/market/fills') do
286
+ response = client.spot_market_fills(symbol: 'BTCUSDT')
287
+ _(response['msg']).must_equal('success')
288
+ _(response).must_include('data')
289
+ _(response['data'].first).must_include('price')
290
+ _(response['data'].first).must_include('size')
291
+ _(response['data'].first).must_include('side')
292
+ _(response['data'].first).must_include('ts')
293
+ end
294
+ end
295
+ end
296
+
297
+ describe "#spot_market_fills_history" do
298
+ it "retrieves market trades history" do
299
+ VCR.use_cassette('v2/spot/market/fills-history') do
300
+ response = client.spot_market_fills_history(symbol: 'BTCUSDT')
301
+ _(response['msg']).must_equal('success')
302
+ _(response).must_include('data')
303
+ _(response['data'].first).must_include('price')
304
+ _(response['data'].first).must_include('size')
305
+ _(response['data'].first).must_include('side')
306
+ _(response['data'].first).must_include('ts')
307
+ end
308
+ end
309
+ end
310
+
311
+ # Trade
312
+
313
+ describe "#spot_trade_place_order" do
314
+ it "places an order" do
315
+ VCR.use_cassette('v2/spot/trade/place-order') do
316
+ response = client.spot_trade_place_order(
317
+ symbol: 'BTCUSDT',
318
+ side: 'buy',
319
+ order_type: 'limit',
320
+ force: 'gtc',
321
+ price: '30000',
322
+ size: '0.001'
323
+ )
324
+ _(response['msg']).must_equal('success')
325
+ _(response).must_include('data')
326
+ _(response['data']).must_include('orderId')
327
+ _(response['data']).must_include('clientOid')
328
+ end
329
+ end
330
+ end
331
+
332
+ describe "#spot_trade_cancel_replace_order" do
333
+ it "cancels and replaces an order" do
334
+ VCR.use_cassette('v2/spot/trade/cancel-replace-order') do
335
+ response = client.spot_trade_cancel_replace_order(
336
+ symbol: 'BTCUSDT',
337
+ price: '31000',
338
+ size: '0.001',
339
+ order_id: '123456'
340
+ )
341
+ _(response).must_include('data')
342
+ _(response['data']).must_include('orderId')
343
+ _(response['data']).must_include('clientOid')
344
+ end
345
+ end
346
+ end
347
+
348
+ describe "#spot_trade_batch_cancel_replace_order" do
349
+ it "batch cancels and replaces orders" do
350
+ VCR.use_cassette('v2/spot/trade/batch-cancel-replace-order') do
351
+ order_list = [
352
+ {
353
+ symbol: 'BTCUSDT',
354
+ price: '31000',
355
+ size: '0.001',
356
+ orderId: '123456',
357
+ },
358
+ {
359
+ symbol: 'ETHUSDT',
360
+ price: '2000',
361
+ size: '0.01',
362
+ orderId: '123457',
363
+ }
364
+ ]
365
+ response = client.spot_trade_batch_cancel_replace_order(order_list: order_list)
366
+ _(response['msg']).must_equal('success')
367
+ _(response).must_include('data')
368
+ _(response['data']).must_be_kind_of(Array)
369
+ _(response['data'].first).must_include('orderId')
370
+ _(response['data'].first).must_include('clientOid')
371
+ _(response['data'].first).must_include('success')
372
+ _(response['data'].first).must_include('msg')
373
+ end
374
+ end
375
+ end
376
+
377
+ describe "#spot_trade_cancel_order" do
378
+ it "cancels an order" do
379
+ VCR.use_cassette('v2/spot/trade/cancel-order') do
380
+ response = client.spot_trade_cancel_order(symbol: 'BTCUSDT')
381
+ _(response['message']).must_equal('success')
382
+ _(response).must_include('data')
383
+ _(response['data']).must_include('orderId')
384
+ _(response['data']).must_include('clientOid')
385
+ end
386
+ end
387
+ end
388
+
389
+ describe "#spot_trade_batch_orders" do
390
+ it "places batch orders" do
391
+ VCR.use_cassette('v2/spot/trade/batch-orders') do
392
+ order_list = [
393
+ {
394
+ symbol: 'BTCUSDT',
395
+ side: 'buy',
396
+ orderType: 'limit',
397
+ force: 'gtc',
398
+ price: '30000',
399
+ size: '0.001'
400
+ },
401
+ {
402
+ symbol: 'ETHUSDT',
403
+ side: 'buy',
404
+ orderType: 'limit',
405
+ force: 'gtc',
406
+ price: '2000',
407
+ size: '0.01'
408
+ }
409
+ ]
410
+ response = client.spot_trade_batch_orders(batch_mode: 'multiple', order_list: order_list)
411
+ _(response['msg']).must_equal('success')
412
+ _(response).must_include('data')
413
+ _(response['data']).must_include('successList')
414
+ _(response['data']).must_include('failureList')
415
+ _(response['data']['successList']).must_be_kind_of(Array)
416
+ _(response['data']['successList'].first).must_include('orderId')
417
+ _(response['data']['successList'].first).must_include('clientOid')
418
+ _(response['data']['failureList']).must_be_kind_of(Array)
419
+ end
420
+ end
421
+ end
422
+
423
+ describe "#spot_trade_batch_cancel_order" do
424
+ it "cancels batch orders" do
425
+ VCR.use_cassette('v2/spot/trade/batch-cancel-order') do
426
+ response = client.spot_trade_batch_cancel_order(symbol: 'BTCUSDT', order_list: ['123456', '123457'])
427
+ _(response['message']).must_equal('success')
428
+ _(response).must_include('data')
429
+ _(response['data']).must_be_kind_of(Hash)
430
+ _(response['data']).must_include('successList')
431
+ _(response['data']['successList'].first).must_include('orderId')
432
+ _(response['data']['successList'].first).must_include('clientOid')
433
+ _(response['data']).must_include('failureList')
434
+ _(response['data']['failureList'].first).must_include('orderId')
435
+ _(response['data']['failureList'].first).must_include('clientOid')
436
+ _(response['data']['failureList'].first).must_include('errorMsg')
437
+ end
438
+ end
439
+ end
440
+
441
+ describe "#spot_trade_cancel_symbol_order" do
442
+ it "cancels all orders for a symbol" do
443
+ VCR.use_cassette('v2/spot/trade/cancel-symbol-order') do
444
+ response = client.spot_trade_cancel_symbol_order(symbol: 'BTCUSDT')
445
+ _(response['msg']).must_equal('success')
446
+ _(response).must_include('data')
447
+ _(response['data']).must_be_kind_of(Hash)
448
+ _(response['data']).must_include('symbol')
449
+ end
450
+ end
451
+ end
452
+
453
+ describe "#spot_trade_order_info" do
454
+ it "retrieves order information" do
455
+ VCR.use_cassette('v2/spot/trade/orderInfo') do
456
+ response = client.spot_trade_order_info(order_id: '123456')
457
+ _(response['msg']).must_equal('success')
458
+ _(response).must_include('data')
459
+ _(response['data']).must_be_kind_of(Array)
460
+ _(response['data'].first).must_include('userId')
461
+ _(response['data'].first).must_include('symbol')
462
+ _(response['data'].first).must_include('orderId')
463
+ _(response['data'].first).must_include('clientOid')
464
+ _(response['data'].first).must_include('price')
465
+ _(response['data'].first).must_include('size')
466
+ _(response['data'].first).must_include('orderType')
467
+ _(response['data'].first).must_include('side')
468
+ _(response['data'].first).must_include('status')
469
+ _(response['data'].first).must_include('priceAvg')
470
+ _(response['data'].first).must_include('baseVolume')
471
+ _(response['data'].first).must_include('quoteVolume')
472
+ _(response['data'].first).must_include('enterPointSource')
473
+ _(response['data'].first).must_include('feeDetail')
474
+ _(response['data'].first).must_include('orderSource')
475
+ _(response['data'].first).must_include('cancelReason')
476
+ _(response['data'].first).must_include('cTime')
477
+ _(response['data'].first).must_include('orderType')
478
+ _(response['data'].first).must_include('uTime')
479
+ end
480
+ end
481
+ end
482
+
483
+ describe "#spot_trade_unfilled_orders" do
484
+ it "gets unfilled orders" do
485
+ VCR.use_cassette('v2/spot/trade/unfilled-orders') do
486
+ response = client.spot_trade_unfilled_orders(symbol: 'BTCUSDT')
487
+ _(response['message']).must_equal('success')
488
+ _(response).must_include('data')
489
+ _(response['data']).must_be_kind_of(Array)
490
+ _(response['data'].first).must_include('orderId')
491
+ _(response['data'].first).must_include('clientOid')
492
+ _(response['data'].first).must_include('symbol')
493
+ _(response['data'].first).must_include('status')
494
+ end
495
+ end
496
+ end
497
+
498
+ describe "#spot_trade_history_orders" do
499
+ it "gets history orders" do
500
+ VCR.use_cassette('v2/spot/trade/history-orders') do
501
+ response = client.spot_trade_history_orders(symbol: 'BTCUSDT')
502
+ _(response['message']).must_equal('success')
503
+ _(response).must_include('data')
504
+ _(response['data']).must_be_kind_of(Array)
505
+ _(response['data'].first).must_include('userId')
506
+ _(response['data'].first).must_include('symbol')
507
+ _(response['data'].first).must_include('orderId')
508
+ _(response['data'].first).must_include('clientOid')
509
+ _(response['data'].first).must_include('price')
510
+ _(response['data'].first).must_include('size')
511
+ _(response['data'].first).must_include('orderType')
512
+ _(response['data'].first).must_include('side')
513
+ _(response['data'].first).must_include('status')
514
+ _(response['data'].first).must_include('priceAvg')
515
+ _(response['data'].first).must_include('baseVolume')
516
+ _(response['data'].first).must_include('quoteVolume')
517
+ _(response['data'].first).must_include('enterPointSource')
518
+ _(response['data'].first).must_include('feeDetail')
519
+ _(response['data'].first).must_include('orderSource')
520
+ _(response['data'].first).must_include('cTime')
521
+ _(response['data'].first).must_include('uTime')
522
+ _(response['data'].first).must_include('tpslType')
523
+ _(response['data'].first).must_include('cancelReason')
524
+ _(response['data'].first).must_include('triggerPrice')
525
+ end
526
+ end
527
+ end
528
+
529
+ describe "#spot_trade_fills" do
530
+ it "gets trade fills" do
531
+ VCR.use_cassette('v2/spot/trade/fills') do
532
+ response = client.spot_trade_fills(symbol: 'BTCUSDT')
533
+ _(response['msg']).must_equal('success')
534
+ _(response).must_include('data')
535
+ _(response['data']).must_be_kind_of(Array)
536
+ _(response['data'].first).must_include('userId')
537
+ _(response['data'].first).must_include('symbol')
538
+ _(response['data'].first).must_include('orderId')
539
+ _(response['data'].first).must_include('tradeId')
540
+ _(response['data'].first).must_include('orderType')
541
+ _(response['data'].first).must_include('side')
542
+ _(response['data'].first).must_include('priceAvg')
543
+ _(response['data'].first).must_include('size')
544
+ _(response['data'].first).must_include('amount')
545
+ _(response['data'].first).must_include('feeDetail')
546
+ _(response['data'].first['feeDetail']).must_include('deduction')
547
+ _(response['data'].first['feeDetail']).must_include('feeCoin')
548
+ _(response['data'].first['feeDetail']).must_include('totalDeductionFee')
549
+ _(response['data'].first['feeDetail']).must_include('totalFee')
550
+ _(response['data'].first).must_include('tradeScope')
551
+ _(response['data'].first).must_include('cTime')
552
+ _(response['data'].first).must_include('uTime')
553
+ end
554
+ end
555
+ end
556
+
557
+ # Trigger
558
+
559
+ describe "#spot_trade_place_plan_order" do
560
+ it "places a plan order" do
561
+ VCR.use_cassette('v2/spot/trade/place-plan-order') do
562
+ response = client.spot_trade_place_plan_order(
563
+ symbol: 'BTCUSDT',
564
+ side: 'buy',
565
+ order_type: 'limit',
566
+ size: '0.001',
567
+ trigger_type: 'mark_price',
568
+ trigger_price: '30000',
569
+ execute_price: '30000'
570
+ )
571
+ _(response['msg']).must_equal('success')
572
+ _(response).must_include('data')
573
+ _(response['data']).must_include('orderId')
574
+ _(response['data']).must_include('clientOid')
575
+ end
576
+ end
577
+ end
578
+
579
+ describe "#spot_trade_modify_plan_order" do
580
+ it "modifies a plan order" do
581
+ VCR.use_cassette('v2/spot/trade/modify-plan-order') do
582
+ response = client.spot_trade_modify_plan_order(order_id: '123456')
583
+ _(response['msg']).must_equal('success')
584
+ _(response).must_include('data')
585
+ _(response['data']).must_include('orderId')
586
+ _(response['data']).must_include('clientOid')
587
+ end
588
+ end
589
+ end
590
+
591
+ describe "#spot_trade_cancel_plan_order" do
592
+ it "cancels a plan order" do
593
+ VCR.use_cassette('v2/spot/trade/cancel-plan-order') do
594
+ response = client.spot_trade_cancel_plan_order(order_id: '123456')
595
+ _(response['msg']).must_equal('success')
596
+ _(response).must_include('data')
597
+ _(response).must_be_kind_of(Hash)
598
+ _(response['data']).must_include('result')
599
+ _(response['data']['result']).must_equal('success')
600
+ end
601
+ end
602
+ end
603
+
604
+ describe "#spot_trade_current_plan_order" do
605
+ it "gets current plan orders" do
606
+ VCR.use_cassette('v2/spot/trade/current-plan-order') do
607
+ response = client.spot_trade_current_plan_order(symbol: 'BTCUSDT')
608
+ _(response['msg']).must_equal('success')
609
+ _(response).must_include('data')
610
+ _(response['data']).must_be_kind_of(Hash)
611
+ _(response['data']).must_include('nextFlag')
612
+ _(response['data']).must_include('idLessThan')
613
+ _(response['data']).must_include('orderList')
614
+ end
615
+ end
616
+ end
617
+
618
+ describe "#spot_trade_plan_sub_order" do
619
+ it "gets plan sub order" do
620
+ VCR.use_cassette('v2/spot/trade/plan-sub-order') do
621
+ response = client.spot_trade_plan_sub_order(order_id: '123456')
622
+ _(response['msg']).must_equal('success')
623
+ _(response).must_include('data')
624
+ _(response['data']).must_be_kind_of(Array)
625
+ _(response['data'].first).must_include('orderId')
626
+ _(response['data'].first).must_include('price')
627
+ _(response['data'].first).must_include('type')
628
+ _(response['data'].first).must_include('status')
629
+ end
630
+ end
631
+ end
632
+
633
+ describe "#spot_trade_history_plan_order" do
634
+ it "gets history plan orders" do
635
+ VCR.use_cassette('v2/spot/trade/history-plan-order') do
636
+ response = client.spot_trade_history_plan_order(
637
+ symbol: 'BTCUSDT',
638
+ start_time: 1747754739537,
639
+ end_time: 1747755739537
640
+ )
641
+ _(response['msg']).must_equal('success')
642
+ _(response).must_include('data')
643
+ _(response['data']).must_be_kind_of(Hash)
644
+ _(response['data']).must_include('nextFlag')
645
+ _(response['data']).must_include('idLessThan')
646
+ _(response['data']).must_include('orderList')
647
+ _(response['data']['orderList'].first).must_include('orderId')
648
+ _(response['data']['orderList'].first).must_include('clientOid')
649
+ _(response['data']['orderList'].first).must_include('symbol')
650
+ _(response['data']['orderList'].first).must_include('size')
651
+ _(response['data']['orderList'].first).must_include('executePrice')
652
+ _(response['data']['orderList'].first).must_include('triggerPrice')
653
+ _(response['data']['orderList'].first).must_include('status')
654
+ _(response['data']['orderList'].first).must_include('orderType')
655
+ _(response['data']['orderList'].first).must_include('side')
656
+ _(response['data']['orderList'].first).must_include('planType')
657
+ _(response['data']['orderList'].first).must_include('triggerType')
658
+ _(response['data']['orderList'].first).must_include('enterPointSource')
659
+ _(response['data']['orderList'].first).must_include('uTime')
660
+ _(response['data']['orderList'].first).must_include('cTime')
661
+ end
662
+ end
663
+ end
664
+
665
+ describe "#spot_trade_batch_cancel_plan_order" do
666
+ it "cancels batch plan orders" do
667
+ VCR.use_cassette('v2/spot/trade/batch-cancel-plan-order') do
668
+ response = client.spot_trade_batch_cancel_plan_order(symbol_list: ['BTCUSDT', 'ETHUSDT'])
669
+ _(response['msg']).must_equal('success')
670
+ _(response).must_include('data')
671
+ _(response['data']).must_be_kind_of(Hash)
672
+ _(response['data']).must_include('successList')
673
+ _(response['data']).must_include('failureList')
674
+ end
675
+ end
676
+ end
677
+
678
+ # Account
679
+
680
+ describe "#spot_account_info" do
681
+ it "retrieves account information" do
682
+ VCR.use_cassette('v2/spot/account/info') do
683
+ response = client.spot_account_info
684
+ _(response['msg']).must_equal('success')
685
+ _(response).must_include('data')
686
+ _(response['data']).must_include('userId')
687
+ _(response['data']).must_include('channelCode')
688
+ _(response['data']).must_include('authorities')
689
+ end
690
+ end
691
+ end
692
+
693
+ describe "#spot_account_assets" do
694
+ it "retrieves account assets" do
695
+ VCR.use_cassette('v2/spot/account/assets') do
696
+ response = client.spot_account_assets
697
+ _(response['msg']).must_equal('success')
698
+ _(response).must_include('data')
699
+ _(response['data'].first).must_include('coin')
700
+ _(response['data'].first).must_include('available')
701
+ _(response['data'].first).must_include('frozen')
702
+ _(response['data'].first).must_include('locked')
703
+ _(response['data'].first).must_include('uTime')
704
+ end
705
+ end
706
+ end
707
+
708
+ describe "#spot_account_subaccount_assets" do
709
+ it "gets subaccount assets" do
710
+ VCR.use_cassette('v2/spot/account/subaccount-assets') do
711
+ response = client.spot_account_subaccount_assets
712
+ _(response['message']).must_equal('success')
713
+ _(response).must_include('data')
714
+ _(response['data']).must_be_kind_of(Array)
715
+ _(response['data'].first).must_include('id')
716
+ _(response['data'].first).must_include('userId')
717
+ _(response['data'].first).must_include('assetsList')
718
+ _(response['data'].first['assetsList']).must_be_kind_of(Array)
719
+ _(response['data'].first['assetsList'].first).must_include('coin')
720
+ _(response['data'].first['assetsList'].first).must_include('available')
721
+ _(response['data'].first['assetsList'].first).must_include('limitAvailable')
722
+ _(response['data'].first['assetsList'].first).must_include('frozen')
723
+ _(response['data'].first['assetsList'].first).must_include('locked')
724
+ _(response['data'].first['assetsList'].first).must_include('uTime')
725
+ end
726
+ end
727
+ end
728
+
729
+ describe "#spot_account_bills" do
730
+ it "gets account bills" do
731
+ VCR.use_cassette('v2/spot/account/bills') do
732
+ response = client.spot_account_bills
733
+ _(response['msg']).must_equal('success')
734
+ _(response).must_include('data')
735
+ _(response['data']).must_be_kind_of(Array)
736
+ _(response['data'].first).must_include('billId')
737
+ _(response['data'].first).must_include('coin')
738
+ _(response['data'].first).must_include('bizOrderId')
739
+ _(response['data'].first).must_include('coin')
740
+ _(response['data'].first).must_include('groupType')
741
+ _(response['data'].first).must_include('businessType')
742
+ _(response['data'].first).must_include('size')
743
+ _(response['data'].first).must_include('balance')
744
+ _(response['data'].first).must_include('fees')
745
+ _(response['data'].first).must_include('cTime')
746
+ end
747
+ end
748
+ end
749
+
750
+ describe "#spot_account_sub_main_trans_record" do
751
+ it "gets main-sub transfer records" do
752
+ VCR.use_cassette('v2/spot/account/sub-main-trans-record') do
753
+ response = client.spot_account_sub_main_trans_record
754
+ _(response['msg']).must_equal('success')
755
+ _(response).must_include('data')
756
+ _(response['data']).must_be_kind_of(Array)
757
+ _(response['data'].first).must_include('coin')
758
+ _(response['data'].first).must_include('status')
759
+ _(response['data'].first).must_include('toType')
760
+ _(response['data'].first).must_include('fromType')
761
+ _(response['data'].first).must_include('size')
762
+ _(response['data'].first).must_include('ts')
763
+ _(response['data'].first).must_include('clientOid')
764
+ _(response['data'].first).must_include('transferId')
765
+ _(response['data'].first).must_include('fromUserId')
766
+ _(response['data'].first).must_include('toUserId')
767
+ end
768
+ end
769
+ end
770
+
771
+ describe "#spot_account_transfer_records" do
772
+ it "gets transfer records" do
773
+ VCR.use_cassette('v2/spot/account/transferRecords') do
774
+ response = client.spot_account_transfer_records
775
+ _(response['msg']).must_equal('success')
776
+ _(response).must_include('data')
777
+ _(response['data']).must_be_kind_of(Array)
778
+ _(response['data'].first).must_include('coin')
779
+ _(response['data'].first).must_include('status')
780
+ _(response['data'].first).must_include('toType')
781
+ _(response['data'].first).must_include('fromType')
782
+ _(response['data'].first).must_include('fromSymbol')
783
+ _(response['data'].first).must_include('size')
784
+ _(response['data'].first).must_include('ts')
785
+ _(response['data'].first).must_include('clientOid')
786
+ _(response['data'].first).must_include('transferId')
787
+ end
788
+ end
789
+ end
790
+
791
+ describe "#spot_account_switch_deduct" do
792
+ it "switches BGB deduct" do
793
+ VCR.use_cassette('v2/spot/account/switch-deduct') do
794
+ response = client.spot_account_switch_deduct(deduct: 'on')
795
+ _(response['msg']).must_equal('success')
796
+ _(response).must_include('data')
797
+ _(response['data']).must_equal(true)
798
+ end
799
+ end
800
+ end
801
+
802
+ describe "#spot_account_deduct_info" do
803
+ it "gets BGB deduct info" do
804
+ VCR.use_cassette('v2/spot/account/deduct-info') do
805
+ response = client.spot_account_deduct_info
806
+ _(response['msg']).must_equal('success')
807
+ _(response).must_include('data')
808
+ _(response['data']).must_include('deduct')
809
+ end
810
+ end
811
+ end
812
+
813
+ describe "#spot_wallet_modify_deposit_account" do
814
+ it "modifies deposit account" do
815
+ VCR.use_cassette('v2/spot/wallet/modify-deposit-account') do
816
+ response = client.spot_wallet_modify_deposit_account(account_type: 'spot', coin: 'TRX')
817
+ _(response['msg']).must_equal('success')
818
+ _(response).must_include('data')
819
+ _(response['data']).must_include('success')
820
+ end
821
+ end
822
+ end
823
+
824
+ describe "#spot_wallet_transfer" do
825
+ it "transfers funds between accounts" do
826
+ VCR.use_cassette('v2/spot/wallet/transfer') do
827
+ response = client.spot_wallet_transfer(
828
+ from_type: 'spot',
829
+ to_type: 'isolated_margin',
830
+ amount: '100',
831
+ coin: 'USDT',
832
+ symbol: 'TRXUSDT'
833
+ )
834
+ _(response['msg']).must_equal('success')
835
+ _(response).must_include('data')
836
+ _(response['data']).must_include('transferId')
837
+ _(response['data']).must_include('clientOid')
838
+ end
839
+ end
840
+ end
841
+
842
+ describe "#spot_wallet_transfer_coin_info" do
843
+ it "gets transferable coin list" do
844
+ VCR.use_cassette('v2/spot/wallet/transfer-coin-info') do
845
+ response = client.spot_wallet_transfer_coin_info(from_type: 'spot', to_type: 'isolated_margin')
846
+ _(response['msg']).must_equal('success')
847
+ _(response).must_include('data')
848
+ _(response['data']).must_be_kind_of(Array)
849
+ _(response['data']).must_include('AGLD')
850
+ end
851
+ end
852
+ end
853
+
854
+ describe "#spot_wallet_subaccount_transfer" do
855
+ it "transfers funds between subaccounts" do
856
+ VCR.use_cassette('v2/spot/wallet/subaccount-transfer') do
857
+ response = client.spot_wallet_subaccount_transfer(
858
+ from_type: 'spot',
859
+ to_type: 'isolated_margin',
860
+ amount: '100',
861
+ coin: 'USDT'
862
+ )
863
+ _(response['msg']).must_equal('success')
864
+ _(response).must_include('data')
865
+ _(response['data']).must_be_kind_of(Hash)
866
+ _(response['data']).must_include('transferId')
867
+ _(response['data']).must_include('clientOid')
868
+ end
869
+ end
870
+ end
871
+
872
+ describe "#spot_wallet_withdrawal" do
873
+ it "withdraws funds" do
874
+ VCR.use_cassette('v2/spot/wallet/withdrawal') do
875
+ response = client.spot_wallet_withdrawal(
876
+ coin: 'USDT',
877
+ transfer_type: 'on_chain',
878
+ address: '0x1234567890abcdef',
879
+ chain: 'TRC20',
880
+ size: 100
881
+ )
882
+ _(response['msg']).must_equal('success')
883
+ _(response).must_include('data')
884
+ _(response).must_be_kind_of(Hash)
885
+ _(response['msg']).must_equal('success')
886
+ _(response['data']).must_include('orderId')
887
+ _(response['data']).must_include('clientOid')
888
+ end
889
+ end
890
+ end
891
+
892
+ describe "#spot_wallet_cancel_withdrawal" do
893
+ it "cancels a withdrawal" do
894
+ VCR.use_cassette('v2/spot/wallet/cancel-withdrawal') do
895
+ response = client.spot_wallet_cancel_withdrawal(order_id: '123456')
896
+ _(response).must_include('data')
897
+ _(response['data']).must_equal('success')
898
+ end
899
+ end
900
+ end
901
+
902
+ describe "#spot_wallet_deposit_address" do
903
+ it "gets deposit address" do
904
+ VCR.use_cassette('v2/spot/wallet/deposit-address') do
905
+ response = client.spot_wallet_deposit_address(coin: 'USDT', chain: 'TRC20')
906
+ _(response['msg']).must_equal('success')
907
+ _(response).must_include('data')
908
+ _(response['data']).must_include('address')
909
+ _(response['data']).must_include('chain')
910
+ _(response['data']).must_include('coin')
911
+ end
912
+ end
913
+ end
914
+
915
+ describe "#spot_wallet_subaccount_deposit_address" do
916
+ it "gets subaccount deposit address" do
917
+ VCR.use_cassette('v2/spot/wallet/subaccount-deposit-address') do
918
+ response = client.spot_wallet_subaccount_deposit_address(
919
+ subaccount_user_id: '123456',
920
+ coin: 'USDT',
921
+ chain: 'TRC20'
922
+ )
923
+ _(response['msg']).must_equal('success')
924
+ _(response).must_include('data')
925
+ _(response['data']).must_include('address')
926
+ _(response['data']).must_include('chain')
927
+ _(response['data']).must_include('coin')
928
+ _(response['data']).must_include('tag')
929
+ _(response['data']).must_include('url')
930
+ end
931
+ end
932
+ end
933
+
934
+ describe "#spot_wallet_subaccount_deposit_records" do
935
+ it "gets subaccount deposit records" do
936
+ VCR.use_cassette('v2/spot/wallet/subaccount-deposit-records') do
937
+ response = client.spot_wallet_subaccount_deposit_records(subaccount_user_id: '123456')
938
+ _(response['msg']).must_equal('success')
939
+ _(response).must_include('data')
940
+ _(response['data']).must_be_kind_of(Array)
941
+ _(response['data'].first).must_include('orderId')
942
+ _(response['data'].first).must_include('tradeId')
943
+ _(response['data'].first).must_include('coin')
944
+ _(response['data'].first).must_include('size')
945
+ _(response['data'].first).must_include('status')
946
+ _(response['data'].first).must_include('toAddress')
947
+ _(response['data'].first).must_include('dest')
948
+ _(response['data'].first).must_include('chain')
949
+ _(response['data'].first).must_include('fromAddress')
950
+ _(response['data'].first).must_include('cTime')
951
+ _(response['data'].first).must_include('uTime')
952
+ end
953
+ end
954
+ end
955
+
956
+ describe "#spot_wallet_withdrawal_records" do
957
+ it "gets withdrawal records" do
958
+ VCR.use_cassette('v2/spot/wallet/withdrawal-records') do
959
+ response = client.spot_wallet_withdrawal_records(start_time: 1690196141868, end_time: 1690197141868)
960
+ _(response['msg']).must_equal('success')
961
+ _(response).must_include('data')
962
+ _(response['data']).must_be_kind_of(Array)
963
+ _(response['data'].first).must_include('orderId')
964
+ _(response['data'].first).must_include('tradeId')
965
+ _(response['data'].first).must_include('coin')
966
+ _(response['data'].first).must_include('dest')
967
+ _(response['data'].first).must_include('clientOid')
968
+ _(response['data'].first).must_include('type')
969
+ _(response['data'].first).must_include('tag')
970
+ _(response['data'].first).must_include('size')
971
+ _(response['data'].first).must_include('fee')
972
+ _(response['data'].first).must_include('status')
973
+ _(response['data'].first).must_include('toAddress')
974
+ _(response['data'].first).must_include('fromAddress')
975
+ _(response['data'].first).must_include('confirm')
976
+ _(response['data'].first).must_include('chain')
977
+ _(response['data'].first).must_include('cTime')
978
+ _(response['data'].first).must_include('uTime')
979
+ end
980
+ end
981
+ end
982
+
983
+ describe "#spot_wallet_deposit_records" do
984
+ it "gets deposit records" do
985
+ VCR.use_cassette('v2/spot/wallet/deposit-records') do
986
+ response = client.spot_wallet_deposit_records(
987
+ start_time: 1690196141868,
988
+ end_time: 1690197141868
989
+ )
990
+ _(response['msg']).must_equal('success')
991
+ _(response).must_include('data')
992
+ _(response['data']).must_be_kind_of(Array)
993
+ _(response['data'].first).must_include('orderId')
994
+ _(response['data'].first).must_include('tradeId')
995
+ _(response['data'].first).must_include('coin')
996
+ _(response['data'].first).must_include('type')
997
+ _(response['data'].first).must_include('size')
998
+ _(response['data'].first).must_include('status')
999
+ _(response['data'].first).must_include('toAddress')
1000
+ _(response['data'].first).must_include('dest')
1001
+ _(response['data'].first).must_include('chain')
1002
+ _(response['data'].first).must_include('fromAddress')
1003
+ _(response['data'].first).must_include('cTime')
1004
+ _(response['data'].first).must_include('uTime')
1005
+ end
1006
+ end
1007
+ end
1008
+
1009
+ describe "error handling" do
1010
+ let(:mock_error){Minitest::Mock.new}
1011
+
1012
+ before do
1013
+ client.logger = Logger.new(StringIO.new)
1014
+ mock_error.expect(:call, nil, [], code: '418', message: "I'm a teapot", body: '')
1015
+ end
1016
+
1017
+ after do
1018
+ client.logger = nil
1019
+ mock_error.verify
1020
+ end
1021
+
1022
+ it "handles errors for public GET endpoints with NO arguments" do
1023
+ VCR.use_cassette('v2/spot/public/coins-when_an_error_occurs') do
1024
+ assert_raises(Bitget::Error) do
1025
+ client.stub(:log_error, mock_error) do
1026
+ client.spot_public_coins
1027
+ end
1028
+ end
1029
+ end
1030
+ end
1031
+
1032
+ it "handles errors for public GET endpoints WITH arguments" do
1033
+ VCR.use_cassette('v2/spot/market/candles-when_an_error_occurs') do
1034
+ assert_raises(Bitget::Error) do
1035
+ client.stub(:log_error, mock_error) do
1036
+ client.spot_market_candles(symbol: 'INVALID', granularity: '1min')
1037
+ end
1038
+ end
1039
+ end
1040
+ end
1041
+
1042
+ it "handles errors for authenticated GET endpoints with NO arguments" do
1043
+ VCR.use_cassette('v2/spot/account/info-when_an_error_occurs') do
1044
+ assert_raises(Bitget::Error) do
1045
+ client.stub(:log_error, mock_error) do
1046
+ client.spot_account_info
1047
+ end
1048
+ end
1049
+ end
1050
+ end
1051
+
1052
+ it "handles errors for authenticated GET endpoints WITH arguments" do
1053
+ VCR.use_cassette('v2/spot/account/bills-when_an_error_occurs') do
1054
+ assert_raises(Bitget::Error) do
1055
+ client.stub(:log_error, mock_error) do
1056
+ client.spot_account_bills(
1057
+ coin: 'BTC',
1058
+ business_type: 'deposit',
1059
+ group_type: 'transfer'
1060
+ )
1061
+ end
1062
+ end
1063
+ end
1064
+ end
1065
+
1066
+ it "handles errors for authenticated POST endpoints WITH arguments" do
1067
+ VCR.use_cassette('v2/spot/trade/place-order-when_an_error_occurs') do
1068
+ assert_raises(Bitget::Error) do
1069
+ client.stub(:log_error, mock_error) do
1070
+ client.spot_trade_place_order(
1071
+ symbol: 'BTCUSDT',
1072
+ side: 'buy',
1073
+ order_type: 'limit',
1074
+ force: 'normal',
1075
+ price: '30000',
1076
+ size: '0.001'
1077
+ )
1078
+ end
1079
+ end
1080
+ end
1081
+ end
1082
+ end
1083
+
1084
+ describe "logging" do
1085
+ let(:log){StringIO.new}
1086
+
1087
+ before do
1088
+ client.logger = Logger.new(log)
1089
+ end
1090
+
1091
+ after do
1092
+ client.logger = nil
1093
+ end
1094
+
1095
+ describe "request logging" do
1096
+ it "logs requests" do
1097
+ VCR.use_cassette("v2/spot/public/coins-when_coin_is_supplied") do
1098
+ client.spot_public_coins(coin: "BTC")
1099
+ end
1100
+ _(log.string).must_match(/GET https:\/\/api.bitget.com\/api\/v2\/spot\/public\/coins/)
1101
+ _(log.string).must_match(/Args: \{coin: "BTC"}/)
1102
+ _(log.string).must_match(/Headers: .+"Content-Type" => "application\/json"/)
1103
+ end
1104
+ end
1105
+
1106
+ describe "response logging" do
1107
+ it "logs responses" do
1108
+ VCR.use_cassette("v2/spot/public/coins-when_coin_is_supplied") do
1109
+ client.spot_public_coins(coin: "BTC")
1110
+ end
1111
+ _(log.string).must_match(/Code:/)
1112
+ _(log.string).must_match(/Message:/)
1113
+ _(log.string).must_match(/Body:/)
1114
+ end
1115
+ end
1116
+
1117
+ describe "error logging" do
1118
+ it "logs error responses" do
1119
+ VCR.use_cassette("v2/spot/public/coins-when_error_occurs") do
1120
+ begin
1121
+ client.spot_public_coins(coin: "INVALID")
1122
+ rescue Bitget::Error
1123
+ end
1124
+ end
1125
+ _(log.string).must_match(/Code:/)
1126
+ _(log.string).must_match(/Message:/)
1127
+ _(log.string).must_match(/Body:/)
1128
+ end
1129
+ end
1130
+ end
1131
+ end