import { Duration , TemporalUnit } from "node-duration" ; import { DockerComposeEnvironment , GenericContainer , Wait } from "testcontainers" ; import { StartedTestContainer , TestContainer } from "testcontainers/dist/test-container" ; import { DockerComposeConfig , EnvironmentVariableMap , JestTestcontainersConfig , SingleContainerConfig , WaitConfig , BindConfig } from "./config" ; const addWaitStrategyToContainer = ( waitStrategy ? ) => ( container ) : TestContainer => { if ( waitStrategy === undefined ) { return container ; } if ( waitStrategy . type === "ports" ) { return container . withStartupTimeout ( new Duration ( waitStrategy . timeout , TemporalUnit . SECONDS ) ) ; } if ( waitStrategy . type === "text" ) { return container . withWaitStrategy ( Wait . forLogMessage ( waitStrategy . text ) ) ; } throw new Error ( "unknown wait strategy for container" ) ; } ; const addEnvironmentVariablesToContainer = ( env ? ) => ( container ) : TestContainer => { if ( env === undefined ) { return container ; } return Object . keys ( env ) . reduce ( ( newContainer , key ) => newContainer . withEnv ( key , env [ key ] ) , container ) ; } ; const addPortsToContainer = ( ports ? ) => ( container ) : TestContainer => { if ( ! Array . isArray ( ports ) || ports . length <= 0 ) { return container ; } return container . withExposedPorts ( ... ports ) ; } ; const addBindsToContainer = ( bindMounts ? ) => ( container ) : TestContainer => { if ( ! bindMounts ) return container ; for ( const bindMount of bindMounts ) { container . withBindMount ( bindMount . source , bindMount . target , bindMount . mode ) ; } return container ; } ; const addNameToContainer = ( name ? ) => ( container ) : TestContainer => { if ( name === undefined ) { return container ; } return container . withName ( name ) ; } ; export function buildTestcontainer ( containerConfig ) { const { image , tag , ports , name , env , wait , bindMounts } = containerConfig ; const container = new GenericContainer ( image , tag ) ; return [ addPortsToContainer ( ports ) , addEnvironmentVariablesToContainer ( env ) , addWaitStrategyToContainer ( wait ) , addBindsToContainer ( bindMounts ) ] . reduce < TestContainer > ( ( res , func ) => func ( res ) , addNameToContainer ( name ) ( container ) ) ; } export function buildDockerComposeEnvironment ( dockerComposeConfig ) { const environment = new DockerComposeEnvironment ( dockerComposeConfig . composeFilePath , dockerComposeConfig . composeFile ) ; if ( dockerComposeConfig ?. startupTimeout ) { return environment . withStartupTimeout ( new Duration ( dockerComposeConfig . startupTimeout , TemporalUnit . MILLISECONDS ) ) ; } return environment ; } export interface StartedContainerAndMetaInfo { ip : string ; name : string ; portMappings : Map < number , number > ; container : StartedTestContainer ; } export function getMetaInfo ( container , ports ? ) { const portMappings = new Map < number , number > ( ) ; return { container , ip : container . getContainerIpAddress ( ) , name : container . getName ( ) , portMappings : ( ports || [ ] ) . reduce ( ( mapping , p ) => container . getMappedPort ( p ) ? mapping . set ( p , container . getMappedPort ( p ) ) : mapping , portMappings ) } ; } export async function startContainer ( containerConfig , containerBuilderFn = buildTestcontainer , infoGetterFn = getMetaInfo ) { const container = containerBuilderFn ( containerConfig ) ; const startedContainer = await container . start ( ) ; return infoGetterFn ( startedContainer , containerConfig . ports ) ; } export async function startDockerComposeContainers ( dockerComposeConfig , dockerComposeBuilderFn = buildDockerComposeEnvironment , infoGetterFn = getMetaInfo ) { const environment = dockerComposeBuilderFn ( dockerComposeConfig ) ; const startedEnvironment = await environment . up ( ) ; const containers = startedEnvironment . startedGenericContainers ; return Object . keys ( containers ) . reduce ( ( acc , containerName ) => ( { ... acc , [ containerName ] : infoGetterFn ( containers [ containerName ] , Array . from ( containers [ containerName ] . boundPorts . ports . keys ( ) ) ) } ) , { } ) ; } export type AllStartedContainersAndMetaInfo = { [ key ] : StartedContainerAndMetaInfo ; } ; export async function startAllContainers ( config , startContainerFn = startContainer , startDockerComposeContainersFn = startDockerComposeContainers ) { if ( "dockerCompose" in config ) { return startDockerComposeContainersFn ( config . dockerCompose as DockerComposeConfig ) ; } const containerKeys = Object . keys ( config ) ; const containerConfigs = Object . values ( config ) ; const startedContainersMetaInfos = await Promise . all ( containerConfigs . map ( containerConfig => startContainerFn ( containerConfig ) ) ) ; return containerKeys . reduce ( ( acc , key , idx ) => ( { ... acc , [ key ] : startedContainersMetaInfos [ idx ] } ) , { } ) ; }