testcontainers_modules/postgres/
mod.rs1use std::{borrow::Cow, collections::HashMap};
2
3use testcontainers::{core::WaitFor, CopyDataSource, CopyToContainer, Image};
4
5const NAME: &str = "postgres";
6const TAG: &str = "11-alpine";
7
8#[derive(Debug, Clone)]
31pub struct Postgres {
32 env_vars: HashMap<String, String>,
33 copy_to_sources: Vec<CopyToContainer>,
34}
35
36impl Postgres {
37 pub fn with_host_auth(mut self) -> Self {
40 self.env_vars
41 .insert("POSTGRES_HOST_AUTH_METHOD".to_owned(), "trust".to_owned());
42 self
43 }
44
45 pub fn with_db_name(mut self, db_name: &str) -> Self {
47 self.env_vars
48 .insert("POSTGRES_DB".to_owned(), db_name.to_owned());
49 self
50 }
51
52 pub fn with_user(mut self, user: &str) -> Self {
54 self.env_vars
55 .insert("POSTGRES_USER".to_owned(), user.to_owned());
56 self
57 }
58
59 pub fn with_password(mut self, password: &str) -> Self {
61 self.env_vars
62 .insert("POSTGRES_PASSWORD".to_owned(), password.to_owned());
63 self
64 }
65
66 pub fn with_init_sql(mut self, init_sql: impl Into<CopyDataSource>) -> Self {
86 let target = format!(
87 "/docker-entrypoint-initdb.d/init_{i}.sql",
88 i = self.copy_to_sources.len()
89 );
90 self.copy_to_sources
91 .push(CopyToContainer::new(init_sql.into(), target));
92 self
93 }
94}
95impl Default for Postgres {
96 fn default() -> Self {
97 let mut env_vars = HashMap::new();
98 env_vars.insert("POSTGRES_DB".to_owned(), "postgres".to_owned());
99 env_vars.insert("POSTGRES_USER".to_owned(), "postgres".to_owned());
100 env_vars.insert("POSTGRES_PASSWORD".to_owned(), "postgres".to_owned());
101
102 Self {
103 env_vars,
104 copy_to_sources: Vec::new(),
105 }
106 }
107}
108
109impl Image for Postgres {
110 fn name(&self) -> &str {
111 NAME
112 }
113
114 fn tag(&self) -> &str {
115 TAG
116 }
117
118 fn ready_conditions(&self) -> Vec<WaitFor> {
119 vec![
120 WaitFor::message_on_stderr("database system is ready to accept connections"),
121 WaitFor::message_on_stdout("database system is ready to accept connections"),
122 ]
123 }
124
125 fn env_vars(
126 &self,
127 ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
128 &self.env_vars
129 }
130 fn copy_to_sources(&self) -> impl IntoIterator<Item = &CopyToContainer> {
131 &self.copy_to_sources
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use testcontainers::{runners::SyncRunner, ImageExt};
138
139 use super::*;
140
141 #[test]
142 fn postgres_one_plus_one() -> Result<(), Box<dyn std::error::Error + 'static>> {
143 let _ = pretty_env_logger::try_init();
144 let postgres_image = Postgres::default().with_host_auth();
145 let node = postgres_image.start()?;
146
147 let connection_string = &format!(
148 "postgres://postgres@{}:{}/postgres",
149 node.get_host()?,
150 node.get_host_port_ipv4(5432)?
151 );
152 let mut conn = postgres::Client::connect(connection_string, postgres::NoTls).unwrap();
153
154 let rows = conn.query("SELECT 1 + 1", &[]).unwrap();
155 assert_eq!(rows.len(), 1);
156
157 let first_row = &rows[0];
158 let first_column: i32 = first_row.get(0);
159 assert_eq!(first_column, 2);
160 Ok(())
161 }
162
163 #[test]
164 fn postgres_custom_version() -> Result<(), Box<dyn std::error::Error + 'static>> {
165 let node = Postgres::default().with_tag("13-alpine").start()?;
166
167 let connection_string = &format!(
168 "postgres://postgres:postgres@{}:{}/postgres",
169 node.get_host()?,
170 node.get_host_port_ipv4(5432)?
171 );
172 let mut conn = postgres::Client::connect(connection_string, postgres::NoTls).unwrap();
173
174 let rows = conn.query("SELECT version()", &[]).unwrap();
175 assert_eq!(rows.len(), 1);
176
177 let first_row = &rows[0];
178 let first_column: String = first_row.get(0);
179 assert!(first_column.contains("13"));
180 Ok(())
181 }
182
183 #[test]
184 fn postgres_with_init_sql() -> Result<(), Box<dyn std::error::Error + 'static>> {
185 let node = Postgres::default()
186 .with_init_sql(
187 "CREATE TABLE foo (bar varchar(255));"
188 .to_string()
189 .into_bytes(),
190 )
191 .start()?;
192
193 let connection_string = &format!(
194 "postgres://postgres:postgres@{}:{}/postgres",
195 node.get_host()?,
196 node.get_host_port_ipv4(5432)?
197 );
198 let mut conn = postgres::Client::connect(connection_string, postgres::NoTls).unwrap();
199
200 let rows = conn
201 .query("INSERT INTO foo(bar) VALUES ($1)", &[&"blub"])
202 .unwrap();
203 assert_eq!(rows.len(), 0);
204
205 let rows = conn.query("SELECT bar FROM foo", &[]).unwrap();
206 assert_eq!(rows.len(), 1);
207 Ok(())
208 }
209}