use crate::common::rand_string;
use crate::{Config, ContainerConfig, Server};
use derive_builder::Builder;
use dockertest::{waitfor, Source};
use std::collections::HashMap;
const IMAGE: &str = "postgres";
const PORT: u32 = 5432;
const LOG_MSG: &str = "database system is ready to accept connections";
const SOURCE: Source = Source::DockerHub;
const USER: &str = "postgres";
#[derive(Clone, Default, Builder)]
#[builder(default)]
pub struct PostgresServerConfig {
#[builder(default = "Vec::new()")]
pub args: Vec<String>,
#[builder(default = "HashMap::new()")]
pub env: HashMap<String, String>,
#[builder(default = "crate::server::new_handle(IMAGE)")]
pub handle: String,
#[builder(default = "rand_string(16)")]
pub password: String,
#[builder(default = "8200")]
pub port: u32,
#[builder(default = "15")]
pub timeout: u16,
#[builder(default = "String::from(\"latest\")")]
pub version: String,
}
impl PostgresServerConfig {
pub fn builder() -> PostgresServerConfigBuilder {
PostgresServerConfigBuilder::default()
}
}
impl Config for PostgresServerConfig {
fn into_composition(self) -> dockertest::Composition {
let ports = vec![(PORT, self.port)];
let mut env = self.env.clone();
env.insert(String::from("POSTGRES_PASSWORD"), self.password.clone());
let mut args = self.args.clone();
args.push("-c".into());
args.push("listen_addresses=*".into());
let timeout = self.timeout;
let wait = Box::new(waitfor::MessageWait {
message: LOG_MSG.into(),
source: waitfor::MessageSource::Stderr,
timeout,
});
ContainerConfig {
args,
env,
handle: self.handle,
name: IMAGE.into(),
source: SOURCE,
version: self.version,
ports: Some(ports),
wait: Some(wait),
bind_mounts: HashMap::new(),
}
.into()
}
fn handle(&self) -> &str {
self.handle.as_str()
}
}
pub struct PostgresServer {
pub external_port: u32,
pub internal_port: u32,
pub ip: String,
pub password: String,
pub username: String,
}
impl PostgresServer {
fn format_address(&self, host: &str, port: u32) -> String {
format!("{}:{}", host, port)
}
fn format_auth_url(&self, host: &str, port: u32) -> String {
format!(
"postgresql://{}:{}@{}",
self.username,
self.password,
self.format_address(host, port)
)
}
fn format_url(&self, host: &str, port: u32) -> String {
format!("postgresql://{}", self.format_address(host, port))
}
pub fn external_address(&self) -> String {
self.format_address("localhost", self.external_port)
}
pub fn external_auth_url(&self) -> String {
self.format_auth_url("localhost", self.external_port)
}
pub fn external_url(&self) -> String {
self.format_url("localhost", self.external_port)
}
pub fn internal_address(&self) -> String {
self.format_address(self.ip.as_str(), self.internal_port)
}
pub fn internal_auth_url(&self) -> String {
self.format_auth_url(self.ip.as_str(), self.internal_port)
}
pub fn internal_url(&self) -> String {
self.format_url(self.ip.as_str(), self.internal_port)
}
}
impl Server for PostgresServer {
type Config = PostgresServerConfig;
fn new(config: &Self::Config, container: &dockertest::RunningContainer) -> Self {
PostgresServer {
external_port: config.port,
internal_port: PORT,
ip: container.ip().to_string(),
password: config.password.clone(),
username: USER.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::{PostgresServer, PostgresServerConfig};
use crate::Test;
use test_log::test;
use tokio_postgres::NoTls;
const PORT: u32 = 6432;
#[test]
fn test_postgres() {
let config = PostgresServerConfig::builder().port(PORT).build().unwrap();
let mut test = Test::new();
test.register(config);
test.run(|instance| async move {
let server: PostgresServer = instance.server();
let res = tokio_postgres::connect(server.external_auth_url().as_str(), NoTls).await;
assert!(res.is_ok())
});
}
}