use std::{
path::{Path, PathBuf},
time::Duration,
};
use crate::port::Port;
use crate::{
config::{Config, ContainerConfig},
container::Container,
image::Image,
port::PortAccess,
probe::{MessageProbe, MessageSource},
service::Service,
TestError,
};
const MOCKING_PORT: u16 = 4771;
const SERVICE_PORT: u16 = 4770;
const GRIP_MOCK_SCHEMA: &str = "tcp";
pub const GRIPMOCK_IMAGE_NAME: &str = "tkpd/gripmock";
#[derive(Debug, Clone)]
pub struct GripMockConfig {
hostname: String,
version: String,
image_name: String,
proto: PathBuf,
stub: Option<PathBuf>,
}
impl GripMockConfig {
pub fn hostname(&self) -> &str {
&self.hostname
}
pub fn version(&self) -> &str {
&self.version
}
pub fn image_name(&self) -> &str {
&self.image_name
}
pub fn proto(&self) -> &Path {
&self.proto
}
pub fn stub(&self) -> Option<&Path> {
self.stub.as_deref()
}
pub fn builder() -> GripMockConfigBuilder {
GripMockConfigBuilder::default()
}
}
#[derive(Debug, Clone, Default)]
pub struct GripMockConfigBuilder {
hostname: Option<String>,
version: Option<String>,
image_name: Option<String>,
proto: Option<PathBuf>,
stub: Option<PathBuf>,
}
impl GripMockConfigBuilder {
pub fn hostname<T: Into<String>>(self, hostname: T) -> Self {
Self {
hostname: Some(hostname.into()),
..self
}
}
pub fn version<T: Into<String>>(self, version: T) -> Self {
Self {
version: Some(version.into()),
..self
}
}
pub fn image_name<T: Into<String>>(self, image_name: T) -> Self {
Self {
image_name: Some(image_name.into()),
..self
}
}
pub fn proto<T: Into<PathBuf>>(self, proto: T) -> Self {
Self {
proto: Some(proto.into()),
..self
}
}
pub fn stub<T: Into<PathBuf>>(self, stub: T) -> Self {
Self {
stub: Some(stub.into()),
..self
}
}
pub fn build(self) -> GripMockConfig {
GripMockConfig {
hostname: self.hostname.unwrap_or_else(|| "gripmock".to_string()),
version: self.version.unwrap_or_else(|| "v1.13".to_string()),
image_name: self
.image_name
.unwrap_or_else(|| GRIPMOCK_IMAGE_NAME.to_string()),
proto: self.proto.expect("missing proto file"),
stub: self.stub,
}
}
}
impl Config for GripMockConfig {
fn hostname(&self) -> &str {
self.hostname()
}
fn port(&self) -> Port {
SERVICE_PORT
}
fn schema(&self) -> &str {
GRIP_MOCK_SCHEMA
}
fn to_container_config(&self) -> Result<ContainerConfig, TestError> {
let mut mounts = vec![(
self.proto()
.parent()
.expect("expected a proto file")
.as_os_str()
.to_str()
.expect("expected a valid proto file"),
"proto",
"",
)];
let mut cmd = Vec::new();
if let Some(stub) = self.stub() {
assert!(stub.is_dir(), "'stub' path must be a directory");
mounts.push((
stub.as_os_str()
.to_str()
.expect("expected a valid stub directory"),
"stubs",
"",
));
cmd.push("--stub=/stubs".to_string());
}
cmd.push(format!(
"/proto/{}",
self.proto()
.file_name()
.and_then(|s| s.to_str())
.expect("expected a valid proto file")
));
Ok(ContainerConfig::builder(
self.hostname(),
Image::from_repository(self.image_name()).with_version(self.version()),
)
.ports([
PortAccess::published(MOCKING_PORT),
PortAccess::exposed(SERVICE_PORT),
])
.mounts(mounts)
.readiness(
MessageProbe::builder("Serving")
.times(2)
.timeout(Duration::from_secs(30))
.source(MessageSource::Any)
.build(),
)
.cmd(cmd)
.build())
}
}
#[derive(Debug)]
pub struct GripMock {
address: String,
}
impl GripMock {
pub fn address(&self) -> &str {
&self.address
}
}
impl Service for GripMock {
type Config = GripMockConfig;
fn new(_: &Self::Config, container: &Container) -> Self {
let socket = container
.socket(MOCKING_PORT)
.expect("port should be configured")
.to_string();
let address = format!("http://{socket}");
Self { address }
}
}