use std::{
collections::{HashMap, VecDeque},
mem,
sync::Mutex,
};
use linera_base::{
abi::ServiceAbi,
data_types::{Amount, BlockHeight, Timestamp},
hex, http,
identifiers::{AccountOwner, ApplicationId, ChainId},
};
use serde::{de::DeserializeOwned, Serialize};
use crate::{DataBlobHash, KeyValueStore, Service, ViewStorageContext};
pub struct MockServiceRuntime<Application>
where
Application: Service,
{
application_parameters: Mutex<Option<Application::Parameters>>,
application_id: Mutex<Option<ApplicationId<Application::Abi>>>,
chain_id: Mutex<Option<ChainId>>,
next_block_height: Mutex<Option<BlockHeight>>,
timestamp: Mutex<Option<Timestamp>>,
chain_balance: Mutex<Option<Amount>>,
owner_balances: Mutex<Option<HashMap<AccountOwner, Amount>>>,
query_application_handler: Mutex<Option<QueryApplicationHandler>>,
expected_http_requests: Mutex<VecDeque<(http::Request, http::Response)>>,
url_blobs: Mutex<Option<HashMap<String, Vec<u8>>>>,
blobs: Mutex<Option<HashMap<DataBlobHash, Vec<u8>>>>,
scheduled_operations: Mutex<Vec<Vec<u8>>>,
key_value_store: KeyValueStore,
}
impl<Application> Default for MockServiceRuntime<Application>
where
Application: Service,
{
fn default() -> Self {
MockServiceRuntime::new()
}
}
impl<Application> MockServiceRuntime<Application>
where
Application: Service,
{
pub fn new() -> Self {
MockServiceRuntime {
application_parameters: Mutex::new(None),
application_id: Mutex::new(None),
chain_id: Mutex::new(None),
next_block_height: Mutex::new(None),
timestamp: Mutex::new(None),
chain_balance: Mutex::new(None),
owner_balances: Mutex::new(None),
query_application_handler: Mutex::new(None),
expected_http_requests: Mutex::new(VecDeque::new()),
url_blobs: Mutex::new(None),
blobs: Mutex::new(None),
scheduled_operations: Mutex::new(vec![]),
key_value_store: KeyValueStore::mock(),
}
}
pub fn key_value_store(&self) -> KeyValueStore {
self.key_value_store.clone()
}
pub fn root_view_storage_context(&self) -> ViewStorageContext {
ViewStorageContext::new_unsafe(self.key_value_store(), Vec::new(), ())
}
pub fn with_application_parameters(
self,
application_parameters: Application::Parameters,
) -> Self {
*self.application_parameters.lock().unwrap() = Some(application_parameters);
self
}
pub fn set_application_parameters(
&self,
application_parameters: Application::Parameters,
) -> &Self {
*self.application_parameters.lock().unwrap() = Some(application_parameters);
self
}
pub fn application_parameters(&self) -> Application::Parameters {
Self::fetch_mocked_value(
&self.application_parameters,
"Application parameters have not been mocked, \
please call `MockServiceRuntime::set_application_parameters` first",
)
}
pub fn with_application_id(self, application_id: ApplicationId<Application::Abi>) -> Self {
*self.application_id.lock().unwrap() = Some(application_id);
self
}
pub fn set_application_id(&self, application_id: ApplicationId<Application::Abi>) -> &Self {
*self.application_id.lock().unwrap() = Some(application_id);
self
}
pub fn application_id(&self) -> ApplicationId<Application::Abi> {
Self::fetch_mocked_value(
&self.application_id,
"Application ID has not been mocked, \
please call `MockServiceRuntime::set_application_id` first",
)
}
pub fn with_chain_id(self, chain_id: ChainId) -> Self {
*self.chain_id.lock().unwrap() = Some(chain_id);
self
}
pub fn set_chain_id(&self, chain_id: ChainId) -> &Self {
*self.chain_id.lock().unwrap() = Some(chain_id);
self
}
pub fn chain_id(&self) -> ChainId {
Self::fetch_mocked_value(
&self.chain_id,
"Chain ID has not been mocked, \
please call `MockServiceRuntime::set_chain_id` first",
)
}
pub fn with_next_block_height(self, next_block_height: BlockHeight) -> Self {
*self.next_block_height.lock().unwrap() = Some(next_block_height);
self
}
pub fn set_next_block_height(&self, next_block_height: BlockHeight) -> &Self {
*self.next_block_height.lock().unwrap() = Some(next_block_height);
self
}
pub fn next_block_height(&self) -> BlockHeight {
Self::fetch_mocked_value(
&self.next_block_height,
"Next block height has not been mocked, \
please call `MockServiceRuntime::set_next_block_height` first",
)
}
pub fn with_system_time(self, timestamp: Timestamp) -> Self {
*self.timestamp.lock().unwrap() = Some(timestamp);
self
}
pub fn set_system_time(&self, timestamp: Timestamp) -> &Self {
*self.timestamp.lock().unwrap() = Some(timestamp);
self
}
pub fn system_time(&self) -> Timestamp {
Self::fetch_mocked_value(
&self.timestamp,
"System time has not been mocked, \
please call `MockServiceRuntime::set_system_time` first",
)
}
pub fn with_chain_balance(self, chain_balance: Amount) -> Self {
*self.chain_balance.lock().unwrap() = Some(chain_balance);
self
}
pub fn set_chain_balance(&self, chain_balance: Amount) -> &Self {
*self.chain_balance.lock().unwrap() = Some(chain_balance);
self
}
pub fn chain_balance(&self) -> Amount {
Self::fetch_mocked_value(
&self.chain_balance,
"Chain balance has not been mocked, \
please call `MockServiceRuntime::set_chain_balance` first",
)
}
pub fn with_owner_balances(
self,
owner_balances: impl IntoIterator<Item = (AccountOwner, Amount)>,
) -> Self {
*self.owner_balances.lock().unwrap() = Some(owner_balances.into_iter().collect());
self
}
pub fn set_owner_balances(
&self,
owner_balances: impl IntoIterator<Item = (AccountOwner, Amount)>,
) -> &Self {
*self.owner_balances.lock().unwrap() = Some(owner_balances.into_iter().collect());
self
}
pub fn with_owner_balance(self, owner: AccountOwner, balance: Amount) -> Self {
self.set_owner_balance(owner, balance);
self
}
pub fn set_owner_balance(&self, owner: AccountOwner, balance: Amount) -> &Self {
self.owner_balances
.lock()
.unwrap()
.get_or_insert_with(HashMap::new)
.insert(owner, balance);
self
}
pub fn owner_balance(&self, owner: AccountOwner) -> Amount {
self.owner_balances
.lock()
.unwrap()
.as_mut()
.and_then(|owner_balances| owner_balances.get(&owner).copied())
.unwrap_or_else(|| {
panic!(
"Balance for owner {owner} was not mocked, \
please include a balance for them with a call to \
`MockServiceRuntime::set_owner_balance`"
)
})
}
pub fn owner_balances(&self) -> Vec<(AccountOwner, Amount)> {
self.owner_balances
.lock()
.unwrap()
.as_ref()
.expect(
"Owner balances have not been mocked, \
please call `MockServiceRuntime::set_owner_balances` first",
)
.iter()
.map(|(owner, amount)| (*owner, *amount))
.collect()
}
pub fn balance_owners(&self) -> Vec<AccountOwner> {
self.owner_balances
.lock()
.unwrap()
.as_ref()
.expect(
"Owner balances have not been mocked, \
please call `MockServiceRuntime::set_owner_balances` first",
)
.keys()
.cloned()
.collect()
}
pub fn schedule_raw_operation(&self, operation: Vec<u8>) {
self.scheduled_operations.lock().unwrap().push(operation);
}
pub fn schedule_operation(&self, operation: &impl Serialize) {
let bytes = bcs::to_bytes(operation).expect("Failed to serialize application operation");
self.schedule_raw_operation(bytes);
}
pub fn raw_scheduled_operations(&self) -> Vec<Vec<u8>> {
mem::take(&mut self.scheduled_operations.lock().unwrap())
}
pub fn scheduled_operations<Operation>(&self) -> Vec<Operation>
where
Operation: DeserializeOwned,
{
self.raw_scheduled_operations()
.into_iter()
.enumerate()
.map(|(index, bytes)| {
bcs::from_bytes(&bytes).unwrap_or_else(|error| {
panic!(
"Failed to deserialize scheduled operation #{index} (0x{}): {error}",
hex::encode(bytes)
)
})
})
.collect()
}
pub fn with_query_application_handler(
self,
handler: impl FnMut(ApplicationId, Vec<u8>) -> Vec<u8> + Send + 'static,
) -> Self {
*self.query_application_handler.lock().unwrap() = Some(Box::new(handler));
self
}
pub fn set_query_application_handler(
&self,
handler: impl FnMut(ApplicationId, Vec<u8>) -> Vec<u8> + Send + 'static,
) -> &Self {
*self.query_application_handler.lock().unwrap() = Some(Box::new(handler));
self
}
pub fn query_application<A: ServiceAbi>(
&self,
application: ApplicationId<A>,
query: &A::Query,
) -> A::QueryResponse {
let query_bytes =
serde_json::to_vec(&query).expect("Failed to serialize query to another application");
let mut handler_guard = self.query_application_handler.lock().unwrap();
let handler = handler_guard.as_mut().expect(
"Handler for `query_application` has not been mocked, \
please call `MockServiceRuntime::set_query_application_handler` first",
);
let response_bytes = handler(application.forget_abi(), query_bytes);
serde_json::from_slice(&response_bytes)
.expect("Failed to deserialize query response from application")
}
pub fn add_expected_http_request(&mut self, request: http::Request, response: http::Response) {
self.expected_http_requests
.lock()
.unwrap()
.push_back((request, response));
}
pub fn http_request(&self, request: http::Request) -> http::Response {
let maybe_request = self.expected_http_requests.lock().unwrap().pop_front();
let (expected_request, response) = maybe_request.expect("Unexpected HTTP request");
assert_eq!(request, expected_request);
response
}
pub fn with_url_blobs(self, url_blobs: impl IntoIterator<Item = (String, Vec<u8>)>) -> Self {
*self.url_blobs.lock().unwrap() = Some(url_blobs.into_iter().collect());
self
}
pub fn set_url_blobs(&self, url_blobs: impl IntoIterator<Item = (String, Vec<u8>)>) -> &Self {
*self.url_blobs.lock().unwrap() = Some(url_blobs.into_iter().collect());
self
}
pub fn with_url_blob(self, url: impl Into<String>, blob: Vec<u8>) -> Self {
self.set_url_blob(url, blob);
self
}
pub fn set_url_blob(&self, url: impl Into<String>, blob: Vec<u8>) -> &Self {
self.url_blobs
.lock()
.unwrap()
.get_or_insert_with(HashMap::new)
.insert(url.into(), blob);
self
}
pub fn fetch_url(&self, url: &str) -> Vec<u8> {
self.url_blobs
.lock()
.unwrap()
.as_mut()
.and_then(|url_blobs| url_blobs.get(url).cloned())
.unwrap_or_else(|| {
panic!(
"Blob for URL {url:?} has not been mocked, \
please call `MockServiceRuntime::set_url_blob` first"
)
})
}
pub fn with_blobs(self, blobs: impl IntoIterator<Item = (DataBlobHash, Vec<u8>)>) -> Self {
*self.blobs.lock().unwrap() = Some(blobs.into_iter().collect());
self
}
pub fn set_blobs(&self, blobs: impl IntoIterator<Item = (DataBlobHash, Vec<u8>)>) -> &Self {
*self.blobs.lock().unwrap() = Some(blobs.into_iter().collect());
self
}
pub fn with_blob(self, hash: impl Into<DataBlobHash>, blob: Vec<u8>) -> Self {
self.set_blob(hash, blob);
self
}
pub fn set_blob(&self, hash: impl Into<DataBlobHash>, blob: Vec<u8>) -> &Self {
self.blobs
.lock()
.unwrap()
.get_or_insert_with(HashMap::new)
.insert(hash.into(), blob);
self
}
pub fn read_data_blob(&self, hash: DataBlobHash) -> Vec<u8> {
self.blobs
.lock()
.unwrap()
.as_ref()
.and_then(|blobs| blobs.get(&hash).cloned())
.unwrap_or_else(|| {
panic!(
"Blob for hash {hash:?} has not been mocked, \
please call `MockServiceRuntime::set_blob` first"
)
})
}
pub fn assert_blob_exists(&self, hash: DataBlobHash) {
self.blobs
.lock()
.unwrap()
.as_ref()
.map(|blobs| blobs.contains_key(&hash))
.unwrap_or_else(|| {
panic!(
"Blob for hash {hash:?} has not been mocked, \
please call `MockServiceRuntime::set_blob` first"
)
});
}
fn fetch_mocked_value<T>(slot: &Mutex<Option<T>>, message: &str) -> T
where
T: Clone,
{
slot.lock().unwrap().clone().expect(message)
}
}
pub type QueryApplicationHandler = Box<dyn FnMut(ApplicationId, Vec<u8>) -> Vec<u8> + Send>;