use koios_sdk::{
models::script::{RedeemerPurpose, ScriptType},
types::{Extended, ScriptHash},
Client,
};
use pretty_assertions::assert_eq;
use serde_json::json;
use wiremock::{
matchers::{body_json, header, method, path, query_param},
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_script_info() {
let (mock_server, client) = setup_test_client().await;
let script_hashes =
vec!["67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656".to_string()];
let mock_response = json!([{
"script_hash": "67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656",
"creation_tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
"type": "plutusv1",
"value": {
"constructor": 0,
"fields": []
},
"bytes": "4e4d01000033222220051200120011",
"size": 42
}]);
Mock::given(method("POST"))
.and(path("/script_info"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_script_hashes": script_hashes
})))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client.get_script_info(&script_hashes).await.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(
response[0].script_hash.as_ref().unwrap(),
"67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656"
);
assert_eq!(
response[0].creation_tx_hash,
"6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541"
);
assert_eq!(response[0].script_type, ScriptType::PlutusV1);
assert_eq!(response[0].size, 42);
}
#[tokio::test]
async fn test_get_native_script_list() {
let (mock_server, client) = setup_test_client().await;
let mock_response = json!([{
"script_hash": "67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656",
"creation_tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
"type": "timelock",
"size": 42
}]);
Mock::given(method("GET"))
.and(path("/native_script_list"))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client.get_native_script_list().await.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(
response[0].script_hash,
"67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656"
);
assert_eq!(response[0].script_type, ScriptType::Timelock);
}
#[tokio::test]
async fn test_get_plutus_script_list() {
let (mock_server, client) = setup_test_client().await;
let mock_response = json!([{
"script_hash": "67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656",
"creation_tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
"type": "plutusv1",
"size": 42
}]);
Mock::given(method("GET"))
.and(path("/plutus_script_list"))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client.get_plutus_script_list().await.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(
response[0].script_hash,
"67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656"
);
assert_eq!(response[0].script_type, ScriptType::PlutusV1);
}
#[tokio::test]
async fn test_get_script_redeemers() {
let (mock_server, client) = setup_test_client().await;
let script_hash = "67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656";
let mock_response = json!([{
"script_hash": script_hash,
"redeemers": [{
"tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
"tx_index": 0,
"unit_mem": "1000",
"unit_steps": "500",
"fee": "500000",
"purpose": "spend",
"datum_hash": "ab01cd23",
"datum_value": {
"constructor": 0,
"fields": []
}
}]
}]);
Mock::given(method("GET"))
.and(path("/script_redeemers"))
.and(query_param("_script_hash", script_hash))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client
.get_script_redeemers(&ScriptHash::new(script_hash))
.await
.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(response[0].script_hash, script_hash);
assert_eq!(response[0].redeemers.len(), 1);
let redeemer = &response[0].redeemers[0];
assert_eq!(
redeemer.tx_hash,
"6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541"
);
assert_eq!(redeemer.fee, "500000");
assert_eq!(redeemer.purpose, RedeemerPurpose::Spend);
assert_eq!(redeemer.datum_hash.as_ref().unwrap(), "ab01cd23");
}
#[tokio::test]
async fn test_get_script_utxos() {
let (mock_server, client) = setup_test_client().await;
let script_hash = ScriptHash::new("67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656");
let mock_response = json!([{
"tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
"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("GET"))
.and(path("/script_utxos"))
.and(query_param("_script_hash", script_hash.value()))
.and(query_param("_extended", "true"))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client
.get_script_utxos(&script_hash, Some(Extended(true)))
.await
.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(
response[0].tx_hash,
"6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541"
);
assert_eq!(response[0].value, "12345678");
assert!(!response[0].is_spent);
}
#[tokio::test]
async fn test_invalid_script_hash() {
let (mock_server, client) = setup_test_client().await;
let script_hashes = vec!["invalid_script_hash".to_string()];
Mock::given(method("POST"))
.and(path("/script_info"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_script_hashes": script_hashes
})))
.respond_with(ResponseTemplate::new(400).set_body_string("Invalid script hash format"))
.mount(&mock_server)
.await;
let error = client.get_script_info(&script_hashes).await.unwrap_err();
match error {
koios_sdk::error::Error::Api { status, message } => {
assert_eq!(status, 400);
assert_eq!(message, "Invalid script hash format");
}
_ => panic!("Expected API error"),
}
}
#[tokio::test]
async fn test_script_not_found() {
let (mock_server, client) = setup_test_client().await;
let script_hash = ScriptHash::new("67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656");
Mock::given(method("GET"))
.and(path("/script_utxos"))
.and(query_param("_script_hash", script_hash.value()))
.respond_with(ResponseTemplate::new(404).set_body_string("Script not found"))
.mount(&mock_server)
.await;
let error = client
.get_script_utxos(&script_hash, None)
.await
.unwrap_err();
match error {
koios_sdk::error::Error::Api { status, message } => {
assert_eq!(status, 404);
assert_eq!(message, "Script not found");
}
_ => panic!("Expected API error"),
}
}
#[tokio::test]
async fn test_script_info_and_redeemers_integration() {
let (mock_server, client) = setup_test_client().await;
let script_hash = "67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656";
let script_hashes = vec![script_hash.to_string()];
let info_response = json!([{
"script_hash": script_hash,
"creation_tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
"type": "plutusv1",
"value": {
"constructor": 0,
"fields": []
},
"bytes": "4e4d01000033222220051200120011",
"size": 42
}]);
Mock::given(method("POST"))
.and(path("/script_info"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_script_hashes": script_hashes
})))
.respond_with(ResponseTemplate::new(200).set_body_json(&info_response))
.mount(&mock_server)
.await;
let redeemers_response = json!([{
"script_hash": script_hash,
"redeemers": [{
"tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
"tx_index": 0,
"unit_mem": "1000",
"unit_steps": "500",
"fee": "500000",
"purpose": "spend",
"datum_hash": "ab01cd23",
"datum_value": null
}]
}]);
Mock::given(method("GET"))
.and(path("/script_redeemers"))
.and(query_param("_script_hash", script_hash))
.respond_with(ResponseTemplate::new(200).set_body_json(&redeemers_response))
.mount(&mock_server)
.await;
let script_hash = ScriptHash::new(script_hash);
let (info, redeemers) = tokio::join!(
client.get_script_info(&script_hashes),
client.get_script_redeemers(&script_hash)
);
let info = info.unwrap();
let redeemers = redeemers.unwrap();
assert_eq!(info.len(), 1);
assert_eq!(redeemers.len(), 1);
assert_eq!(info[0].script_hash.as_ref().unwrap(), script_hash.value());
assert_eq!(redeemers[0].script_hash, script_hash.value());
assert_eq!(
redeemers[0].redeemers[0].tx_hash,
"6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541"
);
}
#[tokio::test]
async fn test_batch_script_info() {
let (mock_server, client) = setup_test_client().await;
let script_hashes = vec![
"67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656".to_string(),
"77f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678657".to_string(),
];
let mock_response = json!([
{
"script_hash": "67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656",
"creation_tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
"type": "plutusv1",
"value": {
"constructor": 0,
"fields": []
},
"bytes": "4e4d01000033222220051200120011",
"size": 42
},
{
"script_hash": "77f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678657",
"creation_tx_hash": "7ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6542",
"type": "plutusv2",
"value": {
"constructor": 1,
"fields": []
},
"bytes": "4e4d01000033222220051200120012",
"size": 42
}
]);
Mock::given(method("POST"))
.and(path("/script_info"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_script_hashes": script_hashes
})))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client.get_script_info(&script_hashes).await.unwrap();
assert_eq!(response.len(), 2);
assert_eq!(
response[0].script_hash.as_ref().unwrap(),
"67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656"
);
assert_eq!(response[0].script_type, ScriptType::PlutusV1);
assert_eq!(
response[1].script_hash.as_ref().unwrap(),
"77f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678657"
);
assert_eq!(response[1].script_type, ScriptType::PlutusV2);
}
#[tokio::test]
async fn test_script_redeemer_with_inline_datum() {
let (mock_server, client) = setup_test_client().await;
let script_hash = "67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656";
let mock_response = json!([{
"script_hash": script_hash,
"redeemers": [{
"tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
"tx_index": 0,
"unit_mem": 1000, "unit_steps": 500, "fee": "500000",
"purpose": "mint",
"datum_hash": null,
"datum_value": {
"constructor": 0,
"fields": [
{"bytes": "48656c6c6f20576f726c64"},
{"int": 42}
]
}
}]
}]);
Mock::given(method("GET"))
.and(path("/script_redeemers"))
.and(query_param("_script_hash", script_hash))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client
.get_script_redeemers(&ScriptHash::new(script_hash))
.await
.unwrap();
assert_eq!(response.len(), 1);
let redeemer = &response[0].redeemers[0];
assert_eq!(redeemer.purpose, RedeemerPurpose::Mint);
assert!(redeemer.datum_hash.is_none());
assert!(redeemer.datum_value.is_some());
}
#[tokio::test]
async fn test_script_utxos_with_reference_script() {
let (mock_server, client) = setup_test_client().await;
let script_hash = ScriptHash::new("67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656");
let mock_response = json!([{
"tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
"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": {
"hash": "67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656",
"type": "plutusv1",
"bytes": "4e4d01000033222220051200120011"
},
"asset_list": [],
"is_spent": false
}]);
Mock::given(method("GET"))
.and(path("/script_utxos"))
.and(query_param("_script_hash", script_hash.value()))
.and(query_param("_extended", "true"))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.mount(&mock_server)
.await;
let response = client
.get_script_utxos(&script_hash, Some(Extended(true)))
.await
.unwrap();
assert_eq!(response.len(), 1);
assert!(response[0].reference_script.is_some());
let reference_script = response[0].reference_script.as_ref().unwrap();
assert_eq!(
reference_script.get("hash").unwrap().as_str().unwrap(),
script_hash.value()
);
}
#[tokio::test]
async fn test_script_types_validation() {
let (mock_server, client) = setup_test_client().await;
let script_hashes =
vec!["67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656".to_string()];
let script_types = vec![
("plutusv1", ScriptType::PlutusV1),
("plutusv2", ScriptType::PlutusV2),
("timelock", ScriptType::Timelock),
("multisig", ScriptType::Multisig),
];
for (type_str, expected_type) in script_types {
let mock_response = json!([{
"script_hash": "67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656",
"creation_tx_hash": "6ed09ba58a56c6e946668038ba4d3cef8eb97a20cbf76c5970e1402e8a8d6541",
"type": type_str,
"value": {
"constructor": 0,
"fields": []
},
"bytes": "4e4d01000033222220051200120011",
"size": 42
}]);
let _mock = Mock::given(method("POST"))
.and(path("/script_info"))
.and(header("Content-Type", "application/json"))
.and(body_json(json!({
"_script_hashes": &script_hashes
})))
.respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
.expect(1) .mount_as_scoped(&mock_server)
.await;
let response = client.get_script_info(&script_hashes).await.unwrap();
assert_eq!(response.len(), 1);
assert_eq!(
response[0].script_type, expected_type,
"Script type mismatch for input '{}': expected {:?}, got {:?}",
type_str, expected_type, response[0].script_type
);
}
}