Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,12 @@ mod tests {
assert_ne!(a, b);
}

#[test]
fn test_deobfuscate_rejects_invalid_hex() {
let result = deobfuscate("not-hex", OBFUSCATION_KEY);
assert!(result.is_err());
}

#[test]
fn test_days_in_month() {
assert_eq!(days_in_month(2024, 1), 31);
Expand Down Expand Up @@ -1447,6 +1453,30 @@ mod tests {
assert!(result.is_none());
}

#[test]
fn test_compute_next_expected_weekly_and_biweekly_future_dates() {
assert_eq!(
compute_next_expected("weekly", 15, "2030-01-01"),
Some("2030-01-08".to_string())
);
assert_eq!(
compute_next_expected("biweekly", 15, "2030-01-01"),
Some("2030-01-15".to_string())
);
}

#[test]
fn test_compute_next_expected_clamps_invalid_day_and_defaults_unknown_frequency() {
assert_eq!(
compute_next_expected("monthly", 0, "2030-01-15"),
Some("2030-02-01".to_string())
);
assert_eq!(
compute_next_expected("unknown", 10, "2030-01-15"),
Some("2030-02-10".to_string())
);
}

#[test]
fn test_anonymize_label() {
assert_eq!(anonymize_label("CARTE 17/03 CARREFOUR CB*1234 5678"), "CARTE CARREFOUR CB*1234");
Expand Down
55 changes: 55 additions & 0 deletions src-tauri/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,58 @@ pub fn get_migrations() -> Vec<Migration> {
},
]
}

#[cfg(test)]
mod tests {
use super::*;
use tauri_plugin_sql::MigrationKind;

#[test]
fn migrations_are_ordered_and_non_empty() {
let migrations = get_migrations();

assert_eq!(migrations.len(), 13);
for (index, migration) in migrations.iter().enumerate() {
assert_eq!(migration.version, (index + 1) as i64);
assert!(!migration.description.trim().is_empty());
assert!(!migration.sql.trim().is_empty());
assert!(matches!(migration.kind, MigrationKind::Up));
}
}

#[test]
fn migrations_cover_current_schema_features() {
let sql = get_migrations()
.into_iter()
.map(|migration| migration.sql.to_string())
.collect::<Vec<_>>()
.join("\n");

for required in [
"CREATE TABLE IF NOT EXISTS accounts",
"CREATE TABLE IF NOT EXISTS transactions",
"CREATE TABLE IF NOT EXISTS budget_series",
"CREATE TABLE IF NOT EXISTS transaction_splits",
"CREATE TABLE IF NOT EXISTS recurring_transactions",
"CREATE TABLE IF NOT EXISTS budget_carry_over",
"CREATE TABLE IF NOT EXISTS app_settings",
"CREATE TABLE IF NOT EXISTS ai_settings",
"ALTER TABLE transactions ADD COLUMN is_reconciled",
"ALTER TABLE accounts ADD COLUMN low_balance_threshold",
] {
assert!(sql.contains(required), "missing migration fragment: {required}");
}
}

#[test]
fn migration_versions_are_unique() {
let mut versions = get_migrations()
.into_iter()
.map(|migration| migration.version)
.collect::<Vec<_>>();
versions.sort_unstable();
versions.dedup();

assert_eq!(versions.len(), 13);
}
}
130 changes: 130 additions & 0 deletions src-tauri/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,4 +656,134 @@ mod tests {
assert_eq!(config.skip_lines, 0);
assert_eq!(config.date_format, "%d/%m/%Y");
}

#[test]
fn test_detect_format() {
assert_eq!(detect_format("<OFX><BANKMSGSRSV1></OFX>"), "ofx");
assert_eq!(detect_format("!Type:Bank\nD15/03/2026\nT-10\n^"), "qif");
assert_eq!(detect_format("date;label;amount\n15/03/2026;EDF;-42,00\n"), "csv");
}

#[test]
fn test_parse_ofx_sgml_without_closing_transaction_tag() {
let ofx = r#"<OFX>
<BANKTRANLIST>
<STMTTRN>
<DTPOSTED>20260315120000
<TRNAMT>-42,35
<NAME>PRLV EDF
<MEMO>Facture mars
<FITID>FIT-1
</BANKTRANLIST>
</OFX>"#;

let (txs, _, _) = parse_ofx(ofx).unwrap();

assert_eq!(txs.len(), 1);
assert_eq!(txs[0].date, "2026-03-15");
assert_eq!(txs[0].label, "PRLV EDF");
assert_eq!(txs[0].note, Some("Facture mars".to_string()));
assert_eq!(txs[0].fitid, Some("FIT-1".to_string()));
assert!((txs[0].amount - (-42.35)).abs() < 0.01);
}

#[test]
fn test_parse_qif_without_trailing_separator_and_two_digit_year() {
let qif = "!Type:Bank\nD15/03/26\nT-12,34\nPBOULANGERIE\nMCarte bancaire";

let txs = parse_qif(qif).unwrap();

assert_eq!(txs.len(), 1);
assert_eq!(txs[0].date, "2026-03-15");
assert_eq!(txs[0].label, "BOULANGERIE");
assert_eq!(txs[0].note, Some("Carte bancaire".to_string()));
assert!((txs[0].amount - (-12.34)).abs() < 0.01);
}

#[test]
fn test_detect_csv_config_for_french_debit_credit_export() {
let csv = "Date operation;Libelle;Debit;Credit\n15/03/2026;CARREFOUR;42,35;\n";

let config = detect_csv_config(csv);

assert_eq!(config.delimiter, ';');
assert_eq!(config.decimal_separator, ',');
assert_eq!(config.date_column, 0);
assert_eq!(config.label_column, 1);
assert_eq!(config.debit_column, Some(2));
assert_eq!(config.credit_column, Some(3));
assert_eq!(config.date_format, "%d/%m/%Y");
}

#[test]
fn test_detect_csv_config_for_comma_decimal_dot_export() {
let csv = "date,label,amount\n2026-03-15,CARREFOUR,-42.35\n";

let config = detect_csv_config(csv);

assert_eq!(config.delimiter, ',');
assert_eq!(config.decimal_separator, '.');
assert_eq!(config.date_format, "%Y-%m-%d");
}

#[test]
fn test_parse_csv_skips_rows_without_required_fields_and_handles_thousands() {
let csv = "date;label;amount\n15/03/2026;LOYER;-1 234,56\n16/03/2026;;-10,00\n;EDF;-42,00\n";
let config = CsvConfig {
delimiter: ';',
date_column: 0,
label_column: 1,
amount_column: 2,
debit_column: None,
credit_column: None,
date_format: "%d/%m/%Y".to_string(),
skip_lines: 1,
decimal_separator: ',',
};

let txs = parse_csv(csv, &config).unwrap();

assert_eq!(txs.len(), 1);
assert_eq!(txs[0].date, "2026-03-15");
assert_eq!(txs[0].label, "LOYER");
assert!((txs[0].amount - (-1234.56)).abs() < 0.01);
}

#[test]
fn test_find_duplicate_indices_uses_fitid_then_transaction_hash() {
let new_txs = vec![
RawTransaction {
date: "2026-03-15".to_string(),
label: "CARREFOUR".to_string(),
original_label: "CARREFOUR".to_string(),
amount: -42.35,
note: None,
fitid: Some("FIT-1".to_string()),
},
RawTransaction {
date: "2026-03-16".to_string(),
label: "EDF".to_string(),
original_label: "EDF".to_string(),
amount: -100.0,
note: None,
fitid: None,
},
RawTransaction {
date: "2026-03-17".to_string(),
label: "NOUVEAU".to_string(),
original_label: "NOUVEAU".to_string(),
amount: -5.0,
note: None,
fitid: None,
},
];

let duplicates = find_duplicate_indices(
&new_txs,
&["FIT-1".to_string()],
&[("2026-03-16".to_string(), -10000, "edf".to_string())],
);

assert_eq!(duplicates, vec![0, 1]);
}
}
Loading