use alpaca_data::{Client, Error, corporate_actions, crypto, news, options, stocks};
fn auth_client() -> Client {
Client::builder()
.api_key("test-key")
.secret_key("test-secret")
.base_url("http://127.0.0.1:9")
.build()
.expect("test client should build")
}
fn crypto_client() -> Client {
Client::builder()
.base_url("http://127.0.0.1:9")
.build()
.expect("test client should build")
}
#[tokio::test]
async fn stocks_batch_requests_reject_empty_symbols() {
let error = auth_client()
.stocks()
.bars(stocks::BarsRequest {
symbols: Vec::new(),
timeframe: stocks::TimeFrame::from("1Min"),
..stocks::BarsRequest::default()
})
.await
.expect_err("empty stock symbols must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbols") && message.contains("empty")
));
}
#[tokio::test]
async fn stocks_single_latest_bar_rejects_empty_symbol() {
let error = auth_client()
.stocks()
.latest_bar(stocks::LatestBarRequest {
symbol: String::new(),
..stocks::LatestBarRequest::default()
})
.await
.expect_err("empty stock symbol must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbol") && message.contains("invalid")
));
}
#[tokio::test]
async fn stocks_single_latest_quote_rejects_empty_symbol() {
let error = auth_client()
.stocks()
.latest_quote(stocks::LatestQuoteRequest {
symbol: String::new(),
..stocks::LatestQuoteRequest::default()
})
.await
.expect_err("empty stock symbol must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbol") && message.contains("invalid")
));
}
#[tokio::test]
async fn stocks_single_latest_quote_rejects_whitespace_only_symbol() {
let error = auth_client()
.stocks()
.latest_quote(stocks::LatestQuoteRequest {
symbol: " ".into(),
..stocks::LatestQuoteRequest::default()
})
.await
.expect_err("blank stock symbol must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbol") && message.contains("invalid")
));
}
#[tokio::test]
async fn stocks_single_latest_trade_rejects_empty_symbol() {
let error = auth_client()
.stocks()
.latest_trade(stocks::LatestTradeRequest {
symbol: String::new(),
..stocks::LatestTradeRequest::default()
})
.await
.expect_err("empty stock symbol must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbol") && message.contains("invalid")
));
}
#[tokio::test]
async fn stocks_single_latest_trade_rejects_whitespace_only_symbol() {
let error = auth_client()
.stocks()
.latest_trade(stocks::LatestTradeRequest {
symbol: " ".into(),
..stocks::LatestTradeRequest::default()
})
.await
.expect_err("blank stock symbol must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbol") && message.contains("invalid")
));
}
#[tokio::test]
async fn stocks_snapshot_rejects_whitespace_only_symbol() {
let error = auth_client()
.stocks()
.snapshot(stocks::SnapshotRequest {
symbol: " ".into(),
..stocks::SnapshotRequest::default()
})
.await
.expect_err("blank stock symbol must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbol") && message.contains("invalid")
));
}
#[tokio::test]
async fn stocks_batch_requests_reject_blank_symbol_entries() {
let error = auth_client()
.stocks()
.bars(stocks::BarsRequest {
symbols: vec!["AAPL".into(), " ".into()],
timeframe: stocks::TimeFrame::from("1Min"),
..stocks::BarsRequest::default()
})
.await
.expect_err("blank stock symbols must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbols") && message.contains("invalid")
));
}
#[tokio::test]
async fn options_requests_reject_symbol_lists_over_one_hundred() {
let symbols = (0..101)
.map(|index| format!("AAPL260406C{:08}", index))
.collect();
let error = auth_client()
.options()
.bars(options::BarsRequest {
symbols,
timeframe: options::TimeFrame::from("1Day"),
..options::BarsRequest::default()
})
.await
.expect_err("oversized option symbol lists must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message)
if message.contains("symbols") && message.contains("100")
));
}
#[tokio::test]
async fn options_chain_rejects_empty_underlying_symbol() {
let error = auth_client()
.options()
.chain(options::ChainRequest {
underlying_symbol: String::new(),
..options::ChainRequest::default()
})
.await
.expect_err("empty underlying symbol must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbol") && message.contains("invalid")
));
}
#[tokio::test]
async fn options_chain_rejects_whitespace_only_underlying_symbol() {
let error = auth_client()
.options()
.chain(options::ChainRequest {
underlying_symbol: " ".into(),
..options::ChainRequest::default()
})
.await
.expect_err("blank underlying symbol must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message)
if message.contains("underlying_symbol") && message.contains("invalid")
));
}
#[tokio::test]
async fn options_requests_reject_blank_symbol_entries() {
let error = auth_client()
.options()
.latest_quotes(options::LatestQuotesRequest {
symbols: vec!["AAPL260406C00180000".into(), " ".into()],
..options::LatestQuotesRequest::default()
})
.await
.expect_err("blank option symbols must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbols") && message.contains("invalid")
));
}
#[tokio::test]
async fn crypto_requests_reject_empty_symbols() {
let error = crypto_client()
.crypto()
.latest_quotes(crypto::LatestQuotesRequest {
symbols: Vec::new(),
loc: None,
})
.await
.expect_err("empty crypto symbols must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbols") && message.contains("empty")
));
}
#[tokio::test]
async fn crypto_requests_reject_blank_symbol_entries() {
let error = crypto_client()
.crypto()
.latest_quotes(crypto::LatestQuotesRequest {
symbols: vec!["BTC/USD".into(), " ".into()],
loc: None,
})
.await
.expect_err("blank crypto symbols must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message) if message.contains("symbols") && message.contains("invalid")
));
}
#[tokio::test]
async fn news_requests_reject_limits_outside_documented_range() {
let error = auth_client()
.news()
.list(news::ListRequest {
limit: Some(51),
..news::ListRequest::default()
})
.await
.expect_err("out-of-range news limit must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message)
if message.contains("limit") && message.contains("50")
));
}
#[tokio::test]
async fn corporate_actions_ids_reject_other_filters() {
let error = auth_client()
.corporate_actions()
.list(corporate_actions::ListRequest {
symbols: Some(vec!["AAPL".into()]),
ids: Some(vec!["ca-1".into()]),
..corporate_actions::ListRequest::default()
})
.await
.expect_err("ids plus other filters must fail before transport");
assert!(matches!(
error,
Error::InvalidRequest(message)
if message.contains("ids") && message.contains("filters")
));
}