use std::collections::HashMap;
use testcontainers::{core::WaitFor, Image};
#[derive(Debug)]
pub struct MssqlServer {
env_vars: HashMap<String, String>,
}
impl MssqlServer {
const NAME: &'static str = "mcr.microsoft.com/mssql/server";
const TAG: &'static str = "2022-CU10-ubuntu-22.04";
const DEFAULT_SA_PASSWORD: &'static str = "yourStrong(!)Password";
pub fn with_sa_password(self, password: impl Into<String>) -> Self {
let mut env_vars = self.env_vars;
env_vars.insert("MSSQL_SA_PASSWORD".to_owned(), password.into());
Self { env_vars }
}
}
impl Default for MssqlServer {
fn default() -> Self {
let mut env_vars = HashMap::new();
env_vars.insert("ACCEPT_EULA".to_owned(), "Y".to_owned());
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 {
type Args = ();
fn name(&self) -> String {
Self::NAME.to_owned()
}
fn tag(&self) -> String {
Self::TAG.to_owned()
}
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) -> Box<dyn Iterator<Item = (&String, &String)> + '_> {
Box::new(self.env_vars.iter())
}
}
#[cfg(test)]
mod tests {
use std::error;
use testcontainers::{clients, RunnableImage};
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 docker = clients::Cli::default();
let container = docker.run(MssqlServer::default());
let config = new_config(container.get_host_port_ipv4(1433), "yourStrong(!)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 docker = clients::Cli::default();
let image = MssqlServer::default().with_sa_password("yourStrongPassword123!");
let container = docker.run(image);
let config = new_config(container.get_host_port_ipv4(1433), "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(())
}
#[tokio::test]
async fn custom_version() -> Result<(), Box<dyn error::Error>> {
let docker = clients::Cli::default();
let image = RunnableImage::from(MssqlServer::default()).with_tag("2019-CU23-ubuntu-20.04");
let container = docker.run(image);
let config = new_config(container.get_host_port_ipv4(1433), "yourStrong(!)Password");
let mut client = get_mssql_client(config).await?;
let stream = client.query("SELECT @@VERSION", &[]).await?;
let row = stream.into_row().await?.unwrap();
assert!(row.get::<&str, _>(0).unwrap().contains("2019"));
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(port: u16, password: &str) -> Config {
let mut config = Config::new();
config.port(port);
config.authentication(AuthMethod::sql_server("sa", password));
config.trust_cert();
config
}
}