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

com.consol.citrus.remote.CitrusRemoteApplication Maven / Gradle / Ivy

/*
 * Copyright 2006-2018 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 com.consol.citrus.remote;

import java.io.File;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.consol.citrus.Citrus;
import com.consol.citrus.CitrusInstanceManager;
import com.consol.citrus.CitrusInstanceStrategy;
import com.consol.citrus.TestClass;
import com.consol.citrus.exceptions.CitrusRuntimeException;
import com.consol.citrus.main.CitrusAppConfiguration;
import com.consol.citrus.main.TestRunConfiguration;
import com.consol.citrus.remote.controller.RunController;
import com.consol.citrus.remote.job.RunJob;
import com.consol.citrus.remote.model.RemoteResult;
import com.consol.citrus.remote.reporter.RemoteTestResultReporter;
import com.consol.citrus.remote.transformer.JsonRequestTransformer;
import com.consol.citrus.remote.transformer.JsonResponseTransformer;
import com.consol.citrus.report.JUnitReporter;
import com.consol.citrus.report.LoggingReporter;
import com.consol.citrus.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import spark.Filter;
import spark.servlet.SparkApplication;

import static spark.Spark.before;
import static spark.Spark.exception;
import static spark.Spark.get;
import static spark.Spark.halt;
import static spark.Spark.path;
import static spark.Spark.post;
import static spark.Spark.put;

/**
 * Remote application creates routes for this web application.
 *
 * @author Christoph Deppisch
 * @since 2.7.4
 */
public class CitrusRemoteApplication implements SparkApplication {

    /** Logger */
    private static final Logger LOG = LoggerFactory.getLogger(CitrusRemoteApplication.class);

    /** Global url encoding */
    private static final String ENCODING = "UTF-8";
    /** Content types */
    private static final String APPLICATION_JSON = "application/json";
    private static final String APPLICATION_XML = "application/xml";

    /** Application configuration */
    private final CitrusRemoteConfiguration configuration;

    /** Single thread job scheduler */
    private final ExecutorService jobs = Executors.newSingleThreadExecutor();
    private Future> remoteResultFuture;

    /** Latest test reports */
    private final RemoteTestResultReporter remoteTestResultReporter = new RemoteTestResultReporter();

    private final JsonRequestTransformer requestTransformer = new JsonRequestTransformer();
    private final JsonResponseTransformer responseTransformer = new JsonResponseTransformer();

    /**
     * Default constructor using default configuration.
     */
    public CitrusRemoteApplication() {
        this(new CitrusRemoteConfiguration());
    }

    /**
     * Constructor with given application configuration.
     * @param configuration
     */
    public CitrusRemoteApplication(CitrusRemoteConfiguration configuration) {
        this.configuration = configuration;
    }

    @Override
    public void init() {
        CitrusInstanceManager.mode(CitrusInstanceStrategy.SINGLETON);
        CitrusInstanceManager.addInstanceProcessor(citrus -> {
            citrus.addTestReporter(remoteTestResultReporter);
        });

        before((Filter) (request, response) -> LOG.info(request.requestMethod() + " " + request.url() + Optional.ofNullable(request.queryString()).map(query -> "?" + query).orElse("")));

        get("/health", (req, res) -> {
            res.type(APPLICATION_JSON);
            return "{ \"status\": \"UP\" }";
        });

        path("/results", () -> {
            get("", APPLICATION_JSON, (req, res) -> {
                res.type(APPLICATION_JSON);

                long timeout = Optional.ofNullable(req.queryParams("timeout"))
                                        .map(Long::valueOf)
                                        .orElse(10000L);

                if (remoteResultFuture != null) {
                    try {
                        return remoteResultFuture.get(timeout, TimeUnit.MILLISECONDS);
                    } catch (TimeoutException e) {
                        res.status(206); // partial content
                    }
                }

                List results = new ArrayList<>();
                remoteTestResultReporter.getLatestResults().doWithResults(result -> results.add(RemoteResult.fromTestResult(result)));
                return results;
            }, responseTransformer);

            get("", (req, res) -> remoteTestResultReporter.getTestReport());

            get("/files", (req, res) -> {
                res.type(APPLICATION_JSON);
                File junitReportsFolder = new File(getJUnitReportsFolder());

                if (junitReportsFolder.exists()) {
                    return Stream.of(Optional.ofNullable(junitReportsFolder.list()).orElse(new String[] {})).collect(Collectors.toList());
                }

                return Collections.emptyList();
            }, responseTransformer);

            get("/file/:name", (req, res) -> {
                res.type(APPLICATION_XML);
                File junitReportsFolder = new File(getJUnitReportsFolder());
                File testResultFile = new File(junitReportsFolder, req.params(":name"));

                if (junitReportsFolder.exists() && testResultFile.exists()) {
                    return FileUtils.readToString(testResultFile);
                }

                throw halt(404, "Failed to find test result file: " + req.params(":name"));
            });

            get("/suite", (req, res) -> {
                res.type(APPLICATION_XML);
                JUnitReporter jUnitReporter = new JUnitReporter();
                File citrusReportsFolder = new File(jUnitReporter.getReportDirectory());
                File suiteResultFile = new File(citrusReportsFolder, String.format(jUnitReporter.getReportFileNamePattern(), jUnitReporter.getSuiteName()));

                if (citrusReportsFolder.exists() && suiteResultFile.exists()) {
                    return FileUtils.readToString(suiteResultFile);
                }

                throw halt(404, "Failed to find suite result file: " + suiteResultFile.getPath());
            });
        });

        path("/run", () -> {
            get("", (req, res) -> {
                TestRunConfiguration runConfiguration = new TestRunConfiguration();

                if (req.queryParams().contains("engine")) {
                    runConfiguration.setEngine(URLDecoder.decode(req.queryParams("engine"), ENCODING));
                } else {
                    runConfiguration.setEngine(configuration.getEngine());
                }

                if (req.queryParams().contains("includes")) {
                    runConfiguration.setIncludes(StringUtils.commaDelimitedListToStringArray(URLDecoder.decode(req.queryParams("includes"), ENCODING)));
                }

                if (req.queryParams().contains("package")) {
                    runConfiguration.setPackages(Collections.singletonList(URLDecoder.decode(req.queryParams("package"), ENCODING)));
                }

                if (req.queryParams().contains("class")) {
                    runConfiguration.setTestClasses(Collections.singletonList(TestClass.fromString(URLDecoder.decode(req.queryParams("class"), ENCODING))));
                }

                res.type(APPLICATION_JSON);

                return runTests(runConfiguration);
            }, responseTransformer);

            put("", (req, res) -> {
                remoteResultFuture = jobs.submit(new RunJob(requestTransformer.read(req.body(), TestRunConfiguration.class)) {
                    @Override
                    public List run(TestRunConfiguration runConfiguration) {
                        return runTests(runConfiguration);
                    }
                });

                return "";
            });

            post("", (req, res) -> {
                TestRunConfiguration runConfiguration = requestTransformer.read(req.body(), TestRunConfiguration.class);
                return runTests(runConfiguration);
            }, responseTransformer);
        });

        path("/configuration", () -> {
            get("", (req, res) -> {
                res.type(APPLICATION_JSON);
                return configuration;
            }, responseTransformer);

            put("", (req, res) -> {
                configuration.apply(requestTransformer.read(req.body(), CitrusAppConfiguration.class));
                return "";
            });
        });

        exception(CitrusRuntimeException.class, (exception, request, response) -> {
            response.status(500);
            response.body(exception.getMessage());
        });
    }

    /**
     * Construct run controller and execute with given configuration.
     * @param runConfiguration
     * @return remote results
     */
    private List runTests(TestRunConfiguration runConfiguration) {
        RunController runController = new RunController(configuration);

        runController.setEngine(runConfiguration.getEngine());
        runController.setIncludes(runConfiguration.getIncludes());

        if (!CollectionUtils.isEmpty(runConfiguration.getDefaultProperties())) {
            runController.addDefaultProperties(runConfiguration.getDefaultProperties());
        }

        if (CollectionUtils.isEmpty(runConfiguration.getPackages()) && CollectionUtils.isEmpty(runConfiguration.getTestClasses())) {
            runController.runAll();
        }

        if (!CollectionUtils.isEmpty(runConfiguration.getPackages())) {
            runController.runPackages(runConfiguration.getPackages());
        }

        if (!CollectionUtils.isEmpty(runConfiguration.getTestClasses())) {
            runController.runClasses(runConfiguration.getTestClasses());
        }

        List results = new ArrayList<>();
        remoteTestResultReporter.getLatestResults().doWithResults(result -> results.add(RemoteResult.fromTestResult(result)));
        return results;
    }

    /**
     * Find reports folder based in unit testing framework present on classpath.
     * @return
     */
    private String getJUnitReportsFolder() {
        if (ClassUtils.isPresent("org.testng.annotations.Test", getClass().getClassLoader())) {
            return "test-output" + File.separator + "junitreports";
        } else if (ClassUtils.isPresent("org.junit.Test", getClass().getClassLoader())) {
            JUnitReporter jUnitReporter = new JUnitReporter();
            return jUnitReporter.getReportDirectory() + File.separator + jUnitReporter.getOutputDirectory();
        } else {
            return new LoggingReporter().getReportDirectory();
        }
    }

    @Override
    public void destroy() {
        Optional citrus = CitrusInstanceManager.get();
        if (citrus.isPresent()) {
            LOG.info("Closing Citrus and its application context");
            citrus.get().close();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy