use crate::{
models::{
NetworkTransactionData, TransactionRepoModel, TransactionStatus, TransactionUpdateRequest,
},
repositories::*,
};
use async_trait::async_trait;
use eyre::Result;
use itertools::Itertools;
use std::collections::HashMap;
use tokio::sync::{Mutex, MutexGuard};
#[async_trait]
pub trait TransactionRepository: Repository<TransactionRepoModel, String> {
async fn find_by_relayer_id(
&self,
relayer_id: &str,
query: PaginationQuery,
) -> Result<PaginatedResult<TransactionRepoModel>, RepositoryError>;
async fn find_by_status(
&self,
status: TransactionStatus,
) -> Result<Vec<TransactionRepoModel>, RepositoryError>;
async fn find_by_nonce(
&self,
relayer_id: &str,
nonce: u64,
) -> Result<Option<TransactionRepoModel>, RepositoryError>;
async fn update_status(
&self,
tx_id: String,
status: TransactionStatus,
) -> Result<TransactionRepoModel, RepositoryError>;
async fn partial_update(
&self,
tx_id: String,
update: TransactionUpdateRequest,
) -> Result<TransactionRepoModel, RepositoryError>;
async fn update_network_data(
&self,
tx_id: String,
network_data: NetworkTransactionData,
) -> Result<TransactionRepoModel, RepositoryError>;
async fn set_sent_at(
&self,
tx_id: String,
sent_at: String,
) -> Result<TransactionRepoModel, RepositoryError>;
async fn set_confirmed_at(
&self,
tx_id: String,
confirmed_at: String,
) -> Result<TransactionRepoModel, RepositoryError>;
}
#[cfg(test)]
mockall::mock! {
pub TransactionRepository {}
#[async_trait]
impl Repository<TransactionRepoModel, String> for TransactionRepository {
async fn create(&self, entity: TransactionRepoModel) -> Result<TransactionRepoModel, RepositoryError>;
async fn get_by_id(&self, id: String) -> Result<TransactionRepoModel, RepositoryError>;
async fn list_all(&self) -> Result<Vec<TransactionRepoModel>, RepositoryError>;
async fn list_paginated(&self, query: PaginationQuery) -> Result<PaginatedResult<TransactionRepoModel>, RepositoryError>;
async fn update(&self, id: String, entity: TransactionRepoModel) -> Result<TransactionRepoModel, RepositoryError>;
async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError>;
async fn count(&self) -> Result<usize, RepositoryError>;
}
#[async_trait]
impl TransactionRepository for TransactionRepository {
async fn find_by_relayer_id(&self, relayer_id: &str, query: PaginationQuery) -> Result<PaginatedResult<TransactionRepoModel>, RepositoryError>;
async fn find_by_status(&self, status: TransactionStatus) -> Result<Vec<TransactionRepoModel>, RepositoryError>;
async fn find_by_nonce(&self, relayer_id: &str, nonce: u64) -> Result<Option<TransactionRepoModel>, RepositoryError>;
async fn update_status(&self, tx_id: String, status: TransactionStatus) -> Result<TransactionRepoModel, RepositoryError>;
async fn partial_update(&self, tx_id: String, update: TransactionUpdateRequest) -> Result<TransactionRepoModel, RepositoryError>;
async fn update_network_data(&self, tx_id: String, network_data: NetworkTransactionData) -> Result<TransactionRepoModel, RepositoryError>;
async fn set_sent_at(&self, tx_id: String, sent_at: String) -> Result<TransactionRepoModel, RepositoryError>;
async fn set_confirmed_at(&self, tx_id: String, confirmed_at: String) -> Result<TransactionRepoModel, RepositoryError>;
}
}
#[derive(Debug)]
pub struct InMemoryTransactionRepository {
store: Mutex<HashMap<String, TransactionRepoModel>>,
}
impl InMemoryTransactionRepository {
pub fn new() -> Self {
Self {
store: Mutex::new(HashMap::new()),
}
}
async fn acquire_lock<T>(lock: &Mutex<T>) -> Result<MutexGuard<T>, RepositoryError> {
Ok(lock.lock().await)
}
}
#[async_trait]
impl Repository<TransactionRepoModel, String> for InMemoryTransactionRepository {
async fn create(
&self,
tx: TransactionRepoModel,
) -> Result<TransactionRepoModel, RepositoryError> {
let mut store = Self::acquire_lock(&self.store).await?;
if store.contains_key(&tx.id) {
return Err(RepositoryError::ConstraintViolation(format!(
"Transaction with ID {} already exists",
tx.id
)));
}
store.insert(tx.id.clone(), tx.clone());
Ok(tx)
}
async fn get_by_id(&self, id: String) -> Result<TransactionRepoModel, RepositoryError> {
let store = Self::acquire_lock(&self.store).await?;
store.get(&id).cloned().ok_or_else(|| {
RepositoryError::NotFound(format!("Transaction with ID {} not found", id))
})
}
#[allow(clippy::map_entry)]
async fn update(
&self,
id: String,
tx: TransactionRepoModel,
) -> Result<TransactionRepoModel, RepositoryError> {
let mut store = Self::acquire_lock(&self.store).await?;
if store.contains_key(&id) {
let mut updated_tx = tx;
updated_tx.id = id.clone();
store.insert(id, updated_tx.clone());
Ok(updated_tx)
} else {
Err(RepositoryError::NotFound(format!(
"Transaction with ID {} not found",
id
)))
}
}
async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError> {
let mut store = Self::acquire_lock(&self.store).await?;
if store.remove(&id).is_some() {
Ok(())
} else {
Err(RepositoryError::NotFound(format!(
"Transaction with ID {} not found",
id
)))
}
}
async fn list_all(&self) -> Result<Vec<TransactionRepoModel>, RepositoryError> {
let store = Self::acquire_lock(&self.store).await?;
Ok(store.values().cloned().collect())
}
async fn list_paginated(
&self,
query: PaginationQuery,
) -> Result<PaginatedResult<TransactionRepoModel>, RepositoryError> {
let total = self.count().await?;
let start = ((query.page - 1) * query.per_page) as usize;
let store = Self::acquire_lock(&self.store).await?;
let items: Vec<TransactionRepoModel> = store
.values()
.skip(start)
.take(query.per_page as usize)
.cloned()
.collect();
Ok(PaginatedResult {
items,
total: total as u64,
page: query.page,
per_page: query.per_page,
})
}
async fn count(&self) -> Result<usize, RepositoryError> {
let store = Self::acquire_lock(&self.store).await?;
Ok(store.len())
}
}
#[async_trait]
impl TransactionRepository for InMemoryTransactionRepository {
async fn find_by_relayer_id(
&self,
relayer_id: &str,
query: PaginationQuery,
) -> Result<PaginatedResult<TransactionRepoModel>, RepositoryError> {
let store = Self::acquire_lock(&self.store).await?;
let filtered: Vec<TransactionRepoModel> = store
.values()
.filter(|tx| tx.relayer_id == relayer_id)
.cloned()
.collect();
let total = filtered.len() as u64;
if total == 0 {
return Ok(PaginatedResult::<TransactionRepoModel> {
items: vec![],
total: 0,
page: query.page,
per_page: query.per_page,
});
}
let start = ((query.page - 1) * query.per_page) as usize;
let items = filtered
.into_iter()
.sorted_by(|a, b| a.created_at.cmp(&b.created_at)) .skip(start)
.take(query.per_page as usize)
.collect();
Ok(PaginatedResult {
items,
total,
page: query.page,
per_page: query.per_page,
})
}
async fn find_by_status(
&self,
status: TransactionStatus,
) -> Result<Vec<TransactionRepoModel>, RepositoryError> {
let store = Self::acquire_lock(&self.store).await?;
Ok(store
.values()
.filter(|tx| tx.status == status)
.cloned()
.collect())
}
async fn find_by_nonce(
&self,
relayer_id: &str,
nonce: u64,
) -> Result<Option<TransactionRepoModel>, RepositoryError> {
let store = Self::acquire_lock(&self.store).await?;
let filtered: Vec<TransactionRepoModel> = store
.values()
.filter(|tx| {
tx.relayer_id == relayer_id
&& match &tx.network_data {
NetworkTransactionData::Evm(data) => data.nonce == Some(nonce),
_ => false,
}
})
.cloned()
.collect();
Ok(filtered.into_iter().next())
}
async fn update_status(
&self,
tx_id: String,
status: TransactionStatus,
) -> Result<TransactionRepoModel, RepositoryError> {
let mut tx = self.get_by_id(tx_id.clone()).await?;
tx.status = status;
self.update(tx_id, tx).await
}
async fn partial_update(
&self,
tx_id: String,
update: TransactionUpdateRequest,
) -> Result<TransactionRepoModel, RepositoryError> {
let mut store = Self::acquire_lock(&self.store).await?;
if let Some(tx) = store.get_mut(&tx_id) {
if let Some(status) = update.status {
tx.status = status;
}
if let Some(sent_at) = update.sent_at {
tx.sent_at = Some(sent_at);
}
if let Some(confirmed_at) = update.confirmed_at {
tx.confirmed_at = Some(confirmed_at);
}
if let Some(network_data) = update.network_data {
tx.network_data = network_data;
}
if let Some(hashes) = update.hashes {
tx.hashes = hashes;
}
if let Some(is_canceled) = update.is_canceled {
tx.is_canceled = Some(is_canceled);
}
Ok(tx.clone())
} else {
Err(RepositoryError::NotFound(format!(
"Transaction with ID {} not found",
tx_id
)))
}
}
async fn update_network_data(
&self,
tx_id: String,
network_data: NetworkTransactionData,
) -> Result<TransactionRepoModel, RepositoryError> {
let mut tx = self.get_by_id(tx_id.clone()).await?;
tx.network_data = network_data;
self.update(tx_id, tx).await
}
async fn set_sent_at(
&self,
tx_id: String,
sent_at: String,
) -> Result<TransactionRepoModel, RepositoryError> {
let mut tx = self.get_by_id(tx_id.clone()).await?;
tx.sent_at = Some(sent_at);
self.update(tx_id, tx).await
}
async fn set_confirmed_at(
&self,
tx_id: String,
confirmed_at: String,
) -> Result<TransactionRepoModel, RepositoryError> {
let mut tx = self.get_by_id(tx_id.clone()).await?;
tx.confirmed_at = Some(confirmed_at);
self.update(tx_id, tx).await
}
}
impl Default for InMemoryTransactionRepository {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use crate::models::{evm::Speed, EvmTransactionData, NetworkType};
use std::str::FromStr;
use crate::models::U256;
use super::*;
fn create_test_transaction(id: &str) -> TransactionRepoModel {
TransactionRepoModel {
id: id.to_string(),
relayer_id: "relayer-1".to_string(),
status: TransactionStatus::Pending,
created_at: "2025-01-27T15:31:10.777083+00:00".to_string(),
sent_at: Some("2025-01-27T15:31:10.777083+00:00".to_string()),
confirmed_at: Some("2025-01-27T15:31:10.777083+00:00".to_string()),
valid_until: None,
network_type: NetworkType::Evm,
priced_at: None,
hashes: vec![],
network_data: NetworkTransactionData::Evm(EvmTransactionData {
gas_price: Some(1000000000),
gas_limit: 21000,
nonce: Some(1),
value: U256::from_str("1000000000000000000").unwrap(),
data: Some("0x".to_string()),
from: "0xSender".to_string(),
to: Some("0xRecipient".to_string()),
chain_id: 1,
signature: None,
hash: Some(format!("0x{}", id)),
speed: Some(Speed::Fast),
max_fee_per_gas: None,
max_priority_fee_per_gas: None,
raw: None,
}),
noop_count: None,
is_canceled: Some(false),
}
}
fn create_test_transaction_pending_state(id: &str) -> TransactionRepoModel {
TransactionRepoModel {
id: id.to_string(),
relayer_id: "relayer-1".to_string(),
status: TransactionStatus::Pending,
created_at: "2025-01-27T15:31:10.777083+00:00".to_string(),
sent_at: None,
confirmed_at: None,
valid_until: None,
network_type: NetworkType::Evm,
priced_at: None,
hashes: vec![],
network_data: NetworkTransactionData::Evm(EvmTransactionData {
gas_price: Some(1000000000),
gas_limit: 21000,
nonce: Some(1),
value: U256::from_str("1000000000000000000").unwrap(),
data: Some("0x".to_string()),
from: "0xSender".to_string(),
to: Some("0xRecipient".to_string()),
chain_id: 1,
signature: None,
hash: Some(format!("0x{}", id)),
speed: Some(Speed::Fast),
max_fee_per_gas: None,
max_priority_fee_per_gas: None,
raw: None,
}),
noop_count: None,
is_canceled: Some(false),
}
}
#[tokio::test]
async fn test_create_transaction() {
let repo = InMemoryTransactionRepository::new();
let tx = create_test_transaction("test-1");
let result = repo.create(tx.clone()).await.unwrap();
assert_eq!(result.id, tx.id);
assert_eq!(repo.count().await.unwrap(), 1);
}
#[tokio::test]
async fn test_get_transaction() {
let repo = InMemoryTransactionRepository::new();
let tx = create_test_transaction("test-1");
repo.create(tx.clone()).await.unwrap();
let stored = repo.get_by_id("test-1".to_string()).await.unwrap();
if let NetworkTransactionData::Evm(stored_data) = &stored.network_data {
if let NetworkTransactionData::Evm(tx_data) = &tx.network_data {
assert_eq!(stored_data.hash, tx_data.hash);
}
}
}
#[tokio::test]
async fn test_update_transaction() {
let repo = InMemoryTransactionRepository::new();
let mut tx = create_test_transaction("test-1");
repo.create(tx.clone()).await.unwrap();
tx.status = TransactionStatus::Confirmed;
let updated = repo.update("test-1".to_string(), tx).await.unwrap();
assert!(matches!(updated.status, TransactionStatus::Confirmed));
}
#[tokio::test]
async fn test_delete_transaction() {
let repo = InMemoryTransactionRepository::new();
let tx = create_test_transaction("test-1");
repo.create(tx).await.unwrap();
repo.delete_by_id("test-1".to_string()).await.unwrap();
let result = repo.get_by_id("test-1".to_string()).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_list_all_transactions() {
let repo = InMemoryTransactionRepository::new();
let tx1 = create_test_transaction("test-1");
let tx2 = create_test_transaction("test-2");
repo.create(tx1).await.unwrap();
repo.create(tx2).await.unwrap();
let transactions = repo.list_all().await.unwrap();
assert_eq!(transactions.len(), 2);
}
#[tokio::test]
async fn test_count_transactions() {
let repo = InMemoryTransactionRepository::new();
let tx = create_test_transaction("test-1");
assert_eq!(repo.count().await.unwrap(), 0);
repo.create(tx).await.unwrap();
assert_eq!(repo.count().await.unwrap(), 1);
}
#[tokio::test]
async fn test_get_nonexistent_transaction() {
let repo = InMemoryTransactionRepository::new();
let result = repo.get_by_id("nonexistent".to_string()).await;
assert!(matches!(result, Err(RepositoryError::NotFound(_))));
}
#[tokio::test]
async fn test_duplicate_transaction_creation() {
let repo = InMemoryTransactionRepository::new();
let tx = create_test_transaction("test-1");
repo.create(tx.clone()).await.unwrap();
let result = repo.create(tx).await;
assert!(matches!(
result,
Err(RepositoryError::ConstraintViolation(_))
));
}
#[tokio::test]
async fn test_update_nonexistent_transaction() {
let repo = InMemoryTransactionRepository::new();
let tx = create_test_transaction("test-1");
let result = repo.update("nonexistent".to_string(), tx).await;
assert!(matches!(result, Err(RepositoryError::NotFound(_))));
}
#[tokio::test]
async fn test_partial_update() {
let repo = InMemoryTransactionRepository::new();
let tx = create_test_transaction_pending_state("test-tx-id");
repo.create(tx.clone()).await.unwrap();
let update1 = TransactionUpdateRequest {
status: Some(TransactionStatus::Sent),
sent_at: None,
confirmed_at: None,
network_data: None,
hashes: None,
priced_at: None,
noop_count: None,
is_canceled: None,
};
let updated_tx1 = repo
.partial_update("test-tx-id".to_string(), update1)
.await
.unwrap();
assert_eq!(updated_tx1.status, TransactionStatus::Sent);
assert_eq!(updated_tx1.sent_at, None);
let update2 = TransactionUpdateRequest {
status: Some(TransactionStatus::Confirmed),
sent_at: Some("2023-01-01T12:00:00Z".to_string()),
confirmed_at: Some("2023-01-01T12:05:00Z".to_string()),
network_data: None,
hashes: None,
priced_at: None,
noop_count: None,
is_canceled: None,
};
let updated_tx2 = repo
.partial_update("test-tx-id".to_string(), update2)
.await
.unwrap();
assert_eq!(updated_tx2.status, TransactionStatus::Confirmed);
assert_eq!(
updated_tx2.sent_at,
Some("2023-01-01T12:00:00Z".to_string())
);
assert_eq!(
updated_tx2.confirmed_at,
Some("2023-01-01T12:05:00Z".to_string())
);
let update3 = TransactionUpdateRequest {
status: Some(TransactionStatus::Failed),
sent_at: None,
confirmed_at: None,
network_data: None,
hashes: None,
priced_at: None,
noop_count: None,
is_canceled: None,
};
let result = repo
.partial_update("non-existent-id".to_string(), update3)
.await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), RepositoryError::NotFound(_)));
}
#[tokio::test]
async fn test_update_status() {
let repo = InMemoryTransactionRepository::new();
let tx = create_test_transaction("test-1");
repo.create(tx).await.unwrap();
let updated = repo
.update_status("test-1".to_string(), TransactionStatus::Confirmed)
.await
.unwrap();
assert_eq!(updated.status, TransactionStatus::Confirmed);
let stored = repo.get_by_id("test-1".to_string()).await.unwrap();
assert_eq!(stored.status, TransactionStatus::Confirmed);
let updated = repo
.update_status("test-1".to_string(), TransactionStatus::Failed)
.await
.unwrap();
assert_eq!(updated.status, TransactionStatus::Failed);
let result = repo
.update_status("non-existent".to_string(), TransactionStatus::Confirmed)
.await;
assert!(matches!(result, Err(RepositoryError::NotFound(_))));
}
#[tokio::test]
async fn test_list_paginated() {
let repo = InMemoryTransactionRepository::new();
for i in 1..=10 {
let tx = create_test_transaction(&format!("test-{}", i));
repo.create(tx).await.unwrap();
}
let query = PaginationQuery {
page: 1,
per_page: 3,
};
let result = repo.list_paginated(query).await.unwrap();
assert_eq!(result.items.len(), 3);
assert_eq!(result.total, 10);
assert_eq!(result.page, 1);
assert_eq!(result.per_page, 3);
let query = PaginationQuery {
page: 2,
per_page: 3,
};
let result = repo.list_paginated(query).await.unwrap();
assert_eq!(result.items.len(), 3);
assert_eq!(result.total, 10);
assert_eq!(result.page, 2);
assert_eq!(result.per_page, 3);
let query = PaginationQuery {
page: 4,
per_page: 3,
};
let result = repo.list_paginated(query).await.unwrap();
assert_eq!(result.items.len(), 1);
assert_eq!(result.total, 10);
assert_eq!(result.page, 4);
assert_eq!(result.per_page, 3);
let query = PaginationQuery {
page: 5,
per_page: 3,
};
let result = repo.list_paginated(query).await.unwrap();
assert_eq!(result.items.len(), 0);
assert_eq!(result.total, 10);
}
#[tokio::test]
async fn test_find_by_nonce() {
let repo = InMemoryTransactionRepository::new();
let tx1 = create_test_transaction("test-1");
let mut tx2 = create_test_transaction("test-2");
if let NetworkTransactionData::Evm(ref mut data) = tx2.network_data {
data.nonce = Some(2);
}
let mut tx3 = create_test_transaction("test-3");
tx3.relayer_id = "relayer-2".to_string();
if let NetworkTransactionData::Evm(ref mut data) = tx3.network_data {
data.nonce = Some(1);
}
repo.create(tx1).await.unwrap();
repo.create(tx2).await.unwrap();
repo.create(tx3).await.unwrap();
let result = repo.find_by_nonce("relayer-1", 1).await.unwrap();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().id, "test-1");
let result = repo.find_by_nonce("relayer-1", 2).await.unwrap();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().id, "test-2");
let result = repo.find_by_nonce("relayer-2", 1).await.unwrap();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().id, "test-3");
let result = repo.find_by_nonce("relayer-1", 99).await.unwrap();
assert!(result.is_none());
}
#[tokio::test]
async fn test_update_network_data() {
let repo = InMemoryTransactionRepository::new();
let tx = create_test_transaction("test-1");
repo.create(tx.clone()).await.unwrap();
let updated_network_data = NetworkTransactionData::Evm(EvmTransactionData {
gas_price: Some(2000000000),
gas_limit: 30000,
nonce: Some(2),
value: U256::from_str("2000000000000000000").unwrap(),
data: Some("0xUpdated".to_string()),
from: "0xSender".to_string(),
to: Some("0xRecipient".to_string()),
chain_id: 1,
signature: None,
hash: Some("0xUpdated".to_string()),
raw: None,
speed: None,
max_fee_per_gas: None,
max_priority_fee_per_gas: None,
});
let updated = repo
.update_network_data("test-1".to_string(), updated_network_data)
.await
.unwrap();
if let NetworkTransactionData::Evm(data) = &updated.network_data {
assert_eq!(data.gas_price, Some(2000000000));
assert_eq!(data.gas_limit, 30000);
assert_eq!(data.nonce, Some(2));
assert_eq!(data.hash, Some("0xUpdated".to_string()));
assert_eq!(data.data, Some("0xUpdated".to_string()));
} else {
panic!("Expected EVM network data");
}
}
#[tokio::test]
async fn test_set_sent_at() {
let repo = InMemoryTransactionRepository::new();
let tx = create_test_transaction("test-1");
repo.create(tx).await.unwrap();
let new_sent_at = "2025-02-01T10:00:00.000000+00:00".to_string();
let updated = repo
.set_sent_at("test-1".to_string(), new_sent_at.clone())
.await
.unwrap();
assert_eq!(updated.sent_at, Some(new_sent_at.clone()));
let stored = repo.get_by_id("test-1".to_string()).await.unwrap();
assert_eq!(stored.sent_at, Some(new_sent_at.clone()));
}
#[tokio::test]
async fn test_set_confirmed_at() {
let repo = InMemoryTransactionRepository::new();
let tx = create_test_transaction("test-1");
repo.create(tx).await.unwrap();
let new_confirmed_at = "2025-02-01T11:30:45.123456+00:00".to_string();
let updated = repo
.set_confirmed_at("test-1".to_string(), new_confirmed_at.clone())
.await
.unwrap();
assert_eq!(updated.confirmed_at, Some(new_confirmed_at.clone()));
let stored = repo.get_by_id("test-1".to_string()).await.unwrap();
assert_eq!(stored.confirmed_at, Some(new_confirmed_at.clone()));
}
#[tokio::test]
async fn test_find_by_relayer_id() {
let repo = InMemoryTransactionRepository::new();
let tx1 = create_test_transaction("test-1");
let tx2 = create_test_transaction("test-2");
let mut tx3 = create_test_transaction("test-3");
tx3.relayer_id = "relayer-2".to_string();
repo.create(tx1).await.unwrap();
repo.create(tx2).await.unwrap();
repo.create(tx3).await.unwrap();
let query = PaginationQuery {
page: 1,
per_page: 10,
};
let result = repo
.find_by_relayer_id("relayer-1", query.clone())
.await
.unwrap();
assert_eq!(result.total, 2);
assert_eq!(result.items.len(), 2);
assert!(result.items.iter().all(|tx| tx.relayer_id == "relayer-1"));
let result = repo
.find_by_relayer_id("relayer-2", query.clone())
.await
.unwrap();
assert_eq!(result.total, 1);
assert_eq!(result.items.len(), 1);
assert!(result.items.iter().all(|tx| tx.relayer_id == "relayer-2"));
let result = repo
.find_by_relayer_id("non-existent", query.clone())
.await
.unwrap();
assert_eq!(result.total, 0);
assert_eq!(result.items.len(), 0);
}
#[tokio::test]
async fn test_find_by_status() {
let repo = InMemoryTransactionRepository::new();
let tx1 = create_test_transaction("test-1");
let mut tx2 = create_test_transaction("test-2");
tx2.status = TransactionStatus::Confirmed;
let mut tx3 = create_test_transaction("test-3");
tx3.status = TransactionStatus::Failed;
repo.create(tx1).await.unwrap();
repo.create(tx2).await.unwrap();
repo.create(tx3).await.unwrap();
let result = repo
.find_by_status(TransactionStatus::Pending)
.await
.unwrap();
assert_eq!(result.len(), 1);
assert!(result
.iter()
.all(|tx| tx.status == TransactionStatus::Pending));
let result = repo
.find_by_status(TransactionStatus::Confirmed)
.await
.unwrap();
assert_eq!(result.len(), 1);
assert!(result
.iter()
.all(|tx| tx.status == TransactionStatus::Confirmed));
let result = repo
.find_by_status(TransactionStatus::Failed)
.await
.unwrap();
assert_eq!(result.len(), 1);
assert!(result
.iter()
.all(|tx| tx.status == TransactionStatus::Failed));
}
}