Skip to content

Commit 255eac3

Browse files
test: added missing tests for coinbase monitor
1 parent 9302de6 commit 255eac3

File tree

1 file changed

+350
-0
lines changed

1 file changed

+350
-0
lines changed

internal/monitor/coinbase/monitor-price/monitor_test.go

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,153 @@ var _ = Describe("Monitor Coinbase", func() {
257257
})
258258
})
259259

260+
Describe("SetSymbols", func() {
261+
When("there is a derivatives product (i.e. has an underlying asset)", func() {
262+
When("a symbol is already mapped to an underlying symbol", func() {
263+
It("should skip adding that symbol to the mapping between product ids and underlying product ids", func() {
264+
// Initial response for first SetSymbols call
265+
server.AppendHandlers(
266+
ghttp.CombineHandlers(
267+
ghttp.VerifyRequest("GET", "/api/v3/brokerage/market/products", "product_ids=BIT-31JAN25-CDE"),
268+
ghttp.RespondWithJSONEncoded(http.StatusOK, unary.Response{
269+
Products: []unary.ResponseQuote{
270+
{
271+
Symbol: "BIT-31JAN25-CDE",
272+
ProductID: "BIT-31JAN25-CDE",
273+
ShortName: "Bitcoin Futures",
274+
Price: "60000.00",
275+
PriceChange24H: "5.00",
276+
Volume24H: "1000000.00",
277+
MarketState: "online",
278+
Currency: "USD",
279+
ExchangeName: "CDE",
280+
ProductType: "FUTURE",
281+
FutureProductDetails: unary.ResponseQuoteFutureProductDetails{
282+
ContractRootUnit: "BTC",
283+
GroupDescription: "Bitcoin January 2025 Future",
284+
ExpirationDate: "2025-01-31",
285+
ExpirationTimezone: "America/New_York",
286+
},
287+
},
288+
},
289+
}),
290+
),
291+
// Response for getting all quotes after mapping is established
292+
ghttp.CombineHandlers(
293+
ghttp.VerifyRequest("GET", "/api/v3/brokerage/market/products", "product_ids=BIT-31JAN25-CDE&product_ids=BTC-USD"),
294+
ghttp.RespondWithJSONEncoded(http.StatusOK, unary.Response{
295+
Products: []unary.ResponseQuote{
296+
{
297+
Symbol: "BIT-31JAN25-CDE",
298+
ProductID: "BIT-31JAN25-CDE",
299+
ShortName: "Bitcoin Futures",
300+
Price: "60000.00",
301+
PriceChange24H: "5.00",
302+
Volume24H: "1000000.00",
303+
MarketState: "online",
304+
Currency: "USD",
305+
ExchangeName: "CDE",
306+
ProductType: "FUTURE",
307+
FutureProductDetails: unary.ResponseQuoteFutureProductDetails{
308+
ContractRootUnit: "BTC",
309+
GroupDescription: "Bitcoin January 2025 Future",
310+
ExpirationDate: "2025-01-31",
311+
ExpirationTimezone: "America/New_York",
312+
},
313+
},
314+
{
315+
Symbol: "BTC",
316+
ProductID: "BTC-USD",
317+
ShortName: "Bitcoin",
318+
Price: "50000.00",
319+
PriceChange24H: "5.00",
320+
Volume24H: "1000000.00",
321+
MarketState: "online",
322+
Currency: "USD",
323+
ExchangeName: "CBE",
324+
ProductType: "SPOT",
325+
},
326+
},
327+
}),
328+
),
329+
)
330+
331+
// Verify that no additional API calls are made to get underlying symbols
332+
// when setting the same symbol again
333+
server.AppendHandlers(
334+
// Only expect the call to get all quotes, not the underlying mapping call
335+
ghttp.CombineHandlers(
336+
ghttp.VerifyRequest("GET", "/api/v3/brokerage/market/products", "product_ids=BIT-31JAN25-CDE"),
337+
ghttp.RespondWithJSONEncoded(http.StatusOK, unary.Response{
338+
Products: []unary.ResponseQuote{
339+
{
340+
Symbol: "BIT-31JAN25-CDE",
341+
ProductID: "BIT-31JAN25-CDE",
342+
ShortName: "Bitcoin Futures",
343+
Price: "60000.00",
344+
PriceChange24H: "5.00",
345+
Volume24H: "1000000.00",
346+
MarketState: "online",
347+
Currency: "USD",
348+
ExchangeName: "CDE",
349+
ProductType: "FUTURE",
350+
FutureProductDetails: unary.ResponseQuoteFutureProductDetails{
351+
ContractRootUnit: "BTC",
352+
GroupDescription: "Bitcoin January 2025 Future",
353+
ExpirationDate: "2025-01-31",
354+
ExpirationTimezone: "America/New_York",
355+
},
356+
},
357+
},
358+
}),
359+
),
360+
)
361+
362+
monitor := monitorPriceCoinbase.NewMonitorPriceCoinbase(monitorPriceCoinbase.Config{
363+
UnaryURL: server.URL(),
364+
Ctx: context.Background(),
365+
ChanRequestCurrencyRates: make(chan []string, 1),
366+
ChanUpdateCurrencyRates: make(chan c.CurrencyRates, 1),
367+
})
368+
369+
// First call to SetSymbols establishes the mapping
370+
err := monitor.SetSymbols([]string{"BIT-31JAN25-CDE"}, 0)
371+
Expect(err).NotTo(HaveOccurred())
372+
373+
// Second call to SetSymbols should skip getting underlying symbols
374+
err = monitor.SetSymbols([]string{"BIT-31JAN25-CDE"}, 1)
375+
Expect(err).NotTo(HaveOccurred())
376+
377+
// Verify that all server handlers were called as expected
378+
Expect(server.ReceivedRequests()).To(HaveLen(3))
379+
})
380+
})
381+
382+
When("there is an error making the request to retrieve the underlying product ids", func() {
383+
It("should return an error", func() {
384+
server.RouteToHandler("GET", "/api/v3/brokerage/market/products",
385+
ghttp.CombineHandlers(
386+
ghttp.VerifyRequest("GET", "/api/v3/brokerage/market/products", "product_ids=BIT-31JAN25-CDE"),
387+
ghttp.RespondWith(http.StatusInternalServerError, "network error"),
388+
),
389+
)
390+
391+
monitor := monitorPriceCoinbase.NewMonitorPriceCoinbase(monitorPriceCoinbase.Config{
392+
UnaryURL: server.URL(),
393+
Ctx: context.Background(),
394+
ChanRequestCurrencyRates: make(chan []string, 1),
395+
ChanUpdateCurrencyRates: make(chan c.CurrencyRates, 1),
396+
})
397+
398+
err := monitor.SetSymbols([]string{"BIT-31JAN25-CDE"}, 0)
399+
Expect(err).To(HaveOccurred())
400+
Expect(err.Error()).To(ContainSubstring("request failed with status 500"))
401+
})
402+
})
403+
404+
})
405+
})
406+
260407
Describe("Start", func() {
261408
It("should start the monitor", func() {
262409
monitor := monitorPriceCoinbase.NewMonitorPriceCoinbase(monitorPriceCoinbase.Config{
@@ -871,6 +1018,209 @@ var _ = Describe("Monitor Coinbase", func() {
8711018
})
8721019
})
8731020
})
1021+
1022+
When("there is a currency rate update", func() {
1023+
It("should replace the currency rate cache", func() {
1024+
var err error
1025+
var outputQuote c.AssetQuote
1026+
1027+
// Set up initial server response for asset quotes
1028+
server.RouteToHandler("GET", "/api/v3/brokerage/market/products", func(w http.ResponseWriter, r *http.Request) {
1029+
query := r.URL.Query()["product_ids"]
1030+
1031+
if len(query) == 1 && query[0] == "BIT-31JAN25-CDE" {
1032+
json.NewEncoder(w).Encode(unary.Response{
1033+
Products: []unary.ResponseQuote{
1034+
{
1035+
Symbol: "BIT-31JAN25-CDE",
1036+
ProductID: "BIT-31JAN25-CDE",
1037+
ShortName: "Bitcoin Futures",
1038+
Price: "60000.00",
1039+
PriceChange24H: "5.00",
1040+
Volume24H: "1000000.00",
1041+
MarketState: "online",
1042+
Currency: "USD",
1043+
ExchangeName: "CDE",
1044+
ProductType: "FUTURE",
1045+
FutureProductDetails: unary.ResponseQuoteFutureProductDetails{
1046+
ContractRootUnit: "BTC",
1047+
GroupDescription: "Bitcoin January 2025 Future",
1048+
ExpirationDate: "2025-01-31",
1049+
ExpirationTimezone: "America/New_York",
1050+
},
1051+
},
1052+
},
1053+
})
1054+
return
1055+
}
1056+
if len(query) == 2 && ((query[0] == "BTC-USD" && query[1] == "BIT-31JAN25-CDE") || (query[0] == "BIT-31JAN25-CDE" && query[1] == "BTC-USD")) {
1057+
json.NewEncoder(w).Encode(unary.Response{
1058+
Products: []unary.ResponseQuote{
1059+
{
1060+
Symbol: "BTC",
1061+
ProductID: "BTC-USD",
1062+
ShortName: "Bitcoin",
1063+
Price: "50000.00",
1064+
PriceChange24H: "2.5",
1065+
Volume24H: "1000000.00",
1066+
MarketState: "online",
1067+
Currency: "USD",
1068+
ExchangeName: "CBE",
1069+
ProductType: "SPOT",
1070+
},
1071+
{
1072+
Symbol: "BIT-31JAN25-CDE",
1073+
ProductID: "BIT-31JAN25-CDE",
1074+
ShortName: "Bitcoin Futures",
1075+
Price: "60000.00",
1076+
PriceChange24H: "5.00",
1077+
Volume24H: "1000000.00",
1078+
MarketState: "online",
1079+
Currency: "USD",
1080+
ExchangeName: "CDE",
1081+
ProductType: "FUTURE",
1082+
FutureProductDetails: unary.ResponseQuoteFutureProductDetails{
1083+
ContractRootUnit: "BTC",
1084+
GroupDescription: "Bitcoin January 2025 Future",
1085+
ExpirationDate: "2025-01-31",
1086+
ExpirationTimezone: "America/New_York",
1087+
},
1088+
},
1089+
},
1090+
})
1091+
return
1092+
}
1093+
w.WriteHeader(http.StatusNotFound)
1094+
})
1095+
1096+
// Create channels for currency rate updates and asset quote updates
1097+
currencyRatesChan := make(chan c.CurrencyRates, 1)
1098+
updateChan := make(chan c.MessageUpdate[c.AssetQuote], 10)
1099+
1100+
// Create and start the monitor
1101+
monitor := monitorPriceCoinbase.NewMonitorPriceCoinbase(monitorPriceCoinbase.Config{
1102+
UnaryURL: server.URL(),
1103+
ChanUpdateAssetQuote: updateChan,
1104+
Ctx: context.Background(),
1105+
ChanRequestCurrencyRates: make(chan []string, 1),
1106+
ChanUpdateCurrencyRates: currencyRatesChan,
1107+
}, monitorPriceCoinbase.WithRefreshInterval(100*time.Millisecond))
1108+
1109+
monitor.SetSymbols([]string{"BIT-31JAN25-CDE"}, 0)
1110+
err = monitor.Start()
1111+
1112+
// Send currency rates update
1113+
currencyRates := c.CurrencyRates{
1114+
"USD": c.CurrencyRate{
1115+
FromCurrency: "USD",
1116+
ToCurrency: "EUR",
1117+
Rate: 0.85,
1118+
},
1119+
}
1120+
currencyRatesChan <- currencyRates
1121+
1122+
Expect(err).NotTo(HaveOccurred())
1123+
1124+
// Wait for and verify the asset quote update with new currency rate
1125+
Eventually(func() float64 {
1126+
select {
1127+
case <-time.After(200 * time.Millisecond):
1128+
quotes, err := monitor.GetAssetQuotes()
1129+
1130+
if err != nil {
1131+
return -1.0
1132+
}
1133+
1134+
outputQuote = quotes[0]
1135+
1136+
return quotes[0].Currency.Rate
1137+
}
1138+
}, 2*time.Second).Should(Equal(0.85))
1139+
1140+
Expect(outputQuote.Currency.FromCurrencyCode).To(Equal("USD"))
1141+
Expect(outputQuote.Currency.ToCurrencyCode).To(Equal("EUR"))
1142+
1143+
monitor.Stop()
1144+
})
1145+
1146+
When("there is an error getting asset quotes and replacing the cache after new currency rates are recieved", func() {
1147+
It("should send a message to the error channel", func() {
1148+
// Set up initial server response for asset quotes
1149+
server.RouteToHandler("GET", "/api/v3/brokerage/market/products",
1150+
ghttp.CombineHandlers(
1151+
ghttp.VerifyRequest("GET", "/api/v3/brokerage/market/products", "product_ids=BTC-USD"),
1152+
ghttp.RespondWithJSONEncoded(http.StatusOK, unary.Response{
1153+
Products: []unary.ResponseQuote{
1154+
{
1155+
Symbol: "BTC",
1156+
ProductID: "BTC-USD",
1157+
ShortName: "Bitcoin",
1158+
Price: "50000.00",
1159+
PriceChange24H: "2.5",
1160+
Volume24H: "1000000.00",
1161+
MarketState: "online",
1162+
Currency: "USD",
1163+
ExchangeName: "CBE",
1164+
ProductType: "SPOT",
1165+
},
1166+
},
1167+
}),
1168+
),
1169+
)
1170+
1171+
// Create channels for updates and errors
1172+
currencyRatesChan := make(chan c.CurrencyRates, 1)
1173+
errorChan := make(chan error, 1)
1174+
1175+
// Create and start the monitor
1176+
monitor := monitorPriceCoinbase.NewMonitorPriceCoinbase(monitorPriceCoinbase.Config{
1177+
UnaryURL: server.URL(),
1178+
ChanError: errorChan,
1179+
Ctx: context.Background(),
1180+
ChanRequestCurrencyRates: make(chan []string, 1),
1181+
ChanUpdateCurrencyRates: currencyRatesChan,
1182+
}, monitorPriceCoinbase.WithRefreshInterval(100*time.Millisecond))
1183+
1184+
monitor.SetSymbols([]string{"BTC-USD"}, 0)
1185+
monitor.Start()
1186+
1187+
// Set up error response for the subsequent asset quote request
1188+
server.RouteToHandler("GET", "/api/v3/brokerage/market/products",
1189+
ghttp.CombineHandlers(
1190+
ghttp.VerifyRequest("GET", "/api/v3/brokerage/market/products", "product_ids=BTC-USD"),
1191+
ghttp.RespondWith(http.StatusInternalServerError, "Internal Server Error"),
1192+
),
1193+
)
1194+
1195+
// Send currency rates update to trigger asset quote refresh
1196+
currencyRates := c.CurrencyRates{
1197+
"USD": c.CurrencyRate{
1198+
FromCurrency: "USD",
1199+
ToCurrency: "EUR",
1200+
Rate: 0.85,
1201+
},
1202+
}
1203+
currencyRatesChan <- currencyRates
1204+
1205+
// Verify that an error is sent to the error channel
1206+
var err error
1207+
Eventually(func() error {
1208+
select {
1209+
case err = <-errorChan:
1210+
return err
1211+
default:
1212+
return nil
1213+
}
1214+
}, 2*time.Second).Should(HaveOccurred())
1215+
1216+
Expect(err.Error()).To(ContainSubstring("request failed with status 500"))
1217+
1218+
monitor.Stop()
1219+
})
1220+
})
1221+
1222+
})
1223+
8741224
})
8751225

8761226
Describe("Stop", func() {

0 commit comments

Comments
 (0)