use std::borrow::Cow;
use std::collections::BTreeMap;
use testcontainers_modules::testcontainers::core::{
ContainerPort, ContainerState, ExecCommand, WaitFor,
};
use testcontainers_modules::testcontainers::{Image, TestcontainersError};
const NAME: &str = "supabase/postgres";
const TAG: &str = "15.8.1.085";
pub const GRAPHQL_PORT: u16 = 5432;
#[derive(Debug, Clone)]
pub struct GraphQL {
env_vars: BTreeMap<String, String>,
tag: String,
}
impl GraphQL {
pub fn new() -> Self {
Self::default()
}
pub fn new_with_env(envs: BTreeMap<&str, &str>) -> Self {
let mut instance = Self::default();
for (key, val) in envs {
instance.env_vars.insert(key.to_string(), val.to_string());
}
instance
}
pub fn with_database(mut self, database: impl Into<String>) -> Self {
self.env_vars
.insert("POSTGRES_DB".to_string(), database.into());
self
}
pub fn with_user(mut self, user: impl Into<String>) -> Self {
self.env_vars
.insert("POSTGRES_USER".to_string(), user.into());
self
}
pub fn with_password(mut self, password: impl Into<String>) -> Self {
self.env_vars
.insert("POSTGRES_PASSWORD".to_string(), password.into());
self
}
pub fn with_host(mut self, host: impl Into<String>) -> Self {
self.env_vars
.insert("POSTGRES_HOST".to_string(), host.into());
self
}
pub fn with_port(mut self, port: u16) -> Self {
self.env_vars
.insert("POSTGRES_PORT".to_string(), port.to_string());
self
}
pub fn with_host_auth_method(mut self, method: impl Into<String>) -> Self {
self.env_vars
.insert("POSTGRES_HOST_AUTH_METHOD".to_string(), method.into());
self
}
pub fn with_postgres_args(mut self, args: impl Into<String>) -> Self {
self.env_vars
.insert("POSTGRES_INITDB_ARGS".to_string(), args.into());
self
}
pub fn with_jwt_secret(mut self, secret: impl Into<String>) -> Self {
self.env_vars
.insert("JWT_SECRET".to_string(), secret.into());
self
}
pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
self.tag = tag.into();
self
}
pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env_vars.insert(key.into(), value.into());
self
}
pub fn connection_string_template(&self) -> String {
let user = self
.env_vars
.get("POSTGRES_USER")
.cloned()
.unwrap_or_else(|| "postgres".to_string());
let password = self
.env_vars
.get("POSTGRES_PASSWORD")
.cloned()
.unwrap_or_else(|| "postgres".to_string());
let database = self
.env_vars
.get("POSTGRES_DB")
.cloned()
.unwrap_or_else(|| "postgres".to_string());
format!(
"postgres://{}:{}@{{host}}:{{port}}/{}",
user, password, database
)
}
}
impl Default for GraphQL {
fn default() -> Self {
let mut env_vars = BTreeMap::new();
env_vars.insert("POSTGRES_DB".to_string(), "postgres".to_string());
env_vars.insert("POSTGRES_USER".to_string(), "postgres".to_string());
env_vars.insert("POSTGRES_PASSWORD".to_string(), "postgres".to_string());
Self {
env_vars,
tag: TAG.to_string(),
}
}
}
impl Image for GraphQL {
fn name(&self) -> &str {
NAME
}
fn tag(&self) -> &str {
&self.tag
}
fn ready_conditions(&self) -> Vec<WaitFor> {
vec![WaitFor::message_on_stderr(
"database system is ready to accept connections",
)]
}
fn expose_ports(&self) -> &[ContainerPort] {
&[ContainerPort::Tcp(GRAPHQL_PORT)]
}
fn env_vars(
&self,
) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
&self.env_vars
}
#[allow(unused_variables)]
fn exec_after_start(
&self,
cs: ContainerState,
) -> Result<Vec<ExecCommand>, TestcontainersError> {
Ok(vec![])
}
}
#[cfg(test)]
#[cfg(feature = "graphql")]
mod tests {
use super::*;
use testcontainers_modules::testcontainers::Image;
#[test]
fn test_default_configuration() {
let graphql = GraphQL::default();
assert_eq!(
graphql.env_vars.get("POSTGRES_DB"),
Some(&"postgres".to_string())
);
assert_eq!(
graphql.env_vars.get("POSTGRES_USER"),
Some(&"postgres".to_string())
);
assert_eq!(
graphql.env_vars.get("POSTGRES_PASSWORD"),
Some(&"postgres".to_string())
);
}
#[test]
fn test_name_returns_correct_image() {
let graphql = GraphQL::default();
assert_eq!(graphql.name(), "supabase/postgres");
}
#[test]
fn test_tag_returns_correct_version() {
let graphql = GraphQL::default();
assert_eq!(graphql.tag(), TAG);
}
#[test]
fn test_graphql_port_constant() {
assert_eq!(GRAPHQL_PORT, 5432);
}
#[test]
fn test_with_database() {
let graphql = GraphQL::default().with_database("mydb");
assert_eq!(
graphql.env_vars.get("POSTGRES_DB"),
Some(&"mydb".to_string())
);
}
#[test]
fn test_with_user() {
let graphql = GraphQL::default().with_user("myuser");
assert_eq!(
graphql.env_vars.get("POSTGRES_USER"),
Some(&"myuser".to_string())
);
}
#[test]
fn test_with_password() {
let graphql = GraphQL::default().with_password("secret");
assert_eq!(
graphql.env_vars.get("POSTGRES_PASSWORD"),
Some(&"secret".to_string())
);
}
#[test]
fn test_with_host() {
let graphql = GraphQL::default().with_host("127.0.0.1");
assert_eq!(
graphql.env_vars.get("POSTGRES_HOST"),
Some(&"127.0.0.1".to_string())
);
}
#[test]
fn test_with_port() {
let graphql = GraphQL::default().with_port(5433);
assert_eq!(
graphql.env_vars.get("POSTGRES_PORT"),
Some(&"5433".to_string())
);
}
#[test]
fn test_with_host_auth_method() {
let graphql = GraphQL::default().with_host_auth_method("trust");
assert_eq!(
graphql.env_vars.get("POSTGRES_HOST_AUTH_METHOD"),
Some(&"trust".to_string())
);
}
#[test]
fn test_with_postgres_args() {
let graphql = GraphQL::default().with_postgres_args("--max_connections=200");
assert_eq!(
graphql.env_vars.get("POSTGRES_INITDB_ARGS"),
Some(&"--max_connections=200".to_string())
);
}
#[test]
fn test_with_jwt_secret() {
let graphql = GraphQL::default().with_jwt_secret("my-jwt-secret");
assert_eq!(
graphql.env_vars.get("JWT_SECRET"),
Some(&"my-jwt-secret".to_string())
);
}
#[test]
fn test_with_tag_overrides_default() {
let graphql = GraphQL::default().with_tag("15.1.0.100");
assert_eq!(graphql.tag(), "15.1.0.100");
}
#[test]
fn test_with_env_adds_custom_variable() {
let graphql = GraphQL::default()
.with_env("CUSTOM_VAR", "custom_value")
.with_env("ANOTHER_VAR", "another_value");
assert_eq!(
graphql.env_vars.get("CUSTOM_VAR"),
Some(&"custom_value".to_string())
);
assert_eq!(
graphql.env_vars.get("ANOTHER_VAR"),
Some(&"another_value".to_string())
);
}
#[test]
fn test_builder_method_chaining() {
let graphql = GraphQL::default()
.with_database("testdb")
.with_user("testuser")
.with_password("testpass")
.with_host("localhost")
.with_port(5433)
.with_jwt_secret("my-jwt-secret")
.with_tag("15.1.0.100");
assert_eq!(
graphql.env_vars.get("POSTGRES_DB"),
Some(&"testdb".to_string())
);
assert_eq!(
graphql.env_vars.get("POSTGRES_USER"),
Some(&"testuser".to_string())
);
assert_eq!(
graphql.env_vars.get("POSTGRES_PASSWORD"),
Some(&"testpass".to_string())
);
assert_eq!(
graphql.env_vars.get("POSTGRES_HOST"),
Some(&"localhost".to_string())
);
assert_eq!(
graphql.env_vars.get("POSTGRES_PORT"),
Some(&"5433".to_string())
);
assert_eq!(
graphql.env_vars.get("JWT_SECRET"),
Some(&"my-jwt-secret".to_string())
);
assert_eq!(graphql.tag(), "15.1.0.100");
}
#[test]
fn test_new_creates_default_instance() {
let graphql = GraphQL::new();
assert_eq!(graphql.name(), NAME);
assert_eq!(graphql.tag(), TAG);
}
#[test]
fn test_new_with_env() {
let mut envs = BTreeMap::new();
envs.insert("CUSTOM_KEY", "custom_value");
envs.insert("POSTGRES_DB", "customdb");
let graphql = GraphQL::new_with_env(envs);
assert_eq!(
graphql.env_vars.get("CUSTOM_KEY"),
Some(&"custom_value".to_string())
);
assert_eq!(
graphql.env_vars.get("POSTGRES_DB"),
Some(&"customdb".to_string())
);
}
#[test]
fn test_expose_ports() {
let graphql = GraphQL::default();
let ports = graphql.expose_ports();
assert_eq!(ports.len(), 1);
assert_eq!(ports[0], ContainerPort::Tcp(5432));
}
#[test]
fn test_ready_conditions() {
let graphql = GraphQL::default();
let conditions = graphql.ready_conditions();
assert_eq!(conditions.len(), 1);
}
#[test]
fn test_connection_string_template() {
let graphql = GraphQL::default()
.with_database("mydb")
.with_user("myuser")
.with_password("mypass");
let template = graphql.connection_string_template();
assert_eq!(template, "postgres://myuser:mypass@{host}:{port}/mydb");
}
#[test]
fn test_connection_string_template_defaults() {
let graphql = GraphQL::default();
let template = graphql.connection_string_template();
assert_eq!(
template,
"postgres://postgres:postgres@{host}:{port}/postgres"
);
}
}