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