Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }})
Expand Down
42 changes: 34 additions & 8 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ members = [
"crates/node",
"crates/data_node",
"crates/gateway",
"crates/integration_tests",
]
default-members = ["crates/platform"]

Expand All @@ -21,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" }
Expand Down
2 changes: 0 additions & 2 deletions crates/gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions crates/gateway/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
52 changes: 52 additions & 0 deletions crates/integration_tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[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"

[dependencies]
blockfrost-gateway.workspace = true
blockfrost-platform.workspace = true
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
serde.workspace = true
serde_json.workspace = true
tokio = { workspace = true, features = [
"macros",
"rt-multi-thread",
"time",
"net",
"sync",
] }
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
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down
19 changes: 19 additions & 0 deletions crates/integration_tests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
pub mod gateway;
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)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![allow(dead_code)]

pub mod asserts;
pub mod mock_data_node;
pub mod tx_builder;
Expand All @@ -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<IcebreakersConfig>) -> Arc<Config> {
dotenvy::dotenv().ok();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
mod common;
use common::*;
use integration_tests::gateway::*;

use base64::Engine;
use blockfrost_gateway::{
Expand Down
98 changes: 98 additions & 0 deletions crates/integration_tests/tests/platform_data_node.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
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::<String>::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"))
);
}
Loading