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