postgresql_embedded 0.20.2

Install and run a PostgreSQL database locally on Linux, MacOS or Windows. PostgreSQL can be bundled with your application, or downloaded on demand.
Documentation
use postgresql_commands::CommandBuilder;
use postgresql_commands::psql::PsqlBuilder;
use postgresql_embedded::{PostgreSQL, Result, Settings, Status};
use std::fs::{remove_dir_all, remove_file};
use test_log::test;

async fn lifecycle() -> Result<()> {
    let mut postgresql = PostgreSQL::default();
    let settings = postgresql.settings();

    // Verify that an ephemeral instance is created by default
    assert_eq!(0, settings.port);
    assert!(settings.temporary);

    let initial_statuses = [Status::NotInstalled, Status::Installed, Status::Stopped];
    assert!(initial_statuses.contains(&postgresql.status()));

    postgresql.setup().await?;
    assert_eq!(Status::Stopped, postgresql.status());

    postgresql.start().await?;
    assert_eq!(Status::Started, postgresql.status());

    let database_name = "test";
    assert!(!postgresql.database_exists(database_name).await?);
    postgresql.create_database(database_name).await?;
    assert!(postgresql.database_exists(database_name).await?);
    postgresql.drop_database(database_name).await?;

    postgresql.stop().await?;
    assert_eq!(Status::Stopped, postgresql.status());

    Ok(())
}

#[test(tokio::test)]
async fn test_embedded_async_lifecycle() -> Result<()> {
    lifecycle().await
}

#[test(tokio::test)]
async fn test_temporary_database() -> Result<()> {
    let settings = Settings::default();
    let data_dir = settings.data_dir.clone();
    let password_file = settings.password_file.clone();

    assert!(settings.temporary);

    {
        let mut postgresql = PostgreSQL::new(settings);
        postgresql.setup().await?;
        postgresql.start().await?;
        assert!(data_dir.exists());
        assert!(password_file.exists());
    }

    // Verify that the data directory and password file are removed automatically when PostgreSQL is dropped
    assert!(!data_dir.exists());
    assert!(!password_file.exists());
    Ok(())
}

#[test(tokio::test)]
async fn test_persistent_database() -> Result<()> {
    let mut settings = Settings::default();
    let data_dir = settings.data_dir.clone();
    let password_file = settings.password_file.clone();

    settings.temporary = false;

    {
        let mut postgresql = PostgreSQL::new(settings);
        postgresql.setup().await?;
        postgresql.start().await?;
        assert!(data_dir.exists());
        assert!(password_file.exists());
    }

    // Verify that the data directory and password file are retained when PostgreSQL is dropped
    assert!(data_dir.exists());
    assert!(password_file.exists());

    let _ = remove_dir_all(&data_dir);
    let _ = remove_file(&password_file);

    Ok(())
}

#[test(tokio::test)]
async fn test_persistent_database_reuse() -> Result<()> {
    let database_name = "test";
    let mut settings = Settings::default();
    let data_dir = settings.data_dir.clone();
    let password = settings.password.clone();
    let password_file = settings.password_file.clone();

    settings.temporary = false;

    {
        let mut postgresql = PostgreSQL::new(settings);
        postgresql.setup().await?;
        postgresql.start().await?;
        postgresql.create_database(database_name).await?;
        assert!(postgresql.database_exists(database_name).await?);
        postgresql.stop().await?;
    }

    // Verify that the data directory and password file are retained when PostgreSQL is dropped
    assert!(data_dir.exists());
    assert!(password_file.exists());

    let settings = Settings {
        data_dir: data_dir.clone(),
        password: password.clone(),
        password_file: password_file.clone(),
        temporary: false,
        ..Default::default()
    };

    {
        let mut postgresql = PostgreSQL::new(settings);
        postgresql.setup().await?;
        postgresql.start().await?;
        assert!(postgresql.database_exists(database_name).await?);
        postgresql.stop().await?;
    }

    let _ = remove_dir_all(&data_dir);
    let _ = remove_file(&password_file);

    Ok(())
}

#[test(tokio::test)]
async fn postgres_concurrency() -> Result<()> {
    let handle1 = tokio::spawn(lifecycle());
    let handle2 = tokio::spawn(lifecycle());
    let handle3 = tokio::spawn(lifecycle());
    match tokio::try_join!(handle1, handle2, handle3) {
        Ok(_) => {}
        Err(error) => {
            assert_eq!("", error.to_string());
        }
    }
    Ok(())
}

#[test(tokio::test)]
async fn test_authentication_success() -> Result<()> {
    let mut postgresql = PostgreSQL::default();
    postgresql.setup().await?;
    postgresql.start().await?;

    let mut psql = PsqlBuilder::from(postgresql.settings())
        .command("SELECT 1")
        .no_psqlrc()
        .tuples_only()
        .build();

    let output = psql.output()?;
    assert!(output.status.success());
    Ok(())
}

#[test(tokio::test)]
async fn test_authentication_invalid_username() -> Result<()> {
    let mut postgresql = PostgreSQL::default();
    postgresql.setup().await?;
    postgresql.start().await?;

    let mut psql = PsqlBuilder::from(postgresql.settings())
        .command("SELECT 1")
        .username("invalid")
        .no_psqlrc()
        .tuples_only()
        .build();

    let output = psql.output()?;
    assert!(!output.status.success());
    Ok(())
}

#[test(tokio::test)]
async fn test_authentication_invalid_password() -> Result<()> {
    let mut postgresql = PostgreSQL::default();
    postgresql.setup().await?;
    postgresql.start().await?;

    let mut psql = PsqlBuilder::from(postgresql.settings())
        .command("SELECT 1")
        .pg_password("invalid")
        .no_psqlrc()
        .tuples_only()
        .build();

    let output = psql.output()?;
    assert!(!output.status.success());
    Ok(())
}

#[test(tokio::test)]
async fn test_username_setting() -> Result<()> {
    let settings = Settings {
        username: "admin".to_string(),
        ..Default::default()
    };
    let mut postgresql = PostgreSQL::new(settings);
    postgresql.setup().await?;
    postgresql.start().await?;

    let database_name = "test";
    postgresql.create_database(database_name).await?;
    let database_exists = postgresql.database_exists(database_name).await?;
    assert!(database_exists);
    postgresql.drop_database(database_name).await?;
    let database_exists = postgresql.database_exists(database_name).await?;
    assert!(!database_exists);
    Ok(())
}