From b6f4e5f1374a26f672faf1c8b1d6486857732165 Mon Sep 17 00:00:00 2001 From: Redouane BENYOUSSEF Date: Wed, 29 Mar 2023 16:29:15 +0200 Subject: [PATCH] fix(server): save campaign - scenario link at scenario execution end --- .../CampaignExecutionEngineTest.java | 6 +++- .../execution/ScenarioExecutionEngine.java | 8 +++-- .../campaign/api/CampaignController.java | 3 +- .../campaign/domain/CampaignRepository.java | 4 ++- .../infra/CampaignExecutionRepository.java | 14 ++++++++ .../infra/DatabaseCampaignRepository.java | 5 +++ .../campaign/CampaignExecutionEngine.java | 28 +++++++++------ .../infra/FakeCampaignRepository.java | 5 +++ .../campaign/CampaignExecutionEngineTest.java | 36 ++++++++++++++----- 9 files changed, 85 insertions(+), 24 deletions(-) diff --git a/component/src/test/java/com/chutneytesting/component/execution/CampaignExecutionEngineTest.java b/component/src/test/java/com/chutneytesting/component/execution/CampaignExecutionEngineTest.java index 48ba25d43..29999000f 100644 --- a/component/src/test/java/com/chutneytesting/component/execution/CampaignExecutionEngineTest.java +++ b/component/src/test/java/com/chutneytesting/component/execution/CampaignExecutionEngineTest.java @@ -19,6 +19,7 @@ import com.chutneytesting.server.core.domain.dataset.DataSetHistoryRepository; import com.chutneytesting.server.core.domain.execution.ExecutionRequest; import com.chutneytesting.server.core.domain.execution.ScenarioExecutionEngine; +import com.chutneytesting.server.core.domain.execution.ScenarioExecutionEngineAsync; import com.chutneytesting.server.core.domain.execution.history.ExecutionHistory; import com.chutneytesting.server.core.domain.execution.history.ExecutionHistoryRepository; import com.chutneytesting.server.core.domain.execution.history.ImmutableExecutionHistory; @@ -29,6 +30,7 @@ import com.chutneytesting.server.core.domain.scenario.TestCaseMetadataImpl; import com.chutneytesting.server.core.domain.scenario.campaign.Campaign; import com.fasterxml.jackson.databind.ObjectMapper; +import io.reactivex.Observable; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -48,6 +50,7 @@ public class CampaignExecutionEngineTest { private final CampaignRepository campaignRepository = mock(CampaignRepository.class); private final ScenarioExecutionEngine scenarioExecutionEngine = mock(ScenarioExecutionEngine.class); + private final ScenarioExecutionEngineAsync scenarioExecutionEngineAsync = mock(ScenarioExecutionEngineAsync.class); private final ExecutionHistoryRepository executionHistoryRepository = mock(ExecutionHistoryRepository.class); private final TestCaseRepositoryAggregator testCaseRepository = mock(TestCaseRepositoryAggregator.class); private final DataSetHistoryRepository dataSetHistoryRepository = mock(DataSetHistoryRepository.class); @@ -78,7 +81,8 @@ public void should_override_scenario_dataset_with_campaign_dataset_before_execut when(campaignRepository.findById(campaign.id)).thenReturn(campaign); when(testCaseRepository.findExecutableById(composedTestCase.id())).thenReturn(of(composedTestCase)); - when(scenarioExecutionEngine.execute(any(ExecutionRequest.class), any())).thenReturn(mock(ScenarioExecutionReport.class)); + when(scenarioExecutionEngineAsync.followExecution(any(), any())).thenReturn(mock(Observable.class)); + when(scenarioExecutionEngineAsync.followExecution(any(), any()).blockingLast()).thenReturn(mock(ScenarioExecutionReport.class)); when(executionHistoryRepository.getExecution(any(), any())).thenReturn(executionWithId(42L)); // When diff --git a/server-core/src/main/java/com/chutneytesting/server/core/domain/execution/ScenarioExecutionEngine.java b/server-core/src/main/java/com/chutneytesting/server/core/domain/execution/ScenarioExecutionEngine.java index a5dd83b36..e401f450b 100644 --- a/server-core/src/main/java/com/chutneytesting/server/core/domain/execution/ScenarioExecutionEngine.java +++ b/server-core/src/main/java/com/chutneytesting/server/core/domain/execution/ScenarioExecutionEngine.java @@ -30,8 +30,12 @@ public ScenarioExecutionEngine(ServerTestEngine executionEngine, * @param executionRequest The request execution. * @return an execution Report. */ - public ScenarioExecutionReport execute(ExecutionRequest executionRequest, Optional> executionDataset) throws ScenarioNotFoundException, ScenarioNotParsableException { - return executionEngineAsync.followExecution(executionRequest.testCase.id(), executionEngineAsync.execute(executionRequest, executionDataset)).blockingLast(); + public Long execute(ExecutionRequest executionRequest, Optional> executionDataset) throws ScenarioNotFoundException, ScenarioNotParsableException { + return executionEngineAsync.execute(executionRequest, executionDataset); + } + + public ScenarioExecutionReport followExecution(String scenarioId, Long scenarioExecutionId) throws ScenarioNotFoundException, ScenarioNotParsableException { + return executionEngineAsync.followExecution(scenarioId, scenarioExecutionId).blockingLast(); } public ScenarioExecutionReport simpleSyncExecution(ExecutionRequest executionRequest) { diff --git a/server/src/main/java/com/chutneytesting/campaign/api/CampaignController.java b/server/src/main/java/com/chutneytesting/campaign/api/CampaignController.java index 6e45e1c5e..ba500e0fe 100644 --- a/server/src/main/java/com/chutneytesting/campaign/api/CampaignController.java +++ b/server/src/main/java/com/chutneytesting/campaign/api/CampaignController.java @@ -126,6 +126,7 @@ private void addCurrentExecution(List currentCampaignEx if (currentCampaignExecutionReports == null) { currentCampaignExecutionReports = new ArrayList<>(); } - currentCampaignExecutionReports.add(0, campaignExecutionReport); + if(!currentCampaignExecutionReports.contains(campaignExecutionReport)) + currentCampaignExecutionReports.add(0, campaignExecutionReport); } } diff --git a/server/src/main/java/com/chutneytesting/campaign/domain/CampaignRepository.java b/server/src/main/java/com/chutneytesting/campaign/domain/CampaignRepository.java index f81a52748..07344c2e8 100644 --- a/server/src/main/java/com/chutneytesting/campaign/domain/CampaignRepository.java +++ b/server/src/main/java/com/chutneytesting/campaign/domain/CampaignRepository.java @@ -7,7 +7,7 @@ /** * Right-side port for secondary actors of the business domain. See {@link CampaignExecutionEngine} - * + *

* Use to Store Campaign */ public interface CampaignRepository { @@ -16,6 +16,8 @@ public interface CampaignRepository { void saveReport(Long campaignId, CampaignExecutionReport report); + void updateCampaignExecutionScenario(Long campaignExecutionId, String scenarioId, Long scenarioExecutionId); + boolean removeById(Long id); Campaign findById(Long campaignId) throws CampaignNotFoundException; diff --git a/server/src/main/java/com/chutneytesting/campaign/infra/CampaignExecutionRepository.java b/server/src/main/java/com/chutneytesting/campaign/infra/CampaignExecutionRepository.java index 7fc71b77d..f8813e7b7 100644 --- a/server/src/main/java/com/chutneytesting/campaign/infra/CampaignExecutionRepository.java +++ b/server/src/main/java/com/chutneytesting/campaign/infra/CampaignExecutionRepository.java @@ -122,6 +122,20 @@ private int saveScenarioExecutionReport(Long campaignId, Long campaignExecutionI return uiNamedParameterJdbcTemplate.update(QUERY_SAVE_CAMPAIGN_EXECUTION_HISTORY, parameters); } + private static final String QUERY_UPDATE_CAMPAIGN_EXECUTION_SCENARIO = + "UPDATE CAMPAIGN_EXECUTION_HISTORY SET " + + "SCENARIO_EXECUTION_ID = :idScenarioExecution " + + "WHERE ID = :idCampaignExecution AND " + + "SCENARIO_ID = :idScenario"; + + int updateCampaignExecutionScenario(Long campaignExecutionId, String scenarioId, Long scenarioExecutionId) { + HashMap parameters = Maps.newHashMap(); + parameters.put("idCampaignExecution", campaignExecutionId); + parameters.put("idScenario", scenarioId); + parameters.put("idScenarioExecution", scenarioExecutionId); + return uiNamedParameterJdbcTemplate.update(QUERY_UPDATE_CAMPAIGN_EXECUTION_SCENARIO, parameters); + } + Long generateCampaignExecutionId() { return uiNamedParameterJdbcTemplate.queryForObject("SELECT nextval('CAMPAIGN_EXECUTION_SEQ')", emptyMap(), Long.class); } diff --git a/server/src/main/java/com/chutneytesting/campaign/infra/DatabaseCampaignRepository.java b/server/src/main/java/com/chutneytesting/campaign/infra/DatabaseCampaignRepository.java index 2b7592562..ef233734e 100644 --- a/server/src/main/java/com/chutneytesting/campaign/infra/DatabaseCampaignRepository.java +++ b/server/src/main/java/com/chutneytesting/campaign/infra/DatabaseCampaignRepository.java @@ -66,6 +66,11 @@ public void saveReport(Long campaignId, CampaignExecutionReport report) { campaignExecutionRepository.saveCampaignReport(campaignId, report); } + @Override + public void updateCampaignExecutionScenario(Long campaignExecutionId, String scenarioId, Long scenarioExecutionId) { + campaignExecutionRepository.updateCampaignExecutionScenario(campaignExecutionId, scenarioId, scenarioExecutionId); + } + /** * Remove a campaign from its id. * diff --git a/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngine.java b/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngine.java index 644717d7f..826862ed3 100644 --- a/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngine.java +++ b/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngine.java @@ -151,11 +151,6 @@ public CampaignExecutionReport executeScenarioInCampaign(List failedIds, currentCampaignExecutionsStopRequests.remove(executionId); currentCampaignExecutions.remove(campaign.id); - Try.exec(() -> { - campaignRepository.saveReport(campaign.id, campaignExecutionReport); - return null; - }).ifFailed(e -> LOGGER.error("Error saving report of campaign {} execution {}", campaign.id, campaignExecutionReport.executionId)); - Try.exec(() -> { metrics.onCampaignExecutionEnded(campaign, campaignExecutionReport); return null; @@ -172,6 +167,10 @@ private CampaignExecutionReport execute(Campaign campaign, CampaignExecutionRepo .collect(Collectors.toList()); campaignExecutionReport.initExecution(testCases, campaign.executionEnvironment(), campaignExecutionReport.userId); + Try.exec(() -> { + campaignRepository.saveReport(campaign.id, campaignExecutionReport); + return null; + }).ifFailed(e -> LOGGER.error("Error saving initial report of campaign {} execution {}", campaign.id, campaignExecutionReport.executionId)); try { if (campaign.parallelRun) { Collection> toExecute = Lists.newArrayList(); @@ -199,10 +198,10 @@ private Consumer executeScenarioInCampaign(Campaign campaign, Campaign // Init scenario execution in campaign report campaignExecutionReport.startScenarioExecution(testCase, campaign.executionEnvironment(), campaignExecutionReport.userId); // Execute scenario - ScenarioExecutionReportCampaign scenarioExecutionReport = executeScenario(campaign, testCase, campaignExecutionReport.userId); + ScenarioExecutionReportCampaign scenarioExecutionReport = executeScenario(campaign, testCase, campaignExecutionReport); // Retry one time if failed if (campaign.retryAuto && ServerReportStatus.FAILURE.equals(scenarioExecutionReport.status())) { - scenarioExecutionReport = executeScenario(campaign, testCase, campaignExecutionReport.userId); + scenarioExecutionReport = executeScenario(campaign, testCase, campaignExecutionReport); } // Add scenario report to campaign's one Optional.ofNullable(scenarioExecutionReport) @@ -216,13 +215,15 @@ private Consumer executeScenarioInCampaign(Campaign campaign, Campaign }; } - private ScenarioExecutionReportCampaign executeScenario(Campaign campaign, TestCase testCase, String userId) { + private ScenarioExecutionReportCampaign executeScenario(Campaign campaign, TestCase testCase, CampaignExecutionReport campaignExecutionReport) { Long executionId; String scenarioName; try { LOGGER.trace("Execute scenario {} for campaign {}", testCase.id(), campaign.id); - ExecutionRequest executionRequest = buildExecutionRequest(campaign, testCase, userId); - ScenarioExecutionReport scenarioExecutionReport = scenarioExecutionEngine.execute(executionRequest, Optional.empty()); // todo + ExecutionRequest executionRequest = buildExecutionRequest(campaign, testCase, campaignExecutionReport.userId); + Long scenarioExecutionId = scenarioExecutionEngine.execute(executionRequest, Optional.empty()); + updateCampaignExecutionScenario(campaignExecutionReport.executionId, testCase.id(), scenarioExecutionId); + ScenarioExecutionReport scenarioExecutionReport = scenarioExecutionEngine.followExecution(executionRequest.testCase.id(), scenarioExecutionId); executionId = scenarioExecutionReport.executionId; scenarioName = scenarioExecutionReport.scenarioName; } catch (FailedExecutionAttempt e) { @@ -277,4 +278,11 @@ private Campaign selectExecutionEnvironment(Campaign campaign, String environmen Optional.ofNullable(environment).ifPresent(campaign::executionEnvironment); return campaign; } + + private void updateCampaignExecutionScenario(Long campaignExecutionId, String scenarioId, Long scenarioExecutionId) { + Try.exec(() -> { + campaignRepository.updateCampaignExecutionScenario(campaignExecutionId, scenarioId, scenarioExecutionId); + return null; + }).ifFailed(e -> LOGGER.error("Error updating campaign scenario execution link of campaignExecution {} scenario {}", campaignExecutionId, scenarioId)); + } } diff --git a/server/src/test/java/com/chutneytesting/campaign/infra/FakeCampaignRepository.java b/server/src/test/java/com/chutneytesting/campaign/infra/FakeCampaignRepository.java index 266e0b95b..27597730e 100644 --- a/server/src/test/java/com/chutneytesting/campaign/infra/FakeCampaignRepository.java +++ b/server/src/test/java/com/chutneytesting/campaign/infra/FakeCampaignRepository.java @@ -57,6 +57,11 @@ public void saveReport(Long campaignId, CampaignExecutionReport report) { } + @Override + public void updateCampaignExecutionScenario(Long campaignExecutionId, String scenarioId, Long scenarioExecutionId) { + throw new NotImplementedException(); + } + @Override public boolean removeById(Long id) { return campaignsById.remove(id) != null; diff --git a/server/src/test/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngineTest.java b/server/src/test/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngineTest.java index 3d0d05507..4f1c73ae5 100644 --- a/server/src/test/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngineTest.java +++ b/server/src/test/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngineTest.java @@ -110,7 +110,7 @@ public void setUp() { public void should_update_jira_xray() { // Given Campaign campaign = createCampaign(firstTestCase, secondTestCase); - when(scenarioExecutionEngine.execute(any(ExecutionRequest.class), any())).thenReturn(mock(ScenarioExecutionReport.class)); + when(scenarioExecutionEngine.followExecution(any(), any())).thenReturn(mock(ScenarioExecutionReport.class)); // When CampaignExecutionReport cer = sut.executeScenarioInCampaign(emptyList(), campaign, "user"); @@ -127,7 +127,7 @@ public void should_execute_scenarios_in_sequence_and_store_reports_in_campaign_r // Given Campaign campaign = createCampaign(firstTestCase, secondTestCase); - when(scenarioExecutionEngine.execute(any(ExecutionRequest.class), any())).thenReturn(mock(ScenarioExecutionReport.class)); + when(scenarioExecutionEngine.followExecution(any(), anyLong())).thenReturn(mock(ScenarioExecutionReport.class)); // When CampaignExecutionReport campaignExecutionReport = sut.executeScenarioInCampaign(emptyList(), campaign, "user"); @@ -148,12 +148,28 @@ public void should_execute_scenarios_in_sequence_and_store_reports_in_campaign_r ); } + @Test + public void should_retrieve_campaign_scenario_execution_link_when_running() { + // Given + Campaign campaign = createCampaign(firstTestCase, secondTestCase); + + when(scenarioExecutionEngine.followExecution(any(), anyLong())).thenReturn(mock(ScenarioExecutionReport.class)); + + // When + CampaignExecutionReport campaignExecutionReport = sut.executeScenarioInCampaign(emptyList(), campaign, "user"); + + // Then + verify(campaignRepository).saveReport(campaign.id, campaignExecutionReport); + verify(campaignRepository).updateCampaignExecutionScenario(eq(campaignExecutionReport.executionId), eq(firstTestCase.id()), anyLong()); + verify(campaignRepository).updateCampaignExecutionScenario(eq(campaignExecutionReport.executionId), eq(secondTestCase.id()), anyLong()); + } + @Test public void should_execute_partially_scenarios_requested() { // Given Campaign campaign = createCampaign(createGwtTestCase("not executed test case"), secondTestCase); - when(scenarioExecutionEngine.execute(any(ExecutionRequest.class), any())).thenReturn(mock(ScenarioExecutionReport.class)); + when(scenarioExecutionEngine.followExecution(any(), any())).thenReturn(mock(ScenarioExecutionReport.class)); // When CampaignExecutionReport campaignExecutionReport = sut.executeScenarioInCampaign(singletonList("2"), campaign, "user"); @@ -174,10 +190,11 @@ public void should_stop_execution_of_scenarios_when_requested() { // Given Campaign campaign = createCampaign(firstTestCase, secondTestCase); - when(scenarioExecutionEngine.execute(any(ExecutionRequest.class), any())).then((Answer) invocationOnMock -> { + when(scenarioExecutionEngine.execute(any(), any())).then((Answer) invocationOnMock -> { awaitDuring(1, SECONDS); - return mock(ScenarioExecutionReport.class); + return anyLong(); }); + when(scenarioExecutionEngine.followExecution(any(), any())).thenReturn(mock(ScenarioExecutionReport.class)); Long firstScenarioExecutionId = 10L; when(executionHistoryRepository.getExecution(eq(firstTestCase.id()), or(eq(0L), eq(10L)))) @@ -210,7 +227,7 @@ public void should_retry_failed_scenario() { // Given Campaign campaign = createCampaign(firstTestCase, secondTestCase, true); - when(scenarioExecutionEngine.execute(any(ExecutionRequest.class), any())).thenReturn(mock(ScenarioExecutionReport.class)); + when(scenarioExecutionEngine.followExecution(any(), any())).thenReturn(mock(ScenarioExecutionReport.class)); when(executionHistoryRepository.getExecution(eq(firstTestCase.id()), or(eq(0L), eq(10L)))).thenReturn(failedExecutionWithId(10L)); when(executionHistoryRepository.getExecution(eq(secondTestCase.id()), or(eq(0L), eq(20L)))).thenReturn(failedExecutionWithId(20L)); @@ -226,10 +243,11 @@ public void should_execute_scenario_in_parallel() { // Given Campaign campaign = createCampaign(firstTestCase, secondTestCase, true, false); - when(scenarioExecutionEngine.execute(any(ExecutionRequest.class), any())).then((Answer) invocationOnMock -> { + when(scenarioExecutionEngine.execute(any(), any())).then((Answer) invocationOnMock -> { awaitDuring(1, SECONDS); - return mock(ScenarioExecutionReport.class); + return anyLong(); }); + when(scenarioExecutionEngine.followExecution(any(), any())).thenReturn(mock(ScenarioExecutionReport.class)); when(executionHistoryRepository.getExecution(eq(firstTestCase.id()), or(eq(0L), eq(10L)))).thenReturn(failedExecutionWithId(10L)); when(executionHistoryRepository.getExecution(eq(secondTestCase.id()), or(eq(0L), eq(20L)))).thenReturn(failedExecutionWithId(20L)); @@ -362,7 +380,7 @@ public void should_override_scenario_dataset_with_campaign_dataset_before_execut when(campaignRepository.findById(campaign.id)).thenReturn(campaign); when(testCaseRepository.findExecutableById(gwtTestCase.id())).thenReturn(of(gwtTestCase)); - when(scenarioExecutionEngine.execute(any(ExecutionRequest.class), any())).thenReturn(mock(ScenarioExecutionReport.class)); + when(scenarioExecutionEngine.followExecution(any(), any())).thenReturn(mock(ScenarioExecutionReport.class)); when(executionHistoryRepository.getExecution(any(), any())).thenReturn(executionWithId(42L)); // When