use std::{borrow::Cow, collections::HashMap};
use testcontainers::{core::WaitFor, Image};
#[derive(Debug, Clone)]
pub struct MssqlServer {
env_vars: HashMap<String, String>,
}
impl MssqlServer {
const NAME: &'static str = "mcr.microsoft.com/mssql/server";
const TAG: &'static str = "2022-CU14-ubuntu-22.04";
pub const DEFAULT_SA_PASSWORD: &'static str = "yourStrong(!)Password";
pub fn with_sa_password(mut self, password: impl Into<String>) -> Self {
self.env_vars
.insert("MSSQL_SA_PASSWORD".into(), password.into());
self
}
pub fn with_accept_eula(mut self) -> Self {
self.env_vars.insert("ACCEPT_EULA".into(), "Y".into());
self
}
}
impl Default for MssqlServer {
fn default() -> Self {
let mut env_vars = HashMap::new();
env_vars.insert(
"MSSQL_SA_PASSWORD".to_owned(),
Self::DEFAULT_SA_PASSWORD.to_owned(),
);
env_vars.insert("MSSQL_PID".to_owned(), "Developer".to_owned());
Self { env_vars }
}
}
impl Image for MssqlServer {
fn name(&self) -> &str {
Self::NAME
}
fn tag(&self) -> &str {
Self::TAG
}
fn ready_conditions(&self) -> Vec<WaitFor> {
vec![
WaitFor::message_on_stdout("SQL Server is now ready for client connections"),
WaitFor::message_on_stdout("Recovery is complete"),
]
}
fn env_vars(
&self,
) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
&self.env_vars
}
}
#[cfg(test)]
mod tests {
use std::error;
use testcontainers::runners::AsyncRunner;
use tiberius::{AuthMethod, Client, Config};
use tokio::net::TcpStream;
use tokio_util::compat::{Compat, TokioAsyncWriteCompatExt};
use super::*;
#[tokio::test]
async fn one_plus_one() -> Result<(), Box<dyn error::Error>> {
let container = MssqlServer::default().with_accept_eula().start().await?;
let config = new_config(
container.get_host().await?,
container.get_host_port_ipv4(1433).await?,
MssqlServer::DEFAULT_SA_PASSWORD,
);
let mut client = get_mssql_client(config).await?;
let stream = client.query("SELECT 1 + 1", &[]).await?;
let row = stream.into_row().await?.unwrap();
assert_eq!(row.get::<i32, _>(0).unwrap(), 2);
Ok(())
}
#[tokio::test]
async fn custom_sa_password() -> Result<(), Box<dyn error::Error>> {
let image = MssqlServer::default()
.with_accept_eula()
.with_sa_password("yourStrongPassword123!");
let container = image.start().await?;
let config = new_config(
container.get_host().await?,
container.get_host_port_ipv4(1433).await?,
"yourStrongPassword123!",
);
let mut client = get_mssql_client(config).await?;
let stream = client.query("SELECT 1 + 1", &[]).await?;
let row = stream.into_row().await?.unwrap();
assert_eq!(row.get::<i32, _>(0).unwrap(), 2);
Ok(())
}
async fn get_mssql_client(
config: Config,
) -> Result<Client<Compat<TcpStream>>, Box<dyn error::Error>> {
let tcp = TcpStream::connect(config.get_addr()).await?;
tcp.set_nodelay(true)?;
let client = Client::connect(config, tcp.compat_write()).await?;
Ok(client)
}
fn new_config(host: impl ToString, port: u16, password: &str) -> Config {
let mut config = Config::new();
config.host(host);
config.port(port);
config.authentication(AuthMethod::sql_server("sa", password));
config.trust_cert();
config
}
}