pg_embedded_setup_unpriv/cluster/
connection.rs1use camino::{Utf8Path, Utf8PathBuf};
4#[cfg(feature = "diesel-support")]
5use color_eyre::eyre::WrapErr;
6use color_eyre::eyre::eyre;
7use postgres::{Client, NoTls};
8use postgresql_embedded::Settings;
9
10use crate::{TestBootstrapSettings, error::BootstrapResult};
11
12pub(crate) fn escape_identifier(name: &str) -> String { name.replace('"', "\"\"") }
17
18pub(crate) fn connect_admin(url: &str) -> BootstrapResult<Client> {
23 Client::connect(url, NoTls).map_err(|err| {
24 crate::error::BootstrapError::from(eyre!("failed to connect to admin database: {err}"))
25 })
26}
27
28#[derive(Debug, Clone)]
42pub struct ConnectionMetadata {
43 settings: Settings,
44 pgpass_file: Utf8PathBuf,
45}
46
47impl ConnectionMetadata {
48 pub(crate) fn from_settings(settings: &TestBootstrapSettings) -> Self {
49 Self {
50 settings: settings.settings.clone(),
51 pgpass_file: settings.environment.pgpass_file.clone(),
52 }
53 }
54
55 #[must_use]
57 pub fn host(&self) -> &str { self.settings.host.as_str() }
58
59 #[must_use]
61 pub const fn port(&self) -> u16 { self.settings.port }
62
63 #[must_use]
65 pub fn superuser(&self) -> &str { self.settings.username.as_str() }
66
67 #[must_use]
69 pub fn password(&self) -> &str { self.settings.password.as_str() }
70
71 #[must_use]
73 pub fn pgpass_file(&self) -> &Utf8Path { self.pgpass_file.as_ref() }
74
75 #[must_use]
78 pub fn database_url(&self, database: &str) -> String { self.settings.url(database) }
79}
80
81#[derive(Debug, Clone)]
98pub struct TestClusterConnection {
99 metadata: ConnectionMetadata,
100}
101
102impl TestClusterConnection {
103 pub(crate) fn new(settings: &TestBootstrapSettings) -> Self {
104 Self {
105 metadata: ConnectionMetadata::from_settings(settings),
106 }
107 }
108
109 #[must_use]
111 pub fn host(&self) -> &str { self.metadata.host() }
112
113 #[must_use]
115 pub const fn port(&self) -> u16 { self.metadata.port() }
116
117 #[must_use]
119 pub fn superuser(&self) -> &str { self.metadata.superuser() }
120
121 #[must_use]
123 pub fn password(&self) -> &str { self.metadata.password() }
124
125 #[must_use]
127 pub fn pgpass_file(&self) -> &Utf8Path { self.metadata.pgpass_file() }
128
129 #[must_use]
131 pub fn metadata(&self) -> ConnectionMetadata { self.metadata.clone() }
132
133 #[must_use]
135 pub fn database_url(&self, database: &str) -> String { self.metadata.database_url(database) }
136
137 #[cfg(feature = "diesel-support")]
142 pub fn diesel_connection(&self, database: &str) -> BootstrapResult<diesel::PgConnection> {
143 use diesel::Connection;
144
145 diesel::PgConnection::establish(&self.database_url(database))
146 .wrap_err(format!("failed to connect to {database} via Diesel"))
147 .map_err(crate::error::BootstrapError::from)
148 }
149
150 pub(super) fn admin_client(&self) -> BootstrapResult<Client> {
152 connect_admin(&self.database_url("postgres"))
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use std::time::Duration;
160
161 use postgresql_embedded::Settings;
162
163 use super::*;
164 use crate::{
165 CleanupMode,
166 TestBootstrapSettings,
167 bootstrap::{ExecutionMode, ExecutionPrivileges, TestBootstrapEnvironment},
168 };
169
170 fn sample_settings() -> TestBootstrapSettings {
171 let settings = Settings {
172 host: "127.0.0.1".into(),
173 port: 55_321,
174 username: "fixture_user".into(),
175 password: "fixture_pass".into(),
176 data_dir: "/tmp/cluster-data".into(),
177 installation_dir: "/tmp/cluster-install".into(),
178 ..Settings::default()
179 };
180
181 TestBootstrapSettings {
182 privileges: ExecutionPrivileges::Unprivileged,
183 execution_mode: ExecutionMode::InProcess,
184 settings,
185 environment: TestBootstrapEnvironment {
186 home: Utf8PathBuf::from("/tmp/home"),
187 xdg_cache_home: Utf8PathBuf::from("/tmp/home/cache"),
188 xdg_runtime_dir: Utf8PathBuf::from("/tmp/home/run"),
189 pgpass_file: Utf8PathBuf::from("/tmp/home/.pgpass"),
190 tz_dir: Some(Utf8PathBuf::from("/usr/share/zoneinfo")),
191 timezone: "UTC".into(),
192 },
193 worker_binary: None,
194 setup_timeout: Duration::from_secs(1),
195 start_timeout: Duration::from_secs(1),
196 shutdown_timeout: Duration::from_secs(1),
197 cleanup_mode: CleanupMode::default(),
198 binary_cache_dir: None,
199 }
200 }
201
202 #[test]
203 fn metadata_reflects_underlying_settings() {
204 let settings = sample_settings();
205 let connection = TestClusterConnection::new(&settings);
206 let metadata = connection.metadata();
207
208 assert_eq!(metadata.host(), "127.0.0.1");
209 assert_eq!(metadata.port(), 55_321);
210 assert_eq!(metadata.superuser(), "fixture_user");
211 assert_eq!(metadata.password(), "fixture_pass");
212 assert_eq!(metadata.pgpass_file(), Utf8Path::new("/tmp/home/.pgpass"));
213 }
214
215 #[test]
216 fn database_url_matches_postgresql_embedded() {
217 let settings = sample_settings();
218 let connection = TestClusterConnection::new(&settings);
219 let expected = settings.settings.url("postgres");
220
221 assert_eq!(connection.database_url("postgres"), expected);
222 }
223}