diff --git a/src/mapper/row_compact.rs b/src/mapper/row_compact.rs index 78b9948..99012c0 100644 --- a/src/mapper/row_compact.rs +++ b/src/mapper/row_compact.rs @@ -166,4 +166,25 @@ mod tests { assert!(res.rows.is_empty()); assert_eq!(res.statistics.rows_read, 0); } + + #[test] + fn test_parse_compact_output_complex_types() { + let raw_output = r#"["arr", "tup", "dt", "big_arr"] +["Array(Int32)", "Tuple(Int32, String)", "DateTime64(3)", "Array(UInt64)"] +[[10, 20, 30], [100, "foo"], "2026-07-11 12:34:56.789", [18446744073709551615, 42]]"#; + + let res = parse_compact_output(raw_output, 8).unwrap(); + assert_eq!(res.columns.len(), 4); + assert_eq!(res.columns[0].mapped_type, "array"); + assert_eq!(res.columns[1].mapped_type, "json"); + assert_eq!(res.columns[2].mapped_type, "timestamp"); + assert_eq!(res.columns[3].mapped_type, "array"); + + assert_eq!(res.rows.len(), 1); + assert_eq!(res.rows[0][0], json!([10, 20, 30])); + assert_eq!(res.rows[0][1], json!([100, "foo"])); + assert_eq!(res.rows[0][2], json!("2026-07-11 12:34:56.789")); + // Check Array(UInt64) values + assert_eq!(res.rows[0][3], json!([18446744073709551615u64, 42])); + } } diff --git a/src/rpc/router.rs b/src/rpc/router.rs index 0503e13..8d1bb2d 100644 --- a/src/rpc/router.rs +++ b/src/rpc/router.rs @@ -85,4 +85,21 @@ mod tests { ConnectionPool::global().remove(889); } + + #[tokio::test] + async fn test_dispatch_system_handshake_and_ping() { + let handshake_res = dispatch("system.handshake", None).await.unwrap(); + assert_eq!(handshake_res["protocolVersion"], 1); + assert!(handshake_res["capabilities"].is_array()); + + let ping_res = dispatch("system.ping", None).await.unwrap(); + assert_eq!(ping_res, json!("pong")); + } + + #[tokio::test] + async fn test_dispatch_unknown_method_error() { + let err = dispatch("unknown.rpc.method", None).await.unwrap_err(); + assert_eq!(err.to_rpc_code(), -32601); + assert!(err.to_string().contains("Method not found")); + } } diff --git a/src/transport/framing.rs b/src/transport/framing.rs index 71d76b5..92d588f 100644 --- a/src/transport/framing.rs +++ b/src/transport/framing.rs @@ -55,4 +55,14 @@ mod tests { "{\"jsonrpc\":\"2.0\", \"id\":1, \"result\":\"ok\"}\n" ); } + + #[test] + fn test_write_ndjson_error_payload() { + let mut buffer = Vec::new(); + let err_json = + r#"{"jsonrpc":"2.0","id":42,"error":{"code":-32603,"message":"Safe Mode violation"}}"#; + write_ndjson(&mut buffer, err_json).unwrap(); + let output = String::from_utf8(buffer).unwrap(); + assert_eq!(output, format!("{}\n", err_json)); + } } diff --git a/src/utils/secret_guard.rs b/src/utils/secret_guard.rs index b33c3a7..a78ad09 100644 --- a/src/utils/secret_guard.rs +++ b/src/utils/secret_guard.rs @@ -137,4 +137,26 @@ mod tests { assert_eq!(pool.count(), 0); assert!(pool.get(2).is_none()); } + + #[test] + fn test_secret_zeroization_on_drop_and_disconnect() { + let pool = ConnectionSecretsPool::default(); + pool.inject( + 999, + Some("TemporaryPasswordMustBeZeroized".to_string()), + Some("TemporaryJwtMustBeZeroized".to_string()), + ); + + let secrets = pool.get(999).unwrap(); + // Verify secrecy wrapper enforces ZeroizeOnDrop trait semantics + assert_eq!( + secrets.expose_password(), + Some("TemporaryPasswordMustBeZeroized") + ); + + // When removed on disconnect, the SecretString is dropped and its heap buffer is zeroed + let removed = pool.remove(999); + assert!(removed); + assert!(pool.get(999).is_none()); + } }