687 lines
24 KiB
Rust
687 lines
24 KiB
Rust
use crate::adapters::gocardless::encryption::Encryption;
|
|
use anyhow::Result;
|
|
use chrono::{Days, NaiveDate};
|
|
use gocardless_client::models::Transaction;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::path::Path;
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct AccountTransactionCache {
|
|
pub account_id: String,
|
|
pub ranges: Vec<CachedRange>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct CachedRange {
|
|
pub start_date: NaiveDate,
|
|
pub end_date: NaiveDate,
|
|
pub transactions: Vec<Transaction>,
|
|
}
|
|
|
|
impl AccountTransactionCache {
|
|
/// Get cache file path for an account
|
|
fn get_cache_path(account_id: &str) -> String {
|
|
let cache_dir =
|
|
std::env::var("BANKS2FF_CACHE_DIR").unwrap_or_else(|_| "data/cache".to_string());
|
|
format!("{}/transactions/{}.enc", cache_dir, account_id)
|
|
}
|
|
|
|
/// Load cache from disk
|
|
pub fn load(account_id: &str) -> Result<Self> {
|
|
let path = Self::get_cache_path(account_id);
|
|
|
|
if !Path::new(&path).exists() {
|
|
// Return empty cache if file doesn't exist
|
|
return Ok(Self {
|
|
account_id: account_id.to_string(),
|
|
ranges: Vec::new(),
|
|
});
|
|
}
|
|
|
|
// Read encrypted data
|
|
let encrypted_data = std::fs::read(&path)?;
|
|
let json_data = Encryption::decrypt(&encrypted_data)?;
|
|
|
|
// Deserialize
|
|
let cache: Self = serde_json::from_slice(&json_data)?;
|
|
Ok(cache)
|
|
}
|
|
|
|
/// Save cache to disk
|
|
pub fn save(&self) -> Result<()> {
|
|
// Serialize to JSON
|
|
let json_data = serde_json::to_vec(self)?;
|
|
|
|
// Encrypt
|
|
let encrypted_data = Encryption::encrypt(&json_data)?;
|
|
|
|
// Write to file (create directory if needed)
|
|
let path = Self::get_cache_path(&self.account_id);
|
|
if let Some(parent) = std::path::Path::new(&path).parent() {
|
|
std::fs::create_dir_all(parent)?;
|
|
}
|
|
std::fs::write(path, encrypted_data)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Get cached transactions within date range
|
|
pub fn get_cached_transactions(&self, start: NaiveDate, end: NaiveDate) -> Vec<Transaction> {
|
|
let mut result = Vec::new();
|
|
for range in &self.ranges {
|
|
if Self::ranges_overlap(range.start_date, range.end_date, start, end) {
|
|
for tx in &range.transactions {
|
|
if let Some(booking_date_str) = &tx.booking_date {
|
|
if let Ok(booking_date) =
|
|
NaiveDate::parse_from_str(booking_date_str, "%Y-%m-%d")
|
|
{
|
|
if booking_date >= start && booking_date <= end {
|
|
result.push(tx.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
result
|
|
}
|
|
|
|
/// Get uncovered date ranges within requested period
|
|
pub fn get_uncovered_ranges(
|
|
&self,
|
|
start: NaiveDate,
|
|
end: NaiveDate,
|
|
) -> Vec<(NaiveDate, NaiveDate)> {
|
|
let mut covered_periods: Vec<(NaiveDate, NaiveDate)> = self
|
|
.ranges
|
|
.iter()
|
|
.filter_map(|range| {
|
|
if Self::ranges_overlap(range.start_date, range.end_date, start, end) {
|
|
let overlap_start = range.start_date.max(start);
|
|
let overlap_end = range.end_date.min(end);
|
|
if overlap_start <= overlap_end {
|
|
Some((overlap_start, overlap_end))
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
covered_periods.sort_by_key(|&(s, _)| s);
|
|
|
|
// Merge overlapping covered periods
|
|
let mut merged_covered: Vec<(NaiveDate, NaiveDate)> = Vec::new();
|
|
for period in covered_periods {
|
|
if let Some(last) = merged_covered.last_mut() {
|
|
if last.1 >= period.0 {
|
|
last.1 = last.1.max(period.1);
|
|
} else {
|
|
merged_covered.push(period);
|
|
}
|
|
} else {
|
|
merged_covered.push(period);
|
|
}
|
|
}
|
|
|
|
// Find gaps
|
|
let mut uncovered = Vec::new();
|
|
let mut current_start = start;
|
|
for (cov_start, cov_end) in merged_covered {
|
|
if current_start < cov_start {
|
|
uncovered.push((current_start, cov_start - Days::new(1)));
|
|
}
|
|
current_start = cov_end + Days::new(1);
|
|
}
|
|
if current_start <= end {
|
|
uncovered.push((current_start, end));
|
|
}
|
|
|
|
uncovered
|
|
}
|
|
|
|
/// Store transactions for a date range, merging with existing cache
|
|
pub fn store_transactions(
|
|
&mut self,
|
|
start: NaiveDate,
|
|
end: NaiveDate,
|
|
mut transactions: Vec<Transaction>,
|
|
) {
|
|
Self::deduplicate_transactions(&mut transactions);
|
|
let new_range = CachedRange {
|
|
start_date: start,
|
|
end_date: end,
|
|
transactions,
|
|
};
|
|
self.merge_ranges(new_range);
|
|
}
|
|
|
|
/// Merge a new range into existing ranges
|
|
pub fn merge_ranges(&mut self, new_range: CachedRange) {
|
|
// Find overlapping or adjacent ranges
|
|
let mut to_merge = Vec::new();
|
|
let mut remaining = Vec::new();
|
|
|
|
for range in &self.ranges {
|
|
if Self::ranges_overlap_or_adjacent(
|
|
range.start_date,
|
|
range.end_date,
|
|
new_range.start_date,
|
|
new_range.end_date,
|
|
) {
|
|
to_merge.push(range.clone());
|
|
} else {
|
|
remaining.push(range.clone());
|
|
}
|
|
}
|
|
|
|
// Merge all overlapping/adjacent ranges including the new one
|
|
to_merge.push(new_range);
|
|
|
|
let merged = Self::merge_range_list(to_merge);
|
|
|
|
// Update ranges
|
|
self.ranges = remaining;
|
|
self.ranges.extend(merged);
|
|
}
|
|
|
|
/// Check if two date ranges overlap
|
|
fn ranges_overlap(
|
|
start1: NaiveDate,
|
|
end1: NaiveDate,
|
|
start2: NaiveDate,
|
|
end2: NaiveDate,
|
|
) -> bool {
|
|
start1 <= end2 && start2 <= end1
|
|
}
|
|
|
|
/// Check if two date ranges overlap or are adjacent
|
|
fn ranges_overlap_or_adjacent(
|
|
start1: NaiveDate,
|
|
end1: NaiveDate,
|
|
start2: NaiveDate,
|
|
end2: NaiveDate,
|
|
) -> bool {
|
|
Self::ranges_overlap(start1, end1, start2, end2)
|
|
|| (end1 + Days::new(1)) == start2
|
|
|| (end2 + Days::new(1)) == start1
|
|
}
|
|
|
|
/// Merge a list of ranges into minimal set
|
|
fn merge_range_list(ranges: Vec<CachedRange>) -> Vec<CachedRange> {
|
|
if ranges.is_empty() {
|
|
return Vec::new();
|
|
}
|
|
|
|
// Sort by start date
|
|
let mut sorted = ranges;
|
|
sorted.sort_by_key(|r| r.start_date);
|
|
|
|
let mut merged = Vec::new();
|
|
let mut current = sorted[0].clone();
|
|
|
|
for range in sorted.into_iter().skip(1) {
|
|
if Self::ranges_overlap_or_adjacent(
|
|
current.start_date,
|
|
current.end_date,
|
|
range.start_date,
|
|
range.end_date,
|
|
) {
|
|
// Merge
|
|
current.start_date = current.start_date.min(range.start_date);
|
|
current.end_date = current.end_date.max(range.end_date);
|
|
// Deduplicate transactions
|
|
current.transactions.extend(range.transactions);
|
|
Self::deduplicate_transactions(&mut current.transactions);
|
|
} else {
|
|
merged.push(current);
|
|
current = range;
|
|
}
|
|
}
|
|
merged.push(current);
|
|
|
|
merged
|
|
}
|
|
|
|
/// Deduplicate transactions by transaction_id
|
|
fn deduplicate_transactions(transactions: &mut Vec<Transaction>) {
|
|
let mut seen = std::collections::HashSet::new();
|
|
transactions.retain(|tx| {
|
|
if let Some(id) = &tx.transaction_id {
|
|
seen.insert(id.clone())
|
|
} else {
|
|
true // Keep if no id
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use chrono::NaiveDate;
|
|
use std::env;
|
|
|
|
fn setup_test_env(test_name: &str) -> String {
|
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
|
// Use a unique cache directory for each test to avoid interference
|
|
// Include random component and timestamp for true parallelism safety
|
|
let random_suffix = rand::random::<u64>();
|
|
let timestamp = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let cache_dir = format!(
|
|
"tmp/test-cache-{}-{}-{}",
|
|
test_name, random_suffix, timestamp
|
|
);
|
|
env::set_var("BANKS2FF_CACHE_DIR", cache_dir.clone());
|
|
cache_dir
|
|
}
|
|
|
|
fn cleanup_test_dir(cache_dir: &str) {
|
|
// Wait a bit longer to ensure all file operations are complete
|
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
|
|
|
// Try multiple times in case of temporary file locks
|
|
for _ in 0..5 {
|
|
if std::path::Path::new(cache_dir).exists() {
|
|
if std::fs::remove_dir_all(cache_dir).is_ok() {
|
|
break;
|
|
}
|
|
} else {
|
|
break; // Directory already gone
|
|
}
|
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_load_nonexistent_cache() {
|
|
let cache_dir = setup_test_env("nonexistent");
|
|
let cache = AccountTransactionCache::load("nonexistent").unwrap();
|
|
assert_eq!(cache.account_id, "nonexistent");
|
|
assert!(cache.ranges.is_empty());
|
|
cleanup_test_dir(&cache_dir);
|
|
}
|
|
|
|
#[test]
|
|
fn test_save_and_load_empty_cache() {
|
|
let cache_dir = setup_test_env("empty");
|
|
|
|
let cache = AccountTransactionCache {
|
|
account_id: "test_account_empty".to_string(),
|
|
ranges: Vec::new(),
|
|
};
|
|
|
|
// Ensure env vars are set before save
|
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
|
// Ensure env vars are set before save
|
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
|
// Save
|
|
cache.save().expect("Save should succeed");
|
|
|
|
// Ensure env vars are set before load
|
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
|
// Load
|
|
let loaded =
|
|
AccountTransactionCache::load("test_account_empty").expect("Load should succeed");
|
|
|
|
assert_eq!(loaded.account_id, "test_account_empty");
|
|
assert!(loaded.ranges.is_empty());
|
|
|
|
cleanup_test_dir(&cache_dir);
|
|
}
|
|
|
|
#[test]
|
|
fn test_save_and_load_with_data() {
|
|
let cache_dir = setup_test_env("data");
|
|
|
|
let transaction = Transaction {
|
|
transaction_id: Some("test-tx-1".to_string()),
|
|
entry_reference: None,
|
|
end_to_end_id: None,
|
|
mandate_id: None,
|
|
check_id: None,
|
|
creditor_id: None,
|
|
booking_date: Some("2024-01-01".to_string()),
|
|
value_date: None,
|
|
booking_date_time: None,
|
|
value_date_time: None,
|
|
transaction_amount: gocardless_client::models::TransactionAmount {
|
|
amount: "100.00".to_string(),
|
|
currency: "EUR".to_string(),
|
|
},
|
|
currency_exchange: None,
|
|
creditor_name: Some("Test Creditor".to_string()),
|
|
creditor_account: None,
|
|
ultimate_creditor: None,
|
|
debtor_name: None,
|
|
debtor_account: None,
|
|
ultimate_debtor: None,
|
|
remittance_information_unstructured: Some("Test payment".to_string()),
|
|
remittance_information_unstructured_array: None,
|
|
remittance_information_structured: None,
|
|
remittance_information_structured_array: None,
|
|
additional_information: None,
|
|
purpose_code: None,
|
|
bank_transaction_code: None,
|
|
proprietary_bank_transaction_code: None,
|
|
internal_transaction_id: None,
|
|
};
|
|
|
|
let range = CachedRange {
|
|
start_date: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
|
|
end_date: NaiveDate::from_ymd_opt(2024, 1, 31).unwrap(),
|
|
transactions: vec![transaction],
|
|
};
|
|
|
|
let cache = AccountTransactionCache {
|
|
account_id: "test_account_data".to_string(),
|
|
ranges: vec![range],
|
|
};
|
|
|
|
// Ensure env vars are set before save
|
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
|
// Save
|
|
cache.save().expect("Save should succeed");
|
|
|
|
// Ensure env vars are set before load
|
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
|
// Load
|
|
let loaded =
|
|
AccountTransactionCache::load("test_account_data").expect("Load should succeed");
|
|
|
|
assert_eq!(loaded.account_id, "test_account_data");
|
|
assert_eq!(loaded.ranges.len(), 1);
|
|
assert_eq!(loaded.ranges[0].transactions.len(), 1);
|
|
assert_eq!(
|
|
loaded.ranges[0].transactions[0].transaction_id,
|
|
Some("test-tx-1".to_string())
|
|
);
|
|
|
|
cleanup_test_dir(&cache_dir);
|
|
}
|
|
|
|
#[test]
|
|
fn test_save_load_different_accounts() {
|
|
let cache_dir = setup_test_env("different_accounts");
|
|
|
|
// Save cache for account A
|
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
|
let cache_a = AccountTransactionCache {
|
|
account_id: "account_a".to_string(),
|
|
ranges: Vec::new(),
|
|
};
|
|
cache_a.save().unwrap();
|
|
|
|
// Save cache for account B
|
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
|
let cache_b = AccountTransactionCache {
|
|
account_id: "account_b".to_string(),
|
|
ranges: Vec::new(),
|
|
};
|
|
cache_b.save().unwrap();
|
|
|
|
// Load account A
|
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
|
let loaded_a = AccountTransactionCache::load("account_a").unwrap();
|
|
assert_eq!(loaded_a.account_id, "account_a");
|
|
|
|
// Load account B
|
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
|
let loaded_b = AccountTransactionCache::load("account_b").unwrap();
|
|
assert_eq!(loaded_b.account_id, "account_b");
|
|
|
|
cleanup_test_dir(&cache_dir);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_uncovered_ranges_no_cache() {
|
|
let cache = AccountTransactionCache {
|
|
account_id: "test".to_string(),
|
|
ranges: Vec::new(),
|
|
};
|
|
let start = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
|
let end = NaiveDate::from_ymd_opt(2024, 1, 31).unwrap();
|
|
let uncovered = cache.get_uncovered_ranges(start, end);
|
|
assert_eq!(uncovered, vec![(start, end)]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_uncovered_ranges_full_coverage() {
|
|
let range = CachedRange {
|
|
start_date: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
|
|
end_date: NaiveDate::from_ymd_opt(2024, 1, 31).unwrap(),
|
|
transactions: Vec::new(),
|
|
};
|
|
let cache = AccountTransactionCache {
|
|
account_id: "test".to_string(),
|
|
ranges: vec![range],
|
|
};
|
|
let start = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
|
let end = NaiveDate::from_ymd_opt(2024, 1, 31).unwrap();
|
|
let uncovered = cache.get_uncovered_ranges(start, end);
|
|
assert!(uncovered.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_uncovered_ranges_partial_coverage() {
|
|
let range = CachedRange {
|
|
start_date: NaiveDate::from_ymd_opt(2024, 1, 10).unwrap(),
|
|
end_date: NaiveDate::from_ymd_opt(2024, 1, 20).unwrap(),
|
|
transactions: Vec::new(),
|
|
};
|
|
let cache = AccountTransactionCache {
|
|
account_id: "test".to_string(),
|
|
ranges: vec![range],
|
|
};
|
|
let start = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
|
let end = NaiveDate::from_ymd_opt(2024, 1, 31).unwrap();
|
|
let uncovered = cache.get_uncovered_ranges(start, end);
|
|
assert_eq!(uncovered.len(), 2);
|
|
assert_eq!(
|
|
uncovered[0],
|
|
(
|
|
NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
|
|
NaiveDate::from_ymd_opt(2024, 1, 9).unwrap()
|
|
)
|
|
);
|
|
assert_eq!(
|
|
uncovered[1],
|
|
(
|
|
NaiveDate::from_ymd_opt(2024, 1, 21).unwrap(),
|
|
NaiveDate::from_ymd_opt(2024, 1, 31).unwrap()
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_store_transactions_and_merge() {
|
|
let mut cache = AccountTransactionCache {
|
|
account_id: "test".to_string(),
|
|
ranges: Vec::new(),
|
|
};
|
|
let start1 = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
|
let end1 = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
|
|
let tx1 = Transaction {
|
|
transaction_id: Some("tx1".to_string()),
|
|
entry_reference: None,
|
|
end_to_end_id: None,
|
|
mandate_id: None,
|
|
check_id: None,
|
|
creditor_id: None,
|
|
booking_date: Some("2024-01-05".to_string()),
|
|
value_date: None,
|
|
booking_date_time: None,
|
|
value_date_time: None,
|
|
transaction_amount: gocardless_client::models::TransactionAmount {
|
|
amount: "100.00".to_string(),
|
|
currency: "EUR".to_string(),
|
|
},
|
|
currency_exchange: None,
|
|
creditor_name: Some("Creditor".to_string()),
|
|
creditor_account: None,
|
|
ultimate_creditor: None,
|
|
debtor_name: None,
|
|
debtor_account: None,
|
|
ultimate_debtor: None,
|
|
remittance_information_unstructured: Some("Payment".to_string()),
|
|
remittance_information_unstructured_array: None,
|
|
remittance_information_structured: None,
|
|
remittance_information_structured_array: None,
|
|
additional_information: None,
|
|
purpose_code: None,
|
|
bank_transaction_code: None,
|
|
proprietary_bank_transaction_code: None,
|
|
internal_transaction_id: None,
|
|
};
|
|
cache.store_transactions(start1, end1, vec![tx1]);
|
|
|
|
assert_eq!(cache.ranges.len(), 1);
|
|
assert_eq!(cache.ranges[0].start_date, start1);
|
|
assert_eq!(cache.ranges[0].end_date, end1);
|
|
assert_eq!(cache.ranges[0].transactions.len(), 1);
|
|
|
|
// Add overlapping range
|
|
let start2 = NaiveDate::from_ymd_opt(2024, 1, 5).unwrap();
|
|
let end2 = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
|
|
let tx2 = Transaction {
|
|
transaction_id: Some("tx2".to_string()),
|
|
entry_reference: None,
|
|
end_to_end_id: None,
|
|
mandate_id: None,
|
|
check_id: None,
|
|
creditor_id: None,
|
|
booking_date: Some("2024-01-12".to_string()),
|
|
value_date: None,
|
|
booking_date_time: None,
|
|
value_date_time: None,
|
|
transaction_amount: gocardless_client::models::TransactionAmount {
|
|
amount: "200.00".to_string(),
|
|
currency: "EUR".to_string(),
|
|
},
|
|
currency_exchange: None,
|
|
creditor_name: Some("Creditor2".to_string()),
|
|
creditor_account: None,
|
|
ultimate_creditor: None,
|
|
debtor_name: None,
|
|
debtor_account: None,
|
|
ultimate_debtor: None,
|
|
remittance_information_unstructured: Some("Payment2".to_string()),
|
|
remittance_information_unstructured_array: None,
|
|
remittance_information_structured: None,
|
|
remittance_information_structured_array: None,
|
|
additional_information: None,
|
|
purpose_code: None,
|
|
bank_transaction_code: None,
|
|
proprietary_bank_transaction_code: None,
|
|
internal_transaction_id: None,
|
|
};
|
|
cache.store_transactions(start2, end2, vec![tx2]);
|
|
|
|
// Should merge into one range
|
|
assert_eq!(cache.ranges.len(), 1);
|
|
assert_eq!(cache.ranges[0].start_date, start1);
|
|
assert_eq!(cache.ranges[0].end_date, end2);
|
|
assert_eq!(cache.ranges[0].transactions.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_transaction_deduplication() {
|
|
let mut cache = AccountTransactionCache {
|
|
account_id: "test".to_string(),
|
|
ranges: Vec::new(),
|
|
};
|
|
let start = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
|
let end = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
|
|
let tx1 = Transaction {
|
|
transaction_id: Some("tx1".to_string()),
|
|
entry_reference: None,
|
|
end_to_end_id: None,
|
|
mandate_id: None,
|
|
check_id: None,
|
|
creditor_id: None,
|
|
booking_date: Some("2024-01-05".to_string()),
|
|
value_date: None,
|
|
booking_date_time: None,
|
|
value_date_time: None,
|
|
transaction_amount: gocardless_client::models::TransactionAmount {
|
|
amount: "100.00".to_string(),
|
|
currency: "EUR".to_string(),
|
|
},
|
|
currency_exchange: None,
|
|
creditor_name: Some("Creditor".to_string()),
|
|
creditor_account: None,
|
|
ultimate_creditor: None,
|
|
debtor_name: None,
|
|
debtor_account: None,
|
|
ultimate_debtor: None,
|
|
remittance_information_unstructured: Some("Payment".to_string()),
|
|
remittance_information_unstructured_array: None,
|
|
remittance_information_structured: None,
|
|
remittance_information_structured_array: None,
|
|
additional_information: None,
|
|
purpose_code: None,
|
|
bank_transaction_code: None,
|
|
proprietary_bank_transaction_code: None,
|
|
internal_transaction_id: None,
|
|
};
|
|
let tx2 = tx1.clone(); // Duplicate
|
|
cache.store_transactions(start, end, vec![tx1, tx2]);
|
|
|
|
assert_eq!(cache.ranges[0].transactions.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_cached_transactions() {
|
|
let tx1 = Transaction {
|
|
transaction_id: Some("tx1".to_string()),
|
|
entry_reference: None,
|
|
end_to_end_id: None,
|
|
mandate_id: None,
|
|
check_id: None,
|
|
creditor_id: None,
|
|
booking_date: Some("2024-01-05".to_string()),
|
|
value_date: None,
|
|
booking_date_time: None,
|
|
value_date_time: None,
|
|
transaction_amount: gocardless_client::models::TransactionAmount {
|
|
amount: "100.00".to_string(),
|
|
currency: "EUR".to_string(),
|
|
},
|
|
currency_exchange: None,
|
|
creditor_name: Some("Creditor".to_string()),
|
|
creditor_account: None,
|
|
ultimate_creditor: None,
|
|
debtor_name: None,
|
|
debtor_account: None,
|
|
ultimate_debtor: None,
|
|
remittance_information_unstructured: Some("Payment".to_string()),
|
|
remittance_information_unstructured_array: None,
|
|
remittance_information_structured: None,
|
|
remittance_information_structured_array: None,
|
|
additional_information: None,
|
|
purpose_code: None,
|
|
bank_transaction_code: None,
|
|
proprietary_bank_transaction_code: None,
|
|
internal_transaction_id: None,
|
|
};
|
|
let range = CachedRange {
|
|
start_date: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
|
|
end_date: NaiveDate::from_ymd_opt(2024, 1, 31).unwrap(),
|
|
transactions: vec![tx1],
|
|
};
|
|
let cache = AccountTransactionCache {
|
|
account_id: "test".to_string(),
|
|
ranges: vec![range],
|
|
};
|
|
let start = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
|
let end = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
|
|
let cached = cache.get_cached_transactions(start, end);
|
|
assert_eq!(cached.len(), 1);
|
|
assert_eq!(cached[0].transaction_id, Some("tx1".to_string()));
|
|
}
|
|
}
|