honeymaker 0.1.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/lib/honeymaker/client.rb +96 -0
  4. data/lib/honeymaker/clients/binance.rb +288 -0
  5. data/lib/honeymaker/clients/binance_us.rb +9 -0
  6. data/lib/honeymaker/clients/bingx.rb +136 -0
  7. data/lib/honeymaker/clients/bitget.rb +157 -0
  8. data/lib/honeymaker/clients/bitmart.rb +146 -0
  9. data/lib/honeymaker/clients/bitrue.rb +130 -0
  10. data/lib/honeymaker/clients/bitvavo.rb +160 -0
  11. data/lib/honeymaker/clients/bybit.rb +166 -0
  12. data/lib/honeymaker/clients/coinbase.rb +219 -0
  13. data/lib/honeymaker/clients/gemini.rb +99 -0
  14. data/lib/honeymaker/clients/hyperliquid.rb +65 -0
  15. data/lib/honeymaker/clients/kraken.rb +162 -0
  16. data/lib/honeymaker/clients/kucoin.rb +180 -0
  17. data/lib/honeymaker/clients/mexc.rb +166 -0
  18. data/lib/honeymaker/exchanges/binance.rb +11 -17
  19. data/lib/honeymaker/exchanges/bitget.rb +2 -2
  20. data/lib/honeymaker/exchanges/bitrue.rb +6 -16
  21. data/lib/honeymaker/exchanges/bitvavo.rb +2 -2
  22. data/lib/honeymaker/exchanges/bybit.rb +2 -2
  23. data/lib/honeymaker/exchanges/coinbase.rb +5 -9
  24. data/lib/honeymaker/exchanges/gemini.rb +4 -6
  25. data/lib/honeymaker/exchanges/kraken.rb +5 -9
  26. data/lib/honeymaker/exchanges/kucoin.rb +2 -2
  27. data/lib/honeymaker/exchanges/mexc.rb +11 -17
  28. data/lib/honeymaker/utils.rb +9 -0
  29. data/lib/honeymaker/version.rb +1 -1
  30. data/lib/honeymaker.rb +38 -0
  31. data/test/fixtures/bingx_symbols.json +26 -0
  32. data/test/fixtures/bitget_symbols.json +28 -0
  33. data/test/fixtures/bitmart_symbols.json +26 -0
  34. data/test/fixtures/bitrue_exchange_info.json +34 -0
  35. data/test/fixtures/bitvavo_markets.json +16 -0
  36. data/test/fixtures/bybit_instruments.json +23 -0
  37. data/test/fixtures/coinbase_products.json +24 -0
  38. data/test/fixtures/gemini_symbol_detail.json +9 -0
  39. data/test/fixtures/gemini_symbols.json +1 -0
  40. data/test/fixtures/hyperliquid_spot_meta.json +12 -0
  41. data/test/fixtures/kucoin_symbols.json +17 -0
  42. data/test/fixtures/mexc_exchange_info.json +40 -0
  43. data/test/honeymaker/client_test.rb +53 -0
  44. data/test/honeymaker/clients/binance_client_test.rb +101 -0
  45. data/test/honeymaker/clients/binance_us_client_test.rb +25 -0
  46. data/test/honeymaker/clients/bingx_client_test.rb +82 -0
  47. data/test/honeymaker/clients/bitget_client_test.rb +109 -0
  48. data/test/honeymaker/clients/bitmart_client_test.rb +96 -0
  49. data/test/honeymaker/clients/bitrue_client_test.rb +69 -0
  50. data/test/honeymaker/clients/bitvavo_client_test.rb +105 -0
  51. data/test/honeymaker/clients/bybit_client_test.rb +102 -0
  52. data/test/honeymaker/clients/coinbase_client_test.rb +132 -0
  53. data/test/honeymaker/clients/gemini_client_test.rb +83 -0
  54. data/test/honeymaker/clients/honeymaker_client_registry_test.rb +44 -0
  55. data/test/honeymaker/clients/hyperliquid_client_test.rb +65 -0
  56. data/test/honeymaker/clients/kraken_client_test.rb +84 -0
  57. data/test/honeymaker/clients/kucoin_client_test.rb +106 -0
  58. data/test/honeymaker/clients/mexc_client_test.rb +93 -0
  59. data/test/honeymaker/clients/validation_test.rb +182 -0
  60. data/test/honeymaker/exchanges/binance_us_test.rb +40 -0
  61. data/test/honeymaker/exchanges/bingx_test.rb +53 -0
  62. data/test/honeymaker/exchanges/bitget_test.rb +52 -0
  63. data/test/honeymaker/exchanges/bitmart_test.rb +52 -0
  64. data/test/honeymaker/exchanges/bitrue_test.rb +53 -0
  65. data/test/honeymaker/exchanges/bitvavo_test.rb +52 -0
  66. data/test/honeymaker/exchanges/bybit_test.rb +43 -0
  67. data/test/honeymaker/exchanges/coinbase_test.rb +52 -0
  68. data/test/honeymaker/exchanges/gemini_test.rb +48 -0
  69. data/test/honeymaker/exchanges/hyperliquid_test.rb +52 -0
  70. data/test/honeymaker/exchanges/kucoin_test.rb +43 -0
  71. data/test/honeymaker/exchanges/mexc_test.rb +64 -0
  72. data/test/honeymaker/utils_test.rb +38 -0
  73. data/test/test_helper.rb +1 -0
  74. metadata +74 -3
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class Honeymaker::Clients::BingXTest < Minitest::Test
6
+ def setup
7
+ @client = Honeymaker::Clients::BingX.new(api_key: "test_key", api_secret: "test_secret")
8
+ end
9
+
10
+ def test_url
11
+ assert_equal "https://open-api.bingx.com", Honeymaker::Clients::BingX::URL
12
+ end
13
+
14
+ def test_get_symbols
15
+ stub_connection(:get, { "data" => { "symbols" => [{ "symbol" => "BTC-USDT" }] } })
16
+ result = @client.get_symbols
17
+ assert result.success?
18
+ end
19
+
20
+ def test_get_ticker
21
+ stub_connection(:get, { "data" => [{ "symbol" => "BTC-USDT" }] })
22
+ result = @client.get_ticker(symbol: "BTC-USDT")
23
+ assert result.success?
24
+ end
25
+
26
+ def test_get_balances
27
+ stub_connection(:get, { "data" => { "balances" => [{ "asset" => "BTC" }] } })
28
+ result = @client.get_balances
29
+ assert result.success?
30
+ end
31
+
32
+ def test_place_order
33
+ stub_connection(:post, { "data" => { "orderId" => "123" } })
34
+ result = @client.place_order(symbol: "BTC-USDT", side: "BUY", type: "MARKET", quantity: "0.001")
35
+ assert result.success?
36
+ end
37
+
38
+ def test_get_order
39
+ stub_connection(:get, { "data" => { "orderId" => "123" } })
40
+ result = @client.get_order(symbol: "BTC-USDT", order_id: "123")
41
+ assert result.success?
42
+ end
43
+
44
+ def test_cancel_order
45
+ stub_connection(:post, { "data" => { "orderId" => "123" } })
46
+ result = @client.cancel_order(symbol: "BTC-USDT", order_id: "123")
47
+ assert result.success?
48
+ end
49
+
50
+ def test_withdraw
51
+ stub_connection(:post, { "data" => { "id" => "w1" } })
52
+ result = @client.withdraw(coin: "BTC", address: "addr", amount: "0.1")
53
+ assert result.success?
54
+ end
55
+
56
+ def test_get_trade_fills
57
+ stub_connection(:get, { "data" => [{ "tradeId" => "t1" }] })
58
+ result = @client.get_trade_fills(symbol: "BTC-USDT")
59
+ assert result.success?
60
+ end
61
+
62
+ def test_deposit_history
63
+ stub_connection(:get, [{ "coin" => "BTC" }])
64
+ result = @client.deposit_history
65
+ assert result.success?
66
+ end
67
+
68
+ def test_withdraw_history
69
+ stub_connection(:get, [{ "coin" => "ETH" }])
70
+ result = @client.withdraw_history
71
+ assert result.success?
72
+ end
73
+
74
+ private
75
+
76
+ def stub_connection(method, body)
77
+ response = stub(body: body)
78
+ connection = stub
79
+ connection.stubs(method).returns(response)
80
+ @client.instance_variable_set(:@connection, connection)
81
+ end
82
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class Honeymaker::Clients::BitgetTest < Minitest::Test
6
+ def setup
7
+ @client = Honeymaker::Clients::Bitget.new(
8
+ api_key: "test_key", api_secret: "test_secret", passphrase: "test_pass"
9
+ )
10
+ end
11
+
12
+ def test_url
13
+ assert_equal "https://api.bitget.com", Honeymaker::Clients::Bitget::URL
14
+ end
15
+
16
+ def test_accepts_passphrase
17
+ assert_equal "test_pass", @client.passphrase
18
+ end
19
+
20
+ def test_get_coins
21
+ stub_connection(:get, { "data" => [{ "coin" => "BTC" }] })
22
+ result = @client.get_coins
23
+ assert result.success?
24
+ end
25
+
26
+ def test_get_symbols
27
+ stub_connection(:get, { "data" => [{ "symbol" => "BTCUSDT" }] })
28
+ result = @client.get_symbols
29
+ assert result.success?
30
+ end
31
+
32
+ def test_get_account_assets
33
+ stub_connection(:get, { "data" => [{ "coin" => "BTC", "available" => "0.5" }] })
34
+ result = @client.get_account_assets
35
+ assert result.success?
36
+ end
37
+
38
+ def test_place_order
39
+ stub_connection(:post, { "data" => { "orderId" => "123" } })
40
+ result = @client.place_order(symbol: "BTCUSDT", side: "buy", order_type: "market", size: "0.001")
41
+ assert result.success?
42
+ end
43
+
44
+ def test_place_order_with_quote_size
45
+ stub_connection(:post, { "data" => { "orderId" => "456" } })
46
+ result = @client.place_order(symbol: "BTCUSDT", side: "buy", order_type: "market", quote_size: "100")
47
+ assert result.success?
48
+ end
49
+
50
+ def test_get_order
51
+ stub_connection(:get, { "data" => [{ "orderId" => "123" }] })
52
+ result = @client.get_order(order_id: "123")
53
+ assert result.success?
54
+ end
55
+
56
+ def test_cancel_order
57
+ stub_connection(:post, { "data" => { "orderId" => "123" } })
58
+ result = @client.cancel_order(symbol: "BTCUSDT", order_id: "123")
59
+ assert result.success?
60
+ end
61
+
62
+ def test_get_candles
63
+ stub_connection(:get, { "data" => [] })
64
+ result = @client.get_candles(symbol: "BTCUSDT", granularity: "1min")
65
+ assert result.success?
66
+ end
67
+
68
+ def test_withdraw
69
+ stub_connection(:post, { "data" => { "orderId" => "w1" } })
70
+ result = @client.withdraw(coin: "BTC", address: "addr", size: "0.1")
71
+ assert result.success?
72
+ end
73
+
74
+ def test_get_fills
75
+ stub_connection(:get, { "data" => [{ "tradeId" => "t1" }] })
76
+ result = @client.get_fills(symbol: "BTCUSDT")
77
+ assert result.success?
78
+ end
79
+
80
+ def test_deposit_list
81
+ stub_connection(:get, { "data" => [{ "coin" => "BTC" }] })
82
+ result = @client.deposit_list
83
+ assert result.success?
84
+ end
85
+
86
+ def test_withdrawal_list
87
+ stub_connection(:get, { "data" => [{ "coin" => "ETH" }] })
88
+ result = @client.withdrawal_list
89
+ assert result.success?
90
+ end
91
+
92
+ def test_signed_headers_include_passphrase
93
+ ts = "1234567890"
94
+ headers = @client.send(:signed_headers, ts, "payload")
95
+ assert_equal "test_key", headers[:"ACCESS-KEY"]
96
+ assert_equal "test_pass", headers[:"ACCESS-PASSPHRASE"]
97
+ assert_equal ts, headers[:"ACCESS-TIMESTAMP"]
98
+ assert headers.key?(:"ACCESS-SIGN")
99
+ end
100
+
101
+ private
102
+
103
+ def stub_connection(method, body)
104
+ response = stub(body: body)
105
+ connection = stub
106
+ connection.stubs(method).returns(response)
107
+ @client.instance_variable_set(:@connection, connection)
108
+ end
109
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class Honeymaker::Clients::BitMartTest < Minitest::Test
6
+ def setup
7
+ @client = Honeymaker::Clients::BitMart.new(
8
+ api_key: "test_key", api_secret: "test_secret", memo: "test_memo"
9
+ )
10
+ end
11
+
12
+ def test_url
13
+ assert_equal "https://api-cloud.bitmart.com", Honeymaker::Clients::BitMart::URL
14
+ end
15
+
16
+ def test_accepts_memo
17
+ assert_equal "test_memo", @client.memo
18
+ end
19
+
20
+ def test_get_symbols_details
21
+ stub_connection(:get, { "data" => { "symbols" => [{ "symbol" => "BTC_USDT" }] } })
22
+ result = @client.get_symbols_details
23
+ assert result.success?
24
+ end
25
+
26
+ def test_get_ticker
27
+ stub_connection(:get, { "data" => [{ "symbol" => "BTC_USDT" }] })
28
+ result = @client.get_ticker(symbol: "BTC_USDT")
29
+ assert result.success?
30
+ end
31
+
32
+ def test_get_wallet
33
+ stub_connection(:get, { "data" => { "wallet" => [{ "id" => "BTC" }] } })
34
+ result = @client.get_wallet
35
+ assert result.success?
36
+ end
37
+
38
+ def test_submit_order
39
+ stub_connection(:post, { "data" => { "order_id" => 123 } })
40
+ result = @client.submit_order(symbol: "BTC_USDT", side: "buy", type: "market", notional: "100")
41
+ assert result.success?
42
+ end
43
+
44
+ def test_get_order
45
+ stub_connection(:post, { "data" => { "order_id" => 123 } })
46
+ result = @client.get_order(order_id: "123")
47
+ assert result.success?
48
+ end
49
+
50
+ def test_cancel_order
51
+ stub_connection(:post, { "data" => true })
52
+ result = @client.cancel_order(symbol: "BTC_USDT", order_id: "123")
53
+ assert result.success?
54
+ end
55
+
56
+ def test_withdraw
57
+ stub_connection(:post, { "data" => { "withdraw_id" => "w1" } })
58
+ result = @client.withdraw(currency: "BTC", amount: "0.1", address: "addr")
59
+ assert result.success?
60
+ end
61
+
62
+ def test_get_trades
63
+ stub_connection(:get, { "data" => [{ "tradeId" => "t1" }] })
64
+ result = @client.get_trades(symbol: "BTC_USDT")
65
+ assert result.success?
66
+ end
67
+
68
+ def test_deposit_list
69
+ stub_connection(:get, { "data" => { "records" => [{ "currency" => "BTC" }] } })
70
+ result = @client.deposit_list
71
+ assert result.success?
72
+ end
73
+
74
+ def test_withdraw_list
75
+ stub_connection(:get, { "data" => { "records" => [{ "currency" => "ETH" }] } })
76
+ result = @client.withdraw_list
77
+ assert result.success?
78
+ end
79
+
80
+ def test_signed_headers_include_memo_in_signature
81
+ ts = "1234567890"
82
+ pre_sign = "#{ts}#test_memo#body"
83
+ headers = @client.send(:signed_headers, ts, pre_sign)
84
+ assert_equal "test_key", headers[:"X-BM-KEY"]
85
+ assert headers.key?(:"X-BM-SIGN")
86
+ end
87
+
88
+ private
89
+
90
+ def stub_connection(method, body)
91
+ response = stub(body: body)
92
+ connection = stub
93
+ connection.stubs(method).returns(response)
94
+ @client.instance_variable_set(:@connection, connection)
95
+ end
96
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class Honeymaker::Clients::BitrueTest < Minitest::Test
6
+ def setup
7
+ @client = Honeymaker::Clients::Bitrue.new(api_key: "test_key", api_secret: "test_secret")
8
+ end
9
+
10
+ def test_url
11
+ assert_equal "https://openapi.bitrue.com", Honeymaker::Clients::Bitrue::URL
12
+ end
13
+
14
+ def test_exchange_information
15
+ stub_connection(:get, { "symbols" => [{ "symbol" => "BTCUSDT" }] })
16
+ result = @client.exchange_information
17
+ assert result.success?
18
+ end
19
+
20
+ def test_symbol_price_ticker
21
+ stub_connection(:get, { "symbol" => "BTCUSDT", "price" => "50000" })
22
+ result = @client.symbol_price_ticker(symbol: "BTCUSDT")
23
+ assert result.success?
24
+ end
25
+
26
+ def test_account_information
27
+ stub_connection(:get, { "balances" => [{ "asset" => "BTC" }] })
28
+ result = @client.account_information
29
+ assert result.success?
30
+ end
31
+
32
+ def test_new_order
33
+ stub_connection(:post, { "orderId" => "123" })
34
+ result = @client.new_order(symbol: "BTCUSDT", side: "BUY", type: "MARKET", quantity: "0.001")
35
+ assert result.success?
36
+ end
37
+
38
+ def test_query_order
39
+ stub_connection(:get, { "orderId" => "123", "status" => "FILLED" })
40
+ result = @client.query_order(symbol: "BTCUSDT", order_id: "123")
41
+ assert result.success?
42
+ end
43
+
44
+ def test_cancel_order
45
+ stub_connection(:delete, { "orderId" => "123" })
46
+ result = @client.cancel_order(symbol: "BTCUSDT", order_id: "123")
47
+ assert result.success?
48
+ end
49
+
50
+ def test_account_trade_list
51
+ stub_connection(:get, [{ "symbol" => "BTCUSDT", "id" => 1 }])
52
+ result = @client.account_trade_list(symbol: "BTCUSDT")
53
+ assert result.success?
54
+ end
55
+
56
+ def test_headers_include_api_key
57
+ headers = @client.send(:auth_headers)
58
+ assert_equal "test_key", headers[:"X-MBX-APIKEY"]
59
+ end
60
+
61
+ private
62
+
63
+ def stub_connection(method, body)
64
+ response = stub(body: body)
65
+ connection = stub
66
+ connection.stubs(method).returns(response)
67
+ @client.instance_variable_set(:@connection, connection)
68
+ end
69
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class Honeymaker::Clients::BitvavoTest < Minitest::Test
6
+ def setup
7
+ @client = Honeymaker::Clients::Bitvavo.new(api_key: "test_key", api_secret: "test_secret")
8
+ end
9
+
10
+ def test_url
11
+ assert_equal "https://api.bitvavo.com", Honeymaker::Clients::Bitvavo::URL
12
+ end
13
+
14
+ def test_get_markets
15
+ stub_connection(:get, [{ "market" => "BTC-EUR" }])
16
+ result = @client.get_markets
17
+ assert result.success?
18
+ end
19
+
20
+ def test_get_ticker_price
21
+ stub_connection(:get, [{ "market" => "BTC-EUR", "price" => "50000" }])
22
+ result = @client.get_ticker_price
23
+ assert result.success?
24
+ end
25
+
26
+ def test_get_balance
27
+ stub_connection(:get, [{ "symbol" => "BTC", "available" => "0.5" }])
28
+ result = @client.get_balance
29
+ assert result.success?
30
+ end
31
+
32
+ def test_place_order
33
+ stub_connection(:post, { "orderId" => "123" })
34
+ result = @client.place_order(market: "BTC-EUR", side: "buy", order_type: "market", amount_quote: "100")
35
+ assert result.success?
36
+ end
37
+
38
+ def test_get_order
39
+ stub_connection(:get, { "orderId" => "123", "status" => "filled" })
40
+ result = @client.get_order(market: "BTC-EUR", order_id: "123")
41
+ assert result.success?
42
+ end
43
+
44
+ def test_cancel_order
45
+ stub_connection(:delete, { "orderId" => "123" })
46
+ result = @client.cancel_order(market: "BTC-EUR", order_id: "123")
47
+ assert result.success?
48
+ end
49
+
50
+ def test_withdraw
51
+ stub_connection(:post, { "success" => true })
52
+ result = @client.withdraw(symbol: "BTC", amount: "0.1", address: "addr")
53
+ assert result.success?
54
+ end
55
+
56
+ def test_get_assets
57
+ stub_connection(:get, [{ "symbol" => "BTC" }])
58
+ result = @client.get_assets
59
+ assert result.success?
60
+ end
61
+
62
+ def test_get_trades
63
+ stub_connection(:get, [{ "id" => "t1", "side" => "buy" }])
64
+ result = @client.get_trades(market: "BTC-EUR")
65
+ assert result.success?
66
+ end
67
+
68
+ def test_get_deposit_history
69
+ stub_connection(:get, [{ "symbol" => "BTC", "amount" => "1.0" }])
70
+ result = @client.get_deposit_history
71
+ assert result.success?
72
+ end
73
+
74
+ def test_get_withdrawal_history
75
+ stub_connection(:get, [{ "symbol" => "ETH", "amount" => "2.0" }])
76
+ result = @client.get_withdrawal_history
77
+ assert result.success?
78
+ end
79
+
80
+ def test_signed_headers_include_access_window
81
+ ts = "1234567890"
82
+ headers = @client.send(:signed_headers, ts, "payload")
83
+ assert_equal "test_key", headers[:"BITVAVO-ACCESS-KEY"]
84
+ assert_equal "10000", headers[:"BITVAVO-ACCESS-WINDOW"]
85
+ assert headers.key?(:"BITVAVO-ACCESS-SIGNATURE")
86
+ end
87
+
88
+ def test_signing_payload_format
89
+ # Verify the signing payload matches Bitvavo's expected format
90
+ sig1 = @client.send(:hmac_sha256, "test_secret", "1234567890GET/v2/balance")
91
+ sig2 = @client.send(:hmac_sha256, "test_secret", "1234567890POST/v2/order{\"market\":\"BTC-EUR\"}")
92
+ assert sig1.is_a?(String)
93
+ assert sig2.is_a?(String)
94
+ refute_equal sig1, sig2
95
+ end
96
+
97
+ private
98
+
99
+ def stub_connection(method, body)
100
+ response = stub(body: body)
101
+ connection = stub
102
+ connection.stubs(method).returns(response)
103
+ @client.instance_variable_set(:@connection, connection)
104
+ end
105
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class Honeymaker::Clients::BybitTest < Minitest::Test
6
+ def setup
7
+ @client = Honeymaker::Clients::Bybit.new(api_key: "test_key", api_secret: "test_secret")
8
+ end
9
+
10
+ def test_url
11
+ assert_equal "https://api.bybit.com", Honeymaker::Clients::Bybit::URL
12
+ end
13
+
14
+ def test_instruments_info
15
+ stub_connection(:get, { "result" => { "list" => [{ "symbol" => "BTCUSDT" }] } })
16
+ result = @client.instruments_info(category: "spot")
17
+ assert result.success?
18
+ end
19
+
20
+ def test_tickers
21
+ stub_connection(:get, { "result" => { "list" => [{ "lastPrice" => "50000" }] } })
22
+ result = @client.tickers(category: "spot", symbol: "BTCUSDT")
23
+ assert result.success?
24
+ end
25
+
26
+ def test_wallet_balance
27
+ stub_connection(:get, { "result" => { "list" => [{ "coin" => [{ "coin" => "BTC" }] }] } })
28
+ result = @client.wallet_balance(account_type: "UNIFIED")
29
+ assert result.success?
30
+ end
31
+
32
+ def test_create_order
33
+ stub_connection(:post, { "result" => { "orderId" => "123" } })
34
+ result = @client.create_order(category: "spot", symbol: "BTCUSDT", side: "Buy", order_type: "Market", qty: "0.001")
35
+ assert result.success?
36
+ assert_equal "123", result.data["result"]["orderId"]
37
+ end
38
+
39
+ def test_get_order
40
+ stub_connection(:get, { "result" => { "list" => [{ "orderId" => "123" }] } })
41
+ result = @client.get_order(category: "spot", order_id: "123")
42
+ assert result.success?
43
+ end
44
+
45
+ def test_cancel_order
46
+ stub_connection(:post, { "result" => { "orderId" => "123" } })
47
+ result = @client.cancel_order(category: "spot", symbol: "BTCUSDT", order_id: "123")
48
+ assert result.success?
49
+ end
50
+
51
+ def test_kline
52
+ stub_connection(:get, { "result" => { "list" => [] } })
53
+ result = @client.kline(category: "spot", symbol: "BTCUSDT", interval: "60")
54
+ assert result.success?
55
+ end
56
+
57
+ def test_withdraw
58
+ stub_connection(:post, { "result" => { "id" => "w1" } })
59
+ result = @client.withdraw(coin: "BTC", chain: "BTC", address: "addr", amount: "0.1")
60
+ assert result.success?
61
+ end
62
+
63
+ def test_execution_list
64
+ stub_connection(:get, { "result" => { "list" => [{ "execId" => "e1" }] } })
65
+ result = @client.execution_list(category: "spot")
66
+ assert result.success?
67
+ end
68
+
69
+ def test_deposit_records
70
+ stub_connection(:get, { "result" => { "rows" => [{ "coin" => "BTC" }] } })
71
+ result = @client.deposit_records
72
+ assert result.success?
73
+ end
74
+
75
+ def test_withdraw_records
76
+ stub_connection(:get, { "result" => { "rows" => [{ "coin" => "ETH" }] } })
77
+ result = @client.withdraw_records
78
+ assert result.success?
79
+ end
80
+
81
+ def test_signed_headers_include_api_key
82
+ headers = @client.send(:signed_headers, "GET", { foo: "bar" })
83
+ assert_equal "test_key", headers[:"X-BAPI-API-KEY"]
84
+ assert headers.key?(:"X-BAPI-SIGN")
85
+ assert headers.key?(:"X-BAPI-TIMESTAMP")
86
+ end
87
+
88
+ def test_unauthenticated_client
89
+ client = Honeymaker::Clients::Bybit.new
90
+ headers = client.send(:signed_headers, "GET", {})
91
+ refute headers.key?(:"X-BAPI-SIGN")
92
+ end
93
+
94
+ private
95
+
96
+ def stub_connection(method, body)
97
+ response = stub(body: body)
98
+ connection = stub
99
+ connection.stubs(method).returns(response)
100
+ @client.instance_variable_set(:@connection, connection)
101
+ end
102
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class Honeymaker::Clients::CoinbaseTest < Minitest::Test
6
+ def setup
7
+ # Use ECDSA key for testing
8
+ @key = OpenSSL::PKey::EC.generate("prime256v1")
9
+ @client = Honeymaker::Clients::Coinbase.new(
10
+ api_key: "test_key",
11
+ api_secret: @key.to_pem
12
+ )
13
+ end
14
+
15
+ def test_url
16
+ assert_equal "https://api.coinbase.com", Honeymaker::Clients::Coinbase::URL
17
+ end
18
+
19
+ def test_get_order
20
+ stub_connection(:get, { "order" => { "order_id" => "abc", "status" => "FILLED" } })
21
+ result = @client.get_order(order_id: "abc")
22
+ assert result.success?
23
+ assert_equal "abc", result.data["order"]["order_id"]
24
+ end
25
+
26
+ def test_create_order
27
+ stub_connection(:post, { "success" => true, "order_id" => "xyz" })
28
+ result = @client.create_order(
29
+ client_order_id: "c1", product_id: "BTC-USD",
30
+ side: "BUY", order_configuration: { market_market_ioc: { quote_size: "100" } }
31
+ )
32
+ assert result.success?
33
+ end
34
+
35
+ def test_cancel_orders
36
+ stub_connection(:post, { "results" => [{ "success" => true }] })
37
+ result = @client.cancel_orders(order_ids: ["abc"])
38
+ assert result.success?
39
+ end
40
+
41
+ def test_list_public_products
42
+ stub_connection(:get, { "products" => [{ "product_id" => "BTC-USD" }] })
43
+ result = @client.list_public_products
44
+ assert result.success?
45
+ end
46
+
47
+ def test_get_public_product
48
+ stub_connection(:get, { "product_id" => "BTC-USD" })
49
+ result = @client.get_public_product(product_id: "BTC-USD")
50
+ assert result.success?
51
+ end
52
+
53
+ def test_get_api_key_permissions
54
+ stub_connection(:get, { "can_trade" => true })
55
+ result = @client.get_api_key_permissions
56
+ assert result.success?
57
+ end
58
+
59
+ def test_list_accounts
60
+ stub_connection(:get, { "accounts" => [] })
61
+ result = @client.list_accounts
62
+ assert result.success?
63
+ end
64
+
65
+ def test_send_money
66
+ stub_connection(:post, { "data" => { "id" => "tx1" } })
67
+ result = @client.send_money(account_id: "acc1", to: "addr", amount: "0.1", currency: "BTC")
68
+ assert result.success?
69
+ end
70
+
71
+ def test_list_fills
72
+ stub_connection(:get, { "fills" => [{ "trade_id" => "f1", "product_id" => "BTC-USD" }] })
73
+ result = @client.list_fills(product_id: "BTC-USD")
74
+ assert result.success?
75
+ assert_equal "f1", result.data["fills"].first["trade_id"]
76
+ end
77
+
78
+ def test_list_transactions
79
+ stub_connection(:get, { "data" => [{ "id" => "tx1", "type" => "send" }] })
80
+ result = @client.list_transactions(account_id: "acc1")
81
+ assert result.success?
82
+ assert_equal "tx1", result.data["data"].first["id"]
83
+ end
84
+
85
+ def test_handles_api_error
86
+ connection = stub
87
+ connection.stubs(:get).raises(Faraday::ClientError.new("401", { status: 401, body: "Unauthorized" }))
88
+ @client.instance_variable_set(:@connection, connection)
89
+ result = @client.list_accounts
90
+ assert result.failure?
91
+ end
92
+
93
+ def test_ecdsa_key_detected
94
+ assert @client.send(:ecdsa_key?)
95
+ end
96
+
97
+ def test_non_ecdsa_key_not_detected
98
+ client = Honeymaker::Clients::Coinbase.new(api_key: "k", api_secret: "not-a-pem-key")
99
+ refute client.send(:ecdsa_key?)
100
+ end
101
+
102
+ def test_ed25519_returns_nil_without_rbnacl
103
+ client = Honeymaker::Clients::Coinbase.new(api_key: "k", api_secret: Base64.encode64("short"))
104
+ # rbnacl may or may not be installed; either way should not raise
105
+ result = client.send(:ed25519_signing_key)
106
+ # Returns nil if rbnacl not installed or key is invalid
107
+ assert_nil result
108
+ end
109
+
110
+ def test_unauthenticated_headers
111
+ client = Honeymaker::Clients::Coinbase.new
112
+ stub_connection_on(client, :get, { "products" => [] })
113
+ result = client.list_public_products
114
+ assert result.success?
115
+ end
116
+
117
+ private
118
+
119
+ def stub_connection(method, body)
120
+ response = stub(body: body)
121
+ connection = stub
122
+ connection.stubs(method).returns(response)
123
+ @client.instance_variable_set(:@connection, connection)
124
+ end
125
+
126
+ def stub_connection_on(client, method, body)
127
+ response = stub(body: body)
128
+ connection = stub
129
+ connection.stubs(method).returns(response)
130
+ client.instance_variable_set(:@connection, connection)
131
+ end
132
+ end