use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
use oxisql_postgres::{PgConnection, TlsMode};
fn encode_message(tag: u8, body: &[u8]) -> Vec<u8> {
let mut buf = Vec::with_capacity(5 + body.len());
buf.push(tag);
let len = u32::try_from(body.len() + 4).expect("test message body fits in u32");
buf.extend_from_slice(&len.to_be_bytes());
buf.extend_from_slice(body);
buf
}
async fn read_startup_message(stream: &mut tokio::net::TcpStream) {
let mut len_buf = [0_u8; 4];
stream
.read_exact(&mut len_buf)
.await
.expect("read StartupMessage length");
let len = u32::from_be_bytes(len_buf) as usize;
let mut rest = vec![0_u8; len - 4];
stream
.read_exact(&mut rest)
.await
.expect("read StartupMessage body");
}
const FAKE_SERVER_VERSION: &str = "16.4 (Fake OxiSQL Test Server)";
async fn spawn_fake_server() -> (tokio::task::JoinHandle<()>, u16) {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("bind loopback listener");
let port = listener.local_addr().expect("read local_addr").port();
let handle = tokio::spawn(async move {
let (mut server, _peer) = listener.accept().await.expect("accept");
read_startup_message(&mut server).await;
server
.write_all(&encode_message(b'R', &0_i32.to_be_bytes()))
.await
.expect("write AuthenticationOk");
let mut ps_body = Vec::new();
ps_body.extend_from_slice(b"server_version\0");
ps_body.extend_from_slice(FAKE_SERVER_VERSION.as_bytes());
ps_body.push(0);
server
.write_all(&encode_message(b'S', &ps_body))
.await
.expect("write ParameterStatus(server_version)");
let mut key_body = Vec::new();
key_body.extend_from_slice(&4242_i32.to_be_bytes());
key_body.extend_from_slice(&9999_i32.to_be_bytes());
server
.write_all(&encode_message(b'K', &key_body))
.await
.expect("write BackendKeyData");
server
.write_all(&encode_message(b'Z', b"I"))
.await
.expect("write ReadyForQuery");
});
(handle, port)
}
#[tokio::test]
async fn server_version_is_populated_from_parameter_status_handshake() {
let (server_task, port) = spawn_fake_server().await;
let conn_str = format!("host=127.0.0.1 port={port} user=oxisql_test dbname=oxisql_test");
let client_script = async {
let conn = PgConnection::connect(&conn_str, TlsMode::Disabled)
.await
.expect("connect to fake server");
assert_eq!(conn.server_version(), Some(FAKE_SERVER_VERSION));
};
tokio::time::timeout(Duration::from_secs(10), client_script)
.await
.expect("client script timed out");
server_task.await.expect("server task panicked");
}
#[tokio::test]
async fn server_version_is_none_for_from_client_connections() {
let (server_task, port) = spawn_fake_server().await;
let conn_str = format!("host=127.0.0.1 port={port} user=oxisql_test dbname=oxisql_test");
let client_script = async {
let (client, connection) = tokio_postgres::connect(&conn_str, tokio_postgres::NoTls)
.await
.expect("tokio_postgres::connect to fake server");
tokio::spawn(async move {
let _ = connection.await;
});
let conn = PgConnection::from_client(client);
assert_eq!(conn.server_version(), None);
};
tokio::time::timeout(Duration::from_secs(10), client_script)
.await
.expect("client script timed out");
server_task.await.expect("server task panicked");
}