pg_embedded_setup_unpriv/cluster/
connection.rs1use camino::{Utf8Path, Utf8PathBuf};
4use color_eyre::eyre::WrapErr;
5use postgres::{Client, NoTls};
6use postgresql_embedded::Settings;
7
8use crate::TestBootstrapSettings;
9use crate::error::BootstrapResult;
10
11pub(crate) fn escape_identifier(name: &str) -> String {
16 name.replace('"', "\"\"")
17}
18
19pub(crate) fn connect_admin(url: &str) -> BootstrapResult<Client> {
24 Client::connect(url, NoTls)
25 .wrap_err("failed to connect to admin database")
26 .map_err(crate::error::BootstrapError::from)
27}
28
29#[derive(Debug, Clone)]
43pub struct ConnectionMetadata {
44 settings: Settings,
45 pgpass_file: Utf8PathBuf,
46}
47
48impl ConnectionMetadata {
49 pub(crate) fn from_settings(settings: &TestBootstrapSettings) -> Self {
50 Self {
51 settings: settings.settings.clone(),
52 pgpass_file: settings.environment.pgpass_file.clone(),
53 }
54 }
55
56 #[must_use]
58 pub fn host(&self) -> &str {
59 self.settings.host.as_str()
60 }
61
62 #[must_use]
64 pub const fn port(&self) -> u16 {
65 self.settings.port
66 }
67
68 #[must_use]
70 pub fn superuser(&self) -> &str {
71 self.settings.username.as_str()
72 }
73
74 #[must_use]
76 pub fn password(&self) -> &str {
77 self.settings.password.as_str()
78 }
79
80 #[must_use]
82 pub fn pgpass_file(&self) -> &Utf8Path {
83 self.pgpass_file.as_ref()
84 }
85
86 #[must_use]
89 pub fn database_url(&self, database: &str) -> String {
90 self.settings.url(database)
91 }
92}
93
94#[derive(Debug, Clone)]
111pub struct TestClusterConnection {
112 metadata: ConnectionMetadata,
113}
114
115impl TestClusterConnection {
116 pub(crate) fn new(settings: &TestBootstrapSettings) -> Self {
117 Self {
118 metadata: ConnectionMetadata::from_settings(settings),
119 }
120 }
121
122 #[must_use]
124 pub fn host(&self) -> &str {
125 self.metadata.host()
126 }
127
128 #[must_use]
130 pub const fn port(&self) -> u16 {
131 self.metadata.port()
132 }
133
134 #[must_use]
136 pub fn superuser(&self) -> &str {
137 self.metadata.superuser()
138 }
139
140 #[must_use]
142 pub fn password(&self) -> &str {
143 self.metadata.password()
144 }
145
146 #[must_use]
148 pub fn pgpass_file(&self) -> &Utf8Path {
149 self.metadata.pgpass_file()
150 }
151
152 #[must_use]
154 pub fn metadata(&self) -> ConnectionMetadata {
155 self.metadata.clone()
156 }
157
158 #[must_use]
160 pub fn database_url(&self, database: &str) -> String {
161 self.metadata.database_url(database)
162 }
163
164 #[cfg(feature = "diesel-support")]
169 pub fn diesel_connection(&self, database: &str) -> BootstrapResult<diesel::PgConnection> {
170 use diesel::Connection;
171
172 diesel::PgConnection::establish(&self.database_url(database))
173 .wrap_err(format!("failed to connect to {database} via Diesel"))
174 .map_err(crate::error::BootstrapError::from)
175 }
176
177 pub(super) fn admin_client(&self) -> BootstrapResult<Client> {
179 connect_admin(&self.database_url("postgres"))
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::TestBootstrapSettings;
187 use crate::bootstrap::{ExecutionMode, ExecutionPrivileges, TestBootstrapEnvironment};
188 use postgresql_embedded::Settings;
189 use std::time::Duration;
190
191 fn sample_settings() -> TestBootstrapSettings {
192 let settings = Settings {
193 host: "127.0.0.1".into(),
194 port: 55_321,
195 username: "fixture_user".into(),
196 password: "fixture_pass".into(),
197 data_dir: "/tmp/cluster-data".into(),
198 installation_dir: "/tmp/cluster-install".into(),
199 ..Settings::default()
200 };
201
202 TestBootstrapSettings {
203 privileges: ExecutionPrivileges::Unprivileged,
204 execution_mode: ExecutionMode::InProcess,
205 settings,
206 environment: TestBootstrapEnvironment {
207 home: Utf8PathBuf::from("/tmp/home"),
208 xdg_cache_home: Utf8PathBuf::from("/tmp/home/cache"),
209 xdg_runtime_dir: Utf8PathBuf::from("/tmp/home/run"),
210 pgpass_file: Utf8PathBuf::from("/tmp/home/.pgpass"),
211 tz_dir: Some(Utf8PathBuf::from("/usr/share/zoneinfo")),
212 timezone: "UTC".into(),
213 },
214 worker_binary: None,
215 setup_timeout: Duration::from_secs(1),
216 start_timeout: Duration::from_secs(1),
217 shutdown_timeout: Duration::from_secs(1),
218 }
219 }
220
221 #[test]
222 fn metadata_reflects_underlying_settings() {
223 let settings = sample_settings();
224 let connection = TestClusterConnection::new(&settings);
225 let metadata = connection.metadata();
226
227 assert_eq!(metadata.host(), "127.0.0.1");
228 assert_eq!(metadata.port(), 55_321);
229 assert_eq!(metadata.superuser(), "fixture_user");
230 assert_eq!(metadata.password(), "fixture_pass");
231 assert_eq!(metadata.pgpass_file(), Utf8Path::new("/tmp/home/.pgpass"));
232 }
233
234 #[test]
235 fn database_url_matches_postgresql_embedded() {
236 let settings = sample_settings();
237 let connection = TestClusterConnection::new(&settings);
238 let expected = settings.settings.url("postgres");
239
240 assert_eq!(connection.database_url("postgres"), expected);
241 }
242}