diff --git a/src/main/java/io/cryostat/ConfigProperties.java b/src/main/java/io/cryostat/ConfigProperties.java index 2c889e870..77ddc7c96 100644 --- a/src/main/java/io/cryostat/ConfigProperties.java +++ b/src/main/java/io/cryostat/ConfigProperties.java @@ -34,9 +34,9 @@ public class ConfigProperties { public static final String CONNECTIONS_FAILED_TIMEOUT = "cryostat.connections.failed-timeout"; public static final String REPORTS_SIDECAR_URL = "cryostat.services.reports.url"; - public static final String MEMORY_CACHE_ENABLED = + public static final String REPORTS_MEMORY_CACHE_ENABLED = "cryostat.services.reports.memory-cache.enabled"; - public static final String STORAGE_CACHE_ENABLED = + public static final String REPORTS_STORAGE_CACHE_ENABLED = "cryostat.services.reports.storage-cache.enabled"; public static final String ARCHIVED_REPORTS_STORAGE_CACHE_NAME = "cryostat.services.reports.storage-cache.name"; diff --git a/src/main/java/io/cryostat/reports/MemoryCachingReportsListener.java b/src/main/java/io/cryostat/reports/MemoryCachingReportsListener.java index 7eb49715b..cbd29a80a 100644 --- a/src/main/java/io/cryostat/reports/MemoryCachingReportsListener.java +++ b/src/main/java/io/cryostat/reports/MemoryCachingReportsListener.java @@ -40,7 +40,7 @@ class MemoryCachingReportsListener { @ConfigProperty(name = "quarkus.cache.enabled") boolean quarkusCache; - @ConfigProperty(name = ConfigProperties.MEMORY_CACHE_ENABLED) + @ConfigProperty(name = ConfigProperties.REPORTS_MEMORY_CACHE_ENABLED) boolean memoryCache; @Inject diff --git a/src/main/java/io/cryostat/reports/MemoryCachingReportsService.java b/src/main/java/io/cryostat/reports/MemoryCachingReportsService.java index 3da2f86e4..433538ff2 100644 --- a/src/main/java/io/cryostat/reports/MemoryCachingReportsService.java +++ b/src/main/java/io/cryostat/reports/MemoryCachingReportsService.java @@ -48,7 +48,7 @@ class MemoryCachingReportsService implements ReportsService { @ConfigProperty(name = "quarkus.cache.enabled") boolean quarkusCache; - @ConfigProperty(name = ConfigProperties.MEMORY_CACHE_ENABLED) + @ConfigProperty(name = ConfigProperties.REPORTS_MEMORY_CACHE_ENABLED) boolean memoryCache; @Inject @@ -98,4 +98,14 @@ public Uni> reportFor( return delegate.reportFor(jvmId, filename); }); } + + @Override + public Uni> reportFor(ActiveRecording recording) { + return reportFor(recording, r -> true); + } + + @Override + public Uni> reportFor(String jvmId, String filename) { + return reportFor(jvmId, filename, r -> true); + } } diff --git a/src/main/java/io/cryostat/reports/Reports.java b/src/main/java/io/cryostat/reports/Reports.java index dfd3677a7..da110a52e 100644 --- a/src/main/java/io/cryostat/reports/Reports.java +++ b/src/main/java/io/cryostat/reports/Reports.java @@ -46,7 +46,7 @@ @Path("") public class Reports { - @ConfigProperty(name = ConfigProperties.STORAGE_CACHE_ENABLED) + @ConfigProperty(name = ConfigProperties.REPORTS_STORAGE_CACHE_ENABLED) boolean storageCacheEnabled; @ConfigProperty(name = ConfigProperties.ARCHIVED_REPORTS_STORAGE_CACHE_NAME) diff --git a/src/main/java/io/cryostat/reports/ReportsService.java b/src/main/java/io/cryostat/reports/ReportsService.java index 4edc2c005..14a22764d 100644 --- a/src/main/java/io/cryostat/reports/ReportsService.java +++ b/src/main/java/io/cryostat/reports/ReportsService.java @@ -29,16 +29,12 @@ public interface ReportsService { Uni> reportFor( ActiveRecording recording, Predicate predicate); - default Uni> reportFor(ActiveRecording recording) { - return reportFor(recording, r -> true); - } + Uni> reportFor(ActiveRecording recording); Uni> reportFor( String jvmId, String filename, Predicate predicate); - default Uni> reportFor(String jvmId, String filename) { - return reportFor(jvmId, filename, r -> true); - } + Uni> reportFor(String jvmId, String filename); static String key(ActiveRecording recording) { return String.format("%s/%d", recording.target.jvmId, recording.id); diff --git a/src/main/java/io/cryostat/reports/ReportsServiceImpl.java b/src/main/java/io/cryostat/reports/ReportsServiceImpl.java index 6d81a748f..ddc38d688 100644 --- a/src/main/java/io/cryostat/reports/ReportsServiceImpl.java +++ b/src/main/java/io/cryostat/reports/ReportsServiceImpl.java @@ -115,6 +115,16 @@ public Uni> reportFor( return Uni.createFrom().future(future); } + @Override + public Uni> reportFor(ActiveRecording recording) { + return reportFor(recording, r -> true); + } + + @Override + public Uni> reportFor(String jvmId, String filename) { + return reportFor(jvmId, filename, r -> true); + } + private Future> process( InputStream stream, Predicate predicate) { return reportGenerator.generateEvalMapInterruptibly( diff --git a/src/main/java/io/cryostat/reports/StorageCachingReportsListener.java b/src/main/java/io/cryostat/reports/StorageCachingReportsListener.java index a44fe133d..d4632f597 100644 --- a/src/main/java/io/cryostat/reports/StorageCachingReportsListener.java +++ b/src/main/java/io/cryostat/reports/StorageCachingReportsListener.java @@ -34,7 +34,7 @@ @ApplicationScoped class StorageCachingReportsListener { - @ConfigProperty(name = ConfigProperties.STORAGE_CACHE_ENABLED) + @ConfigProperty(name = ConfigProperties.REPORTS_STORAGE_CACHE_ENABLED) boolean enabled; @ConfigProperty(name = ConfigProperties.ARCHIVED_REPORTS_STORAGE_CACHE_NAME) diff --git a/src/main/java/io/cryostat/reports/StorageCachingReportsService.java b/src/main/java/io/cryostat/reports/StorageCachingReportsService.java index f3646d6d0..ef6381159 100644 --- a/src/main/java/io/cryostat/reports/StorageCachingReportsService.java +++ b/src/main/java/io/cryostat/reports/StorageCachingReportsService.java @@ -56,7 +56,7 @@ @Dependent class StorageCachingReportsService implements ReportsService { - @ConfigProperty(name = ConfigProperties.STORAGE_CACHE_ENABLED) + @ConfigProperty(name = ConfigProperties.REPORTS_STORAGE_CACHE_ENABLED) boolean enabled; @ConfigProperty(name = ConfigProperties.ARCHIVED_REPORTS_STORAGE_CACHE_NAME) @@ -157,4 +157,14 @@ private Uni> getStorage(String key) { } }); } + + @Override + public Uni> reportFor(ActiveRecording recording) { + return reportFor(recording, r -> true); + } + + @Override + public Uni> reportFor(String jvmId, String filename) { + return reportFor(jvmId, filename, r -> true); + } } diff --git a/src/test/java/itest/ReportGenerationTest.java b/src/test/java/itest/ReportGenerationTest.java new file mode 100644 index 000000000..9970445fc --- /dev/null +++ b/src/test/java/itest/ReportGenerationTest.java @@ -0,0 +1,367 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package itest; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import io.cryostat.resources.LocalStackResource; +import io.cryostat.util.HttpMimeType; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.vertx.core.MultiMap; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.handler.HttpException; +import itest.bases.StandardSelfTest; +import itest.util.ITestCleanupFailedException; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@QuarkusTestResource(LocalStackResource.class) +public class ReportGenerationTest extends StandardSelfTest { + + static final String TEST_RECORDING_NAME = "someRecording"; + + private String archivedReportRequestURL() { + return String.format("/api/beta/reports/%s", getSelfReferenceConnectUrlEncoded()); + } + + private String recordingRequestURL() { + return String.format("/api/v3/targets/%d/recordings", getSelfReferenceTargetId()); + } + + private String archiveListRequestURL() { + return String.format("/api/beta/recordings/%s", getSelfReferenceConnectUrlEncoded()); + } + + @Test + void testGetActiveReport() throws Exception { + JsonObject activeRecording = null; + try { + // Create a recording + CompletableFuture postResponse = new CompletableFuture<>(); + MultiMap form = MultiMap.caseInsensitiveMultiMap(); + form.add("recordingName", TEST_RECORDING_NAME); + form.add("duration", "5"); + form.add("events", "template=ALL"); + + webClient + .post(recordingRequestURL()) + .sendForm( + form, + ar -> { + if (assertRequestStatus(ar, postResponse)) { + postResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + + activeRecording = postResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + // Wait some time to get more recording data + Thread.sleep(5_000); + + // Get a report for the above recording + CompletableFuture reportResponse = new CompletableFuture<>(); + webClient + .get(activeRecording.getString("reportUrl")) + .putHeader(HttpHeaders.ACCEPT.toString(), HttpMimeType.JSON.mime()) + .send( + ar -> { + if (assertRequestStatus(ar, reportResponse)) { + MatcherAssert.assertThat( + ar.result().statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)) + .and(Matchers.lessThan(400))); + MatcherAssert.assertThat( + ar.result() + .getHeader(HttpHeaders.CONTENT_TYPE.toString()), + Matchers.equalTo("application/json;charset=UTF-8")); + reportResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + JsonObject jsonResponse = reportResponse.get(); + MatcherAssert.assertThat(jsonResponse, Matchers.notNullValue()); + MatcherAssert.assertThat( + jsonResponse.getMap(), + Matchers.is(Matchers.aMapWithSize(Matchers.greaterThan(0)))); + } finally { + if (activeRecording != null) { + // Clean up recording + CompletableFuture deleteActiveRecResponse = new CompletableFuture<>(); + webClient + .delete( + String.format( + "%s/%d", + recordingRequestURL(), activeRecording.getLong("remoteId"))) + .send( + ar -> { + if (assertRequestStatus(ar, deleteActiveRecResponse)) { + deleteActiveRecResponse.complete( + ar.result().bodyAsJsonObject()); + } + }); + deleteActiveRecResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + } + } + + @Test + @Disabled + void testGetArchivedReport() throws Exception { + JsonObject activeRecording = null; + CompletableFuture saveRecordingResp = new CompletableFuture<>(); + String savedRecordingName = null; + + try { + // Create a recording + CompletableFuture postResponse = new CompletableFuture<>(); + MultiMap form = MultiMap.caseInsensitiveMultiMap(); + form.add("recordingName", TEST_RECORDING_NAME); + form.add("duration", "5"); + form.add("events", "template=ALL"); + + webClient + .post(recordingRequestURL()) + .sendForm( + form, + ar -> { + if (assertRequestStatus(ar, postResponse)) { + postResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + + activeRecording = postResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + // Wait some time to get more recording data + Thread.sleep(5_000); + + // Save the recording to archive + webClient + .patch( + String.format( + "%s/%d", + recordingRequestURL(), activeRecording.getLong("remoteId"))) + .putHeader(HttpHeaders.CONTENT_TYPE.toString(), "text/plain;charset=UTF-8") + .sendBuffer( + Buffer.buffer("SAVE"), + ar -> { + if (assertRequestStatus(ar, saveRecordingResp)) { + MatcherAssert.assertThat( + ar.result().statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)) + .and(Matchers.lessThan(400))); + MatcherAssert.assertThat( + ar.result() + .getHeader(HttpHeaders.CONTENT_TYPE.toString()), + Matchers.equalTo("text/plain;charset=UTF-8")); + saveRecordingResp.complete(ar.result().bodyAsString()); + } + }); + + savedRecordingName = saveRecordingResp.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + // Get a report for the archived recording + CompletableFuture archiveResponse = new CompletableFuture<>(); + webClient + .get(String.format("%s/%s", "/api/v1/reports", savedRecordingName)) + .putHeader(HttpHeaders.ACCEPT.toString(), HttpMimeType.JSON.mime()) + .send( + ar -> { + if (assertRequestStatus(ar, archiveResponse)) { + MatcherAssert.assertThat( + ar.result().statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)) + .and(Matchers.lessThan(400))); + MatcherAssert.assertThat( + ar.result() + .getHeader(HttpHeaders.CONTENT_TYPE.toString()), + Matchers.equalTo("application/json;charset=UTF-8")); + archiveResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + JsonObject jsonResponse = archiveResponse.get(); + MatcherAssert.assertThat(jsonResponse, Matchers.notNullValue()); + MatcherAssert.assertThat( + jsonResponse.getMap(), + Matchers.is(Matchers.aMapWithSize(Matchers.greaterThan(0)))); + } finally { + if (activeRecording != null) { + // Clean up recording + CompletableFuture deleteActiveRecResponse = new CompletableFuture<>(); + webClient + .delete( + String.format( + "%s/%d", + recordingRequestURL(), activeRecording.getLong("remoteId"))) + .send( + ar -> { + if (assertRequestStatus(ar, deleteActiveRecResponse)) { + deleteActiveRecResponse.complete( + ar.result().bodyAsJsonObject()); + } + }); + deleteActiveRecResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + + CompletableFuture deleteArchivedRecResp = new CompletableFuture<>(); + webClient + .delete(String.format("%s/%s", archiveListRequestURL(), savedRecordingName)) + .send( + ar -> { + if (assertRequestStatus(ar, deleteArchivedRecResp)) { + MatcherAssert.assertThat( + ar.result().statusCode(), Matchers.equalTo(200)); + deleteArchivedRecResp.complete(ar.result().bodyAsJsonObject()); + } + }); + try { + deleteArchivedRecResp.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException e) { + logger.error( + new ITestCleanupFailedException( + String.format( + "Failed to delete archived recording %s", + savedRecordingName), + e)); + } + } + } + + @Test + @Disabled("TODO query parameter filter is not implemented") + void testGetFilteredActiveReport() throws Exception { + JsonObject activeRecording = null; + try { + // Create a recording + CompletableFuture postResponse = new CompletableFuture<>(); + MultiMap form = MultiMap.caseInsensitiveMultiMap(); + form.add("recordingName", TEST_RECORDING_NAME); + form.add("duration", "5"); + form.add("events", "template=ALL"); + + webClient + .post(recordingRequestURL()) + .sendForm( + form, + ar -> { + if (assertRequestStatus(ar, postResponse)) { + postResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + + activeRecording = postResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + // Wait some time to get more recording data + Thread.sleep(5_000); + + // Get a report for the above recording + CompletableFuture reportResponse = new CompletableFuture<>(); + webClient + .get(activeRecording.getString("reportUrl")) + .addQueryParam("filter", "heap") + .putHeader(HttpHeaders.ACCEPT.toString(), HttpMimeType.JSON.mime()) + .send( + ar -> { + if (assertRequestStatus(ar, reportResponse)) { + MatcherAssert.assertThat( + ar.result().statusCode(), + Matchers.both(Matchers.greaterThanOrEqualTo(200)) + .and(Matchers.lessThan(400))); + MatcherAssert.assertThat( + ar.result() + .getHeader(HttpHeaders.CONTENT_TYPE.toString()), + Matchers.equalTo("application/json;charset=UTF-8")); + reportResponse.complete(ar.result().bodyAsJsonObject()); + } + }); + JsonObject jsonResponse = reportResponse.get(); + MatcherAssert.assertThat(jsonResponse, Matchers.notNullValue()); + MatcherAssert.assertThat(jsonResponse.getMap(), Matchers.is(Matchers.aMapWithSize(8))); + Assertions.assertTrue(jsonResponse.containsKey("HeapContent")); + Assertions.assertTrue(jsonResponse.containsKey("StringDeduplication")); + Assertions.assertTrue(jsonResponse.containsKey("PrimitiveToObjectConversion")); + Assertions.assertTrue(jsonResponse.containsKey("GcFreedRatio")); + Assertions.assertTrue(jsonResponse.containsKey("HighGc")); + Assertions.assertTrue(jsonResponse.containsKey("HeapDump")); + Assertions.assertTrue(jsonResponse.containsKey("Allocations.class")); + Assertions.assertTrue(jsonResponse.containsKey("LowOnPhysicalMemory")); + for (var obj : jsonResponse.getMap().entrySet()) { + var value = JsonObject.mapFrom(obj.getValue()); + Assertions.assertTrue(value.containsKey("score")); + MatcherAssert.assertThat( + value.getDouble("score"), + Matchers.anyOf( + Matchers.equalTo(-1d), + Matchers.equalTo(-2d), + Matchers.equalTo(-3d), + Matchers.both(Matchers.lessThanOrEqualTo(100d)) + .and(Matchers.greaterThanOrEqualTo(0d)))); + Assertions.assertTrue(value.containsKey("name")); + MatcherAssert.assertThat( + value.getString("name"), Matchers.not(Matchers.emptyOrNullString())); + Assertions.assertTrue(value.containsKey("topic")); + MatcherAssert.assertThat( + value.getString("topic"), Matchers.not(Matchers.emptyOrNullString())); + Assertions.assertTrue(value.containsKey("description")); + } + } finally { + if (activeRecording != null) { + // Clean up recording + CompletableFuture deleteActiveRecResponse = new CompletableFuture<>(); + webClient + .delete( + String.format( + "%s/%d", + recordingRequestURL(), activeRecording.getLong("id"))) + .send( + ar -> { + if (assertRequestStatus(ar, deleteActiveRecResponse)) { + deleteActiveRecResponse.complete( + ar.result().bodyAsJsonObject()); + } + }); + deleteActiveRecResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + } + } + + @Test + void testGetReportThrowsWithNonExistentRecordingName() throws Exception { + CompletableFuture response = new CompletableFuture<>(); + webClient + .get(String.format("%s/%s", archivedReportRequestURL(), TEST_RECORDING_NAME)) + .putHeader(HttpHeaders.ACCEPT.toString(), HttpMimeType.HTML.mime()) + .send( + ar -> { + assertRequestStatus(ar, response); + }); + ExecutionException ex = + Assertions.assertThrows( + ExecutionException.class, + () -> response.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)); + MatcherAssert.assertThat( + ((HttpException) ex.getCause()).getStatusCode(), Matchers.equalTo(404)); + MatcherAssert.assertThat(ex.getCause().getMessage(), Matchers.equalTo("Not Found")); + } +} diff --git a/src/test/java/itest/ReportIT.java b/src/test/java/itest/ReportIT.java deleted file mode 100644 index a17401ad5..000000000 --- a/src/test/java/itest/ReportIT.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright The Cryostat Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package itest; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -import io.cryostat.util.HttpMimeType; - -import io.quarkus.test.junit.QuarkusIntegrationTest; -import io.vertx.core.MultiMap; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpHeaders; -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.JsonObject; -import io.vertx.ext.web.handler.HttpException; -import itest.bases.StandardSelfTest; -import itest.util.ITestCleanupFailedException; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.select.Elements; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -@QuarkusIntegrationTest -@Disabled("TODO") -public class ReportIT extends StandardSelfTest { - - static final String TEST_RECORDING_NAME = "someRecording"; - static final String REPORT_REQ_URL = - String.format("/api/beta/reports/%s", getSelfReferenceConnectUrl()); - static final String RECORDING_REQ_URL = - String.format("/api/v1/targets/%s/recordings", getSelfReferenceConnectUrl()); - static final String ARCHIVE_REQ_URL = - String.format("/api/beta/recordings/%s", getSelfReferenceConnectUrl()); - static final String TEMP_REPORT = "src/test/resources/reportTest.html"; - - @Test - void testGetReportShouldSendFile() throws Exception { - - CompletableFuture saveRecordingResp = new CompletableFuture<>(); - String savedRecordingName = null; - File file = new File(TEMP_REPORT); - - try { - // Create a recording - CompletableFuture postResponse = new CompletableFuture<>(); - MultiMap form = MultiMap.caseInsensitiveMultiMap(); - form.add("recordingName", TEST_RECORDING_NAME); - form.add("duration", "1"); - form.add("events", "template=ALL"); - - webClient - .post(RECORDING_REQ_URL) - .sendForm( - form, - ar -> { - if (assertRequestStatus(ar, postResponse)) { - MatcherAssert.assertThat( - ar.result().statusCode(), Matchers.equalTo(201)); - postResponse.complete(null); - } - }); - - postResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - - // Make a webserver request to generate some recording data - CompletableFuture targetGetResponse = new CompletableFuture<>(); - webClient - .get("/api/v1/targets") - .send( - ar -> { - if (assertRequestStatus(ar, targetGetResponse)) { - MatcherAssert.assertThat( - ar.result().statusCode(), Matchers.equalTo(200)); - MatcherAssert.assertThat( - ar.result() - .getHeader(HttpHeaders.CONTENT_TYPE.toString()), - Matchers.equalTo(HttpMimeType.JSON.mime())); - targetGetResponse.complete(ar.result().bodyAsJsonArray()); - } - }); - - targetGetResponse.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - - // Save the recording to archive - webClient - .patch(String.format("%s/%s", RECORDING_REQ_URL, TEST_RECORDING_NAME)) - .putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpMimeType.PLAINTEXT.mime()) - .sendBuffer( - Buffer.buffer("SAVE"), - ar -> { - if (assertRequestStatus(ar, saveRecordingResp)) { - MatcherAssert.assertThat( - ar.result().statusCode(), Matchers.equalTo(200)); - MatcherAssert.assertThat( - ar.result() - .getHeader(HttpHeaders.CONTENT_TYPE.toString()), - Matchers.equalTo(HttpMimeType.PLAINTEXT.mime())); - saveRecordingResp.complete(ar.result().bodyAsString()); - } - }); - - savedRecordingName = saveRecordingResp.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - - // Get a report for the above recording - CompletableFuture getResponse = new CompletableFuture<>(); - webClient - .get(String.format("%s/%s", REPORT_REQ_URL, savedRecordingName)) - .putHeader(HttpHeaders.ACCEPT.toString(), HttpMimeType.HTML.mime()) - .send( - ar -> { - if (assertRequestStatus(ar, getResponse)) { - MatcherAssert.assertThat( - ar.result().statusCode(), Matchers.equalTo(200)); - MatcherAssert.assertThat( - ar.result() - .getHeader(HttpHeaders.CONTENT_TYPE.toString()), - Matchers.equalTo(HttpMimeType.HTML.mime())); - getResponse.complete(ar.result().bodyAsBuffer()); - } - }); - - try (FileOutputStream fos = new FileOutputStream(file); - BufferedOutputStream bos = new BufferedOutputStream(fos)) { - - byte[] bytes = getResponse.get().getBytes(); - bos.write(bytes); - } - - Document doc = Jsoup.parse(file, "UTF-8"); - - MatcherAssert.assertThat(file.length(), Matchers.greaterThan(0L)); - - Elements head = doc.getElementsByTag("head"); - Elements titles = head.first().getElementsByTag("title"); - Elements body = doc.getElementsByTag("body"); - Elements script = head.first().getElementsByTag("script"); - - MatcherAssert.assertThat("Expected one ", head.size(), Matchers.equalTo(1)); - MatcherAssert.assertThat(titles.size(), Matchers.equalTo(1)); - MatcherAssert.assertThat("Expected one ", body.size(), Matchers.equalTo(1)); - MatcherAssert.assertThat( - "Expected at least one