View Javadoc
1   package de.juplo.httpresources;
2   
3   
4   import org.junit.jupiter.api.BeforeEach;
5   import org.junit.jupiter.api.Test;
6   import org.mockserver.integration.ClientAndServer;
7   import org.slf4j.Logger;
8   import org.slf4j.LoggerFactory;
9   import org.springframework.beans.factory.annotation.Autowired;
10  import org.springframework.beans.factory.annotation.Value;
11  import org.springframework.boot.autoconfigure.SpringBootApplication;
12  import org.springframework.boot.test.context.SpringBootTest;
13  import org.springframework.cache.Cache;
14  import org.springframework.context.ApplicationContext;
15  import org.springframework.context.annotation.Bean;
16  import org.springframework.core.io.Resource;
17  import org.springframework.util.MimeType;
18  
19  import java.time.Duration;
20  
21  import static org.assertj.core.api.Assertions.assertThat;
22  import static org.junit.jupiter.api.Assertions.*;
23  import static org.mockserver.model.HttpRequest.request;
24  import static org.mockserver.verify.VerificationTimes.exactly;
25  
26  
27  /**
28   *
29   * @author Kai Moritz
30   */
31  @SpringBootTest({
32      "juplo.http-resources.protocol-resolver.enabled=true",
33      "juplo.http-resources.resolver.enabled=false",
34      "juplo.http-resources.resolver.exclusion-patterns=**.txt"
35      })
36  public class HttpResourceChainAwareResourceLoaderIT extends IntegrationTestBase
37  {
38    private final static Logger LOG =
39        LoggerFactory.getLogger(HttpResourceChainAwareResourceLoaderIT.class);
40  
41  
42    @Autowired
43    HttpResourceChainAwareResourceLoader loader;
44    @Autowired
45    Cache cache;
46    @Autowired
47    HttpResources resources;
48  
49    @Value("classpath:static/foo")
50    Resource foo;
51    @Value("classpath:public/bar")
52    Resource bar;
53    @Value("classpath:remote/hello")
54    Resource hello;
55    @Value("classpath:remote/remote.html")
56    Resource remote;
57    @Value("classpath:remote/modified.html")
58    Resource modified;
59    @Value("classpath:fallback/foobar")
60    Resource foobar;
61    @Value("classpath:remote/hallo.txt")
62    Resource hallo;
63  
64  
65    @BeforeEach
66    public void setUp()
67    {
68      cache.clear();
69    }
70  
71  
72    /**
73     * In contrast to {@link HttpResourceResolverIT} and
74     * {@link HttpResourceProtocolResolverIT} it does not
75     * make sense to test through the web-layer here,
76     * because the {@link HttpResourceChainAwareResourceLoader}
77     * is intended to mimic the look-upp through the source,
78     * that are configured for resource-chain for static
79     * http-resources for the look-up of local resources.
80     */
81    @Test
82    public void testResourceHandling() throws Exception
83    {
84      LOG.info("<-- start of test-case");
85  
86      Resource resource;
87  
88      resource = loader.getResource("foo");
89      assertThat(resource.exists());
90      assertThat(resource.getInputStream()).hasSameContentAs(foo.getInputStream());
91  
92      resource = loader.getResource("bar");
93      assertThat(resource.exists());
94      assertThat(resource.getInputStream()).hasSameContentAs(bar.getInputStream());
95  
96      resource = loader.getResource("hello");
97      assertThat(resource.exists());
98      assertThat(resource).isInstanceOf(HttpResource.class);
99      assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("application/octet-stream"));
100     assertThat(resource.getInputStream()).hasSameContentAs(hello.getInputStream());
101 
102     resource = loader.getResource("remote.html");
103     assertThat(resource.exists());
104     assertThat(resource).isInstanceOf(HttpResource.class);
105     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
106     assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
107 
108     resource = loader.getResource("foobar");
109     assertThat(resource.exists());
110     assertThat(resource.getInputStream()).hasSameContentAs(foobar.getInputStream());
111 
112     resource = loader.getResource("/hello");
113     assertThat(resource.exists());
114     assertThat(resource).isInstanceOf(HttpResource.class);
115     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("application/octet-stream"));
116     assertThat(resource.getInputStream()).hasSameContentAs(hello.getInputStream());
117 
118     resource = loader.getResource("hello");
119     assertThat(resource.exists());
120     assertThat(resource).isInstanceOf(HttpResource.class);
121     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("application/octet-stream"));
122     assertThat(resource.getInputStream()).hasSameContentAs(hello.getInputStream());
123 
124     resource = loader.getResource("/hello");
125     assertThat(resource.exists());
126     assertThat(resource).isInstanceOf(HttpResource.class);
127     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("application/octet-stream"));
128     assertThat(resource.getInputStream()).hasSameContentAs(hello.getInputStream());
129 
130     resource = loader.getResource("hallo.txt");
131     assertThat(resource.exists());
132     assertThat(resource).isInstanceOf(HttpResource.class);
133     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/plain"));
134     assertThat(resource.getInputStream()).hasSameContentAs(hallo.getInputStream());
135 
136     server.verify(FETCH("/foo"), exactly(0));
137     server.verify(FETCH("/bar"), exactly(0));
138     server.verify(FETCH("/hello"), exactly(1));
139     server.verify(FETCH("/hallo.txt"), exactly(1));
140     server.verify(FETCH("/remote.html"), exactly(1));
141     server.verify(FETCH("/foobar"), exactly(2));
142   }
143 
144   @Test
145   public void testFetchExistent() throws Exception
146   {
147     LOG.info("<-- Start of test-case");
148 
149     getRemoteHtml();
150 
151     server.verify(FETCH("/remote.html"), exactly(1));
152   }
153 
154   @Test
155   public void testCachingOfExisting() throws Exception
156   {
157     LOG.info("<-- Start of test-case");
158 
159     getRemoteHtml();
160     getRemoteHtml();
161     getRemoteHtml();
162     getRemoteHtml();
163     getRemoteHtml();
164     getRemoteHtml();
165     getRemoteHtml();
166     getRemoteHtml();
167 
168     server.verify(FETCH("/remote.html"), exactly(1));
169   }
170 
171   private void getRemoteHtml() throws Exception
172   {
173     Resource resource = loader.getResource("/remote.html");
174     assertNotNull(resource);
175     assertTrue(resource.exists());
176     assertThat(resource).isInstanceOf(HttpResource.class);
177     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
178     assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
179   }
180 
181   @Test
182   public void testCachingOfModified() throws Exception
183   {
184     LOG.info("<-- Start of test-case");
185 
186     Resource resource;
187 
188     resource = loader.getResource("/remote.html");
189     assertNotNull(resource);
190     assertTrue(resource.exists());
191     assertThat(resource).isInstanceOf(HttpResource.class);
192     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
193     assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
194 
195     CLOCK.timetravel(Duration.ofSeconds(10));
196     server.when(FETCH("/remote.html")).forward(NGINX("/modified.html"));
197 
198     resource = loader.getResource("/remote.html");
199     assertNotNull(resource);
200     assertTrue(resource.exists());
201     assertThat(resource).isInstanceOf(HttpResource.class);
202     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
203     assertThat(resource.getInputStream()).hasSameContentAs(modified.getInputStream());
204 
205     server.verify(FETCH("/remote.html"), exactly(2));
206     server.verify(
207         request()
208             .withPath("/remote.html")
209             .withHeader("If-Modified-Since")
210             .withHeader("If-None-Match"),
211         exactly(1));
212   }
213 
214   @Test
215   public void testFetchNonExistent() throws Exception
216   {
217     LOG.info("<-- Start of test-case");
218 
219     getNonExistingHtml();
220 
221     server.verify(FETCH("/peter.html"), exactly(2));
222   }
223 
224   @Test
225   public void testCachingOfNonExistent() throws Exception
226   {
227     LOG.info("<-- Start of test-case");
228 
229     getNonExistingHtml();
230     getNonExistingHtml();
231     getNonExistingHtml();
232     getNonExistingHtml();
233     getNonExistingHtml();
234     getNonExistingHtml();
235     getNonExistingHtml();
236 
237     // Remote-requests answered with 404 are repeated
238     server.verify(FETCH("/peter.html"), exactly(14));
239   }
240 
241   private void getNonExistingHtml() throws Exception
242   {
243     Resource resource = loader.getResource("/peter.html");
244     assertNotNull(resource);
245     assertFalse(resource.exists());
246   }
247 
248   @Test
249   public void testFetchServerResponse500() throws Exception
250   {
251     LOG.info("<-- Start of test-case");
252 
253     server.when(FETCH("/peter.html")).respond(INTERNAL_SERVER_ERROR());
254 
255     Resource resource = loader.getResource(address("/peter.html"));
256 
257     assertThat(resource.exists()).isFalse();
258     server.verify(FETCH("/peter.html"), exactly(2));
259   }
260 
261   @Test
262   public void testCaching() throws Exception
263   {
264     LOG.info("<-- Start of test-case");
265 
266     Resource resource;
267 
268     LOG.debug("First access /remote.html");
269     resource= loader.getResource("/remote.html");
270     assertThat(resource.exists()).isTrue();
271     assertThat(resource).isInstanceOf(HttpResource.class);
272     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
273     assertThat(((HttpResource)resource).isModified()).isFalse();
274     assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
275 
276     LOG.debug("Second access to /remote.html");
277     resource= loader.getResource("/remote.html");
278     assertThat(resource.exists()).isTrue();
279     assertThat(resource).isInstanceOf(HttpResource.class);
280     assertThat(((HttpResource)resource).isModified()).isFalse();
281     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
282     assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
283 
284     LOG.debug("Third access /remote.html");
285     resource= loader.getResource("/remote.html");
286     assertThat(resource.exists()).isTrue();
287     assertThat(resource).isInstanceOf(HttpResource.class);
288     assertThat(((HttpResource)resource).isModified()).isFalse();
289     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
290     assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
291 
292     LOG.debug("First access to remote.html");
293     resource= loader.getResource("remote.html");
294     assertThat(resource.exists()).isTrue();
295     assertThat(resource).isInstanceOf(HttpResource.class);
296     assertThat(((HttpResource)resource).isModified()).isFalse();
297     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
298     assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
299 
300     LOG.debug("Fourth access to /remote.html after a pause of 10 seconds -- resource should be expired");
301     CLOCK.timetravel(Duration.ofSeconds(10));
302     assertThat(resource.exists()).isTrue();
303     assertThat(resource).isInstanceOf(HttpResource.class);
304     assertThat(((HttpResource)resource).isModified()).isFalse();
305     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
306     assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
307 
308     LOG.debug("Fifth access /remote.html");
309     resource= loader.getResource("/remote.html");
310     assertThat(resource.exists()).isTrue();
311     assertThat(resource).isInstanceOf(HttpResource.class);
312     assertThat(((HttpResource)resource).isModified()).isFalse();
313     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
314     assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
315 
316     LOG.debug("Sixth access /remote.html");
317     resource= loader.getResource("/remote.html");
318     assertThat(resource.exists()).isTrue();
319     assertThat(resource).isInstanceOf(HttpResource.class);
320     assertThat(((HttpResource)resource).isModified()).isFalse();
321     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
322     assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
323 
324     LOG.debug("Second access /remote.html");
325     resource= loader.getResource("remote.html");
326     assertThat(resource.exists()).isTrue();
327     assertThat(resource).isInstanceOf(HttpResource.class);
328     assertThat(((HttpResource)resource).isModified()).isFalse();
329     assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
330     assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
331 
332     server.verify(FETCH("/remote.html"), exactly(2));
333     server.verify(
334         request()
335             .withPath("/remote.html")
336             .withHeader("If-Modified-Since")
337             .withHeader("If-None-Match"),
338         exactly(1));
339   }
340 
341 
342   @SpringBootApplication
343   static class Application
344   {
345     @Bean
346     public HttpResourceChainAwareResourceLoader httpResourceChainAwareResourceLoader(
347         ClientAndServer server,
348         ApplicationContext context)
349     {
350       return new HttpResourceChainAwareResourceLoader(
351           context,
352           new String[]
353           {
354               "classpath:/static/",
355               "classpath:/public/",
356               "http://localhost:" + server.getLocalPort(),
357               "classpath:/fallback/"
358           });
359     }
360   }
361 }