Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

220 changes: 220 additions & 0 deletions crates/minibf/src/routes/scripts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,223 @@ where
cbor: hex::encode(minicbor::to_vec(&datum).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?),
}))
}

#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::{TestApp, TestFault};

fn fixture_app() -> TestApp {
TestApp::new()
}

fn invalid_script_hash() -> &'static str {
"not-a-script-hash"
}

fn missing_script_hash() -> &'static str {
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}

fn invalid_datum_hash() -> &'static str {
"not-a-datum-hash"
}

fn missing_datum_hash() -> &'static str {
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}

async fn assert_status(app: &TestApp, path: &str, expected: StatusCode) {
let (status, bytes) = app.get_bytes(path).await;
assert_eq!(
status,
expected,
"unexpected status {status} with body: {}",
String::from_utf8_lossy(&bytes)
);
}

#[tokio::test]
async fn scripts_by_hash_happy_path() {
let app = fixture_app();
let script_hash = app.vectors().script_hash.as_str();
let path = format!("/scripts/{script_hash}");
let (status, bytes) = app.get_bytes(&path).await;

assert_eq!(
status,
StatusCode::OK,
"unexpected status {status} with body: {}",
String::from_utf8_lossy(&bytes)
);

let item: Script = serde_json::from_slice(&bytes).expect("failed to parse script");
assert_eq!(item.script_hash, script_hash);
assert_eq!(item.r#type, ScriptType::Timelock);
assert_eq!(item.serialised_size, None);
}

#[tokio::test]
async fn scripts_by_hash_not_found_for_invalid_hash() {
let app = fixture_app();
let path = format!("/scripts/{}", invalid_script_hash());
assert_status(&app, &path, StatusCode::NOT_FOUND).await;
}

#[tokio::test]
async fn scripts_by_hash_not_found_for_missing_hash() {
let app = fixture_app();
let path = format!("/scripts/{}", missing_script_hash());
assert_status(&app, &path, StatusCode::NOT_FOUND).await;
}

#[tokio::test]
async fn scripts_by_hash_internal_error() {
let app = TestApp::new_with_fault(Some(TestFault::ArchiveStoreError));
let script_hash = app.vectors().script_hash.as_str();
let path = format!("/scripts/{script_hash}");
assert_status(&app, &path, StatusCode::INTERNAL_SERVER_ERROR).await;
}

#[tokio::test]
async fn scripts_by_hash_json_happy_path() {
let app = fixture_app();
let script_hash = app.vectors().script_hash.as_str();
let path = format!("/scripts/{script_hash}/json");
let (status, bytes) = app.get_bytes(&path).await;

assert_eq!(status, StatusCode::OK);

let item: ScriptJson = serde_json::from_slice(&bytes).expect("failed to parse script json");
assert!(item.json.is_some());
}

#[tokio::test]
async fn scripts_by_hash_json_not_found_for_invalid_hash() {
let app = fixture_app();
let path = format!("/scripts/{}/json", invalid_script_hash());
assert_status(&app, &path, StatusCode::NOT_FOUND).await;
}

#[tokio::test]
async fn scripts_by_hash_json_not_found_for_missing_hash() {
let app = fixture_app();
let path = format!("/scripts/{}/json", missing_script_hash());
assert_status(&app, &path, StatusCode::NOT_FOUND).await;
}

#[tokio::test]
async fn scripts_by_hash_json_internal_error() {
let app = TestApp::new_with_fault(Some(TestFault::ArchiveStoreError));
let script_hash = app.vectors().script_hash.as_str();
let path = format!("/scripts/{script_hash}/json");
assert_status(&app, &path, StatusCode::INTERNAL_SERVER_ERROR).await;
}

#[tokio::test]
async fn scripts_by_hash_cbor_happy_path() {
let app = fixture_app();
let script_hash = app.vectors().script_hash.as_str();
let path = format!("/scripts/{script_hash}/cbor");
let (status, bytes) = app.get_bytes(&path).await;

assert_eq!(status, StatusCode::OK);

let item: ScriptCbor = serde_json::from_slice(&bytes).expect("failed to parse script cbor");
assert_eq!(item.cbor, None);
}

#[tokio::test]
async fn scripts_by_hash_cbor_not_found_for_invalid_hash() {
let app = fixture_app();
let path = format!("/scripts/{}/cbor", invalid_script_hash());
assert_status(&app, &path, StatusCode::NOT_FOUND).await;
}

#[tokio::test]
async fn scripts_by_hash_cbor_not_found_for_missing_hash() {
let app = fixture_app();
let path = format!("/scripts/{}/cbor", missing_script_hash());
assert_status(&app, &path, StatusCode::NOT_FOUND).await;
}

#[tokio::test]
async fn scripts_by_hash_cbor_internal_error() {
let app = TestApp::new_with_fault(Some(TestFault::ArchiveStoreError));
let script_hash = app.vectors().script_hash.as_str();
let path = format!("/scripts/{script_hash}/cbor");
assert_status(&app, &path, StatusCode::INTERNAL_SERVER_ERROR).await;
}

#[tokio::test]
async fn scripts_by_datum_hash_happy_path() {
let app = fixture_app();
let datum_hash = app.vectors().datum_hash.as_str();
let path = format!("/scripts/datum/{datum_hash}");
let (status, bytes) = app.get_bytes(&path).await;

assert_eq!(status, StatusCode::OK);

let item: ScriptDatum =
serde_json::from_slice(&bytes).expect("failed to parse script datum");
assert_eq!(item.json_value.get("int"), Some(&serde_json::json!(42)));
}

#[tokio::test]
async fn scripts_by_datum_hash_not_found_for_invalid_hash() {
let app = fixture_app();
let path = format!("/scripts/datum/{}", invalid_datum_hash());
assert_status(&app, &path, StatusCode::NOT_FOUND).await;
}

#[tokio::test]
async fn scripts_by_datum_hash_not_found_for_missing_hash() {
let app = fixture_app();
let path = format!("/scripts/datum/{}", missing_datum_hash());
assert_status(&app, &path, StatusCode::NOT_FOUND).await;
}

#[tokio::test]
async fn scripts_by_datum_hash_internal_error() {
let app = TestApp::new_with_fault(Some(TestFault::ArchiveStoreError));
let datum_hash = app.vectors().datum_hash.as_str();
let path = format!("/scripts/datum/{datum_hash}");
assert_status(&app, &path, StatusCode::INTERNAL_SERVER_ERROR).await;
}

#[tokio::test]
async fn scripts_by_datum_hash_cbor_happy_path() {
let app = fixture_app();
let datum_hash = app.vectors().datum_hash.as_str();
let path = format!("/scripts/datum/{datum_hash}/cbor");
let (status, bytes) = app.get_bytes(&path).await;

assert_eq!(status, StatusCode::OK);

let item: ScriptDatumCbor =
serde_json::from_slice(&bytes).expect("failed to parse script datum cbor");
assert_eq!(item.cbor, app.vectors().datum_cbor_hex);
}

#[tokio::test]
async fn scripts_by_datum_hash_cbor_not_found_for_invalid_hash() {
let app = fixture_app();
let path = format!("/scripts/datum/{}/cbor", invalid_datum_hash());
assert_status(&app, &path, StatusCode::NOT_FOUND).await;
}

#[tokio::test]
async fn scripts_by_datum_hash_cbor_not_found_for_missing_hash() {
let app = fixture_app();
let path = format!("/scripts/datum/{}/cbor", missing_datum_hash());
assert_status(&app, &path, StatusCode::NOT_FOUND).await;
}

#[tokio::test]
async fn scripts_by_datum_hash_cbor_internal_error() {
let app = TestApp::new_with_fault(Some(TestFault::ArchiveStoreError));
let datum_hash = app.vectors().datum_hash.as_str();
let path = format!("/scripts/datum/{datum_hash}/cbor");
assert_status(&app, &path, StatusCode::INTERNAL_SERVER_ERROR).await;
}
}
16 changes: 16 additions & 0 deletions crates/minibf/src/test_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use dolos_testing::{
build_synthetic_blocks, seed_epoch_logs, seed_reward_logs, SyntheticBlockConfig,
SyntheticVectors,
},
toy_domain::advance_epoch_state_to_slot,
toy_domain::ToyDomain,
};
use http_body_util::BodyExt;
Expand Down Expand Up @@ -48,6 +49,21 @@ impl TestDomainBuilder {
let (blocks, vectors, chain_config) = build_synthetic_blocks(cfg);

let domain = ToyDomain::new_with_genesis_and_config(genesis, chain_config, None, None);
advance_epoch_state_to_slot(&domain, vectors.blocks[0].slot);
let summary = dolos_cardano::eras::load_era_summary::<ToyDomain>(domain.state())
.expect("era summary");
let (expected_epoch, _) = summary.slot_epoch(vectors.blocks[0].slot);
let current_epoch =
dolos_cardano::load_epoch::<ToyDomain>(domain.state()).expect("current epoch");
assert_eq!(
current_epoch.number, expected_epoch,
"epoch preconditioning failed"
);
assert_eq!(
current_epoch.rolling.epoch(),
Some(expected_epoch),
"rolling epoch preconditioning failed"
);
domain
.import_blocks(blocks.clone())
.expect("failed to import synthetic blocks");
Expand Down
5 changes: 5 additions & 0 deletions crates/minikupo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ axum = { version = "0.8.4" }

dolos-core = { path = "../core" }
dolos-cardano = { path = "../cardano" }

[dev-dependencies]
dolos-testing = { path = "../testing" }
tower = { workspace = true, features = ["util"] }
http-body-util = "0.1.2"
2 changes: 2 additions & 0 deletions crates/minikupo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use crate::types::BadRequest;

pub mod patterns;
mod routes;
#[cfg(test)]
mod test_support;
mod types;

#[derive(Clone)]
Expand Down
66 changes: 66 additions & 0 deletions crates/minikupo/src/routes/datums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,69 @@ fn parse_datum_hash(value: &str) -> Result<Hash<32>, StatusCode> {
fn datum_hash_hint() -> String {
"Invalid datum hash. Hash must be 64 lowercase hex characters.".to_string()
}

#[cfg(test)]
mod tests {
use axum::http::StatusCode;

use crate::{
test_support::{TestApp, TestFault},
types::Datum,
};

fn missing_hash() -> &'static str {
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}

async fn assert_status(app: &TestApp, path: &str, expected: StatusCode) {
let (status, bytes) = app.get_bytes(path).await;
assert_eq!(
status,
expected,
"unexpected status {status} with body: {}",
String::from_utf8_lossy(&bytes)
);
}

#[tokio::test]
async fn datums_happy_path() {
let app = TestApp::new();
let path = format!("/datums/{}", app.vectors().datum_hash);
let (status, bytes) = app.get_bytes(&path).await;

assert_eq!(
status,
StatusCode::OK,
"unexpected status {status} with body: {}",
String::from_utf8_lossy(&bytes)
);

let datum: Datum = serde_json::from_slice(&bytes).expect("failed to parse datum response");
assert_eq!(datum.datum, app.vectors().datum_cbor_hex);
}

#[tokio::test]
async fn datums_missing_returns_null() {
let app = TestApp::new();
let path = format!("/datums/{}", missing_hash());
let (status, bytes) = app.get_bytes(&path).await;

assert_eq!(status, StatusCode::OK);
let datum: Option<Datum> =
serde_json::from_slice(&bytes).expect("failed to parse null datum response");
assert_eq!(datum, None);
}

#[tokio::test]
async fn datums_bad_request() {
let app = TestApp::new();
assert_status(&app, "/datums/not-a-hash", StatusCode::BAD_REQUEST).await;
}

#[tokio::test]
async fn datums_internal_error() {
let app = TestApp::new_with_fault(Some(TestFault::ArchiveStoreError));
let path = format!("/datums/{}", app.vectors().datum_hash);
assert_status(&app, &path, StatusCode::INTERNAL_SERVER_ERROR).await;
}
}
Loading
Loading