#![cfg(feature = "websocket")]
use std::env;
use std::time::Duration;
use rust_okx::ws::channels;
use rust_okx::ws::model::{AccountUpdate, OrderUpdate, SpreadOrderUpdate};
use rust_okx::{Credentials, OkxWs, WsEvent};
fn live_credentials() -> Option<Credentials> {
let _ = dotenvy::dotenv();
Some(Credentials::new(
non_empty("OKX_API_KEY")?,
non_empty("OKX_API_SECRET")?,
non_empty("OKX_PASSPHRASE")?,
))
}
fn non_empty(var: &str) -> Option<String> {
env::var(var).ok().filter(|value| !value.is_empty())
}
#[tokio::test]
async fn private_login_and_read_only_subscriptions() {
let Some(credentials) = live_credentials() else {
eprintln!("skipping private_login_and_read_only_subscriptions: OKX_API_* env vars not set");
return;
};
let connect = tokio::time::timeout(
Duration::from_secs(10),
OkxWs::private(credentials).connect(),
)
.await;
let mut ws = match connect {
Ok(Ok(ws)) => ws,
Ok(Err(err)) => {
eprintln!("skipping private_login_and_read_only_subscriptions: connect failed: {err}");
return;
}
Err(_) => {
eprintln!("skipping private_login_and_read_only_subscriptions: connect timed out");
return;
}
};
let args = [channels::trade::orders("ANY")];
if let Err(err) = ws.subscribe(&args).await {
eprintln!("skipping private_login_and_read_only_subscriptions: subscribe failed: {err}");
return;
}
let mut logged_in = false;
let mut account_subscribed = false;
let mut orders_subscribed = false;
let mut account_push_parsed = false;
let deadline = tokio::time::sleep(Duration::from_secs(20));
tokio::pin!(deadline);
loop {
tokio::select! {
_ = &mut deadline => {
eprintln!("skipping private_login_and_read_only_subscriptions: timed out waiting for account snapshot");
return;
}
event = ws.next_event() => {
match event {
Ok(Some(WsEvent::Login)) => logged_in = true,
Ok(Some(WsEvent::Subscribed(arg))) if arg.channel == "account" => {
account_subscribed = true;
}
Ok(Some(WsEvent::Subscribed(arg))) if arg.channel == "orders" => {
orders_subscribed = true;
}
Ok(Some(WsEvent::Push(push))) if push.arg.channel == "account" => {
let rows: Vec<AccountUpdate> = push
.parse()
.expect("account push should parse as Vec<AccountUpdate>");
account_push_parsed = !rows.is_empty();
}
Ok(Some(WsEvent::Push(push))) if push.arg.channel == "orders" => {
let _: Vec<OrderUpdate> = push
.parse()
.expect("orders push should parse as Vec<OrderUpdate>");
}
Ok(Some(WsEvent::Error { code, msg })) => {
panic!("OKX WS error {code}: {msg}")
}
Ok(Some(_)) => {}
Ok(None) => {
eprintln!("skipping private_login_and_read_only_subscriptions: connection closed");
return;
}
Err(err) => {
eprintln!("skipping private_login_and_read_only_subscriptions: receive failed: {err}");
return;
}
}
if logged_in
&& account_subscribed
&& orders_subscribed
&& account_push_parsed
{
break;
}
}
}
}
ws.unsubscribe(&args)
.await
.expect("unsubscribe should send");
ws.close().await.expect("close should send");
}
#[tokio::test]
async fn business_private_spread_subscription_uses_env_login() {
let Some(credentials) = live_credentials() else {
eprintln!(
"skipping business_private_spread_subscription_uses_env_login: OKX_API_* env vars not set"
);
return;
};
let connect = tokio::time::timeout(
Duration::from_secs(10),
OkxWs::business().credentials(credentials).connect(),
)
.await;
let mut ws = match connect {
Ok(Ok(ws)) => ws,
Ok(Err(err)) => {
eprintln!(
"skipping business_private_spread_subscription_uses_env_login: connect failed: {err}"
);
return;
}
Err(_) => {
eprintln!(
"skipping business_private_spread_subscription_uses_env_login: connect timed out"
);
return;
}
};
let arg = channels::spread::orders();
if let Err(err) = ws.subscribe(std::slice::from_ref(&arg)).await {
eprintln!(
"skipping business_private_spread_subscription_uses_env_login: subscribe failed: {err}"
);
return;
}
let mut logged_in = false;
let mut subscribed = false;
let deadline = tokio::time::sleep(Duration::from_secs(20));
tokio::pin!(deadline);
loop {
tokio::select! {
_ = &mut deadline => {
eprintln!("skipping business_private_spread_subscription_uses_env_login: timed out waiting for ack");
return;
}
event = ws.next_event() => {
match event {
Ok(Some(WsEvent::Login)) => logged_in = true,
Ok(Some(WsEvent::Subscribed(ack))) if ack.channel == "sprd-orders" => {
subscribed = true;
}
Ok(Some(WsEvent::Push(push))) if push.arg.channel == "sprd-orders" => {
let _: Vec<SpreadOrderUpdate> = push
.parse()
.expect("sprd-orders push should parse as Vec<SpreadOrderUpdate>");
}
Ok(Some(WsEvent::Error { code, msg })) => {
eprintln!("skipping business_private_spread_subscription_uses_env_login: OKX WS error {code}: {msg}");
return;
}
Ok(Some(_)) => {}
Ok(None) => {
eprintln!("skipping business_private_spread_subscription_uses_env_login: connection closed");
return;
}
Err(err) => {
eprintln!("skipping business_private_spread_subscription_uses_env_login: receive failed: {err}");
return;
}
}
if logged_in && subscribed {
break;
}
}
}
}
ws.unsubscribe(&[arg])
.await
.expect("unsubscribe should send");
ws.close().await.expect("close should send");
}