# Tests for OFX/QFX import functionality # Sample OFX 1.x content for testing (SGML format) sample_ofx_v1 <- ' OFXHEADER:100 DATA:OFXSGML VERSION:102 SECURITY:NONE ENCODING:USASCII CHARSET:1252 COMPRESSION:NONE OLDFILEUID:NONE NEWFILEUID:NONE 0 INFO 20240115120000 ENG Test Bank 12345 1001 0 INFO USD 123456789 9876543210 CHECKING 20240101 20240115 DEBIT 20240105120000 -42.50 2024010500001 GROCERY STORE Purchase groceries CREDIT 20240110 1500.00 2024011000001 DIRECT DEPOSIT Payroll CHECK 20240112 -200.00 2024011200001 Check 1234 5432.10 20240115 ' # Sample OFX 2.x content (XML format) sample_ofx_v2 <- ' 0INFO 20240115120000 ENG XML Test Bank67890 2001 0INFO EUR 987654321 1234567890 SAVINGS 20240101 20240115 DEBIT 20240108 -75.00 XML2024010800001 UTILITY PAYMENT Electric bill CREDIT 20240115 250.00 XML2024011500001 INTEREST PAYMENT ' # Helper to create temp OFX file create_temp_ofx <- function(content, suffix = ".ofx") { f <- tempfile(fileext = suffix) writeLines(content, f) f } test_that("detect_ofx_version_cpp correctly identifies OFX versions", { # OFX 1.x (SGML) v1_result <- detect_ofx_version_cpp(sample_ofx_v1) expect_equal(v1_result, "1") # OFX 2.x (XML) v2_result <- detect_ofx_version_cpp(sample_ofx_v2) expect_equal(v2_result, "2") # Unknown format unknown <- detect_ofx_version_cpp("This is not an OFX file") expect_equal(unknown, "unknown") }) test_that("detect_ofx_version works with file paths", { skip_if_not(exists("detect_ofx_version")) # Create temp files f1 <- create_temp_ofx(sample_ofx_v1) f2 <- create_temp_ofx(sample_ofx_v2) on.exit({ unlink(f1) unlink(f2) }) expect_equal(detect_ofx_version(f1), "1") expect_equal(detect_ofx_version(f2), "2") }) test_that("parse_ofx_cpp extracts transactions from OFX 1.x", { result <- parse_ofx_cpp(sample_ofx_v1) expect_equal(result$n_transactions, 3L) expect_equal(result$currency, "USD") # Check dates expect_equal(result$dates[1], "2024-01-05") expect_equal(result$dates[2], "2024-01-10") expect_equal(result$dates[3], "2024-01-12") # Check amounts expect_equal(result$amounts[1], -42.50) expect_equal(result$amounts[2], 1500.00) expect_equal(result$amounts[3], -200.00) # Check names expect_equal(result$names[1], "GROCERY STORE") expect_equal(result$names[2], "DIRECT DEPOSIT") expect_equal(result$names[3], "Check 1234") # Check FITIDs expect_equal(result$fitids[1], "2024010500001") expect_equal(result$fitids[2], "2024011000001") expect_equal(result$fitids[3], "2024011200001") # Check transaction types expect_equal(result$trntypes[1], "DEBIT") expect_equal(result$trntypes[2], "CREDIT") expect_equal(result$trntypes[3], "CHECK") }) test_that("parse_ofx_cpp extracts transactions from OFX 2.x", { result <- parse_ofx_cpp(sample_ofx_v2) expect_equal(result$n_transactions, 2L) expect_equal(result$currency, "EUR") # Check first transaction expect_equal(result$dates[1], "2024-01-08") expect_equal(result$amounts[1], -75.00) expect_equal(result$names[1], "UTILITY PAYMENT") expect_equal(result$fitids[1], "XML2024010800001") # Check second transaction expect_equal(result$dates[2], "2024-01-15") expect_equal(result$amounts[2], 250.00) expect_equal(result$names[2], "INTEREST PAYMENT") }) test_that("extract_ofx_account_info extracts account metadata", { result <- extract_ofx_account_info(sample_ofx_v1) expect_equal(result$bank_id, "123456789") expect_equal(result$account_id, "9876543210") expect_equal(result$account_type, "CHECKING") expect_equal(result$org_name, "Test Bank") expect_equal(result$fid, "12345") expect_equal(result$date_start, "2024-01-01") expect_equal(result$date_end, "2024-01-15") }) test_that("import_ofx creates proper tibble structure", { skip_if_not(exists("import_ofx")) f <- create_temp_ofx(sample_ofx_v1) on.exit(unlink(f)) result <- import_ofx(f) # Check tibble structure expect_s3_class(result, "tbl_df") expect_equal(nrow(result), 3) # Check column names expected_cols <- c("date", "description", "amount", "currency", "external_id", "memo", "transaction_type") expect_true(all(expected_cols %in% names(result))) # Check data types expect_s3_class(result$date, "Date") expect_type(result$description, "character") expect_type(result$amount, "double") expect_type(result$currency, "character") expect_type(result$external_id, "character") # Check attributes expect_equal(attr(result, "ofx_version"), "1") expect_type(attr(result, "ofx_account"), "list") }) test_that("import_ofx handles empty files gracefully", { skip_if_not(exists("import_ofx")) # Create minimal OFX with no transactions empty_ofx <- ' OFXHEADER:100 DATA:OFXSGML VERSION:102 USD 20240101 20240115 ' f <- create_temp_ofx(empty_ofx) on.exit(unlink(f)) expect_warning(result <- import_ofx(f), "No transactions found") expect_equal(nrow(result), 0) expect_true(all(c("date", "description", "amount") %in% names(result))) }) test_that("import_ofx with account_mapping adds mapped_account column", { skip_if_not(exists("import_ofx")) f <- create_temp_ofx(sample_ofx_v1) on.exit(unlink(f)) mapping <- list( DEBIT = "expense_guid_123", CREDIT = "income_guid_456" ) result <- import_ofx(f, account_mapping = mapping) expect_true("mapped_account" %in% names(result)) # DEBIT transaction should map to expense expect_equal(result$mapped_account[result$transaction_type == "DEBIT"], "expense_guid_123") # CREDIT transaction should map to income expect_equal(result$mapped_account[result$transaction_type == "CREDIT"], "income_guid_456") # CHECK transaction has no mapping expect_true(is.na(result$mapped_account[result$transaction_type == "CHECK"])) }) test_that("import_ofx handles file not found", { skip_if_not(exists("import_ofx")) expect_error( import_ofx("/nonexistent/path/to/file.ofx"), "OFX file not found" ) }) test_that("parse_ofx_cpp handles malformed content gracefully", { # Missing required tags malformed <- "20240101" result <- parse_ofx_cpp(malformed) # Should parse without error but may have missing data expect_type(result, "list") expect_true("n_transactions" %in% names(result)) }) test_that("OFX date parsing handles various formats", { # Test via parse_ofx_cpp with different date formats ofx_short_date <- ' USD DEBIT 20240315 -10.00 TEST001 Short Date Test ' ofx_long_date <- ' USD DEBIT 20240315143022.000[-5:EST] -10.00 TEST002 Long Date Test ' result1 <- parse_ofx_cpp(ofx_short_date) expect_equal(result1$dates[1], "2024-03-15") result2 <- parse_ofx_cpp(ofx_long_date) expect_equal(result2$dates[1], "2024-03-15") }) test_that("print_ofx_summary produces output", { skip_if_not(exists("print_ofx_summary")) f <- create_temp_ofx(sample_ofx_v1) on.exit(unlink(f)) ofx_data <- import_ofx(f) expect_output(print_ofx_summary(ofx_data), "OFX Import Summary") expect_output(print_ofx_summary(ofx_data), "Transactions:") expect_output(print_ofx_summary(ofx_data), "Date Range:") }) test_that("get_ofx_account_info returns proper structure", { skip_if_not(exists("get_ofx_account_info")) f <- create_temp_ofx(sample_ofx_v1) on.exit(unlink(f)) info <- get_ofx_account_info(f) expect_type(info, "list") expect_true("account_id" %in% names(info)) expect_true("bank_id" %in% names(info)) expect_true("account_type" %in% names(info)) expect_true("org_name" %in% names(info)) }) # Integration test with sample.ofx file if it exists test_that("import_ofx works with sample.ofx", { skip_if_not(exists("import_ofx")) sample_path <- system.file("extdata", "sample.ofx", package = "gnucashr") skip_if(sample_path == "", "sample.ofx not found") result <- import_ofx(sample_path) expect_s3_class(result, "tbl_df") expect_gt(nrow(result), 0) })