Skip to content

Commit

Permalink
added tests for Coinbase (#856)
Browse files Browse the repository at this point in the history
  • Loading branch information
vslee authored Nov 23, 2024
1 parent b595119 commit 09a30c9
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 16 deletions.
21 changes: 13 additions & 8 deletions src/ExchangeSharp/API/Common/APIRequestMaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -143,14 +144,14 @@ public Dictionary<string, IReadOnlyList<string>> Headers
}
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="api">API</param>
public APIRequestMaker(IAPIRequestHandler api)
{
this.api = api;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="api">API</param>
public APIRequestMaker(IAPIRequestHandler api) => this.api = api;

/// <summary> Additional headers to add to each request (for testing) </summary>
static internal (string, string)? AdditionalHeader = null;

/// <summary>
/// Make a request to a path on the API
Expand Down Expand Up @@ -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);

Expand Down
83 changes: 75 additions & 8 deletions tests/ExchangeSharpTests/ExchangeCoinbaseAPITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExchangeCoinbaseAPI> MakeMockRequestMaker(string response = null)
{
var requestMaker = new MockAPIRequestMaker();
if (response != null)
{
requestMaker.GlobalResponse = response;
}
private async Task<ExchangeCoinbaseAPI> 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<Task> act = () => api.CancelOrderAsync("1");
await act.Should().ThrowAsync<APIException>();
}
}
}

0 comments on commit 09a30c9

Please sign in to comment.