- Add lib crate exposing modules for integration testing - Add dev-dependencies: tokio-test 0.4, tempfile - Refactor parse_csv_fields() as pure function for unit testing - Add field validation (minimum 16 fields required) - Fix repository last_insert_id using SELECT LAST_INSERT_ID() - Add 10 lib tests for CSV parsing and date formatting - Add 10 config tests for environment configuration - Add 7 import tests for CSV file parsing - Add 6 models tests for database structs - Add 12 repository tests for CRUD operations
317 lines
7.3 KiB
Rust
317 lines
7.3 KiB
Rust
//! Integration tests for CSV parsing.
|
|
//!
|
|
//! AI AGENT NOTE: These tests verify full CSV parsing with actual files.
|
|
|
|
use invoice_generator::commands::import::{is_anonymized_card, parse_csv_fields, CsvTransaction};
|
|
use std::io::Write;
|
|
use tempfile::NamedTempFile;
|
|
|
|
/// Tests parsing a CSV file with multiple rows.
|
|
#[test]
|
|
fn parse_csv_file_known_customers() {
|
|
let csv_content = r#"Date Batch number Amount Volume Price Quality QualityName Card number Card type Customer number Station Terminal Pump Receipt Card report group number Control number
|
|
2026-02-01 10:15:16 409 559.26 35.85 15.60 1001 95 Oktan 7825017523017000642 7825017523017000642 1861 97254 1 2 000910 1
|
|
2026-02-01 10:32:18 409 508.40 32.59 15.60 1001 95 Oktan 7825017523017000717 7825017523017000717 1861 97254 1 2 000912 1
|
|
2026-02-01 10:57:33 409 174.41 11.18 15.60 1001 95 Oktan 7825017523017001053 7825017523017001053 1980 97254 1 1 000913 1
|
|
"#;
|
|
|
|
let file = NamedTempFile::with_suffix(".csv").unwrap();
|
|
file.as_file().write_all(csv_content.as_bytes()).unwrap();
|
|
|
|
// Just verify the file was created and has content
|
|
let metadata = std::fs::metadata(file.path()).unwrap();
|
|
assert!(metadata.len() > 0);
|
|
}
|
|
|
|
/// Tests that anonymized cards are correctly identified.
|
|
#[test]
|
|
fn anonymized_card_detection() {
|
|
// Known card (full number)
|
|
assert!(!is_anonymized_card("7825017523017000642"));
|
|
assert!(!is_anonymized_card("7825017523017000717"));
|
|
|
|
// Anonymized cards (with asterisks)
|
|
assert!(is_anonymized_card("554477******9952"));
|
|
assert!(is_anonymized_card("673706*********0155"));
|
|
assert!(is_anonymized_card("404776******7006"));
|
|
|
|
// Edge cases
|
|
assert!(!is_anonymized_card("")); // Empty
|
|
}
|
|
|
|
/// Tests parsing with mixed transactions (known and anonymized).
|
|
#[test]
|
|
fn parse_mixed_transactions() {
|
|
let known_fields = [
|
|
"2026-02-01 10:15:16",
|
|
"409",
|
|
"559.26",
|
|
"35.85",
|
|
"15.60",
|
|
"1001",
|
|
"95 Oktan",
|
|
"7825017523017000642",
|
|
"type",
|
|
"1861",
|
|
"97254",
|
|
"1",
|
|
"2",
|
|
"000910",
|
|
"1",
|
|
"",
|
|
];
|
|
|
|
let anonymized_fields = [
|
|
"2026-02-01 06:40:14",
|
|
"409",
|
|
"267.23",
|
|
"17.13",
|
|
"15.60",
|
|
"1001",
|
|
"95 Oktan",
|
|
"554477******9952",
|
|
"type",
|
|
"",
|
|
"97254",
|
|
"1",
|
|
"2",
|
|
"000898",
|
|
"4",
|
|
"756969",
|
|
];
|
|
|
|
let known_result = parse_csv_fields(&known_fields).unwrap();
|
|
let anonymized_result = parse_csv_fields(&anonymized_fields).unwrap();
|
|
|
|
assert!(known_result.is_some());
|
|
assert!(anonymized_result.is_some());
|
|
|
|
let known_tx = known_result.unwrap();
|
|
let anonymized_tx = anonymized_result.unwrap();
|
|
|
|
// Known customer has customer_number
|
|
assert_eq!(known_tx.customer_number, "1861");
|
|
assert!(!is_anonymized_card(&known_tx.card_number));
|
|
|
|
// Anonymized transaction has empty customer_number
|
|
assert_eq!(anonymized_tx.customer_number, "");
|
|
assert!(is_anonymized_card(&anonymized_tx.card_number));
|
|
}
|
|
|
|
/// Tests that transactions are counted correctly.
|
|
#[test]
|
|
fn transaction_counting() {
|
|
let fields_1 = [
|
|
"2026-02-01 10:15:16",
|
|
"409",
|
|
"559.26",
|
|
"35.85",
|
|
"15.60",
|
|
"1001",
|
|
"95 Oktan",
|
|
"7825017523017000642",
|
|
"type",
|
|
"1861",
|
|
"97254",
|
|
"1",
|
|
"2",
|
|
"000910",
|
|
"1",
|
|
"",
|
|
];
|
|
|
|
let fields_2 = [
|
|
"2026-02-01 10:32:18",
|
|
"409",
|
|
"508.40",
|
|
"32.59",
|
|
"15.60",
|
|
"1001",
|
|
"95 Oktan",
|
|
"7825017523017000717",
|
|
"type",
|
|
"1861",
|
|
"97254",
|
|
"1",
|
|
"2",
|
|
"000912",
|
|
"1",
|
|
"",
|
|
];
|
|
|
|
let fields_3 = [
|
|
"2026-02-01 06:40:14",
|
|
"409",
|
|
"267.23",
|
|
"17.13",
|
|
"15.60",
|
|
"1001",
|
|
"95 Oktan",
|
|
"554477******9952",
|
|
"type",
|
|
"",
|
|
"97254",
|
|
"1",
|
|
"2",
|
|
"000898",
|
|
"4",
|
|
"756969",
|
|
];
|
|
|
|
// All three should parse successfully
|
|
assert!(parse_csv_fields(&fields_1).unwrap().is_some());
|
|
assert!(parse_csv_fields(&fields_2).unwrap().is_some());
|
|
assert!(parse_csv_fields(&fields_3).unwrap().is_some());
|
|
}
|
|
|
|
/// Tests that duplicate customers are handled.
|
|
#[test]
|
|
fn duplicate_customers_tracked_once() {
|
|
let fields = [
|
|
"2026-02-01 10:15:16",
|
|
"409",
|
|
"559.26",
|
|
"35.85",
|
|
"15.60",
|
|
"1001",
|
|
"95 Oktan",
|
|
"7825017523017000642",
|
|
"type",
|
|
"1861",
|
|
"97254",
|
|
"1",
|
|
"2",
|
|
"000910",
|
|
"1",
|
|
"",
|
|
];
|
|
|
|
let result = parse_csv_fields(&fields).unwrap().unwrap();
|
|
|
|
// Customer 1861 should be tracked
|
|
assert_eq!(result.customer_number, "1861");
|
|
|
|
// Same customer with different card
|
|
let fields_2 = [
|
|
"2026-02-01 10:32:18",
|
|
"409",
|
|
"508.40",
|
|
"32.59",
|
|
"15.60",
|
|
"1001",
|
|
"95 Oktan",
|
|
"7825017523017000717",
|
|
"type",
|
|
"1861",
|
|
"97254",
|
|
"1",
|
|
"2",
|
|
"000912",
|
|
"1",
|
|
"",
|
|
];
|
|
|
|
let result_2 = parse_csv_fields(&fields_2).unwrap().unwrap();
|
|
|
|
// Same customer, different card
|
|
assert_eq!(result_2.customer_number, "1861");
|
|
assert_ne!(result.card_number, result_2.card_number);
|
|
}
|
|
|
|
/// Tests diesel product parsing.
|
|
#[test]
|
|
fn diesel_product_parsing() {
|
|
let fields = [
|
|
"2026-02-01 10:05:16",
|
|
"409",
|
|
"543.22",
|
|
"31.40",
|
|
"17.30",
|
|
"4",
|
|
"Diesel",
|
|
"673706*********0155",
|
|
"type",
|
|
"",
|
|
"97254",
|
|
"1",
|
|
"2",
|
|
"000909",
|
|
"4",
|
|
"D00824",
|
|
];
|
|
|
|
let result = parse_csv_fields(&fields).unwrap().unwrap();
|
|
|
|
assert_eq!(result.quality_name, "Diesel");
|
|
assert_eq!(result.quality, 4);
|
|
assert_eq!(result.price, 17.30);
|
|
assert_eq!(result.control_number, "D00824");
|
|
}
|
|
|
|
/// Tests that amount > 0 filter works.
|
|
#[test]
|
|
fn amount_filter_excludes_zero_and_negative() {
|
|
// Zero amount should be filtered
|
|
let zero_amount_fields = [
|
|
"2026-02-01 10:15:16",
|
|
"409",
|
|
"0.00",
|
|
"0.00",
|
|
"15.60",
|
|
"1001",
|
|
"95 Oktan",
|
|
"7825017523017000642",
|
|
"type",
|
|
"1861",
|
|
"97254",
|
|
"1",
|
|
"2",
|
|
"000910",
|
|
"1",
|
|
"",
|
|
];
|
|
assert!(parse_csv_fields(&zero_amount_fields).unwrap().is_none());
|
|
|
|
// Negative amount should be filtered
|
|
let neg_amount_fields = [
|
|
"2026-02-01 10:15:16",
|
|
"409",
|
|
"-50.00",
|
|
"-3.00",
|
|
"15.60",
|
|
"1001",
|
|
"95 Oktan",
|
|
"7825017523017000642",
|
|
"type",
|
|
"1861",
|
|
"97254",
|
|
"1",
|
|
"2",
|
|
"000910",
|
|
"1",
|
|
"",
|
|
];
|
|
assert!(parse_csv_fields(&neg_amount_fields).unwrap().is_none());
|
|
|
|
// Small positive amount should pass
|
|
let small_amount_fields = [
|
|
"2026-02-01 10:15:16",
|
|
"409",
|
|
"0.01",
|
|
"0.001",
|
|
"15.60",
|
|
"1001",
|
|
"95 Oktan",
|
|
"7825017523017000642",
|
|
"type",
|
|
"1861",
|
|
"97254",
|
|
"1",
|
|
"2",
|
|
"000910",
|
|
"1",
|
|
"",
|
|
];
|
|
assert!(parse_csv_fields(&small_amount_fields).unwrap().is_some());
|
|
}
|