use std::{borrow::Cow, collections::BTreeMap};
use testcontainers::{
core::{ContainerPort, WaitFor},
Image,
};
const NAME: &str = "mcr.microsoft.com/azure-storage/azurite";
const TAG: &str = "3.34.0";
pub const BLOB_PORT: ContainerPort = ContainerPort::Tcp(10000);
pub const QUEUE_PORT: ContainerPort = ContainerPort::Tcp(10001);
const TABLE_PORT: ContainerPort = ContainerPort::Tcp(10002);
const AZURITE_ACCOUNTS: &str = "AZURITE_ACCOUNTS";
#[derive(Debug, Default, Clone)]
pub struct Azurite {
env_vars: BTreeMap<String, String>,
loose: bool,
skip_api_version_check: bool,
disable_telemetry: bool,
}
impl Azurite {
pub fn with_accounts(self, accounts: String) -> Self {
let mut env_vars = self.env_vars;
env_vars.insert(AZURITE_ACCOUNTS.to_owned(), accounts);
Self { env_vars, ..self }
}
pub fn with_loose(self) -> Self {
Self {
loose: true,
..self
}
}
pub fn with_skip_api_version_check(self) -> Self {
Self {
skip_api_version_check: true,
..self
}
}
pub fn with_disable_telemetry(self) -> Self {
Self {
disable_telemetry: true,
..self
}
}
}
impl Image for Azurite {
fn name(&self) -> &str {
NAME
}
fn tag(&self) -> &str {
TAG
}
fn ready_conditions(&self) -> Vec<WaitFor> {
vec![WaitFor::message_on_stdout(
"Azurite Table service is successfully listening at http://0.0.0.0:10002",
)]
}
fn env_vars(
&self,
) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
&self.env_vars
}
fn cmd(&self) -> impl IntoIterator<Item = impl Into<Cow<'_, str>>> {
let mut cmd = vec![
String::from("azurite"),
String::from("--blobHost"),
String::from("0.0.0.0"),
String::from("--queueHost"),
String::from("0.0.0.0"),
String::from("--tableHost"),
String::from("0.0.0.0"),
];
if self.loose {
cmd.push(String::from("--loose"));
}
if self.skip_api_version_check {
cmd.push(String::from("--skipApiVersionCheck"));
}
if self.disable_telemetry {
cmd.push(String::from("--disableTelemetry"));
}
cmd
}
fn expose_ports(&self) -> &[ContainerPort] {
&[BLOB_PORT, QUEUE_PORT, TABLE_PORT]
}
}
#[cfg(test)]
mod tests {
use azure_storage::{prelude::*, CloudLocation};
use azure_storage_blobs::prelude::*;
use base64::{prelude::BASE64_STANDARD, Engine};
use crate::azurite::{Azurite, BLOB_PORT};
#[tokio::test]
async fn starts_with_async_runner() -> Result<(), Box<dyn std::error::Error + 'static>> {
use testcontainers::runners::AsyncRunner;
let azurite = Azurite::default();
azurite.start().await?;
Ok(())
}
#[test]
fn starts_with_sync_runner() -> Result<(), Box<dyn std::error::Error + 'static>> {
use testcontainers::runners::SyncRunner;
let azurite = Azurite::default();
azurite.start()?;
Ok(())
}
#[test]
fn starts_with_loose() -> Result<(), Box<dyn std::error::Error + 'static>> {
use testcontainers::runners::SyncRunner;
let azurite = Azurite::default().with_loose();
azurite.start()?;
Ok(())
}
#[test]
fn starts_with_with_skip_api_version_check() -> Result<(), Box<dyn std::error::Error + 'static>>
{
use testcontainers::runners::SyncRunner;
let azurite = Azurite::default().with_skip_api_version_check();
azurite.start()?;
Ok(())
}
#[tokio::test]
async fn starts_with_accounts() -> Result<(), Box<dyn std::error::Error + 'static>> {
use testcontainers::runners::AsyncRunner;
let data = b"key1";
let account_key = BASE64_STANDARD.encode(data);
let account_name = "account1";
let container = Azurite::default()
.with_accounts(format!("{account_name}:{account_key};"))
.start()
.await?;
ClientBuilder::with_location(
CloudLocation::Custom {
account: account_name.to_string(),
uri: format!(
"http://127.0.0.1:{}/{}",
container.get_host_port_ipv4(BLOB_PORT).await?,
account_name
),
},
StorageCredentials::access_key(account_name, account_key),
)
.container_client("container-name")
.create()
.await?;
Ok(())
}
}