From 0fb4f117f0d68c2e438e019412d2383c906eb2fe Mon Sep 17 00:00:00 2001 From: Andrea Lamparelli Date: Tue, 19 Nov 2024 11:19:26 +0100 Subject: [PATCH] Add AMQ in memory test resource With this resource we can have more control over async processing and even avoid unnecessary async processing when not needed Signed-off-by: Andrea Lamparelli --- horreum-backend/pom.xml | 5 + .../tools/horreum/svc/BaseServiceTest.java | 8 + .../tools/horreum/svc/RunServiceTest.java | 4 +- .../tools/horreum/svc/TestServiceTest.java | 566 +---------------- .../TestServiceWithAsyncProcessingTest.java | 596 ++++++++++++++++++ .../horreum/test/AMQPInMemoryResource.java | 36 ++ .../test/ElasticsearchTestProfile.java | 2 +- .../horreum/test/HorreumTestProfile.java | 3 +- .../horreum/test/InMemoryAMQTestProfile.java | 17 + 9 files changed, 669 insertions(+), 568 deletions(-) create mode 100644 horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/TestServiceWithAsyncProcessingTest.java create mode 100644 horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/AMQPInMemoryResource.java create mode 100644 horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/InMemoryAMQTestProfile.java diff --git a/horreum-backend/pom.xml b/horreum-backend/pom.xml index bdcd38178..182a2973e 100644 --- a/horreum-backend/pom.xml +++ b/horreum-backend/pom.xml @@ -210,6 +210,11 @@ 3.0.2 test + + io.smallrye.reactive + smallrye-reactive-messaging-in-memory + test + diff --git a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/BaseServiceTest.java b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/BaseServiceTest.java index 4eed13c06..a2e93c58d 100644 --- a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/BaseServiceTest.java +++ b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/BaseServiceTest.java @@ -1139,6 +1139,14 @@ protected TestService.TestListing listTestSummary(String roles, String folder, i .as(TestService.TestListing.class); } + protected void updateView(View view) { + View newView = jsonRequest().body(view).post("/api/ui/view") + .then().statusCode(200).extract().body().as(View.class); + if (view.id != null) { + assertEquals(view.id, newView.id); + } + } + protected DataPoint assertValue(BlockingQueue datapointQueue, double value) throws InterruptedException { DataPoint.Event dpe = datapointQueue.poll(10, TimeUnit.SECONDS); assertNotNull(dpe); diff --git a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/RunServiceTest.java b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/RunServiceTest.java index 8cddb87f9..ef6f9802c 100644 --- a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/RunServiceTest.java +++ b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/RunServiceTest.java @@ -61,7 +61,7 @@ import io.hyperfoil.tools.horreum.entity.data.RunDAO; import io.hyperfoil.tools.horreum.mapper.DatasetMapper; import io.hyperfoil.tools.horreum.server.CloseMe; -import io.hyperfoil.tools.horreum.test.HorreumTestProfile; +import io.hyperfoil.tools.horreum.test.InMemoryAMQTestProfile; import io.hyperfoil.tools.horreum.test.PostgresResource; import io.hyperfoil.tools.horreum.test.TestUtil; import io.quarkus.test.common.QuarkusTestResource; @@ -75,7 +75,7 @@ @QuarkusTest @QuarkusTestResource(PostgresResource.class) @QuarkusTestResource(OidcWiremockTestResource.class) -@TestProfile(HorreumTestProfile.class) +@TestProfile(InMemoryAMQTestProfile.class) public class RunServiceTest extends BaseServiceTest { private static final int POLL_DURATION_SECONDS = 10; diff --git a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/TestServiceTest.java b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/TestServiceTest.java index ed4b23a47..ff9e8a72a 100644 --- a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/TestServiceTest.java +++ b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/TestServiceTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -10,7 +9,6 @@ import java.io.File; import java.nio.file.Path; -import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -21,8 +19,6 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import org.apache.groovy.util.Maps; -import org.hibernate.query.NativeQuery; import org.junit.jupiter.api.TestInfo; import com.fasterxml.jackson.core.JsonProcessingException; @@ -30,8 +26,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.JsonNodeType; -import com.fasterxml.jackson.databind.node.ObjectNode; import io.hyperfoil.tools.horreum.action.ExperimentResultToMarkdown; import io.hyperfoil.tools.horreum.api.SortDirection; @@ -41,7 +35,6 @@ import io.hyperfoil.tools.horreum.api.data.ActionLog; import io.hyperfoil.tools.horreum.api.data.Dataset; import io.hyperfoil.tools.horreum.api.data.ExperimentProfile; -import io.hyperfoil.tools.horreum.api.data.ExportedLabelValues; import io.hyperfoil.tools.horreum.api.data.Extractor; import io.hyperfoil.tools.horreum.api.data.FingerprintValue; import io.hyperfoil.tools.horreum.api.data.Fingerprints; @@ -64,10 +57,9 @@ import io.hyperfoil.tools.horreum.entity.data.DatasetDAO; import io.hyperfoil.tools.horreum.entity.data.RunDAO; import io.hyperfoil.tools.horreum.entity.data.TestDAO; -import io.hyperfoil.tools.horreum.hibernate.JsonBinaryType; import io.hyperfoil.tools.horreum.mapper.VariableMapper; import io.hyperfoil.tools.horreum.server.CloseMe; -import io.hyperfoil.tools.horreum.test.HorreumTestProfile; +import io.hyperfoil.tools.horreum.test.InMemoryAMQTestProfile; import io.hyperfoil.tools.horreum.test.PostgresResource; import io.hyperfoil.tools.horreum.test.TestUtil; import io.quarkus.test.common.QuarkusTestResource; @@ -80,7 +72,7 @@ @QuarkusTest @QuarkusTestResource(PostgresResource.class) @QuarkusTestResource(OidcWiremockTestResource.class) -@TestProfile(HorreumTestProfile.class) +@TestProfile(InMemoryAMQTestProfile.class) class TestServiceTest extends BaseServiceTest { @org.junit.jupiter.api.Test @@ -207,123 +199,6 @@ public void testAddTestAction(TestInfo info) { deleteTest(test); } - @org.junit.jupiter.api.Test - public void testUpdateView(TestInfo info) throws InterruptedException { - Test test = createTest(createExampleTest(getTestName(info))); - Schema schema = createExampleSchema(info); - - BlockingQueue newDatasetQueue = serviceMediator.getEventQueue(AsyncEventChannels.DATASET_NEW, - test.id); - uploadRun(runWithValue(42, schema), test.name); - Dataset.EventNew event = newDatasetQueue.poll(10, TimeUnit.SECONDS); - assertNotNull(event); - - ViewComponent vc = new ViewComponent(); - vc.headerName = "Foobar"; - vc.labels = JsonNodeFactory.instance.arrayNode().add("value"); - List views = getViews(test.id); - View defaultView = views.stream().filter(v -> "Default".equals(v.name)).findFirst().orElseThrow(); - defaultView.components.add(vc); - defaultView.testId = test.id; - updateView(defaultView); - - TestUtil.eventually(() -> { - em.clear(); - @SuppressWarnings("unchecked") - List list = em.createNativeQuery( - "SELECT value FROM dataset_view WHERE dataset_id = ?1 AND view_id = ?2") - .setParameter(1, event.datasetId).setParameter(2, defaultView.id) - .unwrap(NativeQuery.class).addScalar("value", JsonBinaryType.INSTANCE) - .getResultList(); - return !list.isEmpty() && !list.get(0).isEmpty(); - }); - } - - private void updateView(View view) { - View newView = jsonRequest().body(view).post("/api/ui/view") - .then().statusCode(200).extract().body().as(View.class); - if (view.id != null) { - assertEquals(view.id, newView.id); - } - } - - @org.junit.jupiter.api.Test - public void testLabelValues(TestInfo info) throws InterruptedException { - Test test = createTest(createExampleTest(getTestName(info))); - Schema schema = createExampleSchema(info); - - BlockingQueue newDatasetQueue = serviceMediator - .getEventQueue(AsyncEventChannels.DATASET_UPDATED_LABELS, test.id); - uploadRun(runWithValue(42, schema), test.name); - uploadRun(JsonNodeFactory.instance.objectNode(), test.name); - assertNotNull(newDatasetQueue.poll(10, TimeUnit.SECONDS)); - assertNotNull(newDatasetQueue.poll(10, TimeUnit.SECONDS)); - - List values = jsonRequest().get("/api/test/" + test.id + "/labelValues").then().statusCode(200) - .extract().body().as(new TypeRef<>() { - }); - assertNotNull(values); - assertFalse(values.isEmpty()); - assertEquals(2, values.size()); - assertTrue(values.get(1).values.containsKey("value")); - } - - @org.junit.jupiter.api.Test - public void testFilterLabelValues(TestInfo info) throws InterruptedException { - Test test = createTest(createExampleTest(getTestName(info))); - - String name = info.getTestClass().map(Class::getName).orElse("") + "." + info.getDisplayName(); - Schema schema = createSchema(name, uriForTest(info, "1.0")); - - addLabel(schema, "filter-1", null, true, false, new Extractor("filter", "$.filter1", false)); - addLabel(schema, "filter-2", null, true, false, new Extractor("filter", "$.filter2", false)); - - BlockingQueue newDatasetQueue = serviceMediator - .getEventQueue(AsyncEventChannels.DATASET_UPDATED_LABELS, test.id); - ObjectNode run; - - run = runWithValue(42, schema); - run.put("filter1", "foo"); - run.put("filter2", "bar"); - uploadRun(run, test.name); - - run = runWithValue(43, schema); - run.put("filter1", "foo"); - run.put("filter2", "bar"); - uploadRun(run, test.name); - - run = runWithValue(44, schema); - run.put("filter1", "biz"); - run.put("filter2", "bar"); - uploadRun(run, test.name); - - run = runWithValue(45, schema); - run.put("filter1", "foo"); - run.put("filter2", "baz"); - uploadRun(run, test.name); - - for (int i = 0; i < 4; i++) { - assertNotNull(newDatasetQueue.poll(10, TimeUnit.SECONDS)); - } - - List values = jsonRequest().get("/api/test/" + test.id + "/filteringLabelValues").then().statusCode(200) - .extract().body().as(new TypeRef<>() { - }); - assertNotNull(values); - assertFalse(values.isEmpty()); - assertEquals(3, values.size()); - assertNotNull(values.stream() - .filter(node -> node.get("filter-1").asText().equals("foo") && node.get("filter-2").asText().equals("bar")) - .findAny().orElse(null)); - assertNotNull(values.stream() - .filter(node -> node.get("filter-1").asText().equals("biz") && node.get("filter-2").asText().equals("bar")) - .findAny().orElse(null)); - assertNotNull(values.stream() - .filter(node -> node.get("filter-1").asText().equals("foo") && node.get("filter-2").asText().equals("baz")) - .findAny().orElse(null)); - - } - @org.junit.jupiter.api.Test public void testImportFromFile() throws JsonProcessingException { Path p = new File(getClass().getClassLoader().getResource(".").getPath()).toPath(); @@ -486,443 +361,6 @@ public void testListFingerprints() throws JsonProcessingException { assertEquals("RulesWithJoinsProvides", ((FingerprintValue) values.get(1).values.get(1).children.get(2)).value); } - @org.junit.jupiter.api.Test - public void labelValuesIncludeExcluded() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, true); - - JsonNode response = jsonRequest() - .get("/api/test/" + t.id + "/labelValues?include=labelFoo&exclude=labelFoo") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(1, arrayResponse.size()); - assertInstanceOf(ObjectNode.class, arrayResponse.get(0)); - ObjectNode objectNode = (ObjectNode) arrayResponse.get(0).get("values"); - assertFalse(objectNode.has("labelFoo"), objectNode.toString()); - assertTrue(objectNode.has("labelBar"), objectNode.toString()); - } - - @org.junit.jupiter.api.Test - public void labelValuesWithTimestampAfterFilter() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, false); - long stop = System.currentTimeMillis(); - long start = System.currentTimeMillis(); - long delta = 5000; // 5 seconds - Integer runId = uploadRun(start, stop, "{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo", - jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); - recalculateDatasetForRun(runId); - runId = uploadRun(start + delta, stop, "{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo", - jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); - recalculateDatasetForRun(runId); - runId = uploadRun(start + delta, stop, "{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo", - jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); - recalculateDatasetForRun(runId); - - JsonNode response = jsonRequest() - .urlEncodingEnabled(true) - // keep only those runs that started after (start+delta-1) - .queryParam("after", Long.toString(start + delta - 1)) - .get("/api/test/" + t.id + "/labelValues") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(2, arrayResponse.size(), "unexpected number of responses " + response); - JsonNode first = arrayResponse.get(0); - assertTrue(first.has("values"), first.toString()); - JsonNode values = first.get("values"); - assertTrue(values.has("labelBar"), values.toString()); - assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); - JsonNode second = arrayResponse.get(1); - assertTrue(first.has("values"), second.toString()); - JsonNode secondValues = second.get("values"); - assertTrue(secondValues.has("labelBar"), secondValues.toString()); - assertEquals(JsonNodeType.STRING, secondValues.get("labelBar").getNodeType()); - - List labelBarValues = List.of(values.get("labelBar").asText(), secondValues.get("labelBar").asText()); - assertTrue(labelBarValues.contains("dos"), labelBarValues.toString()); - assertTrue(labelBarValues.contains("tres"), labelBarValues.toString()); - } - - @org.junit.jupiter.api.Test - public void labelValuesWithISOAfterFilter() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, false); - long stop = System.currentTimeMillis(); - Integer runId = uploadRun(Util.toInstant("2024-10-06T20:20:32.183Z").toEpochMilli(), stop, - "{ \"foo\": 1, \"bar\": \"uno\"}", t.name, - "urn:foo", jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); - recalculateDatasetForRun(runId); - runId = uploadRun(Util.toInstant("2024-10-06T20:20:32.183Z").toEpochMilli(), stop, "{ \"foo\": 2, \"bar\": \"dos\"}", - t.name, - "urn:foo", jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); - recalculateDatasetForRun(runId); - runId = uploadRun(Util.toInstant("2024-10-09T20:20:32.183Z").toEpochMilli(), stop, "{ \"foo\": 3, \"bar\": \"tres\"}", - t.name, - "urn:foo", jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); - recalculateDatasetForRun(runId); - - JsonNode response = jsonRequest() - .urlEncodingEnabled(true) - // keep only those runs that started after (start+delta-1) - .queryParam("after", "2024-10-07T20:20:32.183Z") - .queryParam("filter", Maps.of("labelBar", Arrays.asList("none", "tres"))) - .queryParam("multiFilter", true) - .get("/api/test/" + t.id + "/labelValues") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); - JsonNode first = arrayResponse.get(0); - assertTrue(first.has("values"), first.toString()); - JsonNode values = first.get("values"); - assertTrue(values.has("labelBar"), values.toString()); - assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); - assertEquals("tres", values.get("labelBar").asText()); - } - - @org.junit.jupiter.api.Test - public void labelValuesFilterWithJsonpath() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, false); - uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo"); - uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo"); - uploadRun("{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo"); - JsonNode response = jsonRequest() - .urlEncodingEnabled(true) - .queryParam("filter", "$.labelFoo ? (@ < 2)") - .get("/api/test/" + t.id + "/labelValues") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); - JsonNode first = arrayResponse.get(0); - assertTrue(first.has("values"), first.toString()); - JsonNode values = first.get("values"); - assertTrue(values.has("labelBar"), values.toString()); - assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); - assertEquals("uno", values.get("labelBar").asText()); - } - - @org.junit.jupiter.api.Test - public void labelValuesFilterWithInvalidJsonpath() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, false); - uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo"); - uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo"); - uploadRun("{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo"); - jsonRequest() - .urlEncodingEnabled(true) - .queryParam("filter", "$..name") - .get("/api/test/" + t.id + "/labelValues") - .then() - .statusCode(400); - } - - @org.junit.jupiter.api.Test - public void labelValuesFilterWithObject() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, false); - uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo"); - uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo"); - uploadRun("{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo"); - JsonNode response = jsonRequest() - .urlEncodingEnabled(true) - .queryParam("filter", Maps.of("labelBar", "uno", "labelFoo", 1)) - .queryParam("multiFilter", false) - .get("/api/test/" + t.id + "/labelValues") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); - JsonNode first = arrayResponse.get(0); - assertTrue(first.has("values"), first.toString()); - JsonNode values = first.get("values"); - assertTrue(values.has("labelBar"), values.toString()); - assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); - assertEquals("uno", values.get("labelBar").asText()); - } - - @org.junit.jupiter.api.Test - public void labelValuesFilterWithObjectNoMatch() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, false); - uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo"); - uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo"); - uploadRun("{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo"); - JsonNode response = jsonRequest() - .urlEncodingEnabled(true) - // no runs match both conditions - .queryParam("filter", Maps.of("labelBar", "uno", "labelFoo", "3")) - .queryParam("multiFilter", false) - .get("/api/test/" + t.id + "/labelValues") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(0, arrayResponse.size(), "unexpected number of responses " + response); - } - - @org.junit.jupiter.api.Test - public void labelValuesFilterMultiSelectMultipleValues() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, false); - Integer runId = uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo").get(0); - recalculateDatasetForRun(runId); - runId = uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo").get(0); - recalculateDatasetForRun(runId); - runId = uploadRun("{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo").get(0); - recalculateDatasetForRun(runId); - JsonNode response = jsonRequest() - .urlEncodingEnabled(true) - .queryParam("filter", Maps.of("labelBar", Arrays.asList("uno", "tres"))) - .queryParam("multiFilter", true) - .get("/api/test/" + t.id + "/labelValues") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(2, arrayResponse.size(), "unexpected number of responses " + response); - JsonNode first = arrayResponse.get(0); - assertTrue(first.has("values"), first.toString()); - JsonNode values = first.get("values"); - assertTrue(values.has("labelBar"), values.toString()); - assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); - JsonNode second = arrayResponse.get(1); - assertTrue(first.has("values"), second.toString()); - JsonNode secondValues = second.get("values"); - assertTrue(secondValues.has("labelBar"), secondValues.toString()); - assertEquals(JsonNodeType.STRING, secondValues.get("labelBar").getNodeType()); - } - - @org.junit.jupiter.api.Test - public void labelValuesFilterMultiSelectStrings() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, false); - uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo"); - uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo"); - JsonNode response = jsonRequest() - .urlEncodingEnabled(true) - .queryParam("filter", Maps.of("labelBar", Arrays.asList("uno", 30))) - .queryParam("multiFilter", true) - .get("/api/test/" + t.id + "/labelValues") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); - JsonNode first = arrayResponse.get(0); - assertTrue(first.has("values"), first.toString()); - JsonNode values = first.get("values"); - assertTrue(values.has("labelBar"), values.toString()); - assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); - assertEquals("uno", values.get("labelBar").asText()); - } - - @org.junit.jupiter.api.Test - public void labelValuesFilterMultiSelectNumber() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, false); - uploadRun("{ \"foo\": 1, \"bar\": 10}", t.name, "urn:foo"); - uploadRun("{ \"foo\": 2, \"bar\": 20}", t.name, "urn:foo"); - JsonNode response = jsonRequest() - .urlEncodingEnabled(true) - .queryParam("filter", Maps.of("labelBar", Arrays.asList(10, 30))) - .queryParam("multiFilter", true) - .get("/api/test/" + t.id + "/labelValues") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); - JsonNode first = arrayResponse.get(0); - assertTrue(first.has("values"), first.toString()); - JsonNode values = first.get("values"); - assertTrue(values.has("labelBar"), values.toString()); - assertEquals(JsonNodeType.NUMBER, values.get("labelBar").getNodeType()); - assertEquals("10", values.get("labelBar").toString()); - } - - @org.junit.jupiter.api.Test - public void labelValuesFilterMultiSelectBoolean() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, false); - uploadRun("{ \"foo\": 1, \"bar\": true}", t.name, "urn:foo"); - uploadRun("{ \"foo\": 2, \"bar\": 20}", t.name, "urn:foo"); - JsonNode response = jsonRequest() - .urlEncodingEnabled(true) - .queryParam("filter", Maps.of("labelBar", Arrays.asList(true, 30))) - .queryParam("multiFilter", true) - .get("/api/test/" + t.id + "/labelValues") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); - JsonNode first = arrayResponse.get(0); - assertTrue(first.has("values"), first.toString()); - JsonNode values = first.get("values"); - assertTrue(values.has("labelBar"), values.toString()); - assertEquals(JsonNodeType.BOOLEAN, values.get("labelBar").getNodeType()); - assertEquals("true", values.get("labelBar").toString()); - } - - @org.junit.jupiter.api.Test - public void labelValuesIncludeTwoParams() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, true); - - JsonNode response = jsonRequest() - .get("/api/test/" + t.id + "/labelValues?include=labelFoo&include=labelBar") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(1, arrayResponse.size()); - assertInstanceOf(ObjectNode.class, arrayResponse.get(0)); - ObjectNode objectNode = (ObjectNode) arrayResponse.get(0).get("values"); - assertTrue(objectNode.has("labelFoo"), objectNode.toString()); - assertTrue(objectNode.has("labelBar"), objectNode.toString()); - } - - @org.junit.jupiter.api.Test - public void labelValuesIncludeTwoSeparated() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, true); - - JsonNode response = jsonRequest() - .get("/api/test/" + t.id + "/labelValues?include=labelFoo,labelBar") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(1, arrayResponse.size()); - assertInstanceOf(ObjectNode.class, arrayResponse.get(0)); - ObjectNode objectNode = (ObjectNode) arrayResponse.get(0).get("values"); - assertTrue(objectNode.has("labelFoo"), objectNode.toString()); - assertTrue(objectNode.has("labelBar"), objectNode.toString()); - } - - @org.junit.jupiter.api.Test - public void labelValuesInclude() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, true); - - JsonNode response = jsonRequest() - .get("/api/test/" + t.id + "/labelValues?include=labelFoo") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(1, arrayResponse.size()); - assertInstanceOf(ObjectNode.class, arrayResponse.get(0)); - ObjectNode objectNode = (ObjectNode) arrayResponse.get(0).get("values"); - assertTrue(objectNode.has("labelFoo")); - assertFalse(objectNode.has("labelBar")); - } - - @org.junit.jupiter.api.Test - public void labelValuesExclude() { - Test t = createTest(createExampleTest("my-test")); - labelValuesSetup(t, true); - - JsonNode response = jsonRequest() - .get("/api/test/" + t.id + "/labelValues?exclude=labelFoo") - .then() - .statusCode(200) - .extract() - .body() - .as(JsonNode.class); - assertInstanceOf(ArrayNode.class, response); - ArrayNode arrayResponse = (ArrayNode) response; - assertEquals(1, arrayResponse.size()); - assertInstanceOf(ObjectNode.class, arrayResponse.get(0)); - ObjectNode objectNode = (ObjectNode) arrayResponse.get(0).get("values"); - assertFalse(objectNode.has("labelFoo"), objectNode.toPrettyString()); - assertTrue(objectNode.has("labelBar"), objectNode.toPrettyString()); - - } - - @org.junit.jupiter.api.Test - public void testLabelValues() throws JsonProcessingException { - List toParse = new ArrayList<>(); - toParse.add( - new Object[] { "job", mapper.readTree("\"quarkus-release-startup\""), 10, 10, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Max RSS", mapper.readTree("[]"), 10, 10, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "build-id", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Throughput 1 CPU", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Throughput 2 CPU", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Throughput 4 CPU", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Throughput 8 CPU", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Throughput 32 CPU", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Quarkus - Kafka_tags", mapper.readTree("\"quarkus-release-startup\""), 10, 10, - Instant.now(), Instant.now() }); - List values = LabelValuesService.parse(toParse); - assertEquals(1, values.size()); - assertEquals(9, values.get(0).values.size()); - assertEquals("quarkus-release-startup", values.get(0).values.get("job").asText()); - assertEquals("null", values.get(0).values.get("Throughput 32 CPU").asText()); - - toParse.add( - new Object[] { "job", mapper.readTree("\"quarkus-release-startup\""), 10, 11, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Max RSS", mapper.readTree("[]"), 10, 11, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "build-id", mapper.readTree("null"), 10, 11, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Throughput 1 CPU", mapper.readTree("17570.30"), 10, 11, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Throughput 2 CPU", mapper.readTree("43105.62"), 10, 11, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Throughput 4 CPU", mapper.readTree("84895.13"), 10, 11, Instant.now(), Instant.now() }); - toParse.add(new Object[] { "Throughput 8 CPU", mapper.readTree("141086.29"), 10, 11, Instant.now(), Instant.now() }); - values = LabelValuesService.parse(toParse); - assertEquals(2, values.size()); - assertEquals(9, values.get(0).values.size()); - assertEquals(7, values.get(1).values.size()); - assertEquals(84895.13d, values.get(1).values.get("Throughput 4 CPU").asDouble()); - } - @org.junit.jupiter.api.Test public void testPagination() { int count = 50; diff --git a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/TestServiceWithAsyncProcessingTest.java b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/TestServiceWithAsyncProcessingTest.java new file mode 100644 index 000000000..f95fb64ba --- /dev/null +++ b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/svc/TestServiceWithAsyncProcessingTest.java @@ -0,0 +1,596 @@ +package io.hyperfoil.tools.horreum.svc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.apache.groovy.util.Maps; +import org.hibernate.query.NativeQuery; +import org.junit.jupiter.api.TestInfo; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.JsonNodeType; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import io.hyperfoil.tools.horreum.api.data.Dataset; +import io.hyperfoil.tools.horreum.api.data.ExportedLabelValues; +import io.hyperfoil.tools.horreum.api.data.Extractor; +import io.hyperfoil.tools.horreum.api.data.Schema; +import io.hyperfoil.tools.horreum.api.data.Test; +import io.hyperfoil.tools.horreum.api.data.View; +import io.hyperfoil.tools.horreum.api.data.ViewComponent; +import io.hyperfoil.tools.horreum.bus.AsyncEventChannels; +import io.hyperfoil.tools.horreum.hibernate.JsonBinaryType; +import io.hyperfoil.tools.horreum.test.HorreumTestProfile; +import io.hyperfoil.tools.horreum.test.PostgresResource; +import io.hyperfoil.tools.horreum.test.TestUtil; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import io.quarkus.test.oidc.server.OidcWiremockTestResource; +import io.restassured.common.mapper.TypeRef; + +@QuarkusTest +@QuarkusTestResource(PostgresResource.class) +@QuarkusTestResource(OidcWiremockTestResource.class) +@TestProfile(HorreumTestProfile.class) +class TestServiceWithAsyncProcessingTest extends BaseServiceTest { + + @org.junit.jupiter.api.Test + public void testUpdateView(TestInfo info) throws InterruptedException { + Test test = createTest(createExampleTest(getTestName(info))); + Schema schema = createExampleSchema(info); + + BlockingQueue newDatasetQueue = serviceMediator.getEventQueue(AsyncEventChannels.DATASET_NEW, + test.id); + uploadRun(runWithValue(42, schema), test.name); + Dataset.EventNew event = newDatasetQueue.poll(10, TimeUnit.SECONDS); + assertNotNull(event); + + ViewComponent vc = new ViewComponent(); + vc.headerName = "Foobar"; + vc.labels = JsonNodeFactory.instance.arrayNode().add("value"); + List views = getViews(test.id); + View defaultView = views.stream().filter(v -> "Default".equals(v.name)).findFirst().orElseThrow(); + defaultView.components.add(vc); + defaultView.testId = test.id; + updateView(defaultView); + + TestUtil.eventually(() -> { + em.clear(); + @SuppressWarnings("unchecked") + List list = em.createNativeQuery( + "SELECT value FROM dataset_view WHERE dataset_id = ?1 AND view_id = ?2") + .setParameter(1, event.datasetId).setParameter(2, defaultView.id) + .unwrap(NativeQuery.class).addScalar("value", JsonBinaryType.INSTANCE) + .getResultList(); + return !list.isEmpty() && !list.get(0).isEmpty(); + }); + } + + @org.junit.jupiter.api.Test + public void testLabelValues(TestInfo info) throws InterruptedException { + Test test = createTest(createExampleTest(getTestName(info))); + Schema schema = createExampleSchema(info); + + BlockingQueue newDatasetQueue = serviceMediator + .getEventQueue(AsyncEventChannels.DATASET_UPDATED_LABELS, test.id); + uploadRun(runWithValue(42, schema), test.name); + uploadRun(JsonNodeFactory.instance.objectNode(), test.name); + assertNotNull(newDatasetQueue.poll(10, TimeUnit.SECONDS)); + assertNotNull(newDatasetQueue.poll(10, TimeUnit.SECONDS)); + + List values = jsonRequest().get("/api/test/" + test.id + "/labelValues").then().statusCode(200) + .extract().body().as(new TypeRef<>() { + }); + assertNotNull(values); + assertFalse(values.isEmpty()); + assertEquals(2, values.size()); + assertTrue(values.get(1).values.containsKey("value")); + } + + @org.junit.jupiter.api.Test + public void testFilterLabelValues(TestInfo info) throws InterruptedException { + Test test = createTest(createExampleTest(getTestName(info))); + + String name = info.getTestClass().map(Class::getName).orElse("") + "." + info.getDisplayName(); + Schema schema = createSchema(name, uriForTest(info, "1.0")); + + addLabel(schema, "filter-1", null, true, false, new Extractor("filter", "$.filter1", false)); + addLabel(schema, "filter-2", null, true, false, new Extractor("filter", "$.filter2", false)); + + BlockingQueue newDatasetQueue = serviceMediator + .getEventQueue(AsyncEventChannels.DATASET_UPDATED_LABELS, test.id); + ObjectNode run; + + run = runWithValue(42, schema); + run.put("filter1", "foo"); + run.put("filter2", "bar"); + uploadRun(run, test.name); + + run = runWithValue(43, schema); + run.put("filter1", "foo"); + run.put("filter2", "bar"); + uploadRun(run, test.name); + + run = runWithValue(44, schema); + run.put("filter1", "biz"); + run.put("filter2", "bar"); + uploadRun(run, test.name); + + run = runWithValue(45, schema); + run.put("filter1", "foo"); + run.put("filter2", "baz"); + uploadRun(run, test.name); + + for (int i = 0; i < 4; i++) { + assertNotNull(newDatasetQueue.poll(10, TimeUnit.SECONDS)); + } + + List values = jsonRequest().get("/api/test/" + test.id + "/filteringLabelValues").then().statusCode(200) + .extract().body().as(new TypeRef<>() { + }); + assertNotNull(values); + assertFalse(values.isEmpty()); + assertEquals(3, values.size()); + assertNotNull(values.stream() + .filter(node -> node.get("filter-1").asText().equals("foo") && node.get("filter-2").asText().equals("bar")) + .findAny().orElse(null)); + assertNotNull(values.stream() + .filter(node -> node.get("filter-1").asText().equals("biz") && node.get("filter-2").asText().equals("bar")) + .findAny().orElse(null)); + assertNotNull(values.stream() + .filter(node -> node.get("filter-1").asText().equals("foo") && node.get("filter-2").asText().equals("baz")) + .findAny().orElse(null)); + + } + + @org.junit.jupiter.api.Test + public void labelValuesIncludeExcluded() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, true); + + JsonNode response = jsonRequest() + .get("/api/test/" + t.id + "/labelValues?include=labelFoo&exclude=labelFoo") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(1, arrayResponse.size()); + assertInstanceOf(ObjectNode.class, arrayResponse.get(0)); + ObjectNode objectNode = (ObjectNode) arrayResponse.get(0).get("values"); + assertFalse(objectNode.has("labelFoo"), objectNode.toString()); + assertTrue(objectNode.has("labelBar"), objectNode.toString()); + } + + @org.junit.jupiter.api.Test + public void labelValuesWithTimestampAfterFilter() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, false); + long stop = System.currentTimeMillis(); + long start = System.currentTimeMillis(); + long delta = 5000; // 5 seconds + Integer runId = uploadRun(start, stop, "{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo", + jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); + recalculateDatasetForRun(runId); + runId = uploadRun(start + delta, stop, "{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo", + jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); + recalculateDatasetForRun(runId); + runId = uploadRun(start + delta, stop, "{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo", + jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); + recalculateDatasetForRun(runId); + + JsonNode response = jsonRequest() + .urlEncodingEnabled(true) + // keep only those runs that started after (start+delta-1) + .queryParam("after", Long.toString(start + delta - 1)) + .get("/api/test/" + t.id + "/labelValues") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(2, arrayResponse.size(), "unexpected number of responses " + response); + JsonNode first = arrayResponse.get(0); + assertTrue(first.has("values"), first.toString()); + JsonNode values = first.get("values"); + assertTrue(values.has("labelBar"), values.toString()); + assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); + JsonNode second = arrayResponse.get(1); + assertTrue(first.has("values"), second.toString()); + JsonNode secondValues = second.get("values"); + assertTrue(secondValues.has("labelBar"), secondValues.toString()); + assertEquals(JsonNodeType.STRING, secondValues.get("labelBar").getNodeType()); + + List labelBarValues = List.of(values.get("labelBar").asText(), secondValues.get("labelBar").asText()); + assertTrue(labelBarValues.contains("dos"), labelBarValues.toString()); + assertTrue(labelBarValues.contains("tres"), labelBarValues.toString()); + } + + @org.junit.jupiter.api.Test + public void labelValuesWithISOAfterFilter() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, false); + long stop = System.currentTimeMillis(); + Integer runId = uploadRun(Util.toInstant("2024-10-06T20:20:32.183Z").toEpochMilli(), stop, + "{ \"foo\": 1, \"bar\": \"uno\"}", t.name, + "urn:foo", jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); + recalculateDatasetForRun(runId); + runId = uploadRun(Util.toInstant("2024-10-06T20:20:32.183Z").toEpochMilli(), stop, "{ \"foo\": 2, \"bar\": \"dos\"}", + t.name, + "urn:foo", jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); + recalculateDatasetForRun(runId); + runId = uploadRun(Util.toInstant("2024-10-09T20:20:32.183Z").toEpochMilli(), stop, "{ \"foo\": 3, \"bar\": \"tres\"}", + t.name, + "urn:foo", jakarta.ws.rs.core.Response.Status.ACCEPTED.getStatusCode()).get(0); + recalculateDatasetForRun(runId); + + JsonNode response = jsonRequest() + .urlEncodingEnabled(true) + // keep only those runs that started after (start+delta-1) + .queryParam("after", "2024-10-07T20:20:32.183Z") + .queryParam("filter", Maps.of("labelBar", Arrays.asList("none", "tres"))) + .queryParam("multiFilter", true) + .get("/api/test/" + t.id + "/labelValues") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); + JsonNode first = arrayResponse.get(0); + assertTrue(first.has("values"), first.toString()); + JsonNode values = first.get("values"); + assertTrue(values.has("labelBar"), values.toString()); + assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); + assertEquals("tres", values.get("labelBar").asText()); + } + + @org.junit.jupiter.api.Test + public void labelValuesFilterWithJsonpath() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, false); + uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo"); + uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo"); + uploadRun("{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo"); + JsonNode response = jsonRequest() + .urlEncodingEnabled(true) + .queryParam("filter", "$.labelFoo ? (@ < 2)") + .get("/api/test/" + t.id + "/labelValues") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); + JsonNode first = arrayResponse.get(0); + assertTrue(first.has("values"), first.toString()); + JsonNode values = first.get("values"); + assertTrue(values.has("labelBar"), values.toString()); + assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); + assertEquals("uno", values.get("labelBar").asText()); + } + + @org.junit.jupiter.api.Test + public void labelValuesFilterWithInvalidJsonpath() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, false); + uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo"); + uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo"); + uploadRun("{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo"); + jsonRequest() + .urlEncodingEnabled(true) + .queryParam("filter", "$..name") + .get("/api/test/" + t.id + "/labelValues") + .then() + .statusCode(400); + } + + @org.junit.jupiter.api.Test + public void labelValuesFilterWithObject() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, false); + uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo"); + uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo"); + uploadRun("{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo"); + JsonNode response = jsonRequest() + .urlEncodingEnabled(true) + .queryParam("filter", Maps.of("labelBar", "uno", "labelFoo", 1)) + .queryParam("multiFilter", false) + .get("/api/test/" + t.id + "/labelValues") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); + JsonNode first = arrayResponse.get(0); + assertTrue(first.has("values"), first.toString()); + JsonNode values = first.get("values"); + assertTrue(values.has("labelBar"), values.toString()); + assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); + assertEquals("uno", values.get("labelBar").asText()); + } + + @org.junit.jupiter.api.Test + public void labelValuesFilterWithObjectNoMatch() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, false); + uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo"); + uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo"); + uploadRun("{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo"); + JsonNode response = jsonRequest() + .urlEncodingEnabled(true) + // no runs match both conditions + .queryParam("filter", Maps.of("labelBar", "uno", "labelFoo", "3")) + .queryParam("multiFilter", false) + .get("/api/test/" + t.id + "/labelValues") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(0, arrayResponse.size(), "unexpected number of responses " + response); + } + + @org.junit.jupiter.api.Test + public void labelValuesFilterMultiSelectMultipleValues() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, false); + Integer runId = uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo").get(0); + recalculateDatasetForRun(runId); + runId = uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo").get(0); + recalculateDatasetForRun(runId); + runId = uploadRun("{ \"foo\": 3, \"bar\": \"tres\"}", t.name, "urn:foo").get(0); + recalculateDatasetForRun(runId); + JsonNode response = jsonRequest() + .urlEncodingEnabled(true) + .queryParam("filter", Maps.of("labelBar", Arrays.asList("uno", "tres"))) + .queryParam("multiFilter", true) + .get("/api/test/" + t.id + "/labelValues") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(2, arrayResponse.size(), "unexpected number of responses " + response); + JsonNode first = arrayResponse.get(0); + assertTrue(first.has("values"), first.toString()); + JsonNode values = first.get("values"); + assertTrue(values.has("labelBar"), values.toString()); + assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); + JsonNode second = arrayResponse.get(1); + assertTrue(first.has("values"), second.toString()); + JsonNode secondValues = second.get("values"); + assertTrue(secondValues.has("labelBar"), secondValues.toString()); + assertEquals(JsonNodeType.STRING, secondValues.get("labelBar").getNodeType()); + } + + @org.junit.jupiter.api.Test + public void labelValuesFilterMultiSelectStrings() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, false); + uploadRun("{ \"foo\": 1, \"bar\": \"uno\"}", t.name, "urn:foo"); + uploadRun("{ \"foo\": 2, \"bar\": \"dos\"}", t.name, "urn:foo"); + JsonNode response = jsonRequest() + .urlEncodingEnabled(true) + .queryParam("filter", Maps.of("labelBar", Arrays.asList("uno", 30))) + .queryParam("multiFilter", true) + .get("/api/test/" + t.id + "/labelValues") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); + JsonNode first = arrayResponse.get(0); + assertTrue(first.has("values"), first.toString()); + JsonNode values = first.get("values"); + assertTrue(values.has("labelBar"), values.toString()); + assertEquals(JsonNodeType.STRING, values.get("labelBar").getNodeType()); + assertEquals("uno", values.get("labelBar").asText()); + } + + @org.junit.jupiter.api.Test + public void labelValuesFilterMultiSelectNumber() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, false); + uploadRun("{ \"foo\": 1, \"bar\": 10}", t.name, "urn:foo"); + uploadRun("{ \"foo\": 2, \"bar\": 20}", t.name, "urn:foo"); + JsonNode response = jsonRequest() + .urlEncodingEnabled(true) + .queryParam("filter", Maps.of("labelBar", Arrays.asList(10, 30))) + .queryParam("multiFilter", true) + .get("/api/test/" + t.id + "/labelValues") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); + JsonNode first = arrayResponse.get(0); + assertTrue(first.has("values"), first.toString()); + JsonNode values = first.get("values"); + assertTrue(values.has("labelBar"), values.toString()); + assertEquals(JsonNodeType.NUMBER, values.get("labelBar").getNodeType()); + assertEquals("10", values.get("labelBar").toString()); + } + + @org.junit.jupiter.api.Test + public void labelValuesFilterMultiSelectBoolean() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, false); + uploadRun("{ \"foo\": 1, \"bar\": true}", t.name, "urn:foo"); + uploadRun("{ \"foo\": 2, \"bar\": 20}", t.name, "urn:foo"); + JsonNode response = jsonRequest() + .urlEncodingEnabled(true) + .queryParam("filter", Maps.of("labelBar", Arrays.asList(true, 30))) + .queryParam("multiFilter", true) + .get("/api/test/" + t.id + "/labelValues") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(1, arrayResponse.size(), "unexpected number of responses " + response); + JsonNode first = arrayResponse.get(0); + assertTrue(first.has("values"), first.toString()); + JsonNode values = first.get("values"); + assertTrue(values.has("labelBar"), values.toString()); + assertEquals(JsonNodeType.BOOLEAN, values.get("labelBar").getNodeType()); + assertEquals("true", values.get("labelBar").toString()); + } + + @org.junit.jupiter.api.Test + public void labelValuesIncludeTwoParams() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, true); + + JsonNode response = jsonRequest() + .get("/api/test/" + t.id + "/labelValues?include=labelFoo&include=labelBar") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(1, arrayResponse.size()); + assertInstanceOf(ObjectNode.class, arrayResponse.get(0)); + ObjectNode objectNode = (ObjectNode) arrayResponse.get(0).get("values"); + assertTrue(objectNode.has("labelFoo"), objectNode.toString()); + assertTrue(objectNode.has("labelBar"), objectNode.toString()); + } + + @org.junit.jupiter.api.Test + public void labelValuesIncludeTwoSeparated() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, true); + + JsonNode response = jsonRequest() + .get("/api/test/" + t.id + "/labelValues?include=labelFoo,labelBar") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(1, arrayResponse.size()); + assertInstanceOf(ObjectNode.class, arrayResponse.get(0)); + ObjectNode objectNode = (ObjectNode) arrayResponse.get(0).get("values"); + assertTrue(objectNode.has("labelFoo"), objectNode.toString()); + assertTrue(objectNode.has("labelBar"), objectNode.toString()); + } + + @org.junit.jupiter.api.Test + public void labelValuesInclude() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, true); + + JsonNode response = jsonRequest() + .get("/api/test/" + t.id + "/labelValues?include=labelFoo") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(1, arrayResponse.size()); + assertInstanceOf(ObjectNode.class, arrayResponse.get(0)); + ObjectNode objectNode = (ObjectNode) arrayResponse.get(0).get("values"); + assertTrue(objectNode.has("labelFoo")); + assertFalse(objectNode.has("labelBar")); + } + + @org.junit.jupiter.api.Test + public void labelValuesExclude() { + Test t = createTest(createExampleTest("my-test")); + labelValuesSetup(t, true); + + JsonNode response = jsonRequest() + .get("/api/test/" + t.id + "/labelValues?exclude=labelFoo") + .then() + .statusCode(200) + .extract() + .body() + .as(JsonNode.class); + assertInstanceOf(ArrayNode.class, response); + ArrayNode arrayResponse = (ArrayNode) response; + assertEquals(1, arrayResponse.size()); + assertInstanceOf(ObjectNode.class, arrayResponse.get(0)); + ObjectNode objectNode = (ObjectNode) arrayResponse.get(0).get("values"); + assertFalse(objectNode.has("labelFoo"), objectNode.toPrettyString()); + assertTrue(objectNode.has("labelBar"), objectNode.toPrettyString()); + + } + + @org.junit.jupiter.api.Test + public void testLabelValues() throws JsonProcessingException { + List toParse = new ArrayList<>(); + toParse.add( + new Object[] { "job", mapper.readTree("\"quarkus-release-startup\""), 10, 10, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Max RSS", mapper.readTree("[]"), 10, 10, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "build-id", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Throughput 1 CPU", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Throughput 2 CPU", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Throughput 4 CPU", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Throughput 8 CPU", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Throughput 32 CPU", mapper.readTree("null"), 10, 10, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Quarkus - Kafka_tags", mapper.readTree("\"quarkus-release-startup\""), 10, 10, + Instant.now(), Instant.now() }); + List values = LabelValuesService.parse(toParse); + assertEquals(1, values.size()); + assertEquals(9, values.get(0).values.size()); + assertEquals("quarkus-release-startup", values.get(0).values.get("job").asText()); + assertEquals("null", values.get(0).values.get("Throughput 32 CPU").asText()); + + toParse.add( + new Object[] { "job", mapper.readTree("\"quarkus-release-startup\""), 10, 11, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Max RSS", mapper.readTree("[]"), 10, 11, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "build-id", mapper.readTree("null"), 10, 11, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Throughput 1 CPU", mapper.readTree("17570.30"), 10, 11, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Throughput 2 CPU", mapper.readTree("43105.62"), 10, 11, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Throughput 4 CPU", mapper.readTree("84895.13"), 10, 11, Instant.now(), Instant.now() }); + toParse.add(new Object[] { "Throughput 8 CPU", mapper.readTree("141086.29"), 10, 11, Instant.now(), Instant.now() }); + values = LabelValuesService.parse(toParse); + assertEquals(2, values.size()); + assertEquals(9, values.get(0).values.size()); + assertEquals(7, values.get(1).values.size()); + assertEquals(84895.13d, values.get(1).values.get("Throughput 4 CPU").asDouble()); + } +} diff --git a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/AMQPInMemoryResource.java b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/AMQPInMemoryResource.java new file mode 100644 index 000000000..4fa5b98f0 --- /dev/null +++ b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/AMQPInMemoryResource.java @@ -0,0 +1,36 @@ +package io.hyperfoil.tools.horreum.test; + +import java.util.HashMap; +import java.util.Map; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import io.smallrye.reactive.messaging.memory.InMemoryConnector; + +public class AMQPInMemoryResource implements QuarkusTestResourceLifecycleManager { + @Override + public Map start() { + Map env = new HashMap<>(); + Map props1 = InMemoryConnector.switchIncomingChannelsToInMemory("run-upload-in"); + Map props2 = InMemoryConnector.switchOutgoingChannelsToInMemory("run-upload-out"); + Map props3 = InMemoryConnector.switchIncomingChannelsToInMemory("dataset-event-in"); + Map props4 = InMemoryConnector.switchOutgoingChannelsToInMemory("dataset-event-out"); + Map props5 = InMemoryConnector.switchIncomingChannelsToInMemory("run-recalc-in"); + Map props6 = InMemoryConnector.switchOutgoingChannelsToInMemory("run-recalc-out"); + Map props7 = InMemoryConnector.switchIncomingChannelsToInMemory("schema-sync-in"); + Map props8 = InMemoryConnector.switchOutgoingChannelsToInMemory("schema-sync-out"); + env.putAll(props1); + env.putAll(props2); + env.putAll(props3); + env.putAll(props4); + env.putAll(props5); + env.putAll(props6); + env.putAll(props7); + env.putAll(props8); + return env; + } + + @Override + public void stop() { + InMemoryConnector.clear(); + } +} diff --git a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/ElasticsearchTestProfile.java b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/ElasticsearchTestProfile.java index 610a3fddf..c7585969e 100644 --- a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/ElasticsearchTestProfile.java +++ b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/ElasticsearchTestProfile.java @@ -3,7 +3,7 @@ import java.util.HashMap; import java.util.Map; -public class ElasticsearchTestProfile extends HorreumTestProfile { +public class ElasticsearchTestProfile extends InMemoryAMQTestProfile { @Override public Map getConfigOverrides() { diff --git a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/HorreumTestProfile.java b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/HorreumTestProfile.java index bfba8c40c..81080e158 100644 --- a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/HorreumTestProfile.java +++ b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/HorreumTestProfile.java @@ -31,6 +31,7 @@ public boolean disableGlobalTestResources() { @Override public List testResources() { return Arrays.asList( - new TestResourceEntry(PostgresResource.class), new TestResourceEntry(OidcWiremockTestResource.class)); + new TestResourceEntry(PostgresResource.class), + new TestResourceEntry(OidcWiremockTestResource.class)); } } diff --git a/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/InMemoryAMQTestProfile.java b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/InMemoryAMQTestProfile.java new file mode 100644 index 000000000..a2c020843 --- /dev/null +++ b/horreum-backend/src/test/java/io/hyperfoil/tools/horreum/test/InMemoryAMQTestProfile.java @@ -0,0 +1,17 @@ +package io.hyperfoil.tools.horreum.test; + +import java.util.Arrays; +import java.util.List; + +import io.quarkus.test.oidc.server.OidcWiremockTestResource; + +public class InMemoryAMQTestProfile extends HorreumTestProfile { + + @Override + public List testResources() { + return Arrays.asList( + new TestResourceEntry(PostgresResource.class), + new TestResourceEntry(OidcWiremockTestResource.class), + new TestResourceEntry(AMQPInMemoryResource.class)); + } +}