ledger.rs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. use cucumber::{given, then, when, World};
  2. use futures::future::BoxFuture;
  3. use std::{
  4. collections::HashMap,
  5. path::{Path, PathBuf},
  6. sync::Arc,
  7. };
  8. use tokio::{fs, io};
  9. use verax::{storage::SQLite, AccountId, Amount, Asset, RevId, TokenPayload};
  10. #[derive(Debug, Clone)]
  11. pub enum Variable {
  12. RevId(RevId),
  13. RevIdResult(Result<RevId, String>),
  14. }
  15. impl From<RevId> for Variable {
  16. fn from(rev_id: RevId) -> Self {
  17. Variable::RevId(rev_id)
  18. }
  19. }
  20. impl From<Result<RevId, verax::Error>> for Variable {
  21. fn from(rev_id: Result<RevId, verax::Error>) -> Self {
  22. Variable::RevIdResult(rev_id.map_err(|e| e.to_string()))
  23. }
  24. }
  25. #[derive(Debug, World)]
  26. pub struct LedgerWorld {
  27. ledger: Arc<verax::Ledger<SQLite>>,
  28. variables: HashMap<String, Variable>,
  29. spend: Option<Vec<(AccountId, Amount)>>,
  30. tokens: Vec<TokenPayload>,
  31. receive: Option<Vec<(AccountId, Amount)>>,
  32. }
  33. impl Default for LedgerWorld {
  34. fn default() -> Self {
  35. futures::executor::block_on(async move {
  36. let settings = "sqlite://:memory:"
  37. .parse::<verax::storage::sqlite::SqliteConnectOptions>()
  38. .expect("valid settings")
  39. .journal_mode(verax::storage::sqlite::SqliteJournalMode::Wal)
  40. .create_if_missing(true);
  41. let pool = verax::storage::sqlite::SqlitePoolOptions::new()
  42. .connect_with(settings)
  43. .await
  44. .expect("pool");
  45. let db = SQLite::new(pool);
  46. db.setup().await.expect("setup");
  47. Self {
  48. ledger: verax::Ledger::new(db.into()),
  49. variables: HashMap::new(),
  50. tokens: vec![],
  51. spend: None,
  52. receive: None,
  53. }
  54. })
  55. }
  56. }
  57. impl LedgerWorld {
  58. pub fn get(&self, name: &str) -> &Variable {
  59. self.variables.get(name).as_ref().expect("variable")
  60. }
  61. }
  62. #[given("a new transaction")]
  63. fn new_transaction(world: &mut LedgerWorld) {
  64. world.spend = Some(vec![]);
  65. world.receive = Some(vec![]);
  66. }
  67. #[given(expr = "spend {word} {word} from {word}")]
  68. fn spend(world: &mut LedgerWorld, amount: String, asset: String, account: String) {
  69. let asset = asset.parse::<Asset>().expect("valid asset");
  70. let amount = asset.from_human(&amount).expect("valid amount");
  71. world
  72. .spend
  73. .as_mut()
  74. .expect("has spend")
  75. .push((account.parse().expect("valid account"), amount));
  76. }
  77. #[then(expr = "withdraw from {word} {word} {word}")]
  78. async fn withdraw(world: &mut LedgerWorld, account: String, amount: String, asset: String) {
  79. let asset = asset.parse::<Asset>().expect("valid asset");
  80. let amount = asset.from_human(&amount).expect("valid amount");
  81. let account = account.parse().expect("valid account");
  82. world.variables.insert(
  83. "latest".to_owned(),
  84. world
  85. .ledger
  86. .withdrawal(&account, amount, "settled".into(), "test".to_owned(), None)
  87. .await
  88. .map(|x| x.revision_id)
  89. .into(),
  90. );
  91. }
  92. #[then("it fails")]
  93. async fn it_fails(world: &mut LedgerWorld) {
  94. match world.variables.get("latest").expect("latest") {
  95. Variable::RevIdResult(Err(_)) => {}
  96. latest => panic!("expected error found: {:?}", latest),
  97. }
  98. }
  99. #[given(expr = "receive {word} {word} in {word}")]
  100. fn receive(world: &mut LedgerWorld, amount: String, asset: String, account: String) {
  101. let asset = asset.parse::<Asset>().expect("valid asset");
  102. let amount = asset.from_human(&amount).expect("valid amount");
  103. world
  104. .receive
  105. .as_mut()
  106. .expect("has receive")
  107. .push((account.parse().expect("valid account"), amount));
  108. }
  109. #[when(expr = "commit transaction {word} as {word}")]
  110. async fn commit_transaction(world: &mut LedgerWorld, name: String, status: String) {
  111. let status = status.parse::<verax::Status>().expect("valid status");
  112. let spend = world.spend.take().expect("has spend");
  113. let receive = world.receive.take().expect("has receive");
  114. world.variables.insert(
  115. name,
  116. world
  117. .ledger
  118. .new_transaction("Transaction".to_owned(), status, spend, receive, None)
  119. .await
  120. .map(|x| x.revision_id)
  121. .into(),
  122. );
  123. }
  124. #[given(expr = "a {word} deposit {word} of {word} {word} for {word}")]
  125. async fn deposit_funds(
  126. world: &mut LedgerWorld,
  127. status: String,
  128. name: String,
  129. amount: String,
  130. asset: String,
  131. account: String,
  132. ) {
  133. let asset = asset.parse::<Asset>().expect("valid asset");
  134. world.variables.insert(
  135. name,
  136. world
  137. .ledger
  138. .deposit(
  139. &account.parse().expect("valid account"),
  140. asset.from_human(&amount).expect("valid amount"),
  141. status.parse().expect("valid status"),
  142. vec![],
  143. "Initial deposit".to_owned(),
  144. None,
  145. )
  146. .await
  147. .map(|x| x.revision_id)
  148. .into(),
  149. );
  150. }
  151. #[given(expr = "update {word} set status to {word}")]
  152. async fn update_status_from_last_tx(world: &mut LedgerWorld, tx: String, new_status: String) {
  153. let revision = match world.get(&tx) {
  154. Variable::RevId(rev_id) => rev_id.clone(),
  155. Variable::RevIdResult(Ok(rev_id)) => rev_id.clone(),
  156. _ => panic!("{} is not a RevId", tx),
  157. };
  158. let status = new_status.parse().expect("valid status");
  159. let update_token = world.tokens.pop();
  160. world.variables.insert(
  161. tx,
  162. world
  163. .ledger
  164. .change_status(revision, status, "update status".to_owned(), update_token)
  165. .await
  166. .map(|x| x.revision_id)
  167. .into(),
  168. );
  169. }
  170. #[then(expr = "{word} has failed")]
  171. fn has_failed(world: &mut LedgerWorld, tx: String) {
  172. assert!(matches!(
  173. world.get(&tx).clone(),
  174. Variable::RevIdResult(Err(_))
  175. ));
  176. }
  177. #[then(expr = "{word} has no balance")]
  178. async fn check_balance_empty(world: &mut LedgerWorld, account: String) {
  179. let balance = world
  180. .ledger
  181. .get_balance(&account.parse().expect("valid account"))
  182. .await
  183. .expect("balance")
  184. .into_iter()
  185. .filter(|b| b.cents() != 0)
  186. .collect::<Vec<_>>();
  187. assert_eq!(0, balance.len())
  188. }
  189. #[then(expr = "{word} has balance of {word} {word}")]
  190. async fn check_balance(world: &mut LedgerWorld, account: String, amount: String, asset: String) {
  191. let asset = asset.parse::<Asset>().expect("valid asset");
  192. let amount = asset.from_human(&amount).expect("valid amount");
  193. let balances = world
  194. .ledger
  195. .get_balance(&account.parse().expect("valid account"))
  196. .await
  197. .expect("balance")
  198. .into_iter()
  199. .filter(|b| b.asset() == &asset)
  200. .collect::<Vec<_>>();
  201. assert_eq!(1, balances.len(), "{} is found", asset);
  202. assert_eq!(balances.first(), Some(&amount));
  203. }
  204. fn find_features<A: AsRef<Path> + Send + Sync>(
  205. dir_path: &A,
  206. ) -> BoxFuture<'_, io::Result<Vec<PathBuf>>> {
  207. Box::pin(async move {
  208. let mut entries = fs::read_dir(dir_path).await?;
  209. let mut paths = vec![];
  210. while let Ok(Some(entry)) = entries.next_entry().await {
  211. let path = entry.path();
  212. if path.is_dir() {
  213. paths.extend(find_features(&path).await?);
  214. } else if let Some(extension) = path.extension() {
  215. if extension == "feature" {
  216. paths.push(path);
  217. }
  218. }
  219. }
  220. Ok(paths)
  221. })
  222. }
  223. #[tokio::main]
  224. async fn main() {
  225. for file in find_features(&".").await.expect("files") {
  226. LedgerWorld::run(file).await;
  227. }
  228. }