mockgres 0.0.29

An in-memory database that replicates a reasonable subset of Postgres functionality to make unit tests that rely on a database to run.
Documentation
mod common;

use tokio_postgres::SimpleQueryMessage;

#[tokio::test(flavor = "multi_thread")]
async fn show_and_set_commands() {
    let ctx = common::start().await;

    let messages = ctx
        .client
        .simple_query("show server_version")
        .await
        .expect("show server_version");
    let value = messages
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("server_version row");
    assert_eq!(value, mockgres::POSTGRES_COMPAT_VERSION);

    let messages = ctx
        .client
        .simple_query("show server_version_num")
        .await
        .expect("show server_version_num");
    let value = messages
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("server_version_num row");
    assert_eq!(value, mockgres::POSTGRES_COMPAT_VERSION_NUM);

    let search_path = ctx
        .client
        .simple_query("show search_path")
        .await
        .expect("show search_path");
    let path = search_path
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("search_path row");
    assert_eq!(path, "public");

    ctx.client
        .execute("set search_path = public", &[])
        .await
        .expect("set search_path");

    let search_path = ctx
        .client
        .simple_query("show search_path")
        .await
        .expect("show search_path after set");
    let path = search_path
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("search_path row after set");
    assert_eq!(path, "public");

    ctx.client
        .execute("set client_min_messages = warning", &[])
        .await
        .expect("set client_min_messages");

    let time_zone = ctx
        .client
        .simple_query("show time zone")
        .await
        .expect("show time zone default");
    let tz = time_zone
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("time zone row");
    assert_eq!(tz, "UTC");

    ctx.client
        .execute("set time zone '+05:30'", &[])
        .await
        .expect("set time zone");

    let time_zone = ctx
        .client
        .simple_query("show time zone")
        .await
        .expect("show time zone after set");
    let tz = time_zone
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("time zone row after set");
    assert_eq!(tz, "+05:30");

    ctx.client
        .execute("set time zone default", &[])
        .await
        .expect("reset time zone");

    let time_zone = ctx
        .client
        .simple_query("show time zone")
        .await
        .expect("show time zone after reset");
    let tz = time_zone
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("time zone row after reset");
    assert_eq!(tz, "UTC");

    let _ = ctx.shutdown.send(());
}

#[tokio::test(flavor = "multi_thread")]
async fn set_application_name_is_accepted_for_client_compatibility() {
    let ctx = common::start().await;

    ctx.client
        .execute("set application_name = 'sqlancer'", &[])
        .await
        .expect("set application_name");

    let _ = ctx.shutdown.send(());
}

#[tokio::test(flavor = "multi_thread")]
async fn show_standard_conforming_strings() {
    let ctx = common::start().await;
    let rows = ctx
        .client
        .simple_query("show standard_conforming_strings")
        .await
        .expect("show standard_conforming_strings");
    let val = rows
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("row");
    assert_eq!(val.to_ascii_lowercase(), "on");
    let _ = ctx.shutdown.send(());
}

#[tokio::test(flavor = "multi_thread")]
async fn show_and_set_transaction_isolation() {
    let ctx = common::start().await;

    let iso = ctx
        .client
        .simple_query("show transaction_isolation")
        .await
        .expect("show transaction_isolation");
    let val = iso
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("isolation row");
    assert_eq!(val, "read committed");

    ctx.client
        .execute("set default_transaction_isolation = 'read committed'", &[])
        .await
        .expect("set default_transaction_isolation");

    let err = ctx
        .client
        .execute("set transaction_isolation = 'serializable'", &[])
        .await
        .expect_err("unsupported isolation should error");
    let message = err
        .as_db_error()
        .expect("expected db error")
        .message()
        .to_ascii_lowercase();
    assert!(
        message.contains("not supported"),
        "unexpected error: {message:?}"
    );

    ctx.client.execute("begin", &[]).await.expect("begin");
    ctx.client
        .execute("set transaction_isolation = 'read committed'", &[])
        .await
        .expect("set tx isolation");
    let iso = ctx
        .client
        .simple_query("show transaction_isolation")
        .await
        .expect("show tx iso in txn");
    let val = iso
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("isolation row in txn");
    assert_eq!(val, "read committed");
    ctx.client.execute("commit", &[]).await.expect("commit");

    let _ = ctx.shutdown.send(());
}

#[tokio::test(flavor = "multi_thread")]
async fn show_and_set_lock_timeout() {
    let ctx = common::start().await;

    let value = ctx
        .client
        .simple_query("show lock_timeout")
        .await
        .expect("show lock_timeout default");
    let initial = value
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("lock_timeout row");
    assert_eq!(initial, "0");

    ctx.client
        .execute("set lock_timeout = '50ms'", &[])
        .await
        .expect("set lock_timeout");
    let value = ctx
        .client
        .simple_query("show lock_timeout")
        .await
        .expect("show lock_timeout after 50ms");
    let after_ms = value
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("lock_timeout row after 50ms");
    assert_eq!(after_ms, "50ms");

    ctx.client
        .execute("set lock_timeout = 1000", &[])
        .await
        .expect("set lock_timeout integer");
    let value = ctx
        .client
        .simple_query("show lock_timeout")
        .await
        .expect("show lock_timeout after integer set");
    let after_int = value
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("lock_timeout row after integer");
    assert_eq!(after_int, "1s");

    ctx.client
        .execute("set lock_timeout = default", &[])
        .await
        .expect("reset lock_timeout");
    let value = ctx
        .client
        .simple_query("show lock_timeout")
        .await
        .expect("show lock_timeout reset");
    let reset = value
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("lock_timeout row after reset");
    assert_eq!(reset, "0");

    let _ = ctx.shutdown.send(());
}