From fbfacce43047afaacdaa758ed3e39814657e17c0 Mon Sep 17 00:00:00 2001 From: Michal Rus Date: Wed, 25 Mar 2026 14:05:11 +0100 Subject: [PATCH 1/7] chore(tests): move integration tests to a separate crate For the upcoming end-to-end (E2E) tests between the Gateways and the Platform. --- .github/workflows/ci.yaml | 2 +- Cargo.lock | 27 ++++++++++++ Cargo.toml | 1 + crates/integration_tests/Cargo.toml | 43 +++++++++++++++++++ crates/integration_tests/src/lib.rs | 1 + .../tests/common/asserts.rs | 0 .../tests/common/mock_data_node.rs | 0 .../tests/common/mod.rs | 0 .../tests/common/tx_builder.rs | 0 .../tests/data/ignored_tests.json | 0 .../tests/data/supported_endpoints.json | 0 .../tests/data_node_test.rs | 0 .../tests/icebreakers_test.rs | 0 .../tests/metrics_test.rs | 0 .../tests/root_test.rs | 0 .../tests/submit_test.rs | 0 nix/internal/unix.nix | 6 +-- 17 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 crates/integration_tests/Cargo.toml create mode 100644 crates/integration_tests/src/lib.rs rename crates/{platform => integration_tests}/tests/common/asserts.rs (100%) rename crates/{platform => integration_tests}/tests/common/mock_data_node.rs (100%) rename crates/{platform => integration_tests}/tests/common/mod.rs (100%) rename crates/{platform => integration_tests}/tests/common/tx_builder.rs (100%) rename crates/{platform => integration_tests}/tests/data/ignored_tests.json (100%) rename crates/{platform => integration_tests}/tests/data/supported_endpoints.json (100%) rename crates/{platform => integration_tests}/tests/data_node_test.rs (100%) rename crates/{platform => integration_tests}/tests/icebreakers_test.rs (100%) rename crates/{platform => integration_tests}/tests/metrics_test.rs (100%) rename crates/{platform => integration_tests}/tests/root_test.rs (100%) rename crates/{platform => integration_tests}/tests/submit_test.rs (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0824e935..3033196a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -164,7 +164,7 @@ jobs: timeout-minutes: 10 env: CARDANO_NODE_SOCKET_PATH: /run/cardano-node/node_preview.socket - run: nix develop .# --command cargo test --verbose --test '*_test' + run: nix develop .# --command cargo test --verbose -p blockfrost-platform-integration-tests --no-fail-fast blockfrost_integration_tests: name: ${{ matrix.mode == 'ignore-check' && 'Ignored' || 'Tests' }} (${{ matrix.network }}) diff --git a/Cargo.lock b/Cargo.lock index fc5bf207..54f80388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -656,6 +656,33 @@ dependencies = [ "tracing", ] +[[package]] +name = "blockfrost-platform-integration-tests" +version = "0.0.3-rc.3" +dependencies = [ + "anyhow", + "axum", + "bip39", + "blockfrost", + "blockfrost-platform", + "blockfrost-platform-api-provider", + "blockfrost-platform-common", + "blockfrost-platform-data-node", + "blockfrost-platform-node", + "cardano-serialization-lib", + "dotenvy", + "hex", + "ntest", + "pretty_assertions", + "reqwest 0.13.2", + "serde", + "serde_json", + "tokio", + "tower", + "tracing", + "tracing-subscriber", +] + [[package]] name = "blockfrost-platform-node" version = "0.0.3-rc.3" diff --git a/Cargo.toml b/Cargo.toml index 98b8c367..09143698 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "crates/node", "crates/data_node", "crates/gateway", + "crates/integration_tests", ] default-members = ["crates/platform"] diff --git a/crates/integration_tests/Cargo.toml b/crates/integration_tests/Cargo.toml new file mode 100644 index 00000000..c85286d9 --- /dev/null +++ b/crates/integration_tests/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "blockfrost-platform-integration-tests" +version.workspace = true +license.workspace = true +edition.workspace = true +publish = false + +[lib] +name = "integration_tests" +path = "src/lib.rs" + +[dev-dependencies] +blockfrost-platform = { path = "../platform" } +bf-api-provider.workspace = true +bf-common.workspace = true +bf-data-node.workspace = true +bf-node.workspace = true + +anyhow.workspace = true +axum.workspace = true +bip39.workspace = true +blockfrost.workspace = true +cardano-serialization-lib.workspace = true +dotenvy.workspace = true +hex.workspace = true +ntest.workspace = true +pretty_assertions.workspace = true +reqwest.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio = { workspace = true, features = [ + "macros", + "rt-multi-thread", + "time", + "net", + "sync", +] } +tower.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true + +[lints] +workspace = true diff --git a/crates/integration_tests/src/lib.rs b/crates/integration_tests/src/lib.rs new file mode 100644 index 00000000..5c9856a5 --- /dev/null +++ b/crates/integration_tests/src/lib.rs @@ -0,0 +1 @@ +// Empty lib crate, but we need it for `./tests/`. diff --git a/crates/platform/tests/common/asserts.rs b/crates/integration_tests/tests/common/asserts.rs similarity index 100% rename from crates/platform/tests/common/asserts.rs rename to crates/integration_tests/tests/common/asserts.rs diff --git a/crates/platform/tests/common/mock_data_node.rs b/crates/integration_tests/tests/common/mock_data_node.rs similarity index 100% rename from crates/platform/tests/common/mock_data_node.rs rename to crates/integration_tests/tests/common/mock_data_node.rs diff --git a/crates/platform/tests/common/mod.rs b/crates/integration_tests/tests/common/mod.rs similarity index 100% rename from crates/platform/tests/common/mod.rs rename to crates/integration_tests/tests/common/mod.rs diff --git a/crates/platform/tests/common/tx_builder.rs b/crates/integration_tests/tests/common/tx_builder.rs similarity index 100% rename from crates/platform/tests/common/tx_builder.rs rename to crates/integration_tests/tests/common/tx_builder.rs diff --git a/crates/platform/tests/data/ignored_tests.json b/crates/integration_tests/tests/data/ignored_tests.json similarity index 100% rename from crates/platform/tests/data/ignored_tests.json rename to crates/integration_tests/tests/data/ignored_tests.json diff --git a/crates/platform/tests/data/supported_endpoints.json b/crates/integration_tests/tests/data/supported_endpoints.json similarity index 100% rename from crates/platform/tests/data/supported_endpoints.json rename to crates/integration_tests/tests/data/supported_endpoints.json diff --git a/crates/platform/tests/data_node_test.rs b/crates/integration_tests/tests/data_node_test.rs similarity index 100% rename from crates/platform/tests/data_node_test.rs rename to crates/integration_tests/tests/data_node_test.rs diff --git a/crates/platform/tests/icebreakers_test.rs b/crates/integration_tests/tests/icebreakers_test.rs similarity index 100% rename from crates/platform/tests/icebreakers_test.rs rename to crates/integration_tests/tests/icebreakers_test.rs diff --git a/crates/platform/tests/metrics_test.rs b/crates/integration_tests/tests/metrics_test.rs similarity index 100% rename from crates/platform/tests/metrics_test.rs rename to crates/integration_tests/tests/metrics_test.rs diff --git a/crates/platform/tests/root_test.rs b/crates/integration_tests/tests/root_test.rs similarity index 100% rename from crates/platform/tests/root_test.rs rename to crates/integration_tests/tests/root_test.rs diff --git a/crates/platform/tests/submit_test.rs b/crates/integration_tests/tests/submit_test.rs similarity index 100% rename from crates/platform/tests/submit_test.rs rename to crates/integration_tests/tests/submit_test.rs diff --git a/nix/internal/unix.nix b/nix/internal/unix.nix index 9078ec0a..5a401a98 100644 --- a/nix/internal/unix.nix +++ b/nix/internal/unix.nix @@ -705,8 +705,8 @@ in cp -r ${inputs.blockfrost-tests}/. "$tmpdir"/. chmod -R u+w,g+w "$tmpdir" cd "$tmpdir" - cat ${../../crates/platform/tests/data/supported_endpoints.json} >endpoints-allowlist.json - cp ${../../crates/platform/tests/data/ignored_tests.json} endpoints-ignorelist.json + cat ${../../crates/integration_tests/tests/data/supported_endpoints.json} >endpoints-allowlist.json + cp ${../../crates/integration_tests/tests/data/ignored_tests.json} endpoints-ignorelist.json ignored_count=$(jq --arg net "$NETWORK" '.[$net] | length' endpoints-ignorelist.json) '' @@ -738,7 +738,7 @@ in if grep -E 'Tests.*passed' tests.log; then echo "" echo "ERROR: Some ignored tests are now passing!" - echo "Please remove them from crates/platform/tests/data/ignored_tests.json" + echo "Please remove them from crates/integration_tests/tests/data/ignored_tests.json" exit 1 fi From 6e60c82cb62d08692b9f738d4816019294d00188 Mon Sep 17 00:00:00 2001 From: Michal Rus Date: Wed, 25 Mar 2026 15:22:44 +0100 Subject: [PATCH 2/7] chore(tests): make the platform scope explicit; move `common` to `lib` --- crates/integration_tests/Cargo.toml | 12 ++++++---- crates/integration_tests/src/lib.rs | 19 ++++++++++++++- .../{tests/common => src/platform}/asserts.rs | 0 .../common => src/platform}/mock_data_node.rs | 0 .../{tests/common => src/platform}/mod.rs | 23 +------------------ .../common => src/platform}/tx_builder.rs | 0 ...ata_node_test.rs => platform_data_node.rs} | 9 ++++---- ...eakers_test.rs => platform_icebreakers.rs} | 4 +--- .../{metrics_test.rs => platform_metrics.rs} | 4 +--- .../tests/{root_test.rs => platform_root.rs} | 4 +--- .../{submit_test.rs => platform_submit.rs} | 11 ++++----- 11 files changed, 37 insertions(+), 49 deletions(-) rename crates/integration_tests/{tests/common => src/platform}/asserts.rs (100%) rename crates/integration_tests/{tests/common => src/platform}/mock_data_node.rs (100%) rename crates/integration_tests/{tests/common => src/platform}/mod.rs (86%) rename crates/integration_tests/{tests/common => src/platform}/tx_builder.rs (100%) rename crates/integration_tests/tests/{data_node_test.rs => platform_data_node.rs} (95%) rename crates/integration_tests/tests/{icebreakers_test.rs => platform_icebreakers.rs} (97%) rename crates/integration_tests/tests/{metrics_test.rs => platform_metrics.rs} (96%) rename crates/integration_tests/tests/{root_test.rs => platform_root.rs} (95%) rename crates/integration_tests/tests/{submit_test.rs => platform_submit.rs} (97%) diff --git a/crates/integration_tests/Cargo.toml b/crates/integration_tests/Cargo.toml index c85286d9..3bec10ad 100644 --- a/crates/integration_tests/Cargo.toml +++ b/crates/integration_tests/Cargo.toml @@ -9,7 +9,7 @@ publish = false name = "integration_tests" path = "src/lib.rs" -[dev-dependencies] +[dependencies] blockfrost-platform = { path = "../platform" } bf-api-provider.workspace = true bf-common.workspace = true @@ -23,9 +23,6 @@ blockfrost.workspace = true cardano-serialization-lib.workspace = true dotenvy.workspace = true hex.workspace = true -ntest.workspace = true -pretty_assertions.workspace = true -reqwest.workspace = true serde.workspace = true serde_json.workspace = true tokio = { workspace = true, features = [ @@ -35,9 +32,14 @@ tokio = { workspace = true, features = [ "net", "sync", ] } +tracing-subscriber.workspace = true + +[dev-dependencies] +ntest.workspace = true +pretty_assertions.workspace = true +reqwest.workspace = true tower.workspace = true tracing.workspace = true -tracing-subscriber.workspace = true [lints] workspace = true diff --git a/crates/integration_tests/src/lib.rs b/crates/integration_tests/src/lib.rs index 5c9856a5..eebe6253 100644 --- a/crates/integration_tests/src/lib.rs +++ b/crates/integration_tests/src/lib.rs @@ -1 +1,18 @@ -// Empty lib crate, but we need it for `./tests/`. +pub mod platform; + +use blockfrost::{BlockFrostSettings, BlockfrostAPI}; +use std::sync::LazyLock; + +static INIT_LOGGING: LazyLock<()> = LazyLock::new(|| { + tracing_subscriber::fmt::init(); +}); + +pub fn initialize_logging() { + let _ = INIT_LOGGING; +} + +pub fn get_blockfrost_client() -> BlockfrostAPI { + let settings = BlockFrostSettings::default(); + + BlockfrostAPI::new("previewy2pbyga8FifUwJSverBCwhESegV6I7gT", settings) +} diff --git a/crates/integration_tests/tests/common/asserts.rs b/crates/integration_tests/src/platform/asserts.rs similarity index 100% rename from crates/integration_tests/tests/common/asserts.rs rename to crates/integration_tests/src/platform/asserts.rs diff --git a/crates/integration_tests/tests/common/mock_data_node.rs b/crates/integration_tests/src/platform/mock_data_node.rs similarity index 100% rename from crates/integration_tests/tests/common/mock_data_node.rs rename to crates/integration_tests/src/platform/mock_data_node.rs diff --git a/crates/integration_tests/tests/common/mod.rs b/crates/integration_tests/src/platform/mod.rs similarity index 86% rename from crates/integration_tests/tests/common/mod.rs rename to crates/integration_tests/src/platform/mod.rs index 86cfa515..7fdc4995 100644 --- a/crates/integration_tests/tests/common/mod.rs +++ b/crates/integration_tests/src/platform/mod.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - pub mod asserts; pub mod mock_data_node; pub mod tx_builder; @@ -10,31 +8,12 @@ use bf_common::{ types::{LogLevel, Network}, }; use bf_node::pool::NodePool; -use blockfrost::{BlockFrostSettings, BlockfrostAPI}; use blockfrost_platform::{ AppError, health_monitor, icebreakers::api::IcebreakersAPI, server::{build, state::ApiPrefix}, }; -use std::{ - env, - sync::{Arc, LazyLock}, - time::Duration, -}; - -static INIT_LOGGING: LazyLock<()> = LazyLock::new(|| { - tracing_subscriber::fmt::init(); -}); - -pub fn initialize_logging() { - let _ = INIT_LOGGING; -} - -pub fn get_blockfrost_client() -> BlockfrostAPI { - let settings = BlockFrostSettings::default(); - - BlockfrostAPI::new("previewy2pbyga8FifUwJSverBCwhESegV6I7gT", settings) -} +use std::{env, sync::Arc, time::Duration}; pub fn test_config(icebreakers_config: Option) -> Arc { dotenvy::dotenv().ok(); diff --git a/crates/integration_tests/tests/common/tx_builder.rs b/crates/integration_tests/src/platform/tx_builder.rs similarity index 100% rename from crates/integration_tests/tests/common/tx_builder.rs rename to crates/integration_tests/src/platform/tx_builder.rs diff --git a/crates/integration_tests/tests/data_node_test.rs b/crates/integration_tests/tests/platform_data_node.rs similarity index 95% rename from crates/integration_tests/tests/data_node_test.rs rename to crates/integration_tests/tests/platform_data_node.rs index 192457b5..39d7419b 100644 --- a/crates/integration_tests/tests/data_node_test.rs +++ b/crates/integration_tests/tests/platform_data_node.rs @@ -1,15 +1,14 @@ -mod common; - mod tests { - use crate::common::{ - build_app_with_data_node, initialize_logging, mock_data_node::MockDataNode, - }; use axum::{ Router, body::{Body, to_bytes}, http::Request, }; use blockfrost_platform::api::root::RootResponse; + use integration_tests::{ + initialize_logging, + platform::{build_app_with_data_node, mock_data_node::MockDataNode}, + }; use pretty_assertions::assert_eq; use reqwest::StatusCode; use tower::ServiceExt; diff --git a/crates/integration_tests/tests/icebreakers_test.rs b/crates/integration_tests/tests/platform_icebreakers.rs similarity index 97% rename from crates/integration_tests/tests/icebreakers_test.rs rename to crates/integration_tests/tests/platform_icebreakers.rs index 4e15f1df..e2794c57 100644 --- a/crates/integration_tests/tests/icebreakers_test.rs +++ b/crates/integration_tests/tests/platform_icebreakers.rs @@ -1,15 +1,13 @@ -mod common; - mod tests { use blockfrost_platform::BlockfrostError; use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; use tokio::sync::Mutex; - use crate::common::{build_app_non_solitary, initialize_logging}; use axum::ServiceExt as AxumServiceExt; use axum::extract::Request as AxumExtractRequest; use blockfrost_platform::icebreakers::manager::IcebreakersManager; + use integration_tests::{initialize_logging, platform::build_app_non_solitary}; use tokio::sync::oneshot; use tracing::info; diff --git a/crates/integration_tests/tests/metrics_test.rs b/crates/integration_tests/tests/platform_metrics.rs similarity index 96% rename from crates/integration_tests/tests/metrics_test.rs rename to crates/integration_tests/tests/platform_metrics.rs index 87f5d9bc..0adee830 100644 --- a/crates/integration_tests/tests/metrics_test.rs +++ b/crates/integration_tests/tests/platform_metrics.rs @@ -1,11 +1,9 @@ -mod common; - mod tests { - use crate::common::{build_app, initialize_logging}; use axum::{ body::{Body, to_bytes}, http::Request, }; + use integration_tests::{initialize_logging, platform::build_app}; use reqwest::StatusCode; use tower::ServiceExt; diff --git a/crates/integration_tests/tests/root_test.rs b/crates/integration_tests/tests/platform_root.rs similarity index 95% rename from crates/integration_tests/tests/root_test.rs rename to crates/integration_tests/tests/platform_root.rs index 31f621f3..772208e9 100644 --- a/crates/integration_tests/tests/root_test.rs +++ b/crates/integration_tests/tests/platform_root.rs @@ -1,12 +1,10 @@ -mod common; - mod tests { - use crate::common::{build_app, initialize_logging}; use axum::{ body::{Body, to_bytes}, http::Request, }; use blockfrost_platform::api::root::RootResponse; + use integration_tests::{initialize_logging, platform::build_app}; use pretty_assertions::assert_eq; use reqwest::StatusCode; use tower::ServiceExt; diff --git a/crates/integration_tests/tests/submit_test.rs b/crates/integration_tests/tests/platform_submit.rs similarity index 97% rename from crates/integration_tests/tests/submit_test.rs rename to crates/integration_tests/tests/platform_submit.rs index 584eca82..c7558734 100644 --- a/crates/integration_tests/tests/submit_test.rs +++ b/crates/integration_tests/tests/platform_submit.rs @@ -1,15 +1,12 @@ -mod common; - -use common::asserts; - mod tests { - use crate::asserts; - use crate::common::tx_builder::build_tx; - use crate::common::{build_app, get_blockfrost_client, initialize_logging}; use axum::{ body::{Body, to_bytes}, http::Request, }; + use integration_tests::{ + get_blockfrost_client, initialize_logging, + platform::{asserts, build_app, tx_builder::build_tx}, + }; use pretty_assertions::assert_eq; use reqwest::{Method, StatusCode}; use tower::ServiceExt; From af04611adcb9a60337b4107773cfa2bb898e5087 Mon Sep 17 00:00:00 2001 From: Michal Rus Date: Wed, 25 Mar 2026 15:30:00 +0100 Subject: [PATCH 3/7] =?UTF-8?q?chore(tests):=20drop=20the=20`mod=20tests?= =?UTF-8?q?=20{=20=E2=80=A6=20}`=20wrapper=20(less=20whitespace)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/platform_data_node.rs | 196 +++++----- .../tests/platform_icebreakers.rs | 164 +++++---- .../tests/platform_metrics.rs | 116 +++--- .../integration_tests/tests/platform_root.rs | 64 ++-- .../tests/platform_submit.rs | 338 +++++++++--------- 5 files changed, 434 insertions(+), 444 deletions(-) diff --git a/crates/integration_tests/tests/platform_data_node.rs b/crates/integration_tests/tests/platform_data_node.rs index 39d7419b..34cd6958 100644 --- a/crates/integration_tests/tests/platform_data_node.rs +++ b/crates/integration_tests/tests/platform_data_node.rs @@ -1,100 +1,98 @@ -mod tests { - use axum::{ - Router, - body::{Body, to_bytes}, - http::Request, - }; - use blockfrost_platform::api::root::RootResponse; - use integration_tests::{ - initialize_logging, - platform::{build_app_with_data_node, mock_data_node::MockDataNode}, - }; - use pretty_assertions::assert_eq; - use reqwest::StatusCode; - use tower::ServiceExt; - - async fn get_root_response(app: Router) -> (StatusCode, RootResponse) { - let response = app - .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) - .await - .expect("Request to root route failed"); - - let status = response.status(); - let body_bytes = to_bytes(response.into_body(), usize::MAX) - .await - .expect("Failed to read response body"); - let root_response: RootResponse = - serde_json::from_slice(&body_bytes).expect("Response body is not valid JSON"); - - (status, root_response) - } - - #[tokio::test] - #[ntest::timeout(120_000)] - async fn test_data_node_health_monitoring() { - initialize_logging(); - - let mock = MockDataNode::healthy().await; - let (app, _, _, _, _) = build_app_with_data_node(mock.url) - .await - .expect("Failed to build the application"); - - let (status, root_response) = get_root_response(app).await; - - assert_eq!(status, StatusCode::OK); - assert_eq!(root_response.errors, Vec::::new()); - assert!(root_response.healthy); - - let data_node_info = root_response - .data_node - .expect("Expected data_node info in response"); - - assert_eq!(data_node_info.url, "http://this.is.a.test.url"); - assert_eq!(data_node_info.version, "0.0.0-test"); - assert_eq!(data_node_info.revision, Some("test-revision".to_string())); - } - - #[tokio::test] - #[ntest::timeout(120_000)] - async fn test_data_node_unhealthy_status() { - initialize_logging(); - - let mock = MockDataNode::unhealthy().await; - let (app, _, _, _, _) = build_app_with_data_node(mock.url) - .await - .expect("Failed to build the application"); - - let (status, root_response) = get_root_response(app).await; - - assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE); - assert!(!root_response.healthy); - assert!( - root_response - .errors - .iter() - .any(|e| e.contains("Data node reports unhealthy status")) - ); - } - - #[tokio::test] - #[ntest::timeout(120_000)] - async fn test_data_node_unreachable() { - initialize_logging(); - - let mock = MockDataNode::unreachable(); - let (app, _, _, _, _) = build_app_with_data_node(mock.url) - .await - .expect("Failed to build the application"); - - let (status, root_response) = get_root_response(app).await; - - assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE); - assert!(!root_response.healthy); - assert!( - root_response - .errors - .iter() - .any(|e| e.contains("Data node unreachable")) - ); - } +use axum::{ + Router, + body::{Body, to_bytes}, + http::Request, +}; +use blockfrost_platform::api::root::RootResponse; +use integration_tests::{ + initialize_logging, + platform::{build_app_with_data_node, mock_data_node::MockDataNode}, +}; +use pretty_assertions::assert_eq; +use reqwest::StatusCode; +use tower::ServiceExt; + +async fn get_root_response(app: Router) -> (StatusCode, RootResponse) { + let response = app + .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) + .await + .expect("Request to root route failed"); + + let status = response.status(); + let body_bytes = to_bytes(response.into_body(), usize::MAX) + .await + .expect("Failed to read response body"); + let root_response: RootResponse = + serde_json::from_slice(&body_bytes).expect("Response body is not valid JSON"); + + (status, root_response) +} + +#[tokio::test] +#[ntest::timeout(120_000)] +async fn test_data_node_health_monitoring() { + initialize_logging(); + + let mock = MockDataNode::healthy().await; + let (app, _, _, _, _) = build_app_with_data_node(mock.url) + .await + .expect("Failed to build the application"); + + let (status, root_response) = get_root_response(app).await; + + assert_eq!(status, StatusCode::OK); + assert_eq!(root_response.errors, Vec::::new()); + assert!(root_response.healthy); + + let data_node_info = root_response + .data_node + .expect("Expected data_node info in response"); + + assert_eq!(data_node_info.url, "http://this.is.a.test.url"); + assert_eq!(data_node_info.version, "0.0.0-test"); + assert_eq!(data_node_info.revision, Some("test-revision".to_string())); +} + +#[tokio::test] +#[ntest::timeout(120_000)] +async fn test_data_node_unhealthy_status() { + initialize_logging(); + + let mock = MockDataNode::unhealthy().await; + let (app, _, _, _, _) = build_app_with_data_node(mock.url) + .await + .expect("Failed to build the application"); + + let (status, root_response) = get_root_response(app).await; + + assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE); + assert!(!root_response.healthy); + assert!( + root_response + .errors + .iter() + .any(|e| e.contains("Data node reports unhealthy status")) + ); +} + +#[tokio::test] +#[ntest::timeout(120_000)] +async fn test_data_node_unreachable() { + initialize_logging(); + + let mock = MockDataNode::unreachable(); + let (app, _, _, _, _) = build_app_with_data_node(mock.url) + .await + .expect("Failed to build the application"); + + let (status, root_response) = get_root_response(app).await; + + assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE); + assert!(!root_response.healthy); + assert!( + root_response + .errors + .iter() + .any(|e| e.contains("Data node unreachable")) + ); } diff --git a/crates/integration_tests/tests/platform_icebreakers.rs b/crates/integration_tests/tests/platform_icebreakers.rs index e2794c57..9d8769c8 100644 --- a/crates/integration_tests/tests/platform_icebreakers.rs +++ b/crates/integration_tests/tests/platform_icebreakers.rs @@ -1,89 +1,87 @@ -mod tests { - use blockfrost_platform::BlockfrostError; - use std::net::{IpAddr, SocketAddr}; - use std::sync::Arc; - use tokio::sync::Mutex; - - use axum::ServiceExt as AxumServiceExt; - use axum::extract::Request as AxumExtractRequest; - use blockfrost_platform::icebreakers::manager::IcebreakersManager; - use integration_tests::{initialize_logging, platform::build_app_non_solitary}; - use tokio::sync::oneshot; - use tracing::info; - - // Test: `icebreakers register` success registration - #[tokio::test] - #[ntest::timeout(120_000)] - async fn test_icebreakers_registrations() -> Result<(), BlockfrostError> { - initialize_logging(); - - let (app, _, _, icebreakers_api, api_prefix) = build_app_non_solitary() - .await - .expect("Failed to build the application"); - - let ip_addr: IpAddr = "0.0.0.0".parse().unwrap(); - let address = SocketAddr::new(ip_addr, 3000); - let listener = tokio::net::TcpListener::bind(address).await.unwrap(); - let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); - let (ready_tx, ready_rx) = oneshot::channel(); - - let spawn_task = tokio::spawn({ - let app = app.clone(); - - async move { - let server_future = axum::serve( - listener, - AxumServiceExt::::into_make_service(app), - ) - .with_graceful_shutdown(async { - shutdown_rx.await.ok(); - }); - - let _ = ready_tx.send(()); - server_future.await - } - }); - - if ready_rx.await.is_ok() { - info!("Server is listening on http://{}{}", address, api_prefix); - - if let Some(icebreakers_api) = icebreakers_api { - let health_errors = Arc::new(Mutex::new(vec![])); - - let manager = IcebreakersManager::new( - icebreakers_api.clone(), - health_errors.clone(), - app.clone(), - api_prefix.clone(), - ); - - let response = manager.run_once().await?; - let resp = response; - let errors = health_errors.lock().await; - - info!("run_once response: {}", resp); - - assert!( - errors.is_empty(), - "Expected no WebSocket errors, but found: {:?}", - *errors - ); - - assert!( - resp.contains("Started"), - "Expected successful registration, but got: {resp}", - ); - - tokio::spawn(async move { - manager.run().await; - }); - } +use blockfrost_platform::BlockfrostError; +use std::net::{IpAddr, SocketAddr}; +use std::sync::Arc; +use tokio::sync::Mutex; + +use axum::ServiceExt as AxumServiceExt; +use axum::extract::Request as AxumExtractRequest; +use blockfrost_platform::icebreakers::manager::IcebreakersManager; +use integration_tests::{initialize_logging, platform::build_app_non_solitary}; +use tokio::sync::oneshot; +use tracing::info; + +// Test: `icebreakers register` success registration +#[tokio::test] +#[ntest::timeout(120_000)] +async fn test_icebreakers_registrations() -> Result<(), BlockfrostError> { + initialize_logging(); + + let (app, _, _, icebreakers_api, api_prefix) = build_app_non_solitary() + .await + .expect("Failed to build the application"); + + let ip_addr: IpAddr = "0.0.0.0".parse().unwrap(); + let address = SocketAddr::new(ip_addr, 3000); + let listener = tokio::net::TcpListener::bind(address).await.unwrap(); + let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); + let (ready_tx, ready_rx) = oneshot::channel(); + + let spawn_task = tokio::spawn({ + let app = app.clone(); + + async move { + let server_future = axum::serve( + listener, + AxumServiceExt::::into_make_service(app), + ) + .with_graceful_shutdown(async { + shutdown_rx.await.ok(); + }); + + let _ = ready_tx.send(()); + server_future.await } + }); - let _ = shutdown_tx.send(()); + if ready_rx.await.is_ok() { + info!("Server is listening on http://{}{}", address, api_prefix); - spawn_task.await.unwrap().unwrap(); + if let Some(icebreakers_api) = icebreakers_api { + let health_errors = Arc::new(Mutex::new(vec![])); - Ok(()) + let manager = IcebreakersManager::new( + icebreakers_api.clone(), + health_errors.clone(), + app.clone(), + api_prefix.clone(), + ); + + let response = manager.run_once().await?; + let resp = response; + let errors = health_errors.lock().await; + + info!("run_once response: {}", resp); + + assert!( + errors.is_empty(), + "Expected no WebSocket errors, but found: {:?}", + *errors + ); + + assert!( + resp.contains("Started"), + "Expected successful registration, but got: {resp}", + ); + + tokio::spawn(async move { + manager.run().await; + }); + } } + + let _ = shutdown_tx.send(()); + + spawn_task.await.unwrap().unwrap(); + + Ok(()) } diff --git a/crates/integration_tests/tests/platform_metrics.rs b/crates/integration_tests/tests/platform_metrics.rs index 0adee830..339a342f 100644 --- a/crates/integration_tests/tests/platform_metrics.rs +++ b/crates/integration_tests/tests/platform_metrics.rs @@ -1,60 +1,58 @@ -mod tests { - use axum::{ - body::{Body, to_bytes}, - http::Request, - }; - use integration_tests::{initialize_logging, platform::build_app}; - use reqwest::StatusCode; - use tower::ServiceExt; - - // Test: `/metrics` route sanity check and trailing slash - #[tokio::test] - #[ntest::timeout(120_000)] - async fn test_route_metrics() { - initialize_logging(); - - let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); - - // Test without trailing slash - let response = app - .clone() - .oneshot( - Request::builder() - .uri("/metrics") - .body(Body::empty()) - .unwrap(), - ) - .await - .expect("Request to /metrics route failed"); - - assert_eq!(response.status(), StatusCode::OK); - - let body_bytes = to_bytes(response.into_body(), usize::MAX) - .await - .expect("Failed to read response body"); - - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - assert!(body_str.contains("cardano_node_connections")); - - // Test with trailing slash - let response_trailing = app - .oneshot( - Request::builder() - .uri("/metrics/") - .body(Body::empty()) - .unwrap(), - ) - .await - .expect("Request to /metrics/ route failed"); - - assert_eq!(response_trailing.status(), StatusCode::OK); - - let body_bytes_trailing = to_bytes(response_trailing.into_body(), usize::MAX) - .await - .expect("Failed to read response body for /metrics/"); - - let body_str_trailing = String::from_utf8(body_bytes_trailing.to_vec()).unwrap(); - - assert!(body_str_trailing.contains("cardano_node_connections")); - } +use axum::{ + body::{Body, to_bytes}, + http::Request, +}; +use integration_tests::{initialize_logging, platform::build_app}; +use reqwest::StatusCode; +use tower::ServiceExt; + +// Test: `/metrics` route sanity check and trailing slash +#[tokio::test] +#[ntest::timeout(120_000)] +async fn test_route_metrics() { + initialize_logging(); + + let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); + + // Test without trailing slash + let response = app + .clone() + .oneshot( + Request::builder() + .uri("/metrics") + .body(Body::empty()) + .unwrap(), + ) + .await + .expect("Request to /metrics route failed"); + + assert_eq!(response.status(), StatusCode::OK); + + let body_bytes = to_bytes(response.into_body(), usize::MAX) + .await + .expect("Failed to read response body"); + + let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); + assert!(body_str.contains("cardano_node_connections")); + + // Test with trailing slash + let response_trailing = app + .oneshot( + Request::builder() + .uri("/metrics/") + .body(Body::empty()) + .unwrap(), + ) + .await + .expect("Request to /metrics/ route failed"); + + assert_eq!(response_trailing.status(), StatusCode::OK); + + let body_bytes_trailing = to_bytes(response_trailing.into_body(), usize::MAX) + .await + .expect("Failed to read response body for /metrics/"); + + let body_str_trailing = String::from_utf8(body_bytes_trailing.to_vec()).unwrap(); + + assert!(body_str_trailing.contains("cardano_node_connections")); } diff --git a/crates/integration_tests/tests/platform_root.rs b/crates/integration_tests/tests/platform_root.rs index 772208e9..d0fe198a 100644 --- a/crates/integration_tests/tests/platform_root.rs +++ b/crates/integration_tests/tests/platform_root.rs @@ -1,40 +1,38 @@ -mod tests { - use axum::{ - body::{Body, to_bytes}, - http::Request, - }; - use blockfrost_platform::api::root::RootResponse; - use integration_tests::{initialize_logging, platform::build_app}; - use pretty_assertions::assert_eq; - use reqwest::StatusCode; - use tower::ServiceExt; +use axum::{ + body::{Body, to_bytes}, + http::Request, +}; +use blockfrost_platform::api::root::RootResponse; +use integration_tests::{initialize_logging, platform::build_app}; +use pretty_assertions::assert_eq; +use reqwest::StatusCode; +use tower::ServiceExt; - // Test: `/` route correct response - #[tokio::test] - #[ntest::timeout(120_000)] - async fn test_route_root() { - initialize_logging(); +// Test: `/` route correct response +#[tokio::test] +#[ntest::timeout(120_000)] +async fn test_route_root() { + initialize_logging(); - let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); + let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); - let response = app - .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) - .await - .expect("Request to root route failed"); + let response = app + .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) + .await + .expect("Request to root route failed"); - assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.status(), StatusCode::OK); - let body_bytes = to_bytes(response.into_body(), usize::MAX) - .await - .expect("Failed to read response body"); - let root_response: RootResponse = - serde_json::from_slice(&body_bytes).expect("Response body is not valid JSON"); + let body_bytes = to_bytes(response.into_body(), usize::MAX) + .await + .expect("Failed to read response body"); + let root_response: RootResponse = + serde_json::from_slice(&body_bytes).expect("Response body is not valid JSON"); - assert!(root_response.errors.is_empty()); - assert_eq!(root_response.name, "blockfrost-platform"); - assert!(root_response.healthy); - assert_eq!(root_response.node_info.unwrap().sync_progress, 100.0); - // data_node is not configured in build_app() - assert!(root_response.data_node.is_none()); - } + assert!(root_response.errors.is_empty()); + assert_eq!(root_response.name, "blockfrost-platform"); + assert!(root_response.healthy); + assert_eq!(root_response.node_info.unwrap().sync_progress, 100.0); + // data_node is not configured in build_app() + assert!(root_response.data_node.is_none()); } diff --git a/crates/integration_tests/tests/platform_submit.rs b/crates/integration_tests/tests/platform_submit.rs index c7558734..e7e916cb 100644 --- a/crates/integration_tests/tests/platform_submit.rs +++ b/crates/integration_tests/tests/platform_submit.rs @@ -1,171 +1,169 @@ -mod tests { - use axum::{ - body::{Body, to_bytes}, - http::Request, - }; - use integration_tests::{ - get_blockfrost_client, initialize_logging, - platform::{asserts, build_app, tx_builder::build_tx}, - }; - use pretty_assertions::assert_eq; - use reqwest::{Method, StatusCode}; - use tower::ServiceExt; - - // Test: `/tx/submit` error has same response as blockfrost API - #[tokio::test] - #[ntest::timeout(120_000)] - async fn test_route_submit_cbor_error() { - initialize_logging(); - let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); - - let tx = "AAAAAA"; - - // Local (Platform) - let local_request = Request::builder() - .method(Method::POST) - .uri("/tx/submit") - .header("Content-Type", "application/cbor") - .body(Body::from(tx)) - .unwrap(); - - let local_response = app - .oneshot(local_request) - .await - .expect("Request to /tx/submit failed"); - - let local_body_bytes = to_bytes(local_response.into_body(), usize::MAX) - .await - .expect("Failed to read response body"); - - let local_body_str = String::from_utf8(local_body_bytes.to_vec()) - .expect("Failed to convert bytes to string"); - - let expected = r#"{"error":"Bad Request","message":"{\"tag\":\"TxSubmitFail\",\"contents\":{\"tag\":\"TxCmdTxReadError\",\"contents\":[\"DecoderErrorDeserialiseFailure \\\"Shelley Tx\\\" (DeserialiseFailure 0 (\\\"unexpected type map at position 0: expected array\\\"))\"]}}","status_code":400}"#; - assert_eq!(expected, &local_body_str); - } - - // Test: `/tx/submit` error has same response as blockfrost API - #[tokio::test] - #[ntest::timeout(120_000)] - async fn test_route_submit_error() { - initialize_logging(); - let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); - - let tx = "84a300d90102818258205176274bef11d575edd6aa72392aaf993a07f736e70239c1fb22d4b1426b22bc01018282583900ddf1eb9ce2a1561e8f156991486b97873fb6969190cbc99ddcb3816621dcb03574152623414ed354d2d8f50e310f3f2e7d167cb20e5754271a003d09008258390099a5cb0fa8f19aba38cacf8a243d632149129f882df3a8e67f6bd512bcb0cde66a545e9fbc7ca4492f39bca1f4f265cc1503b4f7d6ff205c1b000000024f127a7c021a0002a2ada100d90102818258208b83e59abc9d7a66a77be5e0825525546a595174f8b929f164fcf5052d7aab7b5840709c64556c946abf267edd90b8027343d065193ef816529d8fa7aa2243f1fd2ec27036a677974199e2264cb582d01925134b9a20997d5a734da298df957eb002f5f6"; - - // Local (Platform) - let local_request = Request::builder() - .method(Method::POST) - .uri("/tx/submit") - .header("Content-Type", "application/cbor") - .body(Body::from(tx)) - .unwrap(); - - let local_response = app - .oneshot(local_request) - .await - .expect("Request to /tx/submit failed"); - - let local_body_bytes = to_bytes(local_response.into_body(), usize::MAX) - .await - .expect("Failed to read response body"); - - // Blockfrost API - let bf_response = reqwest::Client::new() - .post("https://cardano-preview.blockfrost.io/api/v0/tx/submit") - .header("Content-Type", "application/cbor") - .header("project_id", "previewWrlEvs2PlZUw8hEN5usP5wG4DK4L46A3") - .body(hex::decode(tx).unwrap()) - .send() - .await - .expect("Blockfrost request failed"); - - let bf_body_bytes = bf_response - .bytes() - .await - .expect("Failed to read Blockfrost response"); - - asserts::assert_submit_error_responses(&bf_body_bytes, &local_body_bytes); - } - - // validation of the fix: https://github.com/blockfrost/blockfrost-platform/issues/238#issuecomment-2747354365 - #[tokio::test] - #[ntest::timeout(120_000)] - async fn test_route_submit_agent_dequeu() { - initialize_logging(); - let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); - - let tx = "84a800848258204c16d304e6d531c59afd87a9199b7bb4175bc131b3d6746917901046b662963c00825820893c3f630c0b2db16d041c388aa0d58746ccbbc44133b2d7a3127a72c79722f1018258200998adb591c872a241776e39fe855e04b2d7c361008e94c582f59b6b6ccc452c028258208380ce7240ba59187f6450911f74a70cf3d2749228badb2e7cd10fb6499355f503018482581d61e15900a9a62a8fb01f936a25bf54af209c7ed1248c4e5abd05ec4e76821a0023ba63a1581ca0028f350aaabe0545fdcb56b039bfb08e4bb4d8c4d7c3c7d481c235a145484f534b5900a300581d71cba5c6770fe7b30ebc1fa32f01938c150513211360ded23ac76e36b301821a006336d5a3581c239075b83c03c2333eacd0b0beac6b8314f11ce3dc0c047012b0cad4a144706f6f6c01581c3547b4325e495d529619335603ababde10025dceafa9ed34b1fb6611a158208b284793d3bd4967244a2ddd68410d56d06d36ac8d201429b937096a2e8234bc1b7ffffffffffade6b581ca0028f350aaabe0545fdcb56b039bfb08e4bb4d8c4d7c3c7d481c235a145484f534b59195e99028201d818583ad8799fd8799f4040ffd8799f581ca0028f350aaabe0545fdcb56b039bfb08e4bb4d8c4d7c3c7d481c23545484f534b59ff1a006336d5195e99ff825839016d06090559d8ed2988aa5b2fff265d668cf552f4f62278c0128f816c0a48432e080280d0d9b15edb65563995f97ce236035afea568e660d1821a00118f32a1581c2f8b2d1f384485896f38406173fa11df2a4ce53b4b0886138b76597aa1476261746368657201825839016d06090559d8ed2988aa5b2fff265d668cf552f4f62278c0128f816c0a48432e080280d0d9b15edb65563995f97ce236035afea568e660d11a06d9f713021a000ab9e00b582027f17979d848d6472896266dd8bf39f7251ca23798713464bc407bf637286c230d81825820cf5de9189b958f8ad64c1f1837c2fa4711d073494598467a1c1a59589393eae20310825839016d06090559d8ed2988aa5b2fff265d668cf552f4f62278c0128f816c0a48432e080280d0d9b15edb65563995f97ce236035afea568e660d11a08666c75111a001016d01282825820bf93dc59c10c19c35210c2414779d7391ca19128cc7b13794ea85af5ff835f59008258201c37df764f8261edce8678b197767668a91d544b2b203fb5d0cf9acc10366e7600a200818258200eabfa083d7969681d2fc8e825a5f79e1c40f03aeac46ecd94bf5c5790db1bc058409a029ddd3cdde65598bb712c640ea63eeebfee526ce49bd0983b4d1fdca858481ddf931bf0354552cc0a7d3365e2f03fdb457c0466cea8b371b645f9b6d0c2010582840001d8799fd8799f011a006336d5195e991b7ffffffffffade6bd8799f1a000539e7ff01ffff821a000b46e41a0a7f3ca4840003d87d80821a002dccfe1a28868be8f5f6"; - - // Local (Platform) - let local_request = Request::builder() - .method(Method::POST) - .uri("/tx/submit") - .header("Content-Type", "application/cbor") - .body(Body::from(tx)) - .unwrap(); - - let local_response = app - .oneshot(local_request) - .await - .expect("Request to /tx/submit failed"); - - let local_body_bytes = to_bytes(local_response.into_body(), usize::MAX) - .await - .expect("Failed to read response body"); - - let local_body_str = String::from_utf8(local_body_bytes.to_vec()) - .expect("Failed to convert bytes to string"); - - assert!( - local_body_str.contains("MultiAsset cannot contain zeros"), - "Expected error message to contain 'MultiAsset cannot contain zeros', got: {local_body_str}" - ); - } - - // Test: build `/tx/submit` success - tx is accepted by the node - #[tokio::test] - #[ntest::timeout(120_000)] - async fn test_route_submit_success() { - initialize_logging(); - let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); - let blockfrost_client = get_blockfrost_client(); - let tx = build_tx(&blockfrost_client).await.unwrap(); - - let request = Request::builder() - .method(Method::POST) - .uri("/tx/submit") - .header("Content-Type", "application/cbor") - .body(Body::from(tx.to_hex())) - .unwrap(); - - let response = app - .oneshot(request) - .await - .expect("Request to /tx/submit failed"); - - let status = response.status(); - - assert!( - response - .headers() - .contains_key("blockfrost-platform-response"), - "Response is missing the `blockfrost-platform-response` header" - ); - - let local_body_bytes = to_bytes(response.into_body(), usize::MAX) - .await - .expect("Failed to read response body"); - - let local_body_str = String::from_utf8(local_body_bytes.to_vec()) - .expect("Failed to convert bytes to string"); - - assert_eq!( - status, - StatusCode::OK, - "Expected 200 OK, got {status}: {local_body_str}" - ); - - assert_eq!(66, local_body_str.len()); - } +use axum::{ + body::{Body, to_bytes}, + http::Request, +}; +use integration_tests::{ + get_blockfrost_client, initialize_logging, + platform::{asserts, build_app, tx_builder::build_tx}, +}; +use pretty_assertions::assert_eq; +use reqwest::{Method, StatusCode}; +use tower::ServiceExt; + +// Test: `/tx/submit` error has same response as blockfrost API +#[tokio::test] +#[ntest::timeout(120_000)] +async fn test_route_submit_cbor_error() { + initialize_logging(); + let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); + + let tx = "AAAAAA"; + + // Local (Platform) + let local_request = Request::builder() + .method(Method::POST) + .uri("/tx/submit") + .header("Content-Type", "application/cbor") + .body(Body::from(tx)) + .unwrap(); + + let local_response = app + .oneshot(local_request) + .await + .expect("Request to /tx/submit failed"); + + let local_body_bytes = to_bytes(local_response.into_body(), usize::MAX) + .await + .expect("Failed to read response body"); + + let local_body_str = + String::from_utf8(local_body_bytes.to_vec()).expect("Failed to convert bytes to string"); + + let expected = r#"{"error":"Bad Request","message":"{\"tag\":\"TxSubmitFail\",\"contents\":{\"tag\":\"TxCmdTxReadError\",\"contents\":[\"DecoderErrorDeserialiseFailure \\\"Shelley Tx\\\" (DeserialiseFailure 0 (\\\"unexpected type map at position 0: expected array\\\"))\"]}}","status_code":400}"#; + assert_eq!(expected, &local_body_str); +} + +// Test: `/tx/submit` error has same response as blockfrost API +#[tokio::test] +#[ntest::timeout(120_000)] +async fn test_route_submit_error() { + initialize_logging(); + let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); + + let tx = "84a300d90102818258205176274bef11d575edd6aa72392aaf993a07f736e70239c1fb22d4b1426b22bc01018282583900ddf1eb9ce2a1561e8f156991486b97873fb6969190cbc99ddcb3816621dcb03574152623414ed354d2d8f50e310f3f2e7d167cb20e5754271a003d09008258390099a5cb0fa8f19aba38cacf8a243d632149129f882df3a8e67f6bd512bcb0cde66a545e9fbc7ca4492f39bca1f4f265cc1503b4f7d6ff205c1b000000024f127a7c021a0002a2ada100d90102818258208b83e59abc9d7a66a77be5e0825525546a595174f8b929f164fcf5052d7aab7b5840709c64556c946abf267edd90b8027343d065193ef816529d8fa7aa2243f1fd2ec27036a677974199e2264cb582d01925134b9a20997d5a734da298df957eb002f5f6"; + + // Local (Platform) + let local_request = Request::builder() + .method(Method::POST) + .uri("/tx/submit") + .header("Content-Type", "application/cbor") + .body(Body::from(tx)) + .unwrap(); + + let local_response = app + .oneshot(local_request) + .await + .expect("Request to /tx/submit failed"); + + let local_body_bytes = to_bytes(local_response.into_body(), usize::MAX) + .await + .expect("Failed to read response body"); + + // Blockfrost API + let bf_response = reqwest::Client::new() + .post("https://cardano-preview.blockfrost.io/api/v0/tx/submit") + .header("Content-Type", "application/cbor") + .header("project_id", "previewWrlEvs2PlZUw8hEN5usP5wG4DK4L46A3") + .body(hex::decode(tx).unwrap()) + .send() + .await + .expect("Blockfrost request failed"); + + let bf_body_bytes = bf_response + .bytes() + .await + .expect("Failed to read Blockfrost response"); + + asserts::assert_submit_error_responses(&bf_body_bytes, &local_body_bytes); +} + +// validation of the fix: https://github.com/blockfrost/blockfrost-platform/issues/238#issuecomment-2747354365 +#[tokio::test] +#[ntest::timeout(120_000)] +async fn test_route_submit_agent_dequeu() { + initialize_logging(); + let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); + + let tx = "84a800848258204c16d304e6d531c59afd87a9199b7bb4175bc131b3d6746917901046b662963c00825820893c3f630c0b2db16d041c388aa0d58746ccbbc44133b2d7a3127a72c79722f1018258200998adb591c872a241776e39fe855e04b2d7c361008e94c582f59b6b6ccc452c028258208380ce7240ba59187f6450911f74a70cf3d2749228badb2e7cd10fb6499355f503018482581d61e15900a9a62a8fb01f936a25bf54af209c7ed1248c4e5abd05ec4e76821a0023ba63a1581ca0028f350aaabe0545fdcb56b039bfb08e4bb4d8c4d7c3c7d481c235a145484f534b5900a300581d71cba5c6770fe7b30ebc1fa32f01938c150513211360ded23ac76e36b301821a006336d5a3581c239075b83c03c2333eacd0b0beac6b8314f11ce3dc0c047012b0cad4a144706f6f6c01581c3547b4325e495d529619335603ababde10025dceafa9ed34b1fb6611a158208b284793d3bd4967244a2ddd68410d56d06d36ac8d201429b937096a2e8234bc1b7ffffffffffade6b581ca0028f350aaabe0545fdcb56b039bfb08e4bb4d8c4d7c3c7d481c235a145484f534b59195e99028201d818583ad8799fd8799f4040ffd8799f581ca0028f350aaabe0545fdcb56b039bfb08e4bb4d8c4d7c3c7d481c23545484f534b59ff1a006336d5195e99ff825839016d06090559d8ed2988aa5b2fff265d668cf552f4f62278c0128f816c0a48432e080280d0d9b15edb65563995f97ce236035afea568e660d1821a00118f32a1581c2f8b2d1f384485896f38406173fa11df2a4ce53b4b0886138b76597aa1476261746368657201825839016d06090559d8ed2988aa5b2fff265d668cf552f4f62278c0128f816c0a48432e080280d0d9b15edb65563995f97ce236035afea568e660d11a06d9f713021a000ab9e00b582027f17979d848d6472896266dd8bf39f7251ca23798713464bc407bf637286c230d81825820cf5de9189b958f8ad64c1f1837c2fa4711d073494598467a1c1a59589393eae20310825839016d06090559d8ed2988aa5b2fff265d668cf552f4f62278c0128f816c0a48432e080280d0d9b15edb65563995f97ce236035afea568e660d11a08666c75111a001016d01282825820bf93dc59c10c19c35210c2414779d7391ca19128cc7b13794ea85af5ff835f59008258201c37df764f8261edce8678b197767668a91d544b2b203fb5d0cf9acc10366e7600a200818258200eabfa083d7969681d2fc8e825a5f79e1c40f03aeac46ecd94bf5c5790db1bc058409a029ddd3cdde65598bb712c640ea63eeebfee526ce49bd0983b4d1fdca858481ddf931bf0354552cc0a7d3365e2f03fdb457c0466cea8b371b645f9b6d0c2010582840001d8799fd8799f011a006336d5195e991b7ffffffffffade6bd8799f1a000539e7ff01ffff821a000b46e41a0a7f3ca4840003d87d80821a002dccfe1a28868be8f5f6"; + + // Local (Platform) + let local_request = Request::builder() + .method(Method::POST) + .uri("/tx/submit") + .header("Content-Type", "application/cbor") + .body(Body::from(tx)) + .unwrap(); + + let local_response = app + .oneshot(local_request) + .await + .expect("Request to /tx/submit failed"); + + let local_body_bytes = to_bytes(local_response.into_body(), usize::MAX) + .await + .expect("Failed to read response body"); + + let local_body_str = + String::from_utf8(local_body_bytes.to_vec()).expect("Failed to convert bytes to string"); + + assert!( + local_body_str.contains("MultiAsset cannot contain zeros"), + "Expected error message to contain 'MultiAsset cannot contain zeros', got: {local_body_str}" + ); +} + +// Test: build `/tx/submit` success - tx is accepted by the node +#[tokio::test] +#[ntest::timeout(120_000)] +async fn test_route_submit_success() { + initialize_logging(); + let (app, _, _, _, _) = build_app().await.expect("Failed to build the application"); + let blockfrost_client = get_blockfrost_client(); + let tx = build_tx(&blockfrost_client).await.unwrap(); + + let request = Request::builder() + .method(Method::POST) + .uri("/tx/submit") + .header("Content-Type", "application/cbor") + .body(Body::from(tx.to_hex())) + .unwrap(); + + let response = app + .oneshot(request) + .await + .expect("Request to /tx/submit failed"); + + let status = response.status(); + + assert!( + response + .headers() + .contains_key("blockfrost-platform-response"), + "Response is missing the `blockfrost-platform-response` header" + ); + + let local_body_bytes = to_bytes(response.into_body(), usize::MAX) + .await + .expect("Failed to read response body"); + + let local_body_str = + String::from_utf8(local_body_bytes.to_vec()).expect("Failed to convert bytes to string"); + + assert_eq!( + status, + StatusCode::OK, + "Expected 200 OK, got {status}: {local_body_str}" + ); + + assert_eq!(66, local_body_str.len()); } From dc1e52dfea6e058ecdc6003e533b6e54022901c3 Mon Sep 17 00:00:00 2001 From: Michal Rus Date: Wed, 25 Mar 2026 16:50:04 +0100 Subject: [PATCH 4/7] fix(deps): remove unused deps from `crates/integration-tests` --- Cargo.lock | 6 ------ crates/platform/Cargo.toml | 6 ------ 2 files changed, 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54f80388..d03a147d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -557,17 +557,13 @@ dependencies = [ name = "blockfrost-platform" version = "0.0.3-rc.3" dependencies = [ - "anyhow", "axum", "base64", - "bip39", - "blockfrost", "blockfrost-platform-api-provider", "blockfrost-platform-build-utils", "blockfrost-platform-common", "blockfrost-platform-data-node", "blockfrost-platform-node", - "cardano-serialization-lib", "chrono", "dotenvy", "futures", @@ -578,8 +574,6 @@ dependencies = [ "metrics", "metrics-exporter-prometheus", "metrics-process", - "ntest", - "pretty_assertions", "proptest", "reqwest 0.13.2", "rstest", diff --git a/crates/platform/Cargo.toml b/crates/platform/Cargo.toml index d37c5a92..f56bda73 100644 --- a/crates/platform/Cargo.toml +++ b/crates/platform/Cargo.toml @@ -37,17 +37,11 @@ metrics-process.workspace = true chrono.workspace = true dotenvy.workspace = true uuid.workspace = true -anyhow.workspace = true -ntest.workspace = true [dev-dependencies] bf-api-provider.workspace = true rstest.workspace = true -pretty_assertions.workspace = true proptest.workspace = true -cardano-serialization-lib.workspace = true -bip39.workspace = true -blockfrost.workspace = true [target.'cfg(target_env = "musl")'.dependencies] jemalloc.workspace = true From 820d2ef42223d27aac53894f242fb8203e11eb74 Mon Sep 17 00:00:00 2001 From: Michal Rus Date: Wed, 25 Mar 2026 17:24:46 +0100 Subject: [PATCH 5/7] chore(tests): move the existing Gateway integration test, too! --- Cargo.lock | 7 +++++++ crates/integration_tests/Cargo.toml | 7 +++++++ .../tests/common => integration_tests/src/gateway}/mod.rs | 0 crates/integration_tests/src/lib.rs | 1 + .../tests/gateway_load_balancer.rs} | 3 +-- 5 files changed, 16 insertions(+), 2 deletions(-) rename crates/{gateway/tests/common => integration_tests/src/gateway}/mod.rs (100%) rename crates/{gateway/tests/load_balancer.rs => integration_tests/tests/gateway_load_balancer.rs} (99%) diff --git a/Cargo.lock b/Cargo.lock index d03a147d..e5a8a393 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -656,8 +656,10 @@ version = "0.0.3-rc.3" dependencies = [ "anyhow", "axum", + "base64", "bip39", "blockfrost", + "blockfrost-gateway", "blockfrost-platform", "blockfrost-platform-api-provider", "blockfrost-platform-common", @@ -665,16 +667,21 @@ dependencies = [ "blockfrost-platform-node", "cardano-serialization-lib", "dotenvy", + "futures", "hex", + "hyper", "ntest", "pretty_assertions", "reqwest 0.13.2", "serde", "serde_json", "tokio", + "tokio-tungstenite", "tower", "tracing", "tracing-subscriber", + "tungstenite", + "uuid", ] [[package]] diff --git a/crates/integration_tests/Cargo.toml b/crates/integration_tests/Cargo.toml index 3bec10ad..1f75bbdc 100644 --- a/crates/integration_tests/Cargo.toml +++ b/crates/integration_tests/Cargo.toml @@ -10,6 +10,7 @@ name = "integration_tests" path = "src/lib.rs" [dependencies] +blockfrost-gateway = { path = "../gateway" } blockfrost-platform = { path = "../platform" } bf-api-provider.workspace = true bf-common.workspace = true @@ -35,11 +36,17 @@ tokio = { workspace = true, features = [ tracing-subscriber.workspace = true [dev-dependencies] +base64.workspace = true +futures.workspace = true +hyper.workspace = true ntest.workspace = true pretty_assertions.workspace = true reqwest.workspace = true +tokio-tungstenite.workspace = true tower.workspace = true tracing.workspace = true +tungstenite.workspace = true +uuid.workspace = true [lints] workspace = true diff --git a/crates/gateway/tests/common/mod.rs b/crates/integration_tests/src/gateway/mod.rs similarity index 100% rename from crates/gateway/tests/common/mod.rs rename to crates/integration_tests/src/gateway/mod.rs diff --git a/crates/integration_tests/src/lib.rs b/crates/integration_tests/src/lib.rs index eebe6253..ec784fe8 100644 --- a/crates/integration_tests/src/lib.rs +++ b/crates/integration_tests/src/lib.rs @@ -1,3 +1,4 @@ +pub mod gateway; pub mod platform; use blockfrost::{BlockFrostSettings, BlockfrostAPI}; diff --git a/crates/gateway/tests/load_balancer.rs b/crates/integration_tests/tests/gateway_load_balancer.rs similarity index 99% rename from crates/gateway/tests/load_balancer.rs rename to crates/integration_tests/tests/gateway_load_balancer.rs index 33c85277..8f527337 100644 --- a/crates/gateway/tests/load_balancer.rs +++ b/crates/integration_tests/tests/gateway_load_balancer.rs @@ -1,5 +1,4 @@ -mod common; -use common::*; +use integration_tests::gateway::*; use base64::Engine; use blockfrost_gateway::{ From 603a703b466a99f9cb18658f3c21853f96100ac4 Mon Sep 17 00:00:00 2001 From: Michal Rus Date: Wed, 25 Mar 2026 17:43:49 +0100 Subject: [PATCH 6/7] =?UTF-8?q?fix(tests):=20the=20Gateway=20integration?= =?UTF-8?q?=20test=20for=20the=20new=20Axum=200.8.=C3=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/gateway/src/main.rs | 6 +++--- crates/integration_tests/src/gateway/mod.rs | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/gateway/src/main.rs b/crates/gateway/src/main.rs index cc9d954a..fa2e1eb6 100644 --- a/crates/gateway/src/main.rs +++ b/crates/gateway/src/main.rs @@ -48,15 +48,15 @@ async fn main() { .route("/ws", get(load_balancer::api::websocket_route)) .route("/stats", get(load_balancer::api::stats_route)) .route( - "/:uuid", + "/{uuid}", axum::routing::any(load_balancer::api::prefix_route_root), ) .route( - "/:uuid/", + "/{uuid}/", axum::routing::any(load_balancer::api::prefix_route_root), ) .route( - "/:uuid/*rest", + "/{uuid}/{*rest}", axum::routing::any(load_balancer::api::prefix_route), ) .layer(Extension(load_balancer)) diff --git a/crates/integration_tests/src/gateway/mod.rs b/crates/integration_tests/src/gateway/mod.rs index 14dc58fc..ecbf16e6 100644 --- a/crates/integration_tests/src/gateway/mod.rs +++ b/crates/integration_tests/src/gateway/mod.rs @@ -10,8 +10,9 @@ use tokio::task::JoinHandle; pub async fn build_router(lb: LoadBalancerState) -> Router { Router::new() .route("/ws", get(api::websocket_route)) - .route("/:uuid", any(api::prefix_route_root)) - .route("/:uuid/*rest", any(api::prefix_route)) + .route("/{uuid}", any(api::prefix_route_root)) + .route("/{uuid}/", any(api::prefix_route_root)) + .route("/{uuid}/{*rest}", any(api::prefix_route)) .layer(Extension(lb)) } From c9af60c75e96e8bf128f23161c3f5b2be98f86aa Mon Sep 17 00:00:00 2001 From: Michal Rus Date: Wed, 25 Mar 2026 18:00:12 +0100 Subject: [PATCH 7/7] fix(tests): unused deps check --- Cargo.lock | 2 -- Cargo.toml | 2 ++ crates/gateway/Cargo.toml | 2 -- crates/integration_tests/Cargo.toml | 4 ++-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5a8a393..aa479094 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -525,13 +525,11 @@ dependencies = [ "hex", "hyper", "rand 0.8.5", - "reqwest 0.13.2", "rstest", "serde", "serde_json", "thiserror 2.0.18", "tokio", - "tokio-tungstenite", "toml 1.0.2+spec-1.1.0", "tracing", "tracing-subscriber", diff --git a/Cargo.toml b/Cargo.toml index 09143698..2a844311 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,8 @@ anyhow = "1.0.101" axum = { version = "0.8.8", features = ["ws"] } base64 = "0.22.1" bech32 = "0.11.1" +blockfrost-platform = { path = "crates/platform", package = "blockfrost-platform" } +blockfrost-gateway = { path = "crates/gateway", package = "blockfrost-gateway" } bf-api-provider = { path = "crates/api_provider", package = "blockfrost-platform-api-provider" } bf-build-utils = { path = "crates/build_utils", package = "blockfrost-platform-build-utils" } bf-common = { path = "crates/common", package = "blockfrost-platform-common" } diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index 3bfc2fce..69e3e3c1 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -11,7 +11,6 @@ axum.workspace = true tokio = { workspace = true, features = ["full"] } futures.workspace = true futures-util.workspace = true -tokio-tungstenite.workspace = true tungstenite.workspace = true tracing.workspace = true tracing-subscriber.workspace = true @@ -22,7 +21,6 @@ clap.workspace = true toml.workspace = true thiserror.workspace = true chrono = { workspace = true, features = ["serde"] } -reqwest.workspace = true blockfrost.workspace = true dotenvy.workspace = true hex.workspace = true diff --git a/crates/integration_tests/Cargo.toml b/crates/integration_tests/Cargo.toml index 1f75bbdc..a893069c 100644 --- a/crates/integration_tests/Cargo.toml +++ b/crates/integration_tests/Cargo.toml @@ -10,8 +10,8 @@ name = "integration_tests" path = "src/lib.rs" [dependencies] -blockfrost-gateway = { path = "../gateway" } -blockfrost-platform = { path = "../platform" } +blockfrost-gateway.workspace = true +blockfrost-platform.workspace = true bf-api-provider.workspace = true bf-common.workspace = true bf-data-node.workspace = true