use testcontainers::{
ContainerAsync, GenericImage,
core::{ContainerPort, ImageExt, WaitFor, wait::HttpWaitStrategy},
runners::AsyncRunner,
};
use url::{Host, Url};
use crate::{client::Client, error::ContainerError};
#[derive(Debug, Clone)]
pub struct ContainerBuilder {
image_name: String,
image_tag: String,
internal_port: ContainerPort,
api_token: String,
}
impl Default for ContainerBuilder {
fn default() -> Self {
Self {
image_name: "thenativeweb/eventsourcingdb".to_string(),
image_tag: "latest".to_string(),
internal_port: ContainerPort::Tcp(3000),
api_token: "secret".to_string(),
}
}
}
impl ContainerBuilder {
#[must_use]
pub fn with_image_tag(mut self, tag: &str) -> Self {
self.image_tag = tag.to_string();
self
}
#[must_use]
pub fn with_api_token(mut self, token: &str) -> Self {
self.api_token = token.to_string();
self
}
#[must_use]
pub fn with_port(mut self, port: impl Into<ContainerPort>) -> Self {
self.internal_port = port.into();
self
}
pub async fn start(self) -> Result<Container, ContainerError> {
Ok(Container {
internal_port: self.internal_port,
api_token: self.api_token.clone(),
instance: GenericImage::new(self.image_name, self.image_tag)
.with_exposed_port(self.internal_port)
.with_wait_for(WaitFor::Http(Box::new(
HttpWaitStrategy::new("/api/v1/ping")
.with_port(self.internal_port)
.with_expected_status_code(200u16),
)))
.with_startup_timeout(std::time::Duration::from_secs(10))
.with_cmd([
"run",
"--api-token",
&self.api_token,
"--data-directory-temporary",
"--http-enabled",
"--https-enabled=false",
])
.start()
.await?,
})
}
}
#[derive(Debug)]
pub struct Container {
instance: ContainerAsync<GenericImage>,
internal_port: ContainerPort,
api_token: String,
}
impl Container {
#[must_use]
pub fn builder() -> ContainerBuilder {
ContainerBuilder::default()
}
pub async fn start_default() -> Result<Container, ContainerError> {
Self::builder().start().await
}
pub async fn get_host(&self) -> Result<Host, ContainerError> {
Ok(self.instance.get_host().await?)
}
pub async fn get_mapped_port(&self) -> Result<u16, ContainerError> {
Ok(self.instance.get_host_port_ipv4(self.internal_port).await?)
}
pub async fn get_base_url(&self) -> Result<Url, ContainerError> {
let host = self.get_host().await?;
let port = self.get_mapped_port().await?;
Ok(Url::parse(&format!("http://{host}:{port}"))?)
}
#[must_use]
pub fn get_api_token(&self) -> &str {
self.api_token.as_str()
}
pub async fn stop(self) -> Result<(), ContainerError> {
self.instance.stop().await?;
Ok(())
}
pub async fn get_client(&self) -> Result<Client, ContainerError> {
let base_url = self.get_base_url().await?;
Ok(Client::new(base_url, self.api_token.clone()))
}
}