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 schema_search_path_resolution() {
    let ctx = common::start().await;

    ctx.client.execute("create schema s1", &[]).await.unwrap();
    ctx.client.execute("create schema s2", &[]).await.unwrap();
    ctx.client
        .execute("create table s1.t(a int)", &[])
        .await
        .unwrap();
    ctx.client
        .execute("create table s2.t(a int)", &[])
        .await
        .unwrap();
    ctx.client
        .execute("insert into s1.t values (1)", &[])
        .await
        .unwrap();
    ctx.client
        .execute("insert into s2.t values (1), (2)", &[])
        .await
        .unwrap();

    ctx.client
        .execute("set search_path = s1, s2", &[])
        .await
        .unwrap();
    let rows = ctx
        .client
        .query("select count(*) from t", &[])
        .await
        .unwrap();
    assert_eq!(rows[0].get::<_, i64>(0), 1);

    ctx.client
        .execute("set search_path = s2, s1", &[])
        .await
        .unwrap();
    let rows = ctx
        .client
        .query("select count(*) from t", &[])
        .await
        .unwrap();
    assert_eq!(rows[0].get::<_, i64>(0), 2);

    let rows = ctx
        .client
        .query("select count(*) from s1.t", &[])
        .await
        .unwrap();
    assert_eq!(rows[0].get::<_, i64>(0), 1);
    let rows = ctx
        .client
        .query("select count(*) from s2.t", &[])
        .await
        .unwrap();
    assert_eq!(rows[0].get::<_, i64>(0), 2);

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

#[tokio::test(flavor = "multi_thread")]
async fn create_table_uses_search_path() {
    let ctx = common::start().await;
    ctx.client.execute("create schema sp", &[]).await.unwrap();
    ctx.client
        .execute("set search_path = sp, public", &[])
        .await
        .unwrap();
    ctx.client
        .execute("create table x(a int)", &[])
        .await
        .unwrap();
    ctx.client
        .execute("insert into sp.x values (1)", &[])
        .await
        .unwrap();
    let rows = ctx
        .client
        .query("select count(*) from sp.x", &[])
        .await
        .unwrap();
    assert_eq!(rows[0].get::<_, i64>(0), 1);
    let _ = ctx.shutdown.send(());
}

#[tokio::test(flavor = "multi_thread")]
async fn cross_schema_foreign_keys() {
    let ctx = common::start().await;
    ctx.client.execute("create schema p", &[]).await.unwrap();
    ctx.client.execute("create schema c", &[]).await.unwrap();
    ctx.client
        .execute("create table p.parents(id int primary key)", &[])
        .await
        .unwrap();
    ctx.client
        .execute(
            "create table c.children(pid int references p.parents(id))",
            &[],
        )
        .await
        .unwrap();
    ctx.client
        .execute("insert into p.parents values (1)", &[])
        .await
        .unwrap();
    ctx.client
        .execute("insert into c.children values (1)", &[])
        .await
        .unwrap();
    let rows = ctx
        .client
        .query("select count(*) from c.children", &[])
        .await
        .unwrap();
    assert_eq!(rows[0].get::<_, i64>(0), 1);
    let _ = ctx.shutdown.send(());
}

#[tokio::test(flavor = "multi_thread")]
async fn cross_schema_fk_resolves_via_search_path() {
    let ctx = common::start().await;
    ctx.client
        .execute("create schema parents", &[])
        .await
        .unwrap();
    ctx.client
        .execute("create schema children", &[])
        .await
        .unwrap();
    ctx.client
        .execute("create table parents.p(id int primary key)", &[])
        .await
        .unwrap();
    ctx.client
        .execute("set search_path = children, parents", &[])
        .await
        .unwrap();
    ctx.client
        .execute("create table children.c(pid int references p(id))", &[])
        .await
        .unwrap();
    ctx.client
        .execute("insert into parents.p values (1)", &[])
        .await
        .unwrap();
    ctx.client
        .execute("insert into children.c values (1)", &[])
        .await
        .unwrap();
    let err = ctx
        .client
        .execute("insert into children.c values (2)", &[])
        .await
        .unwrap_err();
    common::assert_db_error_contains(&err, "violates foreign key");
    let _ = ctx.shutdown.send(());
}

#[tokio::test(flavor = "multi_thread")]
async fn drop_schema_restrict_and_cascade() {
    let ctx = common::start().await;
    ctx.client.execute("create schema ds", &[]).await.unwrap();
    ctx.client
        .execute("create table ds.t(a int)", &[])
        .await
        .unwrap();

    let err = ctx.client.execute("drop schema ds", &[]).await.unwrap_err();
    common::assert_db_error_contains(&err, "not empty");

    ctx.client
        .execute("drop schema ds cascade", &[])
        .await
        .unwrap();
    let rows = ctx
        .client
        .simple_query("select 1 from pg_catalog.pg_namespace where nspname = 'ds'")
        .await
        .unwrap();
    let found_row = rows
        .iter()
        .any(|msg| matches!(msg, SimpleQueryMessage::Row(_)));
    assert!(
        !found_row,
        "pg_namespace still contains schema entry after drop"
    );
    let _ = ctx.shutdown.send(());
}

#[tokio::test(flavor = "multi_thread")]
async fn rename_schema_updates_search_path_and_functions() {
    let ctx = common::start().await;
    ctx.client
        .execute("create schema to_rename", &[])
        .await
        .unwrap();
    ctx.client
        .execute("alter schema to_rename rename to renamed", &[])
        .await
        .unwrap();
    ctx.client
        .execute("set search_path = renamed, public", &[])
        .await
        .unwrap();
    let rows = ctx.client.simple_query("show search_path").await.unwrap();
    let value = rows
        .iter()
        .find_map(|msg| match msg {
            SimpleQueryMessage::Row(row) => row.get(0).map(|s| s.to_string()),
            _ => None,
        })
        .expect("expected row");
    assert_eq!(value, "renamed, public");

    let rows = ctx
        .client
        .query("select current_schema()", &[])
        .await
        .unwrap();
    assert_eq!(rows[0].get::<_, String>(0), "renamed");

    let rows = ctx
        .client
        .query("select current_schemas(false)", &[])
        .await
        .unwrap();
    assert_eq!(rows[0].get::<_, String>(0), "{renamed,public}");

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