fix: resolve PostgreSQL type mismatches and add willexecutor debug logging

- Fix get_next_address_index: use try_get::<i32> for PG SERIAL/INTEGER columns
- Fix search_tx: use try_get::<i32> for PG status column
- Fix execute_insert: parse locktime (String→i64) and in_vout (String→i32)
  before binding to PG INTEGER columns
- Fix execute_insert: bind tbl_out vout as i32, amount as String for PG
- Fix save_new_address: cast xpub i64 to i32 for PG INTEGER column
- Fix get_pending_txs: cast i64 bind params to i32, use try_get::<i32> for reads
- Fix get_stats: use try_get::<i32> for all numeric PG INTEGER columns
- Add trace logging in parse_request_transactions for xpub address matching
- Add trace logging in get_all_addresses_by_xpub for query debugging
This commit is contained in:
2026-08-18 03:14:03 -04:00
parent 8dc344cbd1
commit 8f764f06b2
65 changed files with 4879 additions and 1188 deletions

View File

@@ -1,15 +1,62 @@
use bal_server::db::open_db;
use sqlite::State;
use std::fs;
use bal_server::db::{DatabasePool, create_database, open_database};
use sqlx::Row;
#[test]
fn test_open_db_blocks_traversal() {
let res = open_db("../etc/passwd");
#[tokio::test]
async fn test_open_database_sqlite() {
let pool = open_database("sqlite", "sqlite::memory:").await;
assert!(pool.is_ok(), "Opening SQLite in-memory should succeed");
}
#[tokio::test]
async fn test_open_database_invalid_backend() {
let pool = open_database("oracle", "connection_string").await;
assert!(pool.is_err(), "Unknown backend should fail");
}
#[tokio::test]
async fn test_create_database_sqlite() {
let pool = open_database("sqlite", "sqlite::memory:").await.unwrap();
let result = create_database(&pool).await;
assert!(result.is_ok(), "Creating SQLite schema should succeed");
}
#[tokio::test]
async fn test_create_database_is_idempotent() {
let pool = open_database("sqlite", "sqlite::memory:").await.unwrap();
create_database(&pool).await.unwrap();
let result = create_database(&pool).await;
assert!(
result.is_ok(),
"Creating schema twice should succeed (idempotent)"
);
}
#[tokio::test]
async fn test_sqlite_wal_mode() {
let tmp_path = std::env::temp_dir().join("tmp_test_wal_mode.db");
let path_str = tmp_path.to_str().unwrap();
let _ = std::fs::remove_file(&tmp_path);
let dsn = format!("sqlite:{}?mode=rwc", path_str);
let pool = open_database("sqlite", &dsn).await.unwrap();
if let DatabasePool::SQLite(p) = &pool {
let row = sqlx::query("PRAGMA journal_mode")
.fetch_one(p)
.await
.unwrap();
let mode: String = row.try_get(0).unwrap();
assert_eq!(mode, "wal", "SQLite journal mode should be WAL");
}
drop(pool);
let _ = std::fs::remove_file(&tmp_path);
let _ = std::fs::remove_file(format!("{}-shm", path_str));
let _ = std::fs::remove_file(format!("{}-wal", path_str));
}
#[tokio::test]
async fn test_open_database_blocks_traversal() {
let res = open_database("sqlite", "sqlite:../etc/passwd").await;
assert!(res.is_err(), "Path with '..' should be rejected");
let err = match res {
Err(e) => e,
Ok(_) => panic!("Expected error for traversal path"),
};
let err = res.err().unwrap();
assert!(
err.contains("'..'"),
"Error should mention directory traversal: {}",
@@ -17,15 +64,17 @@ fn test_open_db_blocks_traversal() {
);
}
#[test]
fn test_open_db_blocks_forbidden_absolute() {
for path in ["/etc/passwd", "/proc/self/mem", "/dev/null", "/usr/bin/ls"] {
let res = open_db(path);
#[tokio::test]
async fn test_open_database_blocks_forbidden_absolute() {
for path in [
"sqlite:/etc/passwd",
"sqlite:/proc/self/mem",
"sqlite:/dev/null",
"sqlite:/usr/bin/ls",
] {
let res = open_database("sqlite", path).await;
assert!(res.is_err(), "Absolute path {} should be rejected", path);
let err = match res {
Err(e) => e,
Ok(_) => panic!("Expected error for forbidden path {}", path),
};
let err = res.err().unwrap();
assert!(
err.contains("forbidden"),
"Error should mention forbidden prefix: {}",
@@ -34,59 +83,26 @@ fn test_open_db_blocks_forbidden_absolute() {
}
}
#[test]
fn test_open_db_allows_relative() {
let test_path = "tmp_test_bal.db";
let _ = fs::remove_file(test_path);
let res = open_db(test_path);
assert!(res.is_ok(), "Valid relative path should be allowed");
let db = res.unwrap();
drop(db);
let _ = fs::remove_file(test_path);
}
#[tokio::test]
async fn test_open_database_rejects_symlink() {
let tmp_dir = std::env::temp_dir();
let real = tmp_dir.join("tmp_test_real_symlink.db");
let link = tmp_dir.join("tmp_test_link_symlink.db");
let _ = std::fs::remove_file(&real);
let _ = std::fs::remove_file(&link);
std::fs::File::create(&real).unwrap();
std::os::unix::fs::symlink(&real, &link).unwrap();
#[test]
fn test_open_db_wal_pragmas_set() {
let test_path = "tmp_test_wal.db";
let _ = fs::remove_file(test_path);
let _ = fs::remove_file(format!("{}-shm", test_path));
let _ = fs::remove_file(format!("{}-wal", test_path));
let db = open_db(test_path).expect("Should open DB");
let mut stmt = db.prepare("PRAGMA journal_mode;").unwrap();
if let Ok(State::Row) = stmt.next() {
let mode: String = stmt.read(0).unwrap();
assert_eq!(mode, "wal", "SQLite journal mode should be WAL");
} else {
panic!("Could not read journal_mode pragma");
}
let _ = fs::remove_file(test_path);
let _ = fs::remove_file(format!("{}-shm", test_path));
let _ = fs::remove_file(format!("{}-wal", test_path));
}
#[test]
fn test_open_db_rejects_symlink() {
let real = "tmp_test_real.db";
let link = "tmp_test_link.db";
let _ = fs::remove_file(real);
let _ = fs::remove_file(link);
fs::File::create(real).unwrap();
std::os::unix::fs::symlink(real, link).unwrap();
let res = open_db(link);
let dsn = format!("sqlite:{}?mode=rwc", link.to_str().unwrap());
let res = open_database("sqlite", &dsn).await;
assert!(res.is_err(), "Symlink DB path should be rejected");
let err = match res {
Err(e) => e,
Ok(_) => panic!("Expected error for symlink"),
};
let err = res.err().unwrap();
assert!(
err.contains("symlink"),
"Error should mention symlink: {}",
err
);
let _ = fs::remove_file(real);
let _ = fs::remove_file(link);
let _ = std::fs::remove_file(&real);
let _ = std::fs::remove_file(&link);
}