View Javadoc
1   package de.juplo.httpresources;
2   
3   
4   import static de.juplo.httpresources.ConfigurableNginxContainer.DEFAULT_NGINX_PORT;
5   import static org.mockserver.model.HttpClassCallback.callback;
6   import static org.mockserver.model.HttpOverrideForwardedRequest.forwardOverriddenRequest;
7   import static org.mockserver.model.HttpRequest.request;
8   import static org.mockserver.model.HttpResponse.response;
9   
10  import org.junit.jupiter.api.AfterEach;
11  import org.junit.jupiter.api.BeforeEach;
12  import org.mockserver.integration.ClientAndServer;
13  import org.mockserver.matchers.TimeToLive;
14  import org.mockserver.matchers.Times;
15  import org.mockserver.mock.action.ExpectationForwardAndResponseCallback;
16  import org.mockserver.model.HttpOverrideForwardedRequest;
17  import org.mockserver.model.HttpRequest;
18  import org.mockserver.model.HttpResponse;
19  import org.slf4j.Logger;
20  import org.slf4j.LoggerFactory;
21  import org.springframework.beans.factory.annotation.Autowired;
22  import org.springframework.cache.Cache;
23  import org.springframework.cache.concurrent.ConcurrentMapCache;
24  import org.springframework.context.annotation.Bean;
25  import org.springframework.context.annotation.Configuration;
26  import org.springframework.http.HttpStatus;
27  import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
28  import org.springframework.util.StringUtils;
29  import org.testcontainers.containers.NginxContainer;
30  import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
31  import org.testcontainers.shaded.org.apache.commons.lang.NotImplementedException;
32  
33  import java.time.*;
34  import java.time.format.DateTimeFormatter;
35  import java.util.Locale;
36  
37  
38  /**
39   * This class starts a
40   * {@link https://www.testcontainers.org/modules/nginx/ NginX-Webserver}
41   * via a {@link https://www.testcontainers.org/ Testcontainer} and a
42   * standalone {@link http://www.mock-server.com/#what-is-mockserver MockServer},
43   * that can be used to intercept requests for assertions / verifications.
44   * <p>
45   * We use the {@link
46   * https://www.testcontainers.org/test_framework_integration/manual_lifecycle_control/#singleton-containers
47   * Singleton Pattern} to start the NginX-Testcontainer and start the MockServer
48   * programmatically via the {@link
49   * http://www.mock-server.com/mock_server/running_mock_server.html#client_api
50   * Client-API}.
51   * @author Kai Moritz
52   */
53  public abstract class IntegrationTestBase
54  {
55    private final static Logger LOG =
56        LoggerFactory.getLogger(IntegrationTestBase.class);
57  
58    public static ClockStub CLOCK = new ClockStub();
59    public static DateTimeFormatter formatter =
60        DateTimeFormatter
61            .ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH)
62            .withZone(ZoneId.of("GMT"));
63  
64    public static final NginxContainer NGINX;
65  
66    static
67    {
68      NGINX =
69          new ConfigurableNginxContainer()
70              .withConfiguration("src/test/resources/nginx/nginx.conf")
71              .withCustomContent("src/test/resources/remote/");
72      NGINX.waitingFor(new HttpWaitStrategy()).start();
73    }
74  
75  
76    public static String NGINIX_IP()
77    {
78      return NGINX.getContainerIpAddress();
79    }
80  
81    public static int NGINX_PORT()
82    {
83      return NGINX.getMappedPort(DEFAULT_NGINX_PORT);
84    }
85  
86    public static String NGINX_URI()
87    {
88      return "http://" + NGINIX_IP() + ":" + NGINX_PORT();
89    }
90  
91    public static HttpRequest FETCH(String path)
92    {
93      return request().withPath(path);
94    }
95  
96    public static HttpResponse RESPONSE()
97    {
98      CLOCK.tick();
99      HttpResponse response =
100         response().withHeader("Date", formatter.format(CLOCK.instant()));
101     CLOCK.tick();
102     return response;
103   }
104 
105   public static HttpOverrideForwardedRequest NGINX()
106   {
107     return NGINX(null);
108   }
109 
110   public static HttpOverrideForwardedRequest NGINX(String path)
111   {
112     HttpRequest request = request().withSocketAddress(NGINIX_IP(),NGINX_PORT());
113     if (StringUtils.hasText(path))
114       request = request.withPath(path);
115     return forwardOverriddenRequest(request, RESPONSE());
116   }
117 
118   public static HttpResponse NOT_FOUND()
119   {
120     return RESPONSE().withStatusCode(HttpStatus.NOT_FOUND.value());
121   }
122 
123   public static HttpResponse INTERNAL_SERVER_ERROR()
124   {
125     return RESPONSE().withStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
126   }
127 
128 
129   @Configuration
130   public static class IntegrationTestConfiguration
131   {
132     @Bean(destroyMethod = "stop")
133     public ClientAndServer server()
134     {
135       return ClientAndServer.startClientAndServer();
136     }
137 
138     @Bean
139     public HttpResourceFetcher fetcher(Cache cache)
140     {
141       return
142           new HttpResourceFetcher(
143               new HttpComponentsClientHttpRequestFactory(),
144               cache,
145               CLOCK);
146     }
147 
148     @Bean
149     public Cache cache()
150     {
151       return new ConcurrentMapCache("TEST");
152     }
153 
154     @Bean
155     public HttpResources httpResources(HttpResourceFetcher fetcher)
156     {
157       return new HttpResources(fetcher, CLOCK);
158     }
159   }
160 
161   @Autowired
162   public ClientAndServer server;
163 
164 
165   /**
166    * Prepares the mock-server to forward any request to the NginX-server,
167    * for which no more specific expectation was specified.
168    */
169   @BeforeEach
170   public void prepareMockServer()
171   {
172     CLOCK.now = ZonedDateTime.now();
173     server
174         .when(request(), Times.unlimited(), TimeToLive.unlimited(), Integer.MIN_VALUE)
175         .forward(callback().withCallbackClass(Callback.class));
176   }
177 
178   @AfterEach
179   public void clearMockServer()
180   {
181     server.reset();
182   }
183 
184 
185   public int getMockServerPort()
186   {
187     return server.getLocalPort();
188   }
189 
190   public String getMockServerUri()
191   {
192     return "http://localhost:" + getMockServerPort();
193   }
194 
195 
196   public String address(String path)
197   {
198     return getMockServerUri() + path;
199   }
200 
201   public static class Callback implements ExpectationForwardAndResponseCallback
202   {
203     @Override
204     public HttpRequest handle(HttpRequest request) throws Exception
205     {
206       return request.withSocketAddress(NGINIX_IP(),NGINX_PORT());
207     }
208 
209     @Override
210     public HttpResponse handle(HttpRequest request, HttpResponse response) throws Exception
211     {
212       CLOCK.tick();
213       HttpResponse modifiedResponse = response.replaceHeader("Date", formatter.format(CLOCK.instant()));
214       CLOCK.tick();
215       return modifiedResponse;
216     }
217   }
218 
219   public static class ClockStub extends Clock
220   {
221     public final static Duration DEFAULT_TICK = Duration.ofMillis(100);
222 
223     public ZonedDateTime now = ZonedDateTime.now();
224 
225 
226     public void tick()
227     {
228       now = now.plus(DEFAULT_TICK);
229     }
230 
231     public void timetravel(Duration duration)
232     {
233       now = now.plus(duration);
234     }
235 
236     @Override
237     public ZoneId getZone()
238     {
239       return now.getZone();
240     }
241 
242     @Override
243     public Clock withZone(ZoneId zone)
244     {
245       throw new NotImplementedException();
246     }
247 
248     @Override
249     public Instant instant()
250     {
251       return now.toInstant();
252     }
253   };
254 }