### Question:
WebJarController extends DefaultController implements BundleTrackerCustomizer<List<BundleWebJarLib>>,
        AssetProvider { public Result serve() { String path = context().parameterFromPath("path"); if (path == null) { logger().error("Cannot server Web Jar resource : no path"); return badRequest(); } Asset<?> asset = assetAt(path); if (asset == null) { return notFound(); } return CacheUtils.fromAsset(context(), asset, configuration); } WebJarController(Crypto crypto, ApplicationConfiguration configuration, String path);  WebJarController(@Context BundleContext context, @Property(value = "assets/libs",
            name = "path") String path); @Validate void start(); @Invalidate void stop(); @Override List<Route> routes(); Result serve(); @Override synchronized List<BundleWebJarLib> addingBundle(Bundle bundle, BundleEvent bundleEvent); void addWebJarLibs(Collection<? extends WebJarLib> list); @Override void modifiedBundle(Bundle bundle, BundleEvent bundleEvent, List<BundleWebJarLib> webJarLibs); @Override void removedBundle(Bundle bundle, BundleEvent bundleEvent, List<BundleWebJarLib> webJarLibs); void removeWebJarLibs(Collection<? extends WebJarLib> webJarLibs); @Override Collection<Asset<?>> assets(); @Override Asset<?> assetAt(String path); static final Pattern WEBJAR_ROOT_REGEX; static final String WEBJAR_LOCATION; static final Pattern WEBJAR_REGEX; }### Answer:
@Test public void testEtagAndCacheControl() throws IOException, InterruptedException { ApplicationConfiguration configuration = mock(ApplicationConfiguration.class); Crypto crypto = mock(Crypto.class); when(crypto.hexSHA1(anyString())).thenAnswer(new Answer<String>() { @Override public String answer(InvocationOnMock invocation) throws Throwable { try { String value = (String) invocation.getArguments()[0]; MessageDigest md; md = MessageDigest.getInstance(Hash.SHA1.toString()); md.update(value.getBytes("utf-8")); byte[] digest = md.digest(); return String.valueOf(Hex.encodeHex(digest)); } catch (Exception ex) { throw new RuntimeException(ex); } } }); root = new File("target/wisdom-test"); webjars.mkdirs(); FileUtils.copyDirectory(new File("target/test-classes/autobahnjs"), new File(webjars, "autobahnjs")); when(configuration.getBaseDir()).thenReturn(root); when(configuration.getWithDefault(CacheUtils.HTTP_CACHE_CONTROL_MAX_AGE, CacheUtils.HTTP_CACHE_CONTROL_DEFAULT)).thenReturn(CacheUtils .HTTP_CACHE_CONTROL_DEFAULT); when(configuration.getBooleanWithDefault(CacheUtils.HTTP_USE_ETAG, CacheUtils.HTTP_USE_ETAG_DEFAULT)).thenReturn(CacheUtils.HTTP_USE_ETAG_DEFAULT); final WebJarController controller = new WebJarController(crypto, configuration, "assets/libs"); assertThat(root).isNotNull(); Action.ActionResult result = action(new Invocation() { @Override public Result invoke() throws Throwable { return controller.serve(); } }).parameter("path", "autobahn.min.js").invoke(); assertThat(result.getResult().getStatusCode()).isEqualTo(200); assertThat(result.getResult().getHeaders().get("Cache-Control")).isEqualTo("max-age=3600"); assertThat(result.getResult().getHeaders().get("Etag")).isNotNull(); String lastModified = result.getResult().getHeaders().get("Last-Modified"); String etag = result.getResult().getHeaders().get("Etag"); result = action(new Invocation() { @Override public Result invoke() throws Throwable { return controller.serve(); } }).parameter("path", "autobahn.min.js").header("If-None-Match", etag).invoke(); assertThat(result.getResult().getStatusCode()).isEqualTo(304); result = action(new Invocation() { @Override public Result invoke() throws Throwable { return controller.serve(); } }).parameter("path", "autobahn.min.js").header("If-Modified-Since", lastModified).invoke(); assertThat(result.getResult().getStatusCode()).isEqualTo(304); }