use oxisql_core::Value;
use oxisql_postgres::{OwnedParam, PgError};
#[test]
fn pg_error_type_conversion_display() {
let e = PgError::TypeConversion("unexpected boolean".to_string());
let msg = e.to_string();
assert!(!msg.is_empty(), "error message must not be empty");
assert!(
msg.contains("type conversion"),
"message should mention 'type conversion', got: {msg}"
);
}
#[test]
fn pg_error_tls_display() {
let e = PgError::Tls("certificate validation failed".to_string());
let msg = e.to_string();
assert!(!msg.is_empty());
assert!(
msg.contains("TLS"),
"message should mention 'TLS', got: {msg}"
);
}
#[test]
fn pg_error_converts_to_oxisql_error() {
use oxisql_core::OxiSqlError;
let pg_err = PgError::TypeConversion("bad int".to_string());
let oxisql_err: OxiSqlError = pg_err.into();
let msg = oxisql_err.to_string();
assert!(!msg.is_empty());
}
#[test]
fn pg_tls_error_converts() {
use oxisql_core::OxiSqlError;
let pg_err = PgError::Tls("handshake timeout".to_string());
let oxisql_err: OxiSqlError = pg_err.into();
match oxisql_err {
OxiSqlError::Other(msg) => assert!(msg.contains("handshake")),
other => panic!("expected OxiSqlError::Other, got {other:?}"),
}
}
#[test]
fn pg_error_constraint_violation_display() {
use oxisql_postgres::PgError;
let e = PgError::ConstraintViolation {
constraint: "users_email_key".into(),
detail: "Key (email)=(test@example.com) already exists.".into(),
};
let s = format!("{e}");
assert!(s.contains("users_email_key"), "got: {s}");
}
#[test]
fn pg_error_constraint_converts_to_oxisql() {
use oxisql_core::OxiSqlError;
use oxisql_postgres::PgError;
let e = PgError::ConstraintViolation {
constraint: "pk_orders".into(),
detail: "Duplicate key value violates unique constraint".into(),
};
let oe: OxiSqlError = e.into();
assert!(matches!(
oe,
OxiSqlError::ConstraintViolation(_) | OxiSqlError::Other(_)
));
}
#[test]
fn pg_timeout_display() {
use oxisql_postgres::PgError;
let e = PgError::Timeout("30s".into());
assert!(format!("{e}").contains("timeout") || format!("{e}").contains("30s"));
}
#[test]
fn pg_connection_builder_constructs() {
use oxisql_postgres::PgConnectionBuilder;
let builder = PgConnectionBuilder::new()
.host("localhost")
.port(5432)
.dbname("testdb")
.user("testuser");
let _ = builder; }
#[test]
fn value_to_param_null() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::Null);
assert!(matches!(p, OwnedParam::Null));
}
#[test]
fn value_to_param_bool_true() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::Bool(true));
assert!(matches!(p, OwnedParam::Bool(true)));
}
#[test]
fn value_to_param_bool_false() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::Bool(false));
assert!(matches!(p, OwnedParam::Bool(false)));
}
#[test]
fn value_to_param_i64() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::I64(42));
assert!(matches!(p, OwnedParam::I64(42)));
}
#[test]
fn value_to_param_i64_min() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::I64(i64::MIN));
assert!(matches!(p, OwnedParam::I64(i64::MIN)));
}
#[test]
fn value_to_param_f64() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::F64(1.5_f64));
match p {
OwnedParam::F64(v) => assert!((v - 1.5_f64).abs() < f64::EPSILON),
other => panic!("expected OwnedParam::F64, got {other:?}"),
}
}
#[test]
fn value_to_param_text() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::Text("hello".to_string()));
match p {
OwnedParam::Text(s) => assert_eq!(s, "hello"),
other => panic!("expected OwnedParam::Text, got {other:?}"),
}
}
#[test]
fn value_to_param_blob() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::Blob(vec![1u8, 2, 3]));
match p {
OwnedParam::Blob(b) => assert_eq!(b, vec![1u8, 2, 3]),
other => panic!("expected OwnedParam::Blob, got {other:?}"),
}
}
#[test]
fn value_to_param_timestamp_is_text() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::Timestamp(1_000_001_i64));
match p {
OwnedParam::Text(s) => {
assert_eq!(s, "1.000001", "unexpected timestamp encoding: {s}");
}
other => panic!("expected OwnedParam::Text for Timestamp, got {other:?}"),
}
}
#[test]
fn value_to_param_date_is_text() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::Date(0_i32));
match p {
OwnedParam::Text(s) => assert_eq!(s, "0"),
other => panic!("expected OwnedParam::Text for Date, got {other:?}"),
}
}
#[test]
fn value_to_param_json_is_text() {
use oxisql_postgres::value_to_param;
let json = r#"{"key":"val"}"#.to_string();
let p = value_to_param(&Value::Json(json.clone()));
match p {
OwnedParam::Text(s) => assert_eq!(s, json),
other => panic!("expected OwnedParam::Text for Json, got {other:?}"),
}
}
#[test]
fn value_to_param_decimal_is_text() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::Decimal("123.456".to_string()));
match p {
OwnedParam::Text(s) => assert_eq!(s, "123.456"),
other => panic!("expected OwnedParam::Text for Decimal, got {other:?}"),
}
}
#[test]
fn value_to_param_empty_array_is_text() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::Array(vec![]));
match p {
OwnedParam::Text(s) => assert_eq!(s, "{}"),
other => panic!("expected OwnedParam::Text for Array, got {other:?}"),
}
}
#[test]
fn value_to_param_int_array_is_text() {
use oxisql_postgres::value_to_param;
let p = value_to_param(&Value::Array(vec![Value::I64(1), Value::I64(2)]));
match p {
OwnedParam::Text(s) => assert_eq!(s, "{1,2}"),
other => panic!("expected OwnedParam::Text for Array, got {other:?}"),
}
}
#[test]
fn pg_error_pool_exhausted_display() {
let e = PgError::PoolExhausted("all 10 connections busy".to_string());
let s = e.to_string();
assert!(!s.is_empty(), "PoolExhausted display must be non-empty");
assert!(
s.contains("pool exhausted") || s.contains("all 10"),
"unexpected PoolExhausted message: {s}"
);
}
#[test]
fn pg_error_copy_display() {
let e = PgError::Copy("unexpected CopyDone message".to_string());
let s = e.to_string();
assert!(!s.is_empty(), "Copy display must be non-empty");
assert!(s.contains("COPY"), "expected 'COPY' in message, got: {s}");
}
#[test]
fn pg_error_notify_display() {
let e = PgError::Notify("invalid channel name".to_string());
let s = e.to_string();
assert!(!s.is_empty(), "Notify display must be non-empty");
assert!(
s.contains("NOTIFY"),
"expected 'NOTIFY' in message, got: {s}"
);
}
#[test]
fn pg_error_pool_exhausted_converts() {
use oxisql_core::OxiSqlError;
let e = PgError::PoolExhausted("pool at limit".to_string());
let oe: OxiSqlError = e.into();
assert!(
matches!(oe, OxiSqlError::ConnectionPool(_)),
"expected ConnectionPool variant, got {oe:?}"
);
}
#[test]
fn tls_mode_skip_verify_constructs() {
use oxisql_postgres::TlsMode;
let result = TlsMode::skip_verify();
assert!(result.is_ok(), "skip_verify() should not fail");
match result.unwrap() {
TlsMode::Rustls(_) => {} TlsMode::Disabled => panic!("expected TlsMode::Rustls from skip_verify"),
}
}
#[cfg(feature = "integration-postgres")]
mod pg_array_interval_integration {
use oxisql_core::{ArrayElementType, Connection, Value};
use oxisql_postgres::{PgConnection, TlsMode};
const CONN_STR: &str = "host=localhost port=5432 user=postgres password=test dbname=postgres";
async fn connect() -> PgConnection {
PgConnection::connect(CONN_STR, TlsMode::Disabled)
.await
.expect("connect")
}
#[tokio::test]
#[ignore]
async fn test_int_array() {
let conn = connect().await;
let rows = conn
.query("SELECT ARRAY[1, 2, 3]::int[] AS arr", &[])
.await
.expect("query");
assert_eq!(rows.len(), 1);
let v = rows[0].get("arr").expect("column arr");
assert_eq!(
*v,
Value::TypedArray {
element_type: ArrayElementType::Int4,
values: vec![Value::I64(1), Value::I64(2), Value::I64(3)],
}
);
}
#[tokio::test]
#[ignore]
async fn test_text_array() {
let conn = connect().await;
let rows = conn
.query("SELECT ARRAY['a', 'b']::text[] AS arr", &[])
.await
.expect("query");
assert_eq!(rows.len(), 1);
let v = rows[0].get("arr").expect("column arr");
assert_eq!(
*v,
Value::TypedArray {
element_type: ArrayElementType::Text,
values: vec![Value::Text("a".to_string()), Value::Text("b".to_string()),],
}
);
}
#[tokio::test]
#[ignore]
async fn test_float_array() {
let conn = connect().await;
let rows = conn
.query("SELECT ARRAY[1.5, 2.5]::float8[] AS arr", &[])
.await
.expect("query");
assert_eq!(rows.len(), 1);
let v = rows[0].get("arr").expect("column arr");
assert_eq!(
*v,
Value::TypedArray {
element_type: ArrayElementType::Float8,
values: vec![Value::F64(1.5), Value::F64(2.5)],
}
);
}
#[tokio::test]
#[ignore]
async fn test_interval() {
let conn = connect().await;
let rows = conn
.query("SELECT INTERVAL '1 hour 30 minutes' AS iv", &[])
.await
.expect("query");
assert_eq!(rows.len(), 1);
let v = rows[0].get("iv").expect("column iv");
match v {
Value::Text(s) => {
assert!(
s.contains("01:30:00"),
"expected '01:30:00' in interval string, got: {s}"
);
}
other => panic!("expected Value::Text for INTERVAL, got {other:?}"),
}
}
#[tokio::test]
#[ignore]
async fn test_null_array() {
let conn = connect().await;
let rows = conn
.query("SELECT NULL::int[] AS arr", &[])
.await
.expect("query");
assert_eq!(rows.len(), 1);
let v = rows[0].get("arr").expect("column arr");
assert_eq!(*v, Value::Null);
}
#[tokio::test]
#[ignore]
async fn test_array_with_null_element() {
let conn = connect().await;
let rows = conn
.query("SELECT ARRAY[1, NULL, 3]::int[] AS arr", &[])
.await
.expect("query");
assert_eq!(rows.len(), 1);
let v = rows[0].get("arr").expect("column arr");
assert_eq!(
*v,
Value::TypedArray {
element_type: ArrayElementType::Int4,
values: vec![Value::I64(1), Value::Null, Value::I64(3)],
}
);
}
}