From a67ce4d4d4962d62c9b095190c895ffafff95d7a Mon Sep 17 00:00:00 2001 From: Victor Lee Date: Sat, 23 Nov 2024 14:17:47 -0800 Subject: [PATCH] added tests for Coinbase --- .../API/Common/APIRequestMaker.cs | 21 +++-- .../ExchangeCoinbaseAPITests.cs | 83 +++++++++++++++++-- 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/ExchangeSharp/API/Common/APIRequestMaker.cs b/src/ExchangeSharp/API/Common/APIRequestMaker.cs index 4992491b..30cf4a4f 100644 --- a/src/ExchangeSharp/API/Common/APIRequestMaker.cs +++ b/src/ExchangeSharp/API/Common/APIRequestMaker.cs @@ -16,6 +16,7 @@ The above copyright notice and this permission notice shall be included in all c using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; @@ -143,14 +144,14 @@ public Dictionary> Headers } } - /// - /// Constructor - /// - /// API - public APIRequestMaker(IAPIRequestHandler api) - { - this.api = api; - } + /// + /// Constructor + /// + /// API + public APIRequestMaker(IAPIRequestHandler api) => this.api = api; + + /// Additional headers to add to each request (for testing) + static internal (string, string)? AdditionalHeader = null; /// /// Make a request to a path on the API @@ -184,6 +185,10 @@ public APIRequestMaker(IAPIRequestHandler api) request.AddHeader("accept-language", "en-US,en;q=0.5"); request.AddHeader("content-type", api.RequestContentType); request.AddHeader("user-agent", BaseAPI.RequestUserAgent); + if (AdditionalHeader != null) + { + request.AddHeader(AdditionalHeader.Value.Item1, AdditionalHeader.Value.Item2); + } request.Timeout = (int)api.RequestTimeout.TotalMilliseconds; await api.ProcessRequestAsync(request, payload); diff --git a/tests/ExchangeSharpTests/ExchangeCoinbaseAPITests.cs b/tests/ExchangeSharpTests/ExchangeCoinbaseAPITests.cs index 9f5b2fbc..a4898f17 100644 --- a/tests/ExchangeSharpTests/ExchangeCoinbaseAPITests.cs +++ b/tests/ExchangeSharpTests/ExchangeCoinbaseAPITests.cs @@ -7,24 +7,91 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; +using NSubstitute.ExceptionExtensions; +using static System.Net.WebRequestMethods; namespace ExchangeSharpTests { [TestClass] public sealed class ExchangeCoinbaseAPITests { - private async Task MakeMockRequestMaker(string response = null) - { - var requestMaker = new MockAPIRequestMaker(); - if (response != null) - { - requestMaker.GlobalResponse = response; - } + private async Task CreateSandboxApiAsync(string response = null) + { // per Coinbase: All responses are static and pre-defined. https://docs.cdp.coinbase.com/advanced-trade/docs/rest-api-sandbox. Thus, no need to use MockAPIRequestMaker and just connect to real sandbox every time + //var requestMaker = new MockAPIRequestMaker(); + //if (response != null) + //{ + // requestMaker.GlobalResponse = response; + //} var api = ( await ExchangeAPI.GetExchangeAPIAsync(ExchangeName.Coinbase) as ExchangeCoinbaseAPI )!; - api.RequestMaker = requestMaker; + //api.RequestMaker = requestMaker; + api.BaseUrl = "https://api-sandbox.coinbase.com/api/v3/brokerage"; return api; } + + [TestMethod] + public async Task PlaceOrderAsync_Success() + { + var api = await CreateSandboxApiAsync(); + { // per Coinbase: Users can make API requests to Advanced sandbox API without authentication. https://docs.cdp.coinbase.com/advanced-trade/docs/rest-api-sandbox. Thus, no need to set api.PublicApiKey and api.PrivateApiKey + var orderRequest = new ExchangeOrderRequest + { + MarketSymbol = "BTC-USD", + Amount = 0.0001m, + Price = 10000m, + IsBuy = true, + OrderType = OrderType.Limit + }; + + var orderResult = await api.PlaceOrderAsync(orderRequest); + orderResult.Should().NotBeNull("an order result should be returned"); + orderResult.ClientOrderId.Should().Be("sandbox_success_order", "this is what the sandbox returns"); + orderResult.OrderId.Should().Be("f898eaf4-6ffc-47be-a159-7ff292e5cdcf", "this is what the sandbox returns"); + orderResult.MarketSymbol.Should().Be(orderRequest.MarketSymbol, "the market symbol should be the same as requested"); + orderResult.IsBuy.Should().Be(false, "the sandbox always returns a SELL, even if a buy is submitted"); + orderResult.Result.Should().Be(ExchangeAPIOrderResult.PendingOpen, "the order should be placed successfully in sandbox"); + } + } + + [TestMethod] + public async Task PlaceOrderAsync_Failure() + { + var api = await CreateSandboxApiAsync(); + { // per Coinbase: Users can make API requests to Advanced sandbox API without authentication. https://docs.cdp.coinbase.com/advanced-trade/docs/rest-api-sandbox. Thus, no need to set api.PublicApiKey and api.PrivateApiKey + var orderRequest = new ExchangeOrderRequest + { + MarketSymbol = "BTC-USD", + Amount = 0.0001m, + Price = 10000m, + IsBuy = true, + OrderType = OrderType.Limit + }; + APIRequestMaker.AdditionalHeader = ("X-Sandbox", "PostOrder_insufficient_fund"); + var orderResult = await api.PlaceOrderAsync(orderRequest); + orderResult.Should().NotBeNull("an order result should be returned"); + orderResult.ClientOrderId.Should().Be("9dde8003-fc68-4ec1-96ab-5a759e5f4f5c", "this is what the sandbox returns"); + orderResult.OrderId.Should().BeNull("this is what the sandbox returns"); + orderResult.MarketSymbol.Should().Be(orderRequest.MarketSymbol, "because the market symbol should be the same as requested"); + orderResult.IsBuy.Should().Be(orderRequest.IsBuy, "this is what was requested"); + orderResult.Result.Should().Be(ExchangeAPIOrderResult.Rejected, "testing for failure"); + } + } + + [TestMethod] + public async Task CancelOrderAsync_Success() + { + var api = await CreateSandboxApiAsync(); + await api.CancelOrderAsync("1"); + } + + [TestMethod] + public async Task CancelOrderAsync_Failure() + { + var api = await CreateSandboxApiAsync(); + APIRequestMaker.AdditionalHeader = ("X-Sandbox", "CancelOrders_failure"); + Func act = () => api.CancelOrderAsync("1"); + await act.Should().ThrowAsync(); + } } }