diff --git a/simulator-docs/src/main/asciidoc/rest-api.adoc b/simulator-docs/src/main/asciidoc/rest-api.adoc
index 53449dda..ffc6ae38 100644
--- a/simulator-docs/src/main/asciidoc/rest-api.adoc
+++ b/simulator-docs/src/main/asciidoc/rest-api.adoc
@@ -17,6 +17,7 @@ For each listed resource, the following operations are supported:
* Listing all entries with a `GET` request to the root URI.
** Pagination and filtering are supported.
* Counting all entries with a `GET /count` endpoint.
+** Note that the `?distinct=true` query parameter is required to count unique results.
* Retrieving a single resource using the `GET /{id}` endpoint.
All REST resources adhere to this pattern, with exceptions noted in subsequent sections.
diff --git a/simulator-samples/sample-rest/src/test/java/org/citrusframework/simulator/SimulatorRestIT.java b/simulator-samples/sample-rest/src/test/java/org/citrusframework/simulator/SimulatorRestIT.java
index 08cffd82..af19838c 100644
--- a/simulator-samples/sample-rest/src/test/java/org/citrusframework/simulator/SimulatorRestIT.java
+++ b/simulator-samples/sample-rest/src/test/java/org/citrusframework/simulator/SimulatorRestIT.java
@@ -25,6 +25,7 @@
import org.citrusframework.testng.spring.TestNGCitrusSpringSupport;
import org.citrusframework.xml.XsdSchemaRepository;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@@ -49,10 +50,18 @@ public class SimulatorRestIT extends TestNGCitrusSpringSupport {
private final String defaultResponse = "This is a default response!";
+ /**
+ * Test Http REST client configured to access the API
+ */
+ @Autowired
+ @Qualifier("apiClient")
+ private HttpClient apiClient;
+
/**
* Test Http REST client
*/
@Autowired
+ @Qualifier("simulatorClient")
private HttpClient simulatorClient;
/**
@@ -263,6 +272,17 @@ public void testSimulationFailingExpectantly() {
$(http().client(simulatorClient)
.receive()
.response(HttpStatus.OK));
+
+ // Make sure we receive exactly one record using "count" resources
+ $(http().client(apiClient)
+ .send()
+ .get("scenario-executions/count?headers=citrus_endpoint_uri~/services/rest/simulator/fail&scenarioName.contains=Fail&status.equals=2&distinct=true"));
+
+ $(http().client(apiClient)
+ .receive()
+ .response(HttpStatus.OK)
+ .getMessageBuilderSupport()
+ .body("1"));
}
/**
@@ -319,6 +339,13 @@ public HttpClient simulatorClient() {
.build();
}
+ @Bean
+ public HttpClient apiClient() {
+ return CitrusEndpoints.http().client()
+ .requestUrl(String.format("http://localhost:%s/api", 8080))
+ .build();
+ }
+
@Bean
@ConditionalOnProperty(name = "simulator.mode", havingValue = "embedded")
public BeforeSuite startEmbeddedSimulator() {
diff --git a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/CriteriaQueryUtils.java b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/CriteriaQueryUtils.java
index 15d2337e..00cf4205 100644
--- a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/CriteriaQueryUtils.java
+++ b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/CriteriaQueryUtils.java
@@ -17,6 +17,7 @@
package org.citrusframework.simulator.service;
import static java.util.Objects.nonNull;
+import static org.springframework.data.domain.Pageable.unpaged;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
@@ -68,7 +69,10 @@ static TypedQuery newSelectIdBySpecificationQuery(Class entityClass
criteriaQuery = orderByPageSort(page, root, criteriaBuilder, criteriaQuery);
TypedQuery query = entityManager.createQuery(criteriaQuery);
- query = selectPage(page, query);
+
+ if (!unpaged().equals(page)) {
+ query = selectPage(page, query);
+ }
return query;
}
diff --git a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/ScenarioExecutionQueryService.java b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/ScenarioExecutionQueryService.java
index ebb60c73..a0c6d088 100644
--- a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/ScenarioExecutionQueryService.java
+++ b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/ScenarioExecutionQueryService.java
@@ -275,7 +275,7 @@ private Specification buildMessageHeaderValueSpecification(Lo
private static SetJoin joinMessageHeaders(Root root) {
return root.join(ScenarioExecution_.scenarioMessages, JoinType.LEFT)
- .join(Message_.headers);
+ .join(Message_.headers, JoinType.LEFT);
}
private static Path joinMessageHeadersAndGetValue(Root root) {
diff --git a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResourceIT.java b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResourceIT.java
index 89081b49..ba05af33 100644
--- a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResourceIT.java
+++ b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResourceIT.java
@@ -560,6 +560,7 @@ void getNonExistingScenarioExecution() throws Exception {
@Nested
class CorrectTimeOnScenarioExecution {
+
public static final TemporalUnitLessThanOffset LESS_THAN_5_SECONDS = new TemporalUnitLessThanOffset(5, SECONDS);
@Autowired