From 55349a7a1956c40145d853ecbb5f06dcd36800fe Mon Sep 17 00:00:00 2001 From: Timon Borter Date: Tue, 15 Oct 2024 22:39:52 +0200 Subject: [PATCH] feat: optionally include all message headers in scenario execution response --- .../ScenarioExecutionRepository.java | 2 +- .../web/rest/ScenarioExecutionResource.java | 27 +++- .../simulator/TechnicalStructureTest.java | 2 +- .../web/rest/ScenarioExecutionResourceIT.java | 1 + .../rest/ScenarioExecutionResourceTest.java | 148 ++++++++++++++++++ .../web/rest/ScenarioResourceTest.java | 7 +- .../service/scenario-execution.service.ts | 16 +- 7 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResourceTest.java diff --git a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/repository/ScenarioExecutionRepository.java b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/repository/ScenarioExecutionRepository.java index f9e5928b8..4bf41e7a0 100644 --- a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/repository/ScenarioExecutionRepository.java +++ b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/repository/ScenarioExecutionRepository.java @@ -43,6 +43,6 @@ public interface ScenarioExecutionRepository extends JpaRepository findOneByExecutionId(@Param("executionId") Long executionId); @Query("FROM ScenarioExecution WHERE executionId IN :scenarioExecutionIds") - @EntityGraph(attributePaths = {"testResult", "scenarioParameters", "scenarioActions", "scenarioMessages"}) + @EntityGraph(attributePaths = {"testResult", "scenarioParameters", "scenarioActions", "scenarioMessages", "scenarioMessages.headers"}) Page findAllWhereExecutionIdIn(@Param("scenarioExecutionIds") List scenarioExecutionIds, Pageable pageable); } diff --git a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResource.java b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResource.java index 6430e799c..53777a92d 100644 --- a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResource.java +++ b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResource.java @@ -34,12 +34,15 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.util.List; import java.util.Optional; +import static java.lang.Boolean.FALSE; + /** * REST controller for managing {@link ScenarioExecution}. */ @@ -56,7 +59,8 @@ public class ScenarioExecutionResource { public ScenarioExecutionResource( ScenarioExecutionService scenarioExecutionService, - ScenarioExecutionQueryService scenarioExecutionQueryService, ScenarioExecutionMapper scenarioExecutionMapper + ScenarioExecutionQueryService scenarioExecutionQueryService, + ScenarioExecutionMapper scenarioExecutionMapper ) { this.scenarioExecutionService = scenarioExecutionService; this.scenarioExecutionQueryService = scenarioExecutionQueryService; @@ -71,10 +75,17 @@ public ScenarioExecutionResource( * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of scenarioExecutions in body. */ @GetMapping("/scenario-executions") - public ResponseEntity> getAllScenarioExecutions(ScenarioExecutionCriteria criteria, @ParameterObject Pageable pageable) { + public ResponseEntity> getAllScenarioExecutions( + ScenarioExecutionCriteria criteria, + @RequestParam(name = "includeActions", required = false, defaultValue = "false") Boolean includeActions, + @RequestParam(name = "includeMessages", required = false, defaultValue = "false") Boolean includeMessages, + @RequestParam(name = "includeParameters", required = false, defaultValue = "false") Boolean includeParameters, + @ParameterObject Pageable pageable + ) { logger.debug("REST request to get ScenarioExecutions by criteria: {}", criteria); Page page = scenarioExecutionQueryService.findByCriteria(criteria, pageable); + stripPageContents(page, includeActions, includeMessages, includeParameters); HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); return ResponseEntity.ok().headers(headers).body(page.getContent().stream().map(scenarioExecutionMapper::toDto).toList()); } @@ -103,4 +114,16 @@ public ResponseEntity getScenarioExecution(@PathVariable(" Optional scenarioExecution = scenarioExecutionService.findOne(id); return ResponseUtil.wrapOrNotFound(scenarioExecution.map(scenarioExecutionMapper::toDto)); } + + private void stripPageContents(Page page, Boolean includeActions, Boolean includeMessages, Boolean includeParameters) { + if (FALSE.equals(includeActions)) { + page.getContent().forEach(scenarioExecution -> scenarioExecution.getScenarioActions().clear()); + } + if (FALSE.equals(includeMessages)) { + page.getContent().forEach(scenarioExecution -> scenarioExecution.getScenarioMessages().clear()); + } + if (FALSE.equals(includeParameters)) { + page.getContent().forEach(scenarioExecution -> scenarioExecution.getScenarioParameters().clear()); + } + } } diff --git a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/TechnicalStructureTest.java b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/TechnicalStructureTest.java index 61f7eea65..256f308eb 100644 --- a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/TechnicalStructureTest.java +++ b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/TechnicalStructureTest.java @@ -40,5 +40,5 @@ public class TechnicalStructureTest { .layer("Scenario").definedBy("..scenario..") .whereLayer("Web").mayOnlyBeAccessedByLayers("Config") - .whereLayer("Domain").mayOnlyBeAccessedByLayers("Config", "DTO", "Service", "Persistence", "Endpoint", "Listener", "Scenario"); + .whereLayer("Domain").mayOnlyBeAccessedByLayers("Config", "DTO", "Service", "Persistence", "Endpoint", "Listener", "Scenario", "Web"); } 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 89081b492..ba05af335 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 diff --git a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResourceTest.java b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResourceTest.java new file mode 100644 index 000000000..79e8109b4 --- /dev/null +++ b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioExecutionResourceTest.java @@ -0,0 +1,148 @@ +/* + * Copyright the original author or 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 org.citrusframework.simulator.web.rest; + +import org.citrusframework.simulator.model.Message; +import org.citrusframework.simulator.model.ScenarioAction; +import org.citrusframework.simulator.model.ScenarioExecution; +import org.citrusframework.simulator.model.ScenarioParameter; +import org.citrusframework.simulator.service.ScenarioExecutionQueryService; +import org.citrusframework.simulator.service.ScenarioExecutionService; +import org.citrusframework.simulator.service.criteria.ScenarioExecutionCriteria; +import org.citrusframework.simulator.web.rest.dto.ScenarioExecutionDTO; +import org.citrusframework.simulator.web.rest.dto.mapper.ScenarioExecutionMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.springframework.http.HttpStatus.OK; + +@ExtendWith({MockitoExtension.class}) +class ScenarioExecutionResourceTest { + + @Mock + private ScenarioExecutionService scenarioExecutionServiceMock; + + @Mock + private ScenarioExecutionQueryService scenarioExecutionQueryServiceMock; + + @Mock + private ScenarioExecutionMapper scenarioExecutionMapperMock; + + private ScenarioExecutionResource fixture; + + @BeforeEach + void beforeEachSetup() { + fixture = new ScenarioExecutionResource(scenarioExecutionServiceMock, scenarioExecutionQueryServiceMock, scenarioExecutionMapperMock); + } + + @Nested + class GetAllScenarioExecutions { + + @Mock + private ScenarioExecutionCriteria criteriaMock; + + @Mock + private Pageable pageableMock; + + @Mock + private ScenarioExecutionDTO scenarioExecutionDTOMock; + + private ScenarioExecution scenarioExecution; + + @BeforeEach + void beforeEachSetup() { + var request = new MockHttpServletRequest(); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + + scenarioExecution = new ScenarioExecution(); + scenarioExecution.getScenarioActions().add(mock(ScenarioAction.class)); + scenarioExecution.getScenarioMessages().add(mock(Message.class)); + scenarioExecution.getScenarioParameters().add(mock(ScenarioParameter.class)); + + var scenarioExecutions = new PageImpl<>(singletonList(scenarioExecution)); + doReturn(scenarioExecutions).when(scenarioExecutionQueryServiceMock).findByCriteria(criteriaMock, pageableMock); + + doReturn(scenarioExecutionDTOMock).when(scenarioExecutionMapperMock).toDto(scenarioExecution); + } + + @Test + void stripsIncludedActions() { + var response = fixture.getAllScenarioExecutions(criteriaMock, FALSE, TRUE, TRUE, pageableMock); + + assertThat(response) + .satisfies( + r -> assertThat(r.getStatusCode()).isEqualTo(OK), + r -> assertThat(r.getBody()) + .singleElement() + .isEqualTo(scenarioExecutionDTOMock) + ); + + assertThat(scenarioExecution.getScenarioActions()).isEmpty(); + assertThat(scenarioExecution.getScenarioMessages()).isNotEmpty(); + assertThat(scenarioExecution.getScenarioParameters()).isNotEmpty(); + } + + @Test + void stripsIncludedMessages() { + var response = fixture.getAllScenarioExecutions(criteriaMock, TRUE, FALSE, TRUE, pageableMock); + + assertThat(response) + .satisfies( + r -> assertThat(r.getStatusCode()).isEqualTo(OK), + r -> assertThat(r.getBody()) + .singleElement() + .isEqualTo(scenarioExecutionDTOMock) + ); + + assertThat(scenarioExecution.getScenarioActions()).isNotEmpty(); + assertThat(scenarioExecution.getScenarioMessages()).isEmpty(); + assertThat(scenarioExecution.getScenarioParameters()).isNotEmpty(); + } + + @Test + void stripsIncludedParameters() { + var response = fixture.getAllScenarioExecutions(criteriaMock, TRUE, TRUE, FALSE, pageableMock); + + assertThat(response) + .satisfies( + r -> assertThat(r.getStatusCode()).isEqualTo(OK), + r -> assertThat(r.getBody()) + .singleElement() + .isEqualTo(scenarioExecutionDTOMock) + ); + + assertThat(scenarioExecution.getScenarioActions()).isNotEmpty(); + assertThat(scenarioExecution.getScenarioMessages()).isNotEmpty(); + assertThat(scenarioExecution.getScenarioParameters()).isEmpty(); + } + } +} diff --git a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioResourceTest.java b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioResourceTest.java index 5493d8324..eee032cf7 100644 --- a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioResourceTest.java +++ b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/web/rest/ScenarioResourceTest.java @@ -42,6 +42,7 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.LIST; import static org.assertj.core.api.InstanceOfAssertFactories.type; import static org.citrusframework.simulator.web.rest.ScenarioResource.Scenario.ScenarioType.STARTER; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -122,7 +123,7 @@ void doesFilterCacheWithNameContains(String filterLetter, String expectedScenari assertThat(result) .extracting(ResponseEntity::getBody) - .asList() + .asInstanceOf(LIST) .hasSize(1) .first() .asInstanceOf(type(Scenario.class)) @@ -141,7 +142,7 @@ void doesFilterCacheWithNameStartsOrEndsWith() { assertThat(result) .extracting(ResponseEntity::getBody) - .asList() + .asInstanceOf(LIST) .hasSize(2) .noneSatisfy(scenario -> assertThat(scenario) @@ -161,7 +162,7 @@ void doesNotFilterCacheWithoutNameContains() { assertThat(result) .extracting(ResponseEntity::getBody) - .asList() + .asInstanceOf(LIST) .isEqualTo(SCENARIO_CACHE); } diff --git a/simulator-ui/src/main/webapp/app/entities/scenario-execution/service/scenario-execution.service.ts b/simulator-ui/src/main/webapp/app/entities/scenario-execution/service/scenario-execution.service.ts index 206ad3c18..460931bbb 100644 --- a/simulator-ui/src/main/webapp/app/entities/scenario-execution/service/scenario-execution.service.ts +++ b/simulator-ui/src/main/webapp/app/entities/scenario-execution/service/scenario-execution.service.ts @@ -36,8 +36,20 @@ export class ScenarioExecutionService { .pipe(map(res => this.convertResponseFromServer(res))); } - query(req?: any): Observable { - const options = createRequestOption(req); + query( + req?: any, + responseConfig: { includeActions: boolean; includeMessages: boolean; includeParameters: boolean } = { + includeActions: false, + includeMessages: false, + includeParameters: false, + }, + ): Observable { + let options = createRequestOption(req); + + for (const key of Object.keys(responseConfig)) { + options = options.append(key, responseConfig[key as keyof typeof responseConfig]); + } + return this.http .get(this.resourceUrl, { params: options, observe: 'response' }) .pipe(map(res => this.convertResponseArrayFromServer(res)));