mod common;
use common::mock_client;
use futures::TryStreamExt;
use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, ResponseTemplate};
#[tokio::test]
async fn list_exchanges_returns_parsed_items() {
let (server, client) = mock_client().await;
Mock::given(method("GET"))
.and(path("/v1/exchanges"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
{ "code": "binance", "display_name": "Binance" },
{ "code": "okx_spot", "display_name": "OKX Spot" }
])))
.mount(&server)
.await;
let exchanges = client.exchanges().list().await.unwrap();
assert_eq!(exchanges.len(), 2);
assert_eq!(exchanges[0].code, "binance");
assert_eq!(exchanges[1].display_name, "OKX Spot");
}
#[tokio::test]
async fn list_instruments_sends_query_params_and_decodes_page() {
let (server, client) = mock_client().await;
Mock::given(method("GET"))
.and(path("/v1/exchanges/binance/instruments"))
.and(query_param("search", "BTC"))
.and(query_param("limit", "10"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"items": [
{ "symbol": "BTCUSDT", "base": "BTC", "quote": "USDT", "active": true }
],
"total": 1
})))
.mount(&server)
.await;
let page = client
.exchanges()
.list_instruments("binance")
.search("BTC")
.limit(10)
.send()
.await
.unwrap();
assert_eq!(page.items.len(), 1);
assert_eq!(page.items[0].symbol, "BTCUSDT");
assert_eq!(page.total, Some(1));
}
#[tokio::test]
async fn list_instruments_stream_paginates() {
let (server, client) = mock_client().await;
Mock::given(method("GET"))
.and(path("/v1/exchanges/binance/instruments"))
.and(query_param("limit", "2"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"items": [
{ "symbol": "BTCUSDT", "base": "BTC", "quote": "USDT", "active": true },
{ "symbol": "ETHUSDT", "base": "ETH", "quote": "USDT", "active": true }
],
"next_page_token": "c_1"
})))
.up_to_n_times(1)
.mount(&server)
.await;
Mock::given(method("GET"))
.and(path("/v1/exchanges/binance/instruments"))
.and(query_param("page_token", "c_1"))
.and(query_param("limit", "2"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"items": [
{ "symbol": "SOLUSDT", "base": "SOL", "quote": "USDT", "active": true }
]
})))
.mount(&server)
.await;
let all: Vec<_> = client
.exchanges()
.list_instruments("binance")
.limit(2)
.stream()
.try_collect()
.await
.unwrap();
let symbols: Vec<_> = all.iter().map(|i| i.symbol.as_str()).collect();
assert_eq!(symbols, vec!["BTCUSDT", "ETHUSDT", "SOLUSDT"]);
}
#[tokio::test]
async fn list_datastreams_sends_path_params_and_query_and_decodes_page() {
let (server, client) = mock_client().await;
Mock::given(method("GET"))
.and(path(
"/v1/exchanges/binance/instruments/BTCUSDT/datastreams",
))
.and(query_param("stream_type", "trade"))
.and(query_param("wire_format", "json"))
.and(query_param("limit", "5"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"items": [
{
"datastream_id": 42,
"exchange": "binance",
"instrument": "BTCUSDT",
"stream_type": "trade",
"wire_format": "json"
}
],
"total": 1
})))
.mount(&server)
.await;
let page = client
.exchanges()
.list_datastreams("binance", "BTCUSDT")
.stream_type("trade")
.wire_format("json")
.limit(5)
.send()
.await
.unwrap();
assert_eq!(page.items.len(), 1);
assert_eq!(page.items[0].datastream_id, 42);
assert_eq!(page.items[0].exchange, "binance");
assert_eq!(page.items[0].instrument, "BTCUSDT");
assert_eq!(page.items[0].stream_type, "trade");
assert_eq!(page.items[0].wire_format, "json");
assert_eq!(page.total, Some(1));
}
#[tokio::test]
async fn list_datastreams_stream_paginates() {
let (server, client) = mock_client().await;
Mock::given(method("GET"))
.and(path(
"/v1/exchanges/binance/instruments/BTCUSDT/datastreams",
))
.and(query_param("limit", "2"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"items": [
{ "datastream_id": 1, "exchange": "binance", "instrument": "BTCUSDT",
"stream_type": "trade", "wire_format": "json" },
{ "datastream_id": 2, "exchange": "binance", "instrument": "BTCUSDT",
"stream_type": "depth", "wire_format": "json" }
],
"next_page_token": "c_1"
})))
.up_to_n_times(1)
.mount(&server)
.await;
Mock::given(method("GET"))
.and(path(
"/v1/exchanges/binance/instruments/BTCUSDT/datastreams",
))
.and(query_param("page_token", "c_1"))
.and(query_param("limit", "2"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"items": [
{ "datastream_id": 3, "exchange": "binance", "instrument": "BTCUSDT",
"stream_type": "ticker", "wire_format": "json" }
]
})))
.mount(&server)
.await;
let all: Vec<_> = client
.exchanges()
.list_datastreams("binance", "BTCUSDT")
.limit(2)
.stream()
.try_collect()
.await
.unwrap();
let ids: Vec<_> = all.iter().map(|d| d.datastream_id).collect();
assert_eq!(ids, vec![1, 2, 3]);
}