use koios_sdk::Client;
use pretty_assertions::assert_eq;
use serde_json::json;
use wiremock::{
matchers::{body_json, header, method, path},
Mock, MockServer, ResponseTemplate,
};
async fn setup_test_client() -> (MockServer, Client) {
let mock_server = MockServer::start().await;
let client = Client::builder()
.base_url(mock_server.uri())
.build()
.unwrap();
(mock_server, client)
}
#[tokio::test]
async fn test_get_address_info() {
let (mock_server, client) = setup_test_client().await;
let addresses = vec![
"addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz".to_string()
];
let mock_response = json!([{
"address": "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz",
"balance": "12345678",
"stake_address": "stake1u9ylzsgxaa6xctf4juup682ar3juj85n8tx3hthnljg47zctvm3rc",
"script_address": false,
"utxo_set": [{
"tx_hash": "f144a8264acf4bdfe2e1241170969c930d64ab6b0996a4a45237b623f1dd670e",
"tx_index": 0,
"block_height": 7017300,
"block_time": 1630106091,
"value": "12345678",
"datum_hash": null,
"inline_datum": null,
"reference_script": null,
"asset_list": []
}]
}]);
Mock::given(method("POST"))
.and(path("/address_info"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_addresses": addresses
})))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client.get_address_info(&addresses).await.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(response[0].address, addresses[0]);
assert_eq!(response[0].balance, "12345678");
assert_eq!(response[0].utxo_set.len(), 1);
assert!(!response[0].script_address);
}
#[tokio::test]
async fn test_get_address_utxos() {
let (mock_server, client) = setup_test_client().await;
let addresses = vec![
"addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz".to_string()
];
let mock_response = json!([{
"tx_hash": "f144a8264acf4bdfe2e1241170969c930d64ab6b0996a4a45237b623f1dd670e",
"tx_index": 0,
"address": "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz",
"value": "12345678",
"stake_address": "stake1u9ylzsgxaa6xctf4juup682ar3juj85n8tx3hthnljg47zctvm3rc",
"payment_cred": "a2944a17b8d8b7ede6e365432cf59ff5276c7c3a99e2b89d47c87661",
"epoch_no": 321,
"block_height": 7017300,
"block_time": 1630106091,
"datum_hash": null,
"inline_datum": null,
"reference_script": null,
"asset_list": [],
"is_spent": false
}]);
Mock::given(method("POST"))
.and(path("/address_utxos"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_addresses": addresses,
"_extended": true
})))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client
.get_address_utxos(&addresses, Some(true))
.await
.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(
response[0].tx_hash,
"f144a8264acf4bdfe2e1241170969c930d64ab6b0996a4a45237b623f1dd670e"
);
assert_eq!(response[0].value, "12345678");
assert!(!response[0].is_spent);
}
#[tokio::test]
async fn test_get_credential_utxos() {
let (mock_server, client) = setup_test_client().await;
let payment_credentials =
vec!["025b0a8f85cb8a46e1dda3fae5d22f07e2d56abb4019a2129c5d6c52".to_string()];
let mock_response = json!([{
"tx_hash": "f144a8264acf4bdfe2e1241170969c930d64ab6b0996a4a45237b623f1dd670e",
"tx_index": 0,
"address": "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz",
"value": "12345678",
"stake_address": "stake1u9ylzsgxaa6xctf4juup682ar3juj85n8tx3hthnljg47zctvm3rc",
"payment_cred": "025b0a8f85cb8a46e1dda3fae5d22f07e2d56abb4019a2129c5d6c52",
"epoch_no": 321,
"block_height": 7017300,
"block_time": 1630106091,
"datum_hash": null,
"inline_datum": null,
"reference_script": null,
"asset_list": [],
"is_spent": false
}]);
Mock::given(method("POST"))
.and(path("/credential_utxos"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_payment_credentials": payment_credentials,
"_extended": true
})))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client
.get_credential_utxos(&payment_credentials, Some(true))
.await
.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(
response[0].tx_hash,
"f144a8264acf4bdfe2e1241170969c930d64ab6b0996a4a45237b623f1dd670e"
);
assert_eq!(
response[0].payment_cred.as_ref().unwrap(),
"025b0a8f85cb8a46e1dda3fae5d22f07e2d56abb4019a2129c5d6c52"
);
}
#[tokio::test]
async fn test_get_address_transactions() {
let (mock_server, client) = setup_test_client().await;
let addresses = vec![
"addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz".to_string()
];
let mock_response = json!([{
"tx_hash": "f144a8264acf4bdfe2e1241170969c930d64ab6b0996a4a45237b623f1dd670e",
"epoch_no": 321,
"block_height": 7017300,
"block_time": 1630106091
}]);
Mock::given(method("POST"))
.and(path("/address_txs"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_addresses": addresses,
"_after_block_height": 7017000
})))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client
.get_address_transactions(&addresses, Some(7017000))
.await
.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(
response[0].tx_hash,
"f144a8264acf4bdfe2e1241170969c930d64ab6b0996a4a45237b623f1dd670e"
);
assert_eq!(response[0].epoch_no, 321);
assert_eq!(response[0].block_height, 7017300);
}
#[tokio::test]
async fn test_get_credential_transactions() {
let (mock_server, client) = setup_test_client().await;
let payment_credentials =
vec!["025b0a8f85cb8a46e1dda3fae5d22f07e2d56abb4019a2129c5d6c52".to_string()];
let mock_response = json!([{
"tx_hash": "f144a8264acf4bdfe2e1241170969c930d64ab6b0996a4a45237b623f1dd670e",
"epoch_no": 321,
"block_height": 7017300,
"block_time": 1630106091
}]);
Mock::given(method("POST"))
.and(path("/credential_txs"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_payment_credentials": payment_credentials,
"_after_block_height": 7017000
})))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client
.get_credential_transactions(&payment_credentials, Some(7017000))
.await
.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(
response[0].tx_hash,
"f144a8264acf4bdfe2e1241170969c930d64ab6b0996a4a45237b623f1dd670e"
);
assert_eq!(response[0].block_height, 7017300);
}
#[tokio::test]
async fn test_get_address_assets() {
let (mock_server, client) = setup_test_client().await;
let addresses = vec![
"addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz".to_string()
];
let mock_response = json!([{
"address": "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz",
"policy_id": "1e349c9bdea19fd6c147626a5260bc44b71635f398b67c59881df209",
"asset_name": "token1",
"fingerprint": "asset1sl88uq7vjx4vf7qq98v5vjml8ufztrs9tq9l4m",
"decimals": 6,
"quantity": "1000000"
}]);
Mock::given(method("POST"))
.and(path("/address_assets"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_addresses": addresses
})))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client.get_address_assets(&addresses).await.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(response[0].address, addresses[0]);
assert_eq!(
response[0].policy_id,
"1e349c9bdea19fd6c147626a5260bc44b71635f398b67c59881df209"
);
assert_eq!(response[0].asset_name, "token1");
assert_eq!(response[0].quantity, "1000000");
assert_eq!(response[0].decimals.unwrap(), 6);
}
#[tokio::test]
async fn test_invalid_address_format() {
let (mock_server, client) = setup_test_client().await;
let addresses = vec!["invalid_address_format".to_string()];
Mock::given(method("POST"))
.and(path("/address_info"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_addresses": addresses
})))
.respond_with(ResponseTemplate::new(400).set_body_string("Invalid address format"))
.mount(&mock_server)
.await;
let error = client.get_address_info(&addresses).await.unwrap_err();
match error {
koios_sdk::error::Error::Api { status, message } => {
assert_eq!(status, 400);
assert_eq!(message, "Invalid address format");
}
_ => panic!("Expected API error"),
}
}
#[tokio::test]
async fn test_address_not_found() {
let (mock_server, client) = setup_test_client().await;
let addresses = vec![
"addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz".to_string()
];
Mock::given(method("POST"))
.and(path("/address_info"))
.respond_with(ResponseTemplate::new(404).set_body_string("Address not found"))
.mount(&mock_server)
.await;
let error = client.get_address_info(&addresses).await.unwrap_err();
match error {
koios_sdk::error::Error::Api { status, message } => {
assert_eq!(status, 404);
assert_eq!(message, "Address not found");
}
_ => panic!("Expected API error"),
}
}
#[tokio::test]
async fn test_rate_limit_exceeded() {
let (mock_server, client) = setup_test_client().await;
let addresses = vec![
"addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz".to_string()
];
Mock::given(method("POST"))
.and(path("/address_info"))
.respond_with(ResponseTemplate::new(429).set_body_string("Rate limit exceeded"))
.mount(&mock_server)
.await;
let error = client.get_address_info(&addresses).await.unwrap_err();
match error {
koios_sdk::error::Error::Api { status, message } => {
assert_eq!(status, 429);
assert_eq!(message, "Rate limit exceeded");
}
_ => panic!("Expected API error"),
}
}
#[tokio::test]
async fn test_server_error() {
let (mock_server, client) = setup_test_client().await;
let addresses = vec![
"addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz".to_string()
];
Mock::given(method("POST"))
.and(path("/address_info"))
.respond_with(ResponseTemplate::new(500).set_body_string("Internal server error"))
.mount(&mock_server)
.await;
let error = client.get_address_info(&addresses).await.unwrap_err();
match error {
koios_sdk::error::Error::Api { status, message } => {
assert_eq!(status, 500);
assert_eq!(message, "Internal server error");
}
_ => panic!("Expected API error"),
}
}
#[tokio::test]
async fn test_address_info_and_assets_integration() {
let (mock_server, client) = setup_test_client().await;
let addresses = vec![
"addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq946axfcejy5n4x0y99wqpgtp2gd0k09qsgy6pz".to_string()
];
let info_response = json!([{
"address": &addresses[0],
"balance": "12345678",
"stake_address": "stake1u9ylzsgxaa6xctf4juup682ar3juj85n8tx3hthnljg47zctvm3rc",
"script_address": false,
"utxo_set": []
}]);
Mock::given(method("POST"))
.and(path("/address_info"))
.respond_with(ResponseTemplate::new(200).set_body_json(&info_response))
.mount(&mock_server)
.await;
let assets_response = json!([{
"address": &addresses[0],
"policy_id": "1e349c9bdea19fd6c147626a5260bc44b71635f398b67c59881df209",
"asset_name": "token1",
"fingerprint": "asset1sl88uq7vjx4vf7qq98v5vjml8ufztrs9tq9l4m",
"decimals": 6,
"quantity": "1000000"
}]);
Mock::given(method("POST"))
.and(path("/address_assets"))
.respond_with(ResponseTemplate::new(200).set_body_json(&assets_response))
.mount(&mock_server)
.await;
let (info, assets) = tokio::join!(
client.get_address_info(&addresses),
client.get_address_assets(&addresses)
);
let info = info.unwrap();
let assets = assets.unwrap();
assert_eq!(info[0].address, addresses[0]);
assert_eq!(info[0].balance, "12345678");
assert_eq!(assets[0].address, addresses[0]);
assert_eq!(assets[0].quantity, "1000000");
}