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
66 changes: 45 additions & 21 deletions examples/how-to/perform-http-requests/tests/http_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@

#![cfg(not(target_arch = "wasm32"))]

use assert_matches::assert_matches;
use axum::{routing::get, Router};
use how_to_perform_http_requests::Abi;
use linera_sdk::test::{HttpServer, QueryOutcome, TestValidator};
use linera_sdk::test::{
ExecutionError, HttpServer, QueryOutcome, TestValidator, WasmExecutionError,
};

/// Tests if service query performs HTTP request to allowed host.
#[test_log::test(tokio::test)]
Expand Down Expand Up @@ -58,16 +61,22 @@ async fn service_query_performs_http_request() -> anyhow::Result<()> {

/// Tests if service query can't perform HTTP requests to hosts that aren't allowed.
#[test_log::test(tokio::test)]
#[should_panic(expected = "UnauthorizedHttpRequest")]
async fn service_query_cant_send_http_request_to_unauthorized_host() {
let url = "http://localhost/".to_owned();
let url = "http://localhost/";

let (_validator, application_id, chain) =
TestValidator::with_current_application::<Abi, _, _>(url, ()).await;

chain
.graphql_query(application_id, "query { performHttpRequest }")
.await;
TestValidator::with_current_application::<Abi, _, _>(url.to_owned(), ()).await;

let error = chain
.try_graphql_query(application_id, "query { performHttpRequest }")
.await
.expect_err("Expected GraphQL query to fail");

assert_matches!(
error.expect_execution_error(),
ExecutionError::UnauthorizedHttpRequest(attempted_url)
if attempted_url.to_string() == url
);
}

/// Tests if the service sends a valid HTTP response to the contract.
Expand Down Expand Up @@ -100,7 +109,6 @@ async fn service_sends_valid_http_response_to_contract() -> anyhow::Result<()> {

/// Tests if the contract rejects an invalid HTTP response sent by the service.
#[test_log::test(tokio::test)]
#[should_panic(expected = "Failed to execute block")]
async fn contract_rejects_invalid_http_response_from_service() {
const HTTP_RESPONSE_BODY: &str = "Untrusted response";

Expand All @@ -112,7 +120,7 @@ async fn contract_rejects_invalid_http_response_from_service() {
let url = format!("http://localhost:{port}/");

let (validator, application_id, chain) =
TestValidator::with_current_application::<Abi, _, _>(url, ()).await;
TestValidator::with_current_application::<Abi, _, _>(url.clone(), ()).await;

validator
.change_resource_control_policy(|policy| {
Expand All @@ -122,9 +130,15 @@ async fn contract_rejects_invalid_http_response_from_service() {
})
.await;

chain
.graphql_mutation(application_id, "mutation { performHttpRequest }")
.await;
let error = chain
.try_graphql_mutation(application_id, "mutation { performHttpRequest }")
.await
.expect_err("Expected GraphQL mutation to fail");

assert_matches!(
error.expect_proposal_execution_error(0),
ExecutionError::WasmError(WasmExecutionError::ExecuteModule(_))
);
}

/// Tests if the contract accepts a valid HTTP response it obtains by itself.
Expand Down Expand Up @@ -157,7 +171,6 @@ async fn contract_accepts_valid_http_response_it_obtains_by_itself() -> anyhow::

/// Tests if the contract rejects an invalid HTTP response it obtains by itself.
#[test_log::test(tokio::test)]
#[should_panic(expected = "Failed to execute block")]
async fn contract_rejects_invalid_http_response_it_obtains_by_itself() {
const HTTP_RESPONSE_BODY: &str = "Untrusted response";

Expand All @@ -179,9 +192,15 @@ async fn contract_rejects_invalid_http_response_it_obtains_by_itself() {
})
.await;

chain
.graphql_mutation(application_id, "mutation { performHttpRequestInContract }")
.await;
let error = chain
.try_graphql_mutation(application_id, "mutation { performHttpRequestInContract }")
.await
.expect_err("Expected GraphQL mutation to fail");

assert_matches!(
error.expect_proposal_execution_error(0),
ExecutionError::WasmError(WasmExecutionError::ExecuteModule(_))
);
}

/// Tests if the contract accepts a valid HTTP response it obtains from the service acting as an
Expand Down Expand Up @@ -216,7 +235,6 @@ async fn contract_accepts_valid_http_response_from_oracle() -> anyhow::Result<()
/// Tests if the contract rejects an invalid HTTP response it obtains from the service acting as an
/// oracle.
#[test_log::test(tokio::test)]
#[should_panic(expected = "Failed to execute block")]
async fn contract_rejects_invalid_http_response_from_oracle() {
const HTTP_RESPONSE_BODY: &str = "Invalid response";

Expand All @@ -238,7 +256,13 @@ async fn contract_rejects_invalid_http_response_from_oracle() {
})
.await;

chain
.graphql_mutation(application_id, "mutation { performHttpRequestAsOracle }")
.await;
let error = chain
.try_graphql_mutation(application_id, "mutation { performHttpRequestAsOracle }")
.await
.expect_err("Expected GraphQL mutation to fail");

assert_matches!(
error.expect_proposal_execution_error(0),
ExecutionError::WasmError(WasmExecutionError::ExecuteModule(_))
);
}
1 change: 1 addition & 0 deletions linera-chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ impl From<ViewError> for ChainError {
}

#[derive(Copy, Clone, Debug)]
#[cfg_attr(with_testing, derive(Eq, PartialEq))]
pub enum ChainExecutionContext {
Query,
DescribeApplication,
Expand Down
24 changes: 24 additions & 0 deletions linera-core/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use linera_base::{
identifiers::{AccountOwner, ApplicationId, BlobId, ChainId},
time::timer::{sleep, timeout},
};
#[cfg(with_testing)]
use linera_chain::ChainExecutionContext;
use linera_chain::{
data_types::{
BlockExecutionOutcome, BlockProposal, MessageBundle, Origin, ProposedBlock, Target,
Expand Down Expand Up @@ -248,6 +250,28 @@ impl From<ViewError> for WorkerError {
}
}

#[cfg(with_testing)]
impl WorkerError {
/// Returns the inner [`ExecutionError`] in this error.
///
/// # Panics
///
/// If this is not caused by an [`ExecutionError`].
pub fn expect_execution_error(self, expected_context: ChainExecutionContext) -> ExecutionError {
let WorkerError::ChainError(chain_error) = self else {
panic!("Expected an `ExecutionError`. Got: {self:#?}");
};

let ChainError::ExecutionError(execution_error, context) = *chain_error else {
panic!("Expected an `ExecutionError`. Got: {chain_error:#?}");
};

assert_eq!(context, expected_context);

*execution_error
}
}

/// State of a worker in a validator or a local node.
#[derive(Clone)]
pub struct WorkerState<StorageClient>
Expand Down
3 changes: 2 additions & 1 deletion linera-sdk/src/test/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use linera_chain::{
},
types::{ConfirmedBlock, ConfirmedBlockCertificate},
};
use linera_core::worker::WorkerError;
use linera_execution::{
committee::Epoch,
system::{Recipient, SystemOperation},
Expand Down Expand Up @@ -203,7 +204,7 @@ impl BlockBuilder {
pub(crate) async fn try_sign(
self,
blobs: &[Blob],
) -> anyhow::Result<ConfirmedBlockCertificate> {
) -> Result<ConfirmedBlockCertificate, WorkerError> {
let published_blobs = self
.block
.published_blob_ids()
Expand Down
Loading