-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #92 from CosmWasm/52-better-tests
Have cw_storey use a mock CosmWasm contract for integration tests
- Loading branch information
Showing
5 changed files
with
243 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters