All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.chutneytesting.execution.infra.storage.DatabaseExecutionHistoryRepository Maven / Gradle / Ivy

The newest version!
/*
 * SPDX-FileCopyrightText: 2017-2024 Enedis
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 */

package com.chutneytesting.execution.infra.storage;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;

import com.chutneytesting.campaign.infra.CampaignExecutionJpaRepository;
import com.chutneytesting.campaign.infra.CampaignJpaRepository;
import com.chutneytesting.campaign.infra.jpa.CampaignExecutionEntity;
import com.chutneytesting.execution.infra.storage.jpa.ScenarioExecutionEntity;
import com.chutneytesting.execution.infra.storage.jpa.ScenarioExecutionReportEntity;
import com.chutneytesting.index.infra.ScenarioExecutionReportIndexRepository;
import com.chutneytesting.server.core.domain.dataset.DataSet;
import com.chutneytesting.server.core.domain.execution.history.ExecutionHistory.DetachedExecution;
import com.chutneytesting.server.core.domain.execution.history.ExecutionHistory.Execution;
import com.chutneytesting.server.core.domain.execution.history.ExecutionHistory.ExecutionSummary;
import com.chutneytesting.server.core.domain.execution.history.ExecutionHistoryRepository;
import com.chutneytesting.server.core.domain.execution.history.ImmutableExecutionHistory;
import com.chutneytesting.server.core.domain.execution.report.ReportNotFoundException;
import com.chutneytesting.server.core.domain.execution.report.ScenarioExecutionReport;
import com.chutneytesting.server.core.domain.execution.report.ServerReportStatus;
import com.chutneytesting.server.core.domain.execution.report.StepExecutionReportCore;
import com.chutneytesting.server.core.domain.scenario.TestCaseRepository;
import com.chutneytesting.server.core.domain.scenario.campaign.CampaignExecution;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@Transactional(readOnly = true)
class DatabaseExecutionHistoryRepository implements ExecutionHistoryRepository {

    private final DatabaseExecutionJpaRepository scenarioExecutionsJpaRepository;
    private final ScenarioExecutionReportJpaRepository scenarioExecutionReportJpaRepository;
    private final CampaignJpaRepository campaignJpaRepository;
    private final CampaignExecutionJpaRepository campaignExecutionJpaRepository;
    private final TestCaseRepository testCaseRepository;
    private final ScenarioExecutionReportIndexRepository scenarioExecutionReportIndexRepository;
    private final ObjectMapper objectMapper;
    private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseExecutionHistoryRepository.class);


    DatabaseExecutionHistoryRepository(
        DatabaseExecutionJpaRepository scenarioExecutionsJpaRepository,
        ScenarioExecutionReportJpaRepository scenarioExecutionReportJpaRepository,
        CampaignJpaRepository campaignJpaRepository, TestCaseRepository testCaseRepository,
        CampaignExecutionJpaRepository campaignExecutionJpaRepository,
        ScenarioExecutionReportIndexRepository scenarioExecutionReportIndexRepository,
        @Qualifier("reportObjectMapper") ObjectMapper objectMapper) {
        this.scenarioExecutionsJpaRepository = scenarioExecutionsJpaRepository;
        this.scenarioExecutionReportJpaRepository = scenarioExecutionReportJpaRepository;
        this.campaignJpaRepository = campaignJpaRepository;
        this.testCaseRepository = testCaseRepository;
        this.campaignExecutionJpaRepository = campaignExecutionJpaRepository;
        this.scenarioExecutionReportIndexRepository = scenarioExecutionReportIndexRepository;
        this.objectMapper = objectMapper;
    }

    @Override
    public Map getLastExecutions(List scenariosIds) {
        List validScenariosIds = scenariosIds.stream().filter(id -> !invalidScenarioId(id)).toList();
        List executionsIds = scenarioExecutionsJpaRepository.findLastByStatusAndScenariosIds(validScenariosIds, ServerReportStatus.RUNNING)
            .stream().map(t -> t.get(0, Long.class)).toList();
        Iterable lastExecutions = scenarioExecutionsJpaRepository.findAllById(executionsIds);
        return StreamSupport.stream(lastExecutions.spliterator(), true)
            .collect(Collectors.toMap(ScenarioExecutionEntity::scenarioId, ScenarioExecutionEntity::toDomain));
    }

    @Override
    public List getExecutions(String scenarioId) {
        if (invalidScenarioId(scenarioId)) {
            return emptyList();
        }
        List scenarioExecutions = scenarioExecutionsJpaRepository.findByScenarioIdOrderByIdDesc(scenarioId);
        return scenarioExecutions.stream()
            .map(this::scenarioExecutionToExecutionSummary)
            .toList();
    }

    @Override
    public List getExecutions() {
        return scenarioExecutionsJpaRepository.findAll().stream()
            .map(this::scenarioExecutionToExecutionSummary)
            .toList();
    }

    @Override
    public ExecutionSummary getExecutionSummary(Long executionId) {
        return scenarioExecutionsJpaRepository.findById(executionId)
            .map(this::scenarioExecutionToExecutionSummary)
            .orElseThrow(
                () -> new ReportNotFoundException(executionId)
            );
    }

    private ExecutionSummary scenarioExecutionToExecutionSummary(ScenarioExecutionEntity scenarioExecution) {
        CampaignExecution campaignExecution = ofNullable(scenarioExecution.campaignExecution())
            .map(ce -> ce.toDomain(campaignJpaRepository.findById(ce.campaignId()).get().title()))
            .orElse(null);
        ScenarioExecutionReportEntity scenarioExecutionReportEntity = scenarioExecutionReportJpaRepository.findByScenarioExecutionId(scenarioExecution.id());
        DataSet dataset = ofNullable(scenarioExecutionReportEntity).flatMap(scenarioExecutionReport -> scenarioExecutionReport.toDomain().dataset())
            .orElse(null);
        return scenarioExecution.toDomain(campaignExecution, dataset);
    }

    @Override
    @Transactional
    public Execution store(String scenarioId, DetachedExecution detachedExecution) throws IllegalStateException {
        if (invalidScenarioId(scenarioId)) {
            throw new IllegalStateException("Scenario id is null or empty");
        }
        ScenarioExecutionEntity scenarioExecution = ScenarioExecutionEntity.fromDomain(scenarioId, detachedExecution);
        if (detachedExecution.campaignReport().isPresent()) {
            Optional campaignExecution = campaignExecutionJpaRepository.findById(detachedExecution.campaignReport().get().executionId);
            scenarioExecution.forCampaignExecution(campaignExecution.get());
        }
        scenarioExecution = scenarioExecutionsJpaRepository.save(scenarioExecution);
        ScenarioExecutionReportEntity reportEntity = new ScenarioExecutionReportEntity(scenarioExecution, detachedExecution.report());
        scenarioExecutionReportJpaRepository.save(reportEntity);
        Execution execution = detachedExecution.attach(scenarioExecution.id(), scenarioId);
        return ImmutableExecutionHistory.Execution.builder().from(execution).build();
    }

    @Override
    // TODO remove scenarioId params
    public Execution getExecution(String scenarioId, Long reportId) throws ReportNotFoundException {
        if (invalidScenarioId(scenarioId) || testCaseRepository.findById(scenarioId).isEmpty()) {
            throw new ReportNotFoundException(scenarioId, reportId);
        }
        return scenarioExecutionReportJpaRepository.findById(reportId).map(ScenarioExecutionReportEntity::toDomain)
            .orElseThrow(
                () -> new ReportNotFoundException(scenarioId, reportId)
            );
    }

    @Override
    public List getExecutionReportMatchKeyword(String keyword) {
        List matchedReportsIds = scenarioExecutionReportIndexRepository.idsByKeywordInReport(keyword);
        return scenarioExecutionsJpaRepository
            .getExecutionReportByIds(matchedReportsIds)
            .stream()
            .map(this::scenarioExecutionToExecutionSummary)
            .toList();
    }

    @Override
    @Transactional
    public void update(String scenarioId, Execution updatedExecution) throws ReportNotFoundException {
        if (!scenarioExecutionsJpaRepository.existsById(updatedExecution.executionId())) {
            throw new ReportNotFoundException(scenarioId, updatedExecution.executionId());
        }
        update(updatedExecution);
    }

    private void update(Execution updatedExecution) throws ReportNotFoundException {
        ScenarioExecutionEntity execution = scenarioExecutionsJpaRepository.findById(updatedExecution.executionId()).orElseThrow(
            () -> new ReportNotFoundException(updatedExecution.executionId())
        );

        execution.updateFromExecution(updatedExecution);
        scenarioExecutionsJpaRepository.save(execution);
        updateReport(updatedExecution);
    }

    private void updateReport(Execution execution) throws ReportNotFoundException {
        ScenarioExecutionReportEntity scenarioExecutionReport = scenarioExecutionReportJpaRepository.findById(execution.executionId()).orElseThrow(
            () -> new ReportNotFoundException(execution.executionId())
        );
        scenarioExecutionReport.updateReport(execution);
        scenarioExecutionReportJpaRepository.save(scenarioExecutionReport);
    }

    @Override
    @Transactional
    public int setAllRunningExecutionsToKO() {
        List runningExecutions = getExecutionsWithStatus(ServerReportStatus.RUNNING);
        updateExecutionsToKO(runningExecutions);

        List pausedExecutions = getExecutionsWithStatus(ServerReportStatus.PAUSED);
        updateExecutionsToKO(pausedExecutions);

        return runningExecutions.size() + pausedExecutions.size();
    }

    @Override
    public List getExecutionsWithStatus(ServerReportStatus status) {
        return scenarioExecutionsJpaRepository.findByStatus(status).stream().map(ScenarioExecutionEntity::toDomain).toList();
    }

    @Override
    @Transactional
    public void deleteExecutions(Set executionsIds) {
        Set campaignExecutionsIds = getCampaignExecutionsWithOnlyOneScenarioExecution(executionsIds);

        campaignExecutionJpaRepository.deleteAllByIdInBatch(campaignExecutionsIds);
        scenarioExecutionReportJpaRepository.deleteAllById(executionsIds);
        scenarioExecutionsJpaRepository.deleteAllByIdInBatch(executionsIds);
    }

    private Set getCampaignExecutionsWithOnlyOneScenarioExecution(Set executionsIds) {
        return executionsIds.stream()
            .map(this::getExecutionSummary)
            .map(ExecutionSummary::campaignReport)
            .flatMap(Optional::stream)
            .filter(campaignExecution -> campaignExecution.scenarioExecutionReports().size() == 1)
            .map(campaignExecution -> campaignExecution.executionId)
            .collect(Collectors.toSet());
    }

    private void updateExecutionsToKO(List executions) {
        executions.stream()
            .map(this::buildKnockoutExecutionFrom)
            .forEach(this::update);
    }

    private ImmutableExecutionHistory.Execution buildKnockoutExecutionFrom(ExecutionSummary executionSummary) {
        String reportStoppedRunningOrPausedStatus = stopRunningOrPausedReport(executionSummary);
        return ImmutableExecutionHistory.Execution.builder()
            .executionId(executionSummary.executionId())
            .status(ServerReportStatus.FAILURE)
            .time(executionSummary.time())
            .duration(executionSummary.duration())
            .info(executionSummary.info())
            .error("Execution was interrupted !")
            .report(reportStoppedRunningOrPausedStatus)
            .testCaseTitle(executionSummary.testCaseTitle())
            .environment(executionSummary.environment())
            .user(executionSummary.user())
            .scenarioId(executionSummary.scenarioId())
            .tags(executionSummary.tags())
            .build();
    }

    private String stopRunningOrPausedReport(ExecutionSummary executionSummary) {
        return scenarioExecutionReportJpaRepository.findById(executionSummary.executionId()).map(ScenarioExecutionReportEntity::toDomain).map(execution -> {
            try {
                ScenarioExecutionReport newScenarioExecutionReport = updateStatusInScenarioExecutionReportWithStoppedStatusIfRunningOrPaused(execution);
                return objectMapper.writeValueAsString(newScenarioExecutionReport);
            } catch (JsonProcessingException exception) {
                LOGGER.error("Unexpected error while deserializing report for execution id " + executionSummary.executionId(), exception);
                return "";
            }
        }).orElseGet(() -> {
            LOGGER.warn("Report not found for execution id {}", executionSummary.executionId());
            return "";
        });
    }

    private ScenarioExecutionReport updateStatusInScenarioExecutionReportWithStoppedStatusIfRunningOrPaused(Execution execution) throws JsonProcessingException {
        ScenarioExecutionReport scenarioExecutionReport = objectMapper.readValue(execution.report(), ScenarioExecutionReport.class);
        StepExecutionReportCore report = updateStepWithStoppedStatusIfRunningOrPaused(scenarioExecutionReport.report);
        return updateScenarioExecutionReport(scenarioExecutionReport, report);
    }

    private ScenarioExecutionReport updateScenarioExecutionReport(ScenarioExecutionReport scenarioExecutionReport, StepExecutionReportCore report) {
        return new ScenarioExecutionReport(
            scenarioExecutionReport.executionId,
            scenarioExecutionReport.scenarioName,
            scenarioExecutionReport.environment,
            scenarioExecutionReport.user,
            scenarioExecutionReport.tags,
            scenarioExecutionReport.datasetId,
            scenarioExecutionReport.constants,
            scenarioExecutionReport.datatable,
            report);
    }

    private List updateStepListWithStoppedStatusIfRunningOrPaused(List steps) {
        return steps.stream().map(this::updateStepWithStoppedStatusIfRunningOrPaused).collect(Collectors.toList());
    }

    private boolean isExecutionRunningOrPaused(ServerReportStatus status) {
        return status.equals(ServerReportStatus.RUNNING) || status.equals(ServerReportStatus.PAUSED);
    }

    private StepExecutionReportCore updateStepWithStoppedStatusIfRunningOrPaused(StepExecutionReportCore step) {
        ServerReportStatus status = isExecutionRunningOrPaused(step.status) ? ServerReportStatus.STOPPED : step.status;
        List steps = updateStepListWithStoppedStatusIfRunningOrPaused(step.steps);
        return new StepExecutionReportCore(
            step.name,
            step.duration,
            step.startDate,
            status,
            step.information,
            step.errors,
            steps,
            step.type,
            step.targetName,
            step.targetUrl,
            step.strategy,
            step.evaluatedInputs,
            step.stepOutputs
        );
    }

    private boolean invalidScenarioId(String scenarioId) {
        return isNullOrEmpty(scenarioId);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy