diff --git a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/impl/TimeProvider.java b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/common/TimeProvider.java similarity index 69% rename from simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/impl/TimeProvider.java rename to simulator-spring-boot/src/main/java/org/citrusframework/simulator/common/TimeProvider.java index 80032ce06..b92c3f458 100644 --- a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/impl/TimeProvider.java +++ b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/common/TimeProvider.java @@ -14,19 +14,19 @@ * limitations under the License. */ -package org.citrusframework.simulator.service.impl; +package org.citrusframework.simulator.common; import java.time.Instant; import java.time.LocalDateTime; -import java.time.ZoneOffset; -class TimeProvider { +import static java.time.ZoneOffset.UTC; - TimeProvider() { - // Separate class that allows mocking - } +/** + * Should be used whenever the current time is needed, to ensure consistent Date/Time values + */ +public class TimeProvider { - Instant getTimeNow() { - return LocalDateTime.now().toInstant(ZoneOffset.UTC); + public Instant getTimeNow() { + return LocalDateTime.now().toInstant(UTC); } } diff --git a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/model/AbstractAuditingEntity.java b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/model/AbstractAuditingEntity.java index da9adbc40..fb663bfa4 100644 --- a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/model/AbstractAuditingEntity.java +++ b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/model/AbstractAuditingEntity.java @@ -21,6 +21,7 @@ import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; import lombok.Data; +import org.citrusframework.simulator.common.TimeProvider; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -39,16 +40,18 @@ @JsonIgnoreProperties(value = {"createdDate", "lastModifiedDate"}, allowGetters = true) public abstract class AbstractAuditingEntity implements Serializable { + private static TimeProvider timeProvider = new TimeProvider(); + @Serial private static final long serialVersionUID = 1L; @CreatedDate @Column(name = "created_date", nullable = false, updatable = false) - private Instant createdDate = Instant.now(); + private Instant createdDate = timeProvider.getTimeNow(); @LastModifiedDate @Column(name = "last_modified_date") - private Instant lastModifiedDate = Instant.now(); + private Instant lastModifiedDate = timeProvider.getTimeNow(); public static abstract class AuditingEntityBuilder, E extends AbstractAuditingEntity, A> { diff --git a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/impl/ScenarioActionServiceImpl.java b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/impl/ScenarioActionServiceImpl.java index 16a4c360e..6cfc18e9c 100644 --- a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/impl/ScenarioActionServiceImpl.java +++ b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/impl/ScenarioActionServiceImpl.java @@ -21,6 +21,7 @@ import org.citrusframework.TestAction; import org.citrusframework.TestCase; import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.simulator.common.TimeProvider; import org.citrusframework.simulator.model.ScenarioAction; import org.citrusframework.simulator.repository.ScenarioActionRepository; import org.citrusframework.simulator.service.ScenarioActionService; diff --git a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/impl/ScenarioExecutionServiceImpl.java b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/impl/ScenarioExecutionServiceImpl.java index c9304429e..8f442a1ed 100644 --- a/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/impl/ScenarioExecutionServiceImpl.java +++ b/simulator-spring-boot/src/main/java/org/citrusframework/simulator/service/impl/ScenarioExecutionServiceImpl.java @@ -18,6 +18,7 @@ import jakarta.annotation.Nullable; import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.simulator.common.TimeProvider; import org.citrusframework.simulator.model.ScenarioExecution; import org.citrusframework.simulator.model.ScenarioParameter; import org.citrusframework.simulator.model.TestResult; diff --git a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/model/AbstractAuditingEntityIT.java b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/model/AbstractAuditingEntityIT.java new file mode 100644 index 000000000..f8f08f7cf --- /dev/null +++ b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/model/AbstractAuditingEntityIT.java @@ -0,0 +1,31 @@ +package org.citrusframework.simulator.model; + +import org.assertj.core.data.TemporalUnitLessThanOffset; +import org.citrusframework.simulator.common.TimeProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; + +class AbstractAuditingEntityIT { + + public static final TemporalUnitLessThanOffset LESS_THAN_1_SECOND = new TemporalUnitLessThanOffset(1, SECONDS); + + AuditedEntity fixture; + + @BeforeEach + void setup() { + fixture = new AuditedEntity(); + } + + @Test + void shouldUseCorrectTime() { + var timeProvider = new TimeProvider(); + assertThat(fixture.getCreatedDate()).isCloseTo(timeProvider.getTimeNow(), LESS_THAN_1_SECOND); + assertThat(fixture.getLastModifiedDate()).isCloseTo(timeProvider.getTimeNow(), LESS_THAN_1_SECOND); + } + + private static class AuditedEntity extends AbstractAuditingEntity { + } +} diff --git a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/service/impl/ScenarioActionServiceImplTest.java b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/service/impl/ScenarioActionServiceImplTest.java index 5e9481b90..72b5fa08d 100644 --- a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/service/impl/ScenarioActionServiceImplTest.java +++ b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/service/impl/ScenarioActionServiceImplTest.java @@ -19,6 +19,7 @@ import org.citrusframework.TestAction; import org.citrusframework.TestCase; import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.simulator.common.TimeProvider; import org.citrusframework.simulator.model.ScenarioAction; import org.citrusframework.simulator.model.ScenarioExecution; import org.citrusframework.simulator.repository.ScenarioActionRepository; diff --git a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/service/impl/ScenarioExecutionServiceImplTest.java b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/service/impl/ScenarioExecutionServiceImplTest.java index 8970ca14e..6259adbf3 100644 --- a/simulator-spring-boot/src/test/java/org/citrusframework/simulator/service/impl/ScenarioExecutionServiceImplTest.java +++ b/simulator-spring-boot/src/test/java/org/citrusframework/simulator/service/impl/ScenarioExecutionServiceImplTest.java @@ -17,6 +17,7 @@ package org.citrusframework.simulator.service.impl; import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.simulator.common.TimeProvider; import org.citrusframework.simulator.model.ScenarioExecution; import org.citrusframework.simulator.model.ScenarioParameter; import org.citrusframework.simulator.model.TestResult; 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 4ee3068e2..ac81ff1a6 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 @@ -16,7 +16,9 @@ package org.citrusframework.simulator.web.rest; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.persistence.EntityManager; +import org.assertj.core.data.TemporalUnitLessThanOffset; import org.citrusframework.simulator.IntegrationTest; import org.citrusframework.simulator.model.Message; import org.citrusframework.simulator.model.ScenarioAction; @@ -25,7 +27,11 @@ import org.citrusframework.simulator.model.ScenarioParameter; import org.citrusframework.simulator.model.TestResult; import org.citrusframework.simulator.repository.ScenarioExecutionRepository; +import org.citrusframework.simulator.scenario.AbstractSimulatorScenario; +import org.citrusframework.simulator.scenario.Scenario; +import org.citrusframework.simulator.scenario.ScenarioRunner; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -34,11 +40,18 @@ import org.springframework.transaction.annotation.Transactional; import java.time.Instant; +import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import static java.time.LocalDateTime.now; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.citrusframework.simulator.model.TestResult.Status.FAILURE; import static org.citrusframework.simulator.model.TestResult.Status.SUCCESS; import static org.hamcrest.Matchers.hasItem; +import static org.springframework.http.HttpStatus.OK; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -520,4 +533,93 @@ void getNonExistingScenarioExecution() throws Exception { // Get the scenarioExecution mockMvc.perform(get(ENTITY_API_URL_ID, Long.MAX_VALUE)).andExpect(status().isNotFound()); } + + @Nested + class CorrectTimeOnScenarioExecution { + public static final TemporalUnitLessThanOffset LESS_THAN_5_SECONDS = new TemporalUnitLessThanOffset(5, SECONDS); + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private MockMvc mockMvc; + + @Test + void shouldInvokeScenario() throws Exception { + String mockEndpointResult = mockMvc + .perform(get("/services/rest/api/v1/ZmNrqCkoGQ")) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertThat(mockEndpointResult).contains("E5a084sOZw7"); + + String scenarioExecutionsResult = mockMvc + .perform(get("/api/scenario-executions")) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + List scenarioExecutions = objectMapper.readValue(scenarioExecutionsResult, ScenarioExecutions.class); + + assertThat(scenarioExecutions) + .hasSize(1) + .anySatisfy(execution -> { + assertThat(execution.startDate()).isCloseTo(now(), LESS_THAN_5_SECONDS); + assertThat(execution.endDate()).isCloseTo(now(), LESS_THAN_5_SECONDS); + assertThat(execution.scenarioActions()).allSatisfy(action -> { + assertThat(action.startDate()).isCloseTo(now(), LESS_THAN_5_SECONDS); + assertThat(action.endDate()).isCloseTo(now(), LESS_THAN_5_SECONDS); + }); + assertThat(execution.scenarioMessages()).anySatisfy(action -> { + assertThat(action.createdDate()).isCloseTo(now(), LESS_THAN_5_SECONDS); + }); + }); + } + + @Scenario("DEFAULT_SCENARIO") + public static class HelloScenario extends AbstractSimulatorScenario { + @Override + public void run(ScenarioRunner scenario) { + scenario.$(scenario.http() + .receive() + .get() + .path("/services/rest/api/v1/ZmNrqCkoGQ")); + + scenario.$(scenario.http() + .send() + .response(OK) + .message() + .body("E5a084sOZw7")); + } + } + + + public static class ScenarioExecutions extends ArrayList { + } + + public record ScenarioExecution( + LocalDateTime startDate, + LocalDateTime endDate, + List scenarioActions, + List scenarioMessages + ) { + + } + + public record ScenarioActions( + LocalDateTime startDate, + LocalDateTime endDate + ) { + + } + + public record ScenarioMessages( + LocalDateTime createdDate + ) { + + } + } }