Skip to content

Commit

Permalink
Merge pull request #92 from CosmWasm/52-better-tests
Browse files Browse the repository at this point in the history
Have cw_storey use a mock CosmWasm contract for integration tests
  • Loading branch information
uint authored Jan 22, 2025
2 parents b3a1769 + 7a8da6d commit 358546a
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 48 deletions.
89 changes: 89 additions & 0 deletions packages/cw-storey/tests/contract/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
pub mod msg;

use cosmwasm_std::{
to_json_binary, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Response, StdError,
StdResult,
};

use cw_storey::containers::{Item, Map};

const ITEM: Item<u32> = Item::new(0);
const MAP: Map<String, Item<u32>> = Map::new(1);

//#[entry_point]
pub fn instantiate(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: Empty,
) -> Result<Response, StdError> {
Ok(Response::default())
}

//#[entry_point]
pub fn execute(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: msg::ExecuteMsg,
) -> Result<Response, StdError> {
use msg::ExecuteMsg::*;

match msg {
SetItem { val } => execute::set_item(deps, val),
SetMapEntry { key, val } => execute::set_map_entry(deps, key, val),
}
}

//#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: msg::QueryMsg) -> StdResult<QueryResponse> {
use msg::QueryMsg::*;

match msg {
Item {} => to_json_binary(&query::get_item(deps)?),
MapEntry { key } => to_json_binary(&query::get_map_entry(deps, key)?),
MapEntries {} => to_json_binary(&query::get_map_entries(deps)?),
}
}

mod execute {
use super::*;

pub(crate) fn set_item(deps: DepsMut, val: u32) -> Result<Response, StdError> {
ITEM.access(deps.storage).set(&val)?;

Ok(Response::default())
}

pub(crate) fn set_map_entry(
deps: DepsMut,
key: String,
val: u32,
) -> Result<Response, StdError> {
MAP.access(deps.storage).entry_mut(&key).set(&val)?;

Ok(Response::default())
}
}

mod query {
use storey::containers::IterableAccessor as _;

use super::*;

pub(crate) fn get_item(deps: Deps) -> StdResult<Option<u32>> {
ITEM.access(deps.storage).get()
}

pub(crate) fn get_map_entry(deps: Deps, key: String) -> StdResult<Option<u32>> {
MAP.access(deps.storage).entry(&key).get()
}

pub(crate) fn get_map_entries(deps: Deps) -> StdResult<Vec<(String, u32)>> {
MAP.access(deps.storage)
.pairs()
.map(|res| res.map_err(|e| StdError::generic_err(e.to_string())))
.map(|res| res.map(|((k, ()), v)| (k, v)))
.collect()
}
}
10 changes: 10 additions & 0 deletions packages/cw-storey/tests/contract/msg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pub enum ExecuteMsg {
SetItem { val: u32 },
SetMapEntry { key: String, val: u32 },
}

pub enum QueryMsg {
Item {},
MapEntry { key: String },
MapEntries {},
}
121 changes: 83 additions & 38 deletions packages/cw-storey/tests/smoke_test.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,107 @@
use cw_storey::containers::Item;
mod contract;

use storey::containers::{IterableAccessor as _, Map};
use storey::storage::IntoStorage as _;
use cosmwasm_std::testing::{
message_info, mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage,
};
use cosmwasm_std::{from_json, Empty, OwnedDeps};

// The tests in this module are meant to briefly test the integration of `storey`
// with `cosmwasm_std::Storage` and MessagePack serialization.
//
// They're not meant to comprehensively test the storage abstractions provided by `storey`.
// That's already done in the `storey` crate itself.

// this mimicks how storage is accessed from CosmWasm smart contracts,
// where developers have access to `&dyn cosmwasm_std::Storage` or
// `&mut dyn cosmwasm_std::Storage`, but not the concrete type.
fn give_me_storage() -> Box<dyn cosmwasm_std::Storage> {
Box::new(cosmwasm_std::testing::MockStorage::new())
}
// That's already done in the `storey` crate itself. These are "smoke tests".

#[test]
fn smoke_test() {
let mut storage = give_me_storage();

let item1 = Item::<u64>::new(0);

item1.access(&mut *storage).set(&42).unwrap();
assert_eq!(item1.access(&mut *storage).get().unwrap(), Some(42));

let item2 = Item::<u64>::new(1);
assert_eq!(item2.access(&mut *storage).get().unwrap(), None);
fn item() {
let mut deps = setup();

assert_eq!((&mut *storage,).into_storage().0.get(&[0]), Some(vec![42]));
assert_eq!(None, get_item(&deps));
set_item(&mut deps, 42);
assert_eq!(Some(42), get_item(&deps));
}

#[test]
fn map() {
let mut storage = give_me_storage();
let mut deps = setup();

let map = Map::<String, Item<u32>>::new(0);
assert_eq!(None, get_map_entry(&deps, "foo".to_string()));
set_map_entry(&mut deps, "foo".to_string(), 42);
assert_eq!(Some(42), get_map_entry(&deps, "foo".to_string()));
assert_eq!(None, get_map_entry(&deps, "foobar".to_string()));
}

map.access(&mut *storage).entry_mut("foo").set(&42).unwrap();
#[test]
fn iteration() {
let mut deps = setup();

set_map_entry(&mut deps, "foo".to_string(), 42);
set_map_entry(&mut deps, "bar".to_string(), 43);
set_map_entry(&mut deps, "baz".to_string(), 44);

assert_eq!(
map.access(&mut *storage).entry("foo").get().unwrap(),
Some(42)
vec![
("bar".to_string(), 43),
("baz".to_string(), 44),
("foo".to_string(), 42),
],
get_map_entries(&deps)
);
}

#[test]
fn iteration() {
let mut storage = give_me_storage();
// The following code provides helper functions to test a mock CosmWasm contract.
// The mock contract itself can be found in the `contract` module.
//
// This kind of setup is common in CosmWasm repos. For example, see the core CosmWasm
// repo: https://github.com/CosmWasm/cosmwasm/tree/main/contracts

fn setup() -> OwnedDeps<MockStorage, MockApi, MockQuerier> {
let mut deps = mock_dependencies();
let creator = deps.api.addr_make("creator");
let msg = Empty {};
let info = message_info(&creator, &[]);
let res = contract::instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(0, res.messages.len());
deps
}

let map = Map::<String, Item<u32>>::new(0);
#[track_caller]
fn set_item(deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>, val: u32) {
let caller = deps.api.addr_make("caller");
let msg = contract::msg::ExecuteMsg::SetItem { val };
contract::execute(deps.as_mut(), mock_env(), message_info(&caller, &[]), msg).unwrap();
}

map.access(&mut *storage).entry_mut("foo").set(&42).unwrap();
map.access(&mut *storage).entry_mut("bar").set(&43).unwrap();
#[track_caller]
fn set_map_entry(deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>, key: String, val: u32) {
let caller = deps.api.addr_make("caller");
let msg = contract::msg::ExecuteMsg::SetMapEntry { key, val };
contract::execute(deps.as_mut(), mock_env(), message_info(&caller, &[]), msg).unwrap();
}

#[track_caller]
fn get_item(deps: &OwnedDeps<MockStorage, MockApi, MockQuerier>) -> Option<u32> {
let res = contract::query(deps.as_ref(), mock_env(), contract::msg::QueryMsg::Item {}).unwrap();
from_json(&res).unwrap()
}

#[track_caller]
fn get_map_entry(deps: &OwnedDeps<MockStorage, MockApi, MockQuerier>, key: String) -> Option<u32> {
let res = contract::query(
deps.as_ref(),
mock_env(),
contract::msg::QueryMsg::MapEntry { key },
)
.unwrap();
from_json(&res).unwrap()
}

let access = map.access(&mut *storage);
let mut iter = access.keys();
assert_eq!(iter.next().unwrap().unwrap().0, "bar");
assert_eq!(iter.next().unwrap().unwrap().0, "foo");
assert!(iter.next().is_none());
#[track_caller]
fn get_map_entries(deps: &OwnedDeps<MockStorage, MockApi, MockQuerier>) -> Vec<(String, u32)> {
let res = contract::query(
deps.as_ref(),
mock_env(),
contract::msg::QueryMsg::MapEntries {},
)
.unwrap();
from_json(&res).unwrap()
}
8 changes: 0 additions & 8 deletions packages/storey-storage/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@ pub trait Storage {
}
}

/// A trait for converting a type into one that implements [`Storage`].
///
/// This trait is meant to be implemented for a tuple of the intended type, as in
/// `impl IntoStorage<T> for (T,)`. This is to allow blanket implementations on foreign
/// types without stumbling into [E0210](https://stackoverflow.com/questions/63119000/why-am-i-required-to-cover-t-in-impl-foreigntraitlocaltype-for-t-e0210).
///
/// Implementing this trait for foreign types allows to use those foreign types directly
/// with functions like [`Item::access`](crate::Item::access).
pub trait IntoStorage<O>: Sized {
fn into_storage(self) -> O;
}
Expand Down
63 changes: 61 additions & 2 deletions packages/storey/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,65 @@ mod branch;

pub use branch::StorageBranch;
pub use storey_storage::{
IntoStorage, IterableStorage, RevIterableStorage, Storage, StorageBackend, StorageBackendMut,
StorageMut,
IterableStorage, RevIterableStorage, Storage, StorageBackend, StorageBackendMut, StorageMut,
};

/// A trait for converting a type into one that implements [`Storage`].
///
/// This trait is meant to be implemented for a tuple of the intended type, as in
/// `impl IntoStorage<T> for (T,)`. This is to allow blanket implementations on foreign
/// types without stumbling into [E0210](https://stackoverflow.com/questions/63119000/why-am-i-required-to-cover-t-in-impl-foreigntraitlocaltype-for-t-e0210).
///
/// Implementing this trait for foreign types allows to use those foreign types directly
/// with functions like [`Item::access`](crate::containers::Item::access).
///
/// # Example
///
/// This example should give you an idea of how to allow a foreign type to be used directly with functions
/// like [`Item::access`](crate::containers::Item::access).
///
/// Blanket implementations should also be possible!
///
/// ```
/// use storey::storage::{IntoStorage, StorageBackend};
///
/// mod foreign_crate {
/// // This is a foreign type that we want to use with `storey`. Note that due to orphan rules,
/// // we can't implement `StorageBackend` for this type.
/// pub struct ExtStorage;
///
/// impl ExtStorage {
/// pub fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
/// todo!()
/// }
///
/// pub fn has(&self, key: &[u8]) -> bool {
/// todo!()
/// }
/// }
/// }
///
/// use foreign_crate::ExtStorage;
///
/// // Our wrapper can be used as a storage backend. It delegates all calls to the foreign type.
/// struct MyStorage(ExtStorage);
///
/// impl StorageBackend for MyStorage {
/// fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
/// self.0.get(key)
/// }
///
/// fn has(&self, key: &[u8]) -> bool {
/// self.0.has(key)
/// }
/// }
///
/// // Implementing `IntoStorage` like this makes it possible to use `ExtStorage` directly with
/// // functions like `Item::access`, without users having to wrap it in `MyStorage`.
/// impl IntoStorage<MyStorage> for (ExtStorage,) {
/// fn into_storage(self) -> MyStorage {
/// MyStorage(self.0)
/// }
/// }
/// ```
pub use storey_storage::IntoStorage;

0 comments on commit 358546a

Please sign in to comment.