dockertest-server 0.1.7

A test framework built around dockertest for testing against server containers.
Documentation
/// Contains traits and helper functions for creating servers
use std::collections::HashMap;

use dockertest::{waitfor::WaitFor, Composition, Image, RunningContainer, Source};

/// A configuration capable of configuring a [Server].
///
/// Types that implement this trait are intended to have a one-to-one
/// relationship with a [Server] as specified by [Server::Config]. The
/// implementation is left intentionally sparse in order to maximize the process
/// of creating a container [Composition]. The [generate_composition] helper
/// function is provided for creating a [Composition] from the usual required
/// configuration options.
///
/// See also [Test][crate::test::Test].
pub trait Config: Clone + Send + Sync {
    fn into_composition(self) -> Composition;
    fn handle(&self) -> &str;
}

/// A running instance of a specific container generated by a [Config].
///
/// Types that implement this trait are intended to have a one-to-one
/// relationship with a [Config] as specified by [Server::Config]. When a
/// [Test][crate::test::Test] is created it is passed one or more
/// [Configs][Config] which determine what containers are brought up during the
/// test. A [Server] represents a running container within the context of a
/// single test. This is reflected by the fact that a [Server] is created using
/// a [Config] as well as the runtime data provided by a [RunningContainer].
///
/// Types implementing this trait should provide as much utility to the end-user
/// as possible for interacting with the running container. For example, if the
/// container is a web server, this trait should provide functionality for
/// obtaining it's URL.
pub trait Server {
    type Config: Config + 'static;

    fn new(config: &Self::Config, container: &RunningContainer) -> Self;
}

/// A helper struct for creating [Compositions][Composition] from a set of
/// common configuration parameters.
///
/// This type can be freely cast into a [Composition]. It is only intended for
/// basic use-cases where limited control over how the [Composition] is
/// configured is acceptable.
pub struct ContainerConfig {
    pub args: Vec<String>,
    pub env: HashMap<String, String>,
    pub handle: String,
    pub name: String,
    pub source: Source,
    pub version: String,
    pub ports: Option<Vec<(u32, u32)>>,
    pub wait: Option<Box<dyn WaitFor>>,
    pub bind_mounts: HashMap<String, String>,
}

#[allow(clippy::from_over_into)] // Only supports one-way casting
impl Into<Composition> for ContainerConfig {
    fn into(self) -> Composition {
        let image = Image::with_repository(self.name)
            .source(self.source)
            .tag(self.version);
        let mut comp = Composition::with_image(image);

        if let Some(p) = self.ports {
            for pair in p {
                comp.port_map(pair.0, pair.1);
            }
        };

        let mut composition = match self.wait {
            Some(w) => comp
                .with_cmd(self.args)
                .with_env(self.env)
                .with_wait_for(w)
                .with_container_name(self.handle),
            None => comp
                .with_cmd(self.args)
                .with_env(self.env)
                .with_container_name(self.handle),
        };

        for (container_path, host_path) in &self.bind_mounts {
            composition.bind_mount(host_path, container_path);
        }
        composition
    }
}

/// A helper function for generating random handles.
///
/// The returned handle is a combination of the given name and a random 10
/// character string.
pub fn new_handle(name: &str) -> String {
    format!("{}{}", name, crate::common::rand_string(10))
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_new_handle() {
        let result = super::new_handle("test");
        assert_eq!(result.len(), 14);
    }
}