11import { ContainerInspectInfo , ImageInspectInfo } from "dockerode" ;
2- import path from "path" ;
3- import { randomUuid } from "../common/uuid" ;
42import { ContainerRuntimeClient } from "../container-runtime" ;
5- import { checkContainerIsHealthy , getHealthCheckStatus } from "../utils/test-helper" ;
63import { HealthCheckWaitStrategy } from "../wait-strategies/health-check-wait-strategy" ;
74import { HostPortWaitStrategy } from "../wait-strategies/host-port-wait-strategy" ;
85import { Wait } from "../wait-strategies/wait" ;
6+ import { WaitStrategy } from "../wait-strategies/wait-strategy" ;
97import { GenericContainer } from "./generic-container" ;
108
11- const fixtures = path . resolve ( __dirname , ".." , ".." , "fixtures" , "docker" ) ;
12-
139class TestGenericContainer extends GenericContainer {
14- public selectWaitStrategyForTest ( client : ContainerRuntimeClient , inspectResult : ContainerInspectInfo ) {
15- return this . selectWaitStrategy ( client , inspectResult ) ;
10+ public selectWaitStrategyForTest (
11+ client : ContainerRuntimeClient ,
12+ inspectResult : ContainerInspectInfo ,
13+ waitStrategy ?: WaitStrategy
14+ ) {
15+ return this . selectWaitStrategy ( client , inspectResult , waitStrategy ) ;
1616 }
1717}
1818
19- const containerInspectResult = ( healthcheck ?: { Test : string [ ] } ) : ContainerInspectInfo =>
19+ type ContainerInspectResultOptions = {
20+ healthcheck ?: { Test : string [ ] } ;
21+ healthcheckStatus ?: string ;
22+ } ;
23+
24+ const containerInspectResult = ( {
25+ healthcheck,
26+ healthcheckStatus,
27+ } : ContainerInspectResultOptions = { } ) : ContainerInspectInfo =>
2028 ( {
2129 Config : {
2230 Hostname : "hostname" ,
31+ Image : "image:latest" ,
2332 Labels : { } ,
2433 Healthcheck : healthcheck ,
2534 } ,
@@ -28,168 +37,137 @@ const containerInspectResult = (healthcheck?: { Test: string[] }): ContainerInsp
2837 Running : true ,
2938 StartedAt : "2026-05-14T10:00:00.000Z" ,
3039 FinishedAt : "0001-01-01T00:00:00.000Z" ,
40+ ...( healthcheckStatus === undefined ? { } : { Healthcheck : { Status : healthcheckStatus } } ) ,
3141 } ,
3242 NetworkSettings : {
3343 Ports : { } ,
3444 Networks : { } ,
3545 } ,
3646 } ) as unknown as ContainerInspectInfo ;
3747
48+ const imageInspectResultWithHealthCheck = ( ) : ImageInspectInfo =>
49+ ( {
50+ Config : {
51+ Healthcheck : {
52+ Test : [ "CMD-SHELL" , "test -f /tmp/ready" ] ,
53+ } ,
54+ } ,
55+ } ) as unknown as ImageInspectInfo ;
56+
3857const client = ( imageInspectResult : ImageInspectInfo ) : ContainerRuntimeClient =>
3958 ( {
4059 image : {
4160 inspect : vi . fn ( ) . mockResolvedValue ( imageInspectResult ) ,
4261 } ,
4362 } ) as unknown as ContainerRuntimeClient ;
4463
45- describe ( "GenericContainer default wait strategy" , { timeout : 180_000 } , ( ) => {
46- it ( "should select listening ports when no healthcheck is configured" , async ( ) => {
64+ const clientWithImageInspectFailure = ( ) : ContainerRuntimeClient =>
65+ ( {
66+ image : {
67+ inspect : vi . fn ( ) . mockRejectedValue ( new Error ( "inspect failed" ) ) ,
68+ } ,
69+ } ) as unknown as ContainerRuntimeClient ;
70+
71+ describe ( "GenericContainer default wait strategy" , ( ) => {
72+ it ( "should use an explicitly defined wait strategy" , async ( ) => {
73+ const runtimeClient = client ( imageInspectResultWithHealthCheck ( ) ) ;
74+ const waitStrategy = Wait . forLogMessage ( "ready" ) ;
75+
4776 await expect (
48- new TestGenericContainer ( "image:latest" ) . selectWaitStrategyForTest (
49- client ( { } as ImageInspectInfo ) ,
50- containerInspectResult ( )
51- )
52- ) . resolves . toBeInstanceOf ( HostPortWaitStrategy ) ;
77+ new TestGenericContainer ( "image:latest" )
78+ . withHealthCheck ( {
79+ test : [ "CMD-SHELL" , "test -f /tmp/ready" ] ,
80+ } )
81+ . selectWaitStrategyForTest (
82+ runtimeClient ,
83+ containerInspectResult ( {
84+ healthcheck : {
85+ Test : [ "CMD-SHELL" , "test -f /tmp/ready" ] ,
86+ } ,
87+ } ) ,
88+ waitStrategy
89+ )
90+ ) . resolves . toBe ( waitStrategy ) ;
91+ expect ( runtimeClient . image . inspect ) . not . toHaveBeenCalled ( ) ;
5392 } ) ;
5493
55- it ( "should select image healthcheck when container inspect omits healthcheck config" , async ( ) => {
56- const imageInspectResult = {
57- Config : {
58- Healthcheck : {
59- Test : [ "CMD-SHELL" , "test -f /tmp/ready" ] ,
60- } ,
61- } ,
62- } as unknown as ImageInspectInfo ;
94+ it ( "should select a healthcheck configured with withHealthCheck" , async ( ) => {
95+ const runtimeClient = client ( { } as ImageInspectInfo ) ;
6396
6497 await expect (
65- new TestGenericContainer ( "image:latest" ) . selectWaitStrategyForTest (
66- client ( imageInspectResult ) ,
67- containerInspectResult ( )
68- )
98+ new TestGenericContainer ( "image:latest" )
99+ . withHealthCheck ( {
100+ test : [ "CMD-SHELL" , "test -f /tmp/ready" ] ,
101+ } )
102+ . selectWaitStrategyForTest ( runtimeClient , containerInspectResult ( ) )
69103 ) . resolves . toBeInstanceOf ( HealthCheckWaitStrategy ) ;
104+ expect ( runtimeClient . image . inspect ) . not . toHaveBeenCalled ( ) ;
70105 } ) ;
71106
72- it ( "should select listening ports when the container disables image healthchecks" , async ( ) => {
73- const imageInspectResult = {
74- Config : {
75- Healthcheck : {
76- Test : [ "CMD-SHELL" , "test -f /tmp/ready" ] ,
77- } ,
78- } ,
79- } as unknown as ImageInspectInfo ;
107+ it ( "should select a healthcheck configured on the container" , async ( ) => {
108+ const runtimeClient = client ( { } as ImageInspectInfo ) ;
80109
81110 await expect (
82111 new TestGenericContainer ( "image:latest" ) . selectWaitStrategyForTest (
83- client ( imageInspectResult ) ,
84- containerInspectResult ( { Test : [ "NONE" ] } )
112+ runtimeClient ,
113+ containerInspectResult ( {
114+ healthcheck : {
115+ Test : [ "CMD-SHELL" , "test -f /tmp/ready" ] ,
116+ } ,
117+ } )
85118 )
86- ) . resolves . toBeInstanceOf ( HostPortWaitStrategy ) ;
119+ ) . resolves . toBeInstanceOf ( HealthCheckWaitStrategy ) ;
120+ expect ( runtimeClient . image . inspect ) . not . toHaveBeenCalled ( ) ;
87121 } ) ;
88122
89- it ( "should wait for a healthcheck configured with withHealthCheck" , async ( ) => {
90- await using container = await new GenericContainer ( "cristianrgreco/testcontainer:1.1.14" )
91- . withExposedPorts ( 8080 )
92- . withCommand ( [ "sh" , "-c" , "rm -f /tmp/ready; (sleep 4; touch /tmp/ready) & node index.js" ] )
93- . withHealthCheck ( {
94- test : [ "CMD-SHELL" , "test -f /tmp/ready" ] ,
95- interval : 1_000 ,
96- timeout : 1_000 ,
97- retries : 10 ,
98- } )
99- . start ( ) ;
100-
101- expect ( await getHealthCheckStatus ( container ) ) . toBe ( "healthy" ) ;
102- await checkContainerIsHealthy ( container ) ;
103- } ) ;
123+ it ( "should select a healthcheck when container inspect includes healthcheck status" , async ( ) => {
124+ const runtimeClient = client ( { } as ImageInspectInfo ) ;
104125
105- it ( "should prefer a healthcheck configured with withHealthCheck over an image healthcheck" , async ( ) => {
106- const context = path . resolve ( fixtures , "docker-with-delayed-health-check" ) ;
107- const genericContainer = await GenericContainer . fromDockerfile ( context ) . build ( ) ;
108- await using container = await genericContainer
109- . withExposedPorts ( 8080 )
110- . withCommand ( [ "sh" , "-c" , "rm -f /tmp/ready /tmp/custom-ready; touch /tmp/custom-ready; node index.js" ] )
111- . withHealthCheck ( {
112- test : [ "CMD-SHELL" , "test -f /tmp/custom-ready" ] ,
113- interval : 1_000 ,
114- timeout : 1_000 ,
115- retries : 10 ,
116- } )
117- . start ( ) ;
118-
119- expect ( await getHealthCheckStatus ( container ) ) . toBe ( "healthy" ) ;
120- await checkContainerIsHealthy ( container ) ;
126+ await expect (
127+ new TestGenericContainer ( "image:latest" ) . selectWaitStrategyForTest (
128+ runtimeClient ,
129+ containerInspectResult ( { healthcheckStatus : "starting" } )
130+ )
131+ ) . resolves . toBeInstanceOf ( HealthCheckWaitStrategy ) ;
132+ expect ( runtimeClient . image . inspect ) . not . toHaveBeenCalled ( ) ;
121133 } ) ;
122134
123- it ( "should wait for a healthcheck defined in the image " , async ( ) => {
124- const context = path . resolve ( fixtures , "docker-with-delayed-health-check" ) ;
125- const genericContainer = await GenericContainer . fromDockerfile ( context ) . build ( ) ;
126- await using container = await genericContainer . withExposedPorts ( 8080 ) . start ( ) ;
127-
128- expect ( await getHealthCheckStatus ( container ) ) . toBe ( "healthy" ) ;
129- await checkContainerIsHealthy ( container ) ;
135+ it ( "should select listening ports when no healthcheck is configured " , async ( ) => {
136+ await expect (
137+ new TestGenericContainer ( "image:latest" ) . selectWaitStrategyForTest (
138+ client ( { } as ImageInspectInfo ) ,
139+ containerInspectResult ( )
140+ )
141+ ) . resolves . toBeInstanceOf ( HostPortWaitStrategy ) ;
130142 } ) ;
131143
132- it ( "should use listening ports if the image disables healthcheck" , async ( ) => {
133- const context = path . resolve ( fixtures , "docker-with-disabled-health-check" ) ;
134- const genericContainer = await GenericContainer . fromDockerfile ( context ) . build ( ) ;
135- await using container = await genericContainer . withExposedPorts ( 8080 ) . withStartupTimeout ( 1_000 ) . start ( ) ;
136-
137- await checkContainerIsHealthy ( container ) ;
138- expect ( await getHealthCheckStatus ( container ) ) . toBeUndefined ( ) ;
144+ it ( "should select image healthcheck when container inspect omits healthcheck config " , async ( ) => {
145+ await expect (
146+ new TestGenericContainer ( "image:latest" ) . selectWaitStrategyForTest (
147+ client ( imageInspectResultWithHealthCheck ( ) ) ,
148+ containerInspectResult ( )
149+ )
150+ ) . resolves . toBeInstanceOf ( HealthCheckWaitStrategy ) ;
139151 } ) ;
140152
141- it . sequential ( "should wait for an image healthcheck when reusing a stopped container" , async ( ) => {
142- vi . stubEnv ( "TESTCONTAINERS_REUSE_ENABLE" , "true" ) ;
143-
144- const imageName = `localhost/${ randomUuid ( ) } :${ randomUuid ( ) } ` ;
145- const containerName = `reusable-healthcheck-${ randomUuid ( ) } ` ;
146- const context = path . resolve ( fixtures , "docker-with-delayed-health-check" ) ;
147- await GenericContainer . fromDockerfile ( context ) . build ( imageName ) ;
148-
149- const container1 = await new GenericContainer ( imageName )
150- . withName ( containerName )
151- . withExposedPorts ( 8080 )
152- . withReuse ( )
153- . start ( ) ;
154- await container1 . stop ( { remove : false , timeout : 10_000 } ) ;
155-
156- await using container2 = await new GenericContainer ( imageName )
157- . withName ( containerName )
158- . withExposedPorts ( 8080 )
159- . withReuse ( )
160- . start ( ) ;
161-
162- expect ( container2 . getId ( ) ) . toBe ( container1 . getId ( ) ) ;
163- expect ( await getHealthCheckStatus ( container2 ) ) . toBe ( "healthy" ) ;
164- await container2 . stop ( { remove : true } ) ;
153+ it ( "should select listening ports when image inspect fails" , async ( ) => {
154+ await expect (
155+ new TestGenericContainer ( "image:latest" ) . selectWaitStrategyForTest (
156+ clientWithImageInspectFailure ( ) ,
157+ containerInspectResult ( )
158+ )
159+ ) . resolves . toBeInstanceOf ( HostPortWaitStrategy ) ;
165160 } ) ;
166161
167- it ( "should use an explicitly defined wait strategy even if image defines healthcheck" , async ( ) => {
168- const context = path . resolve ( fixtures , "docker-with-delayed-health-check" ) ;
169- const genericContainer = await GenericContainer . fromDockerfile ( context ) . build ( ) ;
170- await using container = await genericContainer
171- . withExposedPorts ( 8080 )
172- . withWaitStrategy ( Wait . forListeningPorts ( ) )
173- . withStartupTimeout ( 1_000 )
174- . start ( ) ;
175-
176- await checkContainerIsHealthy ( container ) ;
177- } ) ;
162+ it ( "should select listening ports when the container disables image healthchecks" , async ( ) => {
163+ const runtimeClient = client ( imageInspectResultWithHealthCheck ( ) ) ;
178164
179- it ( "should use an explicitly defined wait strategy even if withHealthCheck is called" , async ( ) => {
180- await using container = await new GenericContainer ( "cristianrgreco/testcontainer:1.1.14" )
181- . withExposedPorts ( 8080 )
182- . withCommand ( [ "sh" , "-c" , "rm -f /tmp/ready; (sleep 4; touch /tmp/ready) & node index.js" ] )
183- . withHealthCheck ( {
184- test : [ "CMD-SHELL" , "test -f /tmp/ready" ] ,
185- interval : 1_000 ,
186- timeout : 1_000 ,
187- retries : 10 ,
188- } )
189- . withWaitStrategy ( Wait . forListeningPorts ( ) )
190- . withStartupTimeout ( 1_000 )
191- . start ( ) ;
192-
193- await checkContainerIsHealthy ( container ) ;
165+ await expect (
166+ new TestGenericContainer ( "image:latest" ) . selectWaitStrategyForTest (
167+ runtimeClient ,
168+ containerInspectResult ( { healthcheck : { Test : [ "NONE" ] } } )
169+ )
170+ ) . resolves . toBeInstanceOf ( HostPortWaitStrategy ) ;
171+ expect ( runtimeClient . image . inspect ) . not . toHaveBeenCalled ( ) ;
194172 } ) ;
195173} ) ;
0 commit comments