testcontainers_modules/mssql_server/
mod.rs1use std::{borrow::Cow, collections::HashMap};
2
3use testcontainers::{core::WaitFor, Image};
4
5#[derive(Debug, Clone)]
54pub struct MssqlServer {
55 env_vars: HashMap<String, String>,
56}
57
58impl MssqlServer {
59 const NAME: &'static str = "mcr.microsoft.com/mssql/server";
60 const TAG: &'static str = "2022-CU14-ubuntu-22.04";
61 pub const DEFAULT_SA_PASSWORD: &'static str = "yourStrong(!)Password";
64
65 pub fn with_sa_password(mut self, password: impl Into<String>) -> Self {
67 self.env_vars
68 .insert("MSSQL_SA_PASSWORD".into(), password.into());
69 self
70 }
71
72 pub fn with_accept_eula(mut self) -> Self {
77 self.env_vars.insert("ACCEPT_EULA".into(), "Y".into());
78 self
79 }
80}
81
82impl Default for MssqlServer {
83 fn default() -> Self {
84 let mut env_vars = HashMap::new();
85 env_vars.insert(
86 "MSSQL_SA_PASSWORD".to_owned(),
87 Self::DEFAULT_SA_PASSWORD.to_owned(),
88 );
89 env_vars.insert("MSSQL_PID".to_owned(), "Developer".to_owned());
90
91 Self { env_vars }
92 }
93}
94
95impl Image for MssqlServer {
96 fn name(&self) -> &str {
97 Self::NAME
98 }
99
100 fn tag(&self) -> &str {
101 Self::TAG
102 }
103
104 fn ready_conditions(&self) -> Vec<WaitFor> {
105 vec![
107 WaitFor::message_on_stdout("SQL Server is now ready for client connections"),
108 WaitFor::message_on_stdout("Recovery is complete"),
109 ]
110 }
111
112 fn env_vars(
113 &self,
114 ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
115 &self.env_vars
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use std::error;
122
123 use testcontainers::runners::AsyncRunner;
124 use tiberius::{AuthMethod, Client, Config};
125 use tokio::net::TcpStream;
126 use tokio_util::compat::{Compat, TokioAsyncWriteCompatExt};
127
128 use super::*;
129
130 #[tokio::test]
131 async fn one_plus_one() -> Result<(), Box<dyn error::Error>> {
132 let container = MssqlServer::default().with_accept_eula().start().await?;
133 let config = new_config(
134 container.get_host().await?,
135 container.get_host_port_ipv4(1433).await?,
136 MssqlServer::DEFAULT_SA_PASSWORD,
137 );
138 let mut client = get_mssql_client(config).await?;
139
140 let stream = client.query("SELECT 1 + 1", &[]).await?;
141 let row = stream.into_row().await?.unwrap();
142
143 assert_eq!(row.get::<i32, _>(0).unwrap(), 2);
144
145 Ok(())
146 }
147
148 #[tokio::test]
149 async fn custom_sa_password() -> Result<(), Box<dyn error::Error>> {
150 let image = MssqlServer::default()
151 .with_accept_eula()
152 .with_sa_password("yourStrongPassword123!");
153 let container = image.start().await?;
154 let config = new_config(
155 container.get_host().await?,
156 container.get_host_port_ipv4(1433).await?,
157 "yourStrongPassword123!",
158 );
159 let mut client = get_mssql_client(config).await?;
160
161 let stream = client.query("SELECT 1 + 1", &[]).await?;
162 let row = stream.into_row().await?.unwrap();
163
164 assert_eq!(row.get::<i32, _>(0).unwrap(), 2);
165
166 Ok(())
167 }
168
169 async fn get_mssql_client(
170 config: Config,
171 ) -> Result<Client<Compat<TcpStream>>, Box<dyn error::Error>> {
172 let tcp = TcpStream::connect(config.get_addr()).await?;
173 tcp.set_nodelay(true)?;
174
175 let client = Client::connect(config, tcp.compat_write()).await?;
176
177 Ok(client)
178 }
179
180 fn new_config(host: impl ToString, port: u16, password: &str) -> Config {
181 let mut config = Config::new();
182 config.host(host);
183 config.port(port);
184 config.authentication(AuthMethod::sql_server("sa", password));
185 config.trust_cert();
186
187 config
188 }
189}