Skip to content

Adding more API Server web server tests #1278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 18, 2023
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
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.

8 changes: 5 additions & 3 deletions api-server/stack-test-suite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ edition.workspace = true
rust-version.workspace = true

[dev-dependencies]
api-blockchain-scanner-lib = { path = "../scanner-lib" }
api-server-common = { path = "../api-server-common" }
chainstate-test-framework = { path = '../../chainstate/test-framework' }
api-web-server = { path = "../web-server" }
chainstate = { path = "../../chainstate" }
chainstate-test-framework = { path = "../../chainstate/test-framework" }
common = { path = "../../common" }
crypto = { path = "../../crypto" }
api-blockchain-scanner-lib = { path = "../scanner-lib" }
serialization = { path = "../../serialization" }
utils = { path = "../../utils" }
api-web-server = { path = "../web-server" }

async-trait.workspace = true
axum.workspace = true
Expand Down
53 changes: 53 additions & 0 deletions api-server/stack-test-suite/tests/in_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,56 @@
// limitations under the License.

mod v1;

use api_server_common::storage::impls::in_memory::transactional::TransactionalApiServerInMemoryStorage;
use api_web_server::{api::web_server, ApiServerWebServerState};
use common::chain::config::create_unit_test_config;
use std::{net::TcpListener, sync::Arc};

pub async fn spawn_webserver(url: &str) -> (tokio::task::JoinHandle<()>, reqwest::Response) {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();

let task = tokio::spawn(async move {
let web_server_state = {
let chain_config = Arc::new(create_unit_test_config());
let storage = TransactionalApiServerInMemoryStorage::new(&chain_config);

ApiServerWebServerState {
db: Arc::new(storage),
chain_config: Arc::clone(&chain_config),
}
};

web_server(listener, web_server_state).await.unwrap();
});

// Given that the listener port is open, this will block until a
// response is made (by the web server, which takes the listener
// over)
let response = reqwest::get(format!("http://{}:{}{url}", addr.ip(), addr.port()))
.await
.unwrap();

(task, response)
}

#[tokio::test]
async fn server_status() {
let (task, response) = spawn_webserver("/").await;

assert_eq!(response.status(), 200);
assert_eq!(response.text().await.unwrap(), r#"{"versions":["1.0.0"]}"#);

task.abort();
}

#[tokio::test]
async fn bad_request() {
let (task, response) = spawn_webserver("/non-existent-url").await;

assert_eq!(response.status(), 400);
assert_eq!(response.text().await.unwrap(), r#"{"error":"Bad request"}"#);

task.abort();
}
37 changes: 15 additions & 22 deletions api-server/stack-test-suite/tests/v1/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ async fn ok(#[case] seed: Seed) {
let block_height = rng.gen_range(1..50);
let n_blocks = rng.gen_range(block_height..100);

tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;

let chain_config = create_unit_test_config();

let chainstate_blocks = {
Expand All @@ -80,9 +78,16 @@ async fn ok(#[case] seed: Seed) {
let block = tf.block(tf.to_chain_block_id(&block_id));

let expected_block = json!({
"previous_block_id": block.prev_block_id().to_hash().encode_hex::<String>(),
"timestamp": block.timestamp(),
"merkle_root": block.merkle_root().encode_hex::<String>(),
"header": {
"previous_block_id": block.prev_block_id(),
"merkle_root": block.merkle_root(),
"witness_merkle_root": block.witness_merkle_root(),
"timestamp": block.timestamp(),
},
"body": {
"reward": block.block_reward().outputs().iter().clone().collect::<Vec<_>>(),
"transactions": block.transactions().iter().map(|tx| tx.transaction()).collect::<Vec<_>>(),
},
});

_ = tx.send((block_id.to_hash().encode_hex::<String>(), expected_block));
Expand Down Expand Up @@ -118,7 +123,9 @@ async fn ok(#[case] seed: Seed) {
let (block_id, expected_block) = rx.await.unwrap();
let url = format!("/api/v1/block/{block_id}");

// Given that the listener port is open, this will block until a response is made (by the web server, which takes the listener over)
// Given that the listener port is open, this will block until a
// response is made (by the web server, which takes the listener
// over)
let response = reqwest::get(format!("http://{}:{}{url}", addr.ip(), addr.port()))
.await
.unwrap();
Expand All @@ -127,22 +134,8 @@ async fn ok(#[case] seed: Seed) {

let body = response.text().await.unwrap();
let body: serde_json::Value = serde_json::from_str(&body).unwrap();
let body = body.as_object().unwrap();

assert_eq!(
body.get("previous_block_id").unwrap(),
&expected_block["previous_block_id"]
);
assert_eq!(body.get("timestamp").unwrap(), &expected_block["timestamp"]);
assert_eq!(
body.get("merkle_root").unwrap(),
&expected_block["merkle_root"]
);

assert!(body.contains_key("transactions"));

// TODO check transactions fields
// assert...

assert_eq!(body, expected_block);

task.abort();
}
26 changes: 9 additions & 17 deletions api-server/stack-test-suite/tests/v1/block_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,14 @@ async fn ok(#[case] seed: Seed) {
let block_id = chainstate_block_ids[block_height - 1];
let block = tf.block(tf.to_chain_block_id(&block_id));

let expected_block = json!({
let expected_header = json!({
"previous_block_id": block.prev_block_id().to_hash().encode_hex::<String>(),
"timestamp": block.timestamp(),
"merkle_root": block.merkle_root().encode_hex::<String>(),
"witness_merkle_root": block.witness_merkle_root(),
});

_ = tx.send((block_id.to_hash().encode_hex::<String>(), expected_block));
_ = tx.send((block_id.to_hash().encode_hex::<String>(), expected_header));

chainstate_block_ids
.iter()
Expand Down Expand Up @@ -113,10 +114,12 @@ async fn ok(#[case] seed: Seed) {
web_server(listener, web_server_state).await
});

let (block_id, expected_block) = rx.await.unwrap();
let (block_id, expected_header) = rx.await.unwrap();
let url = format!("/api/v1/block/{block_id}/header");

// Given that the listener port is open, this will block until a response is made (by the web server, which takes the listener over)
// Given that the listener port is open, this will block until a
// response is made (by the web server, which takes the listener
// over)
let response = reqwest::get(format!("http://{}:{}{url}", addr.ip(), addr.port()))
.await
.unwrap();
Expand All @@ -125,19 +128,8 @@ async fn ok(#[case] seed: Seed) {

let body = response.text().await.unwrap();
let body: serde_json::Value = serde_json::from_str(&body).unwrap();
let body = body.as_object().unwrap();

assert_eq!(
body.get("previous_block_id").unwrap(),
&expected_block["previous_block_id"]
);
assert_eq!(body.get("timestamp").unwrap(), &expected_block["timestamp"]);
assert_eq!(
body.get("merkle_root").unwrap(),
&expected_block["merkle_root"]
);

assert!(!body.contains_key("transactions"));

assert_eq!(body, expected_header);

task.abort();
}
108 changes: 96 additions & 12 deletions api-server/stack-test-suite/tests/v1/block_reward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async fn block_not_found() {
#[trace]
#[case(Seed::from_entropy())]
#[tokio::test]
async fn ok(#[case] seed: Seed) {
async fn no_reward(#[case] seed: Seed) {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();

Expand All @@ -76,13 +76,8 @@ async fn ok(#[case] seed: Seed) {

// Need the "- 1" to account for the genesis block not in the vec
let block_id = chainstate_block_ids[block_height - 1];
let block = tf.block(tf.to_chain_block_id(&block_id));
let expected_block_reward = block.block_reward().clone();

_ = tx.send((
block_id.to_hash().encode_hex::<String>(),
expected_block_reward,
));
_ = tx.send(block_id.to_hash().encode_hex::<String>());

chainstate_block_ids
.iter()
Expand Down Expand Up @@ -113,10 +108,101 @@ async fn ok(#[case] seed: Seed) {
}
});

let (block_id, _expected_block_reward) = rx.await.unwrap();
let block_id = rx.await.unwrap();
let url = format!("/api/v1/block/{block_id}/reward");

// Given that the listener port is open, this will block until a
// response is made (by the web server, which takes the listener
// over)
let response = reqwest::get(format!("http://{}:{}{url}", addr.ip(), addr.port()))
.await
.unwrap();

assert_eq!(response.status(), 200);

let body = response.text().await.unwrap();
let body: serde_json::Value = serde_json::from_str(&body).unwrap();
let body = body.as_array().unwrap();

assert!(body.is_empty());

task.abort();
}

#[rstest]
#[trace]
#[case(Seed::from_entropy())]
#[tokio::test]
async fn has_reward(#[case] seed: Seed) {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();

let (tx, rx) = tokio::sync::oneshot::channel();

let task = tokio::spawn({
async move {
let web_server_state = {
let mut rng = make_seedable_rng(seed);

let chain_config = create_unit_test_config();

let block = {
let mut tf = TestFramework::builder(&mut rng)
.with_chain_config(chain_config.clone())
.build();

let genesis_id = tf.genesis().get_id();

let block = tf
.make_block_builder()
.with_parent(genesis_id.into())
.with_reward(vec![TxOutput::LockThenTransfer(
OutputValue::Coin(Amount::from_atoms(100)),
Destination::AnyoneCanSpend,
OutputTimeLock::ForBlockCount(0),
)])
.build();

let block_index =
tf.process_block(block.clone(), BlockSource::Local).unwrap().unwrap();

_ = tx.send((
block_index.block_id().to_hash().encode_hex::<String>(),
json!(block.block_reward().outputs().iter().clone().collect::<Vec<_>>()),
));

block
};

let storage = {
let mut storage = TransactionalApiServerInMemoryStorage::new(&chain_config);

let mut db_tx = storage.transaction_rw().await.unwrap();
db_tx.initialize_storage(&chain_config).await.unwrap();
db_tx.commit().await.unwrap();

storage
};

let mut local_node = BlockchainState::new(storage);
local_node.scan_blocks(BlockHeight::new(0), vec![block]).await.unwrap();

ApiServerWebServerState {
db: Arc::new(local_node.storage().clone_storage().await),
chain_config: Arc::new(chain_config),
}
};

web_server(listener, web_server_state).await
}
});

let (block_id, expected_reward) = rx.await.unwrap();
let url = format!("/api/v1/block/{block_id}/reward");

// Given that the listener port is open, this will block until a response is made (by the web server, which takes the listener over)
// Given that the listener port is open, this will block until a
// response is made (by the web server, which takes the listener
// over)
let response = reqwest::get(format!("http://{}:{}{url}", addr.ip(), addr.port()))
.await
.unwrap();
Expand All @@ -125,10 +211,8 @@ async fn ok(#[case] seed: Seed) {

let body = response.text().await.unwrap();
let body: serde_json::Value = serde_json::from_str(&body).unwrap();
let _body = body.as_object().unwrap();

// TODO check block reward fields
// assert...
assert_eq!(body, expected_reward);

task.abort();
}
15 changes: 5 additions & 10 deletions api-server/stack-test-suite/tests/v1/block_transaction_ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ async fn ok(#[case] seed: Seed) {

_ = tx.send((
block_id.to_hash().encode_hex::<String>(),
expected_transaction_ids,
json!(expected_transaction_ids),
));

chainstate_block_ids
Expand Down Expand Up @@ -119,7 +119,9 @@ async fn ok(#[case] seed: Seed) {
let (block_id, expected_transaction_ids) = rx.await.unwrap();
let url = format!("/api/v1/block/{block_id}/transaction-ids");

// Given that the listener port is open, this will block until a response is made (by the web server, which takes the listener over)
// Given that the listener port is open, this will block until a
// response is made (by the web server, which takes the listener
// over)
let response = reqwest::get(format!("http://{}:{}{url}", addr.ip(), addr.port()))
.await
.unwrap();
Expand All @@ -129,14 +131,7 @@ async fn ok(#[case] seed: Seed) {
let body = response.text().await.unwrap();
let body: serde_json::Value = serde_json::from_str(&body).unwrap();

let body_transaction_ids =
body.as_object().unwrap().get("transaction_ids").unwrap().as_array().unwrap();

for transaction_id in expected_transaction_ids {
assert!(
body_transaction_ids.contains(&json!(transaction_id.to_hash().encode_hex::<String>()))
);
}
assert_eq!(body, expected_transaction_ids);

task.abort();
}
Loading