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

org.sejda.tests.tasks.TaskTestContext Maven / Gradle / Ivy

There is a newer version: 5.1.6
Show newest version
/*
 * Created on 12 gen 2016
 * Copyright 2015 by Andrea Vacondio ([email protected]).
 * This file is part of Sejda.
 *
 * Sejda is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Sejda is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Sejda.  If not, see .
 */
package org.sejda.tests.tasks;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.junit.jupiter.api.Assertions;
import org.sejda.commons.util.IOUtils;
import org.sejda.core.Sejda;
import org.sejda.core.notification.context.GlobalNotificationContext;
import org.sejda.io.SeekableSource;
import org.sejda.io.SeekableSources;
import org.sejda.model.SejdaFileExtensions;
import org.sejda.model.notification.EventListener;
import org.sejda.model.notification.event.TaskExecutionFailedEvent;
import org.sejda.model.notification.event.TaskExecutionWarningEvent;
import org.sejda.model.output.DirectoryTaskOutput;
import org.sejda.model.output.FileOrDirectoryTaskOutput;
import org.sejda.model.output.FileTaskOutput;
import org.sejda.model.parameter.base.MultipleOutputTaskParameters;
import org.sejda.model.parameter.base.SingleOrMultipleOutputTaskParameters;
import org.sejda.model.parameter.base.SingleOutputTaskParameters;
import org.sejda.model.pdf.PdfVersion;
import org.sejda.sambox.input.PDFParser;
import org.sejda.sambox.pdmodel.PDDocument;
import org.sejda.sambox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.sejda.sambox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import org.sejda.sambox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
import org.sejda.tests.PixelCompareUtils;
import org.sejda.tests.TestListenerFactory;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static java.util.Objects.nonNull;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * Context to be used in tests
 *
 * @author Andrea Vacondio
 */
public class TaskTestContext implements Closeable {

    private ByteArrayOutputStream streamOutput;
    private File fileOutput;
    private PDDocument outputDocument;

    /**
     * Initialize the given params with a {@link FileTaskOutput}
     *
     * @param params
     * @return
     */
    public TaskTestContext pdfOutputTo(SingleOutputTaskParameters params) throws IOException {
        this.fileOutput = File.createTempFile("SejdaTest", ".pdf");
        this.fileOutput.deleteOnExit();
        params.setOutput(new FileTaskOutput(fileOutput));
        return this;
    }

    /**
     * Initialize the given params with a {@link FileTaskOutput} on a file with the given extension
     *
     * @param params
     * @param extension
     * @return
     * @throws IOException
     */
    public TaskTestContext fileOutputTo(SingleOutputTaskParameters params, String extension) throws IOException {
        this.fileOutput = File.createTempFile("SejdaTest", extension);
        this.fileOutput.deleteOnExit();
        params.setOutput(new FileTaskOutput(fileOutput));
        return this;
    }

    /**
     * Initialize the given params with a {@link DirectoryTaskOutput}
     *
     * @param params
     * @return
     * @throws IOException
     */
    public TaskTestContext directoryOutputTo(MultipleOutputTaskParameters params) throws IOException {
        this.fileOutput = Files.createTempDirectory("SejdaTest").toFile();
        this.fileOutput.deleteOnExit();
        params.setOutput(new DirectoryTaskOutput(fileOutput));
        return this;
    }

    /**
     * Initialize the given params with a directory as {@link FileOrDirectoryTaskOutput}
     *
     * @param params
     * @return
     */
    public TaskTestContext directoryOutputTo(SingleOrMultipleOutputTaskParameters params) throws IOException {
        this.fileOutput = Files.createTempDirectory("SejdaTest").toFile();
        this.fileOutput.deleteOnExit();
        params.setOutput(new FileOrDirectoryTaskOutput(fileOutput));
        return this;
    }

    /**
     * Initialize the given params with a pdf file as {@link FileOrDirectoryTaskOutput}
     *
     * @param params
     * @return
     */
    public TaskTestContext pdfOutputTo(SingleOrMultipleOutputTaskParameters params) throws IOException {
        this.fileOutput = Files.createTempFile("SejdaTest", ".pdf").toFile();
        this.fileOutput.deleteOnExit();
        params.setOutput(new FileOrDirectoryTaskOutput(fileOutput));
        return this;
    }

    /**
     * asserts the creator has been set to the info dictionary
     *
     * @return
     */
    public TaskTestContext assertCreator() {
        requirePDDocument();
        Assertions.assertEquals(Sejda.CREATOR, outputDocument.getDocumentInformation().getCreator());
        return this;
    }

    /**
     * asserts the PDF version of the output is the given one
     *
     * @return
     */
    public TaskTestContext assertVersion(PdfVersion version) {
        requirePDDocument();
        Assertions.assertEquals(version.getVersionString(), outputDocument.getVersion(), "Wrong output PDF version");
        return this;
    }

    /**
     * asserts the output document has that number of pages
     *
     * @return
     */
    public TaskTestContext assertPages(int expected) {
        requirePDDocument();
        Assertions.assertEquals(expected, outputDocument.getNumberOfPages(), "Wrong number of pages");
        return this;
    }

    /**
     * asserts the output document with the given filename exists and has that number of pages. This assert will work only for multiple output task.
     *
     * @return
     * @throws IOException
     * @see this{@link #assertPages(int)}
     */
    public TaskTestContext assertPages(String filename, int expected) throws IOException {
        assertOutputContainsFilenames(filename);
        try (PDDocument doc = PDFParser.parse(SeekableSources.seekableSourceFrom(new File(fileOutput, filename)))) {
            Assertions.assertEquals(expected, doc.getNumberOfPages());
        }
        return this;
    }

    /**
     * @param hasOutline
     */
    public TaskTestContext assertHasOutline(boolean hasOutline) {
        requirePDDocument();
        if (hasOutline) {
            assertNotNull(outputDocument.getDocumentCatalog().getDocumentOutline());
        } else {
            assertNull(outputDocument.getDocumentCatalog().getDocumentOutline());
        }
        return this;
    }

    /**
     * assert that the document has an acroform with some field in it
     *
     * @param hasForms
     */
    public TaskTestContext assertHasAcroforms(boolean hasForms) {
        requirePDDocument();
        if (hasForms) {
            assertNotNull(outputDocument.getDocumentCatalog().getAcroForm());
            assertTrue(outputDocument.getDocumentCatalog().getAcroForm().getFields().size() > 0);
        } else {
            assertNull(outputDocument.getDocumentCatalog().getAcroForm());
        }
        return this;
    }

    /**
     * assert the document outline contains an item with the given title
     *
     * @param title
     * @return
     */
    public TaskTestContext assertOutlineContains(String title) {
        requirePDDocument();
        PDDocumentOutline outline = outputDocument.getDocumentCatalog().getDocumentOutline();
        assertNotNull(outline);
        if (!findOutlineItem(title, outline)) {
            fail("Unable to find outline node with title: " + title);
        }
        return this;
    }

    /**
     * assert the document outline doesn't contain an item with the given title
     *
     * @param title
     * @return
     */
    public TaskTestContext assertOutlineDoesntContain(String title) {
        requirePDDocument();
        PDDocumentOutline outline = outputDocument.getDocumentCatalog().getDocumentOutline();
        if (nonNull(outline)) {
            if (findOutlineItem(title, outline)) {
                fail("Found outline node with title: " + title);
            }
        }
        return this;

    }

    private boolean findOutlineItem(String title, PDOutlineNode node) {
        boolean found = false;
        if (node.hasChildren()) {
            for (PDOutlineItem current : node.children()) {
                found = findOutlineItem(title, current);
                if (found) {
                    return true;
                }
            }
        }
        if (node instanceof PDOutlineItem && title.equals(((PDOutlineItem) node).getTitle())) {
            return true;
        }
        return false;
    }

    /**
     * asserts that a multiple output task has generated the given number of output files, hidden files don't count
     *
     * @return
     * @throws IOException
     */
    public TaskTestContext assertOutputSize(int size) throws IOException {
        if (size == 0) {
            return assertEmptyMultipleOutput();
        }
        requireMultipleOutputs();
        assertEquals(size, Files.list(fileOutput.toPath()).filter(p -> {
            if (IS_OS_WINDOWS) {
                try {
                    return !(Boolean) Files.getAttribute(p, "dos:hidden");
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return false;
            }
            if (p.getFileName().toString().startsWith(".")) {
                return false;
            }
            return true;
        }).count(), "An unexpected number of output files has been created");
        return this;
    }

    /**
     * asserts that a multiple output task has generated the given number of output files, including hidden files (in case the task is expected to have some hidden leftover)
     *
     * @param size
     * @return
     */
    public TaskTestContext assertOutputSizeIncludingHidden(int size) {
        if (size == 0) {
            return assertEmptyMultipleOutput();
        }
        requireMultipleOutputs();

        String[] files = fileOutput.list();
        assertEquals(size, files.length,
                "An unexpected number of output files has been created: " + StringUtils.join(files, ","));
        return this;
    }

    /**
     * asserts that a multiple output task has generated no output
     *
     * @return
     */
    public TaskTestContext assertEmptyMultipleOutput() {
        assertNotNull(fileOutput);
        assertTrue(fileOutput.isDirectory(), "Expected an output directory");
        assertEquals(0, fileOutput.listFiles((d, n) -> !n.endsWith(".tmp")).length,
                "Found output files while expecting none");
        return this;
    }

    /**
     * asserts that a multiple output task has generated the given file names
     *
     * @param filenames
     * @return
     */
    public TaskTestContext assertOutputContainsFilenames(String... filenames) {
        requireMultipleOutputs();
        Set outputFiles = Arrays.stream(fileOutput.listFiles()).map(File::getName).collect(Collectors.toSet());
        Arrays.stream(filenames).forEach(f -> assertTrue(outputFiles.contains(f),
                f + " missing but expected. Files were: " + StringUtils.join(outputFiles)));
        return this;
    }

    /**
     * Applies the given consumer to every generated output
     *
     * @param consumer
     * @return
     * @throws IOException
     */
    public TaskTestContext forEachPdfOutput(Consumer consumer) throws IOException {
        if (nonNull(outputDocument)) {
            requirePDDocument();
            consumer.accept(outputDocument);
        } else if (nonNull(fileOutput) && fileOutput.isDirectory()) {
            requireMultipleOutputs();
            for (File current : fileOutput.listFiles()) {
                try (PDDocument doc = PDFParser.parse(SeekableSources.seekableSourceFrom(current))) {
                    consumer.accept(doc);
                }
            }
        } else {
            fail("No output to apply to");
        }
        return this;
    }

    /**
     * Applies the given consumer to generated single output PDF document
     *
     * @param consumer
     * @return
     * @throws IOException
     */
    public TaskTestContext forPdfOutput(Consumer consumer) {
        requirePDDocument();
        consumer.accept(outputDocument);
        return this;
    }

    /**
     * Applies the given consumer to generated output PDF document with the given name
     *
     * @param consumer
     * @return
     * @throws IOException
     */
    public TaskTestContext forPdfOutput(String filename, Consumer consumer) throws IOException {
        requireMultipleOutputs();
        assertTrue(isNotEmpty(filename) && filename.toLowerCase().endsWith(SejdaFileExtensions.PDF_EXTENSION),
                "Not a PDF output");
        try (PDDocument doc = PDFParser.parse(SeekableSources.seekableSourceFrom(new File(fileOutput, filename)))) {
            consumer.accept(doc);
        }
        return this;
    }

    /**
     * Applies the given consumer to every generated output
     *
     * @param consumer
     * @return
     * @throws IOException
     */
    public TaskTestContext forEachRawOutput(Consumer consumer) throws IOException {
        requireMultipleOutputs();
        Files.list(fileOutput.toPath()).forEach(consumer);
        return this;
    }

    /**
     * Applies the given consumer to a single generated output
     *
     * @param consumer
     * @return
     * @throws IOException
     */
    public TaskTestContext forRawOutput(Consumer consumer) {
        assertNotNull(fileOutput);
        consumer.accept(fileOutput.toPath());
        return this;
    }

    public TaskTestContext forRawOutput(String filename, Consumer consumer) {
        requireMultipleOutputs();
        File file = new File(fileOutput, filename);
        assertTrue(file.exists());
        consumer.accept(file.toPath());
        return this;
    }

    private void requireMultipleOutputs() {
        assertNotNull(fileOutput);
        assertTrue(fileOutput.isDirectory(), "Expected an output directory");
        assertTrue(fileOutput.listFiles().length > 0, "No output has been created");
    }

    private void requirePDDocument() {
        Assertions.assertNotNull(outputDocument,
                "No output document, make sure to call TaskTestContext::assertTaskCompleted before any other assert method");
    }

    /**
     * Asserts that the task has completed and generated some output. If a single output task, then the output is paresed and a {@link PDDocument} is returned
     *
     * @return
     * @throws IOException
     */
    public PDDocument assertTaskCompleted() throws IOException {
        return this.assertTaskCompleted(null);
    }

    /**
     * Asserts that the task has completed and generated some output. If the task generated a single output, then the output is parsed and a {@link PDDocument} is returned
     *
     * @param password
     * @return
     * @throws IOException
     */
    public PDDocument assertTaskCompleted(String password) throws IOException {
        // First and foremost, check if the task failed.
        this.assertTaskDidNotFail();

        // look at outputs
        // TODO: ignore leftover tmp buffers while doing this
        if (nonNull(fileOutput)) {
            if (fileOutput.isDirectory()) {
                File[] files = fileOutput.listFiles();
                assertTrue(files.length > 0, "No output has been created");
                if (files.length == 1) {
                    initOutputFromSource(files[0], password);
                }
            } else {
                initOutputFromSource(fileOutput, password);
            }
        } else if (nonNull(streamOutput)) {
            org.sejda.commons.util.IOUtils.close(streamOutput);
            initOutputFromSource(SeekableSources.inMemorySeekableSourceFrom(streamOutput.toByteArray()), password);
        }
        return outputDocument;
    }

    Throwable taskFailureCause = null;
    String failedSource;
    List taskWarnings = new ArrayList<>();
    
    public List getTaskWarnings() {
        return Collections.unmodifiableList(taskWarnings);
    }

    private TestListenerFactory.TestListenerStart startListener = TestListenerFactory.newStartListener();

    private EventListener warningsListener = new EventListener<>() {
        @Override
        public void onEvent(TaskExecutionWarningEvent event) {
            taskWarnings.add(event.getWarning());
        }
    };

    private EventListener failureListener = new EventListener<>() {
        @Override
        public void onEvent(TaskExecutionFailedEvent event) {
            taskFailureCause = event.getFailingCause();
            failedSource = event.getNotifiableTaskMetadata().getCurrentSource();
        }
    };

    public void listenForTaskFailure() {
        taskFailureCause = null;
        GlobalNotificationContext.getContext().removeListener(failureListener);
        GlobalNotificationContext.getContext().addListener(failureListener);
    }

    public void listenForTaskStart() {
        GlobalNotificationContext.getContext().removeListener(startListener);
        GlobalNotificationContext.getContext().addListener(startListener);
    }

    public TaskTestContext assertTaskFailed(String message) {
        assertNotNull(taskFailureCause);
        assertThat(taskFailureCause.getMessage(), startsWith(message));
        return this;
    }

    public TaskTestContext assertTaskFailed(Class cause) {
        assertNotNull(taskFailureCause);
        assertThat(taskFailureCause, instanceOf(cause));
        return this;
    }

    public TaskTestContext assertTaskFailed() {
        assertNotNull(taskFailureCause);
        return this;
    }

    public TaskTestContext assertFailedSource(String source) {
        assertEquals(source, failedSource);
        return this;
    }

    public TaskTestContext assertTaskStarted() {
        Assertions.assertTrue(startListener.isStarted());
        return this;
    }

    public TaskTestContext assertTaskDidNotFail() {
        assertNull(taskFailureCause, "Task failed");
        return this;
    }

    @Deprecated
    public void expectTaskWillProduceWarnings() {
        listenForTaskWarnings();
    }

    public void listenForTaskWarnings() {
        taskWarnings = new ArrayList<>();
        GlobalNotificationContext.getContext().removeListener(warningsListener);
        GlobalNotificationContext.getContext().addListener(warningsListener);
    }

    public TaskTestContext assertTaskWarning(String message) {
        assertThat(taskWarnings, hasItem(containsString(message)));
        return this;
    }

    public TaskTestContext assertTaskWarnings(List messages) {
        assertEquals(taskWarnings, messages);
        return this;
    }

    public TaskTestContext assertNoTaskWarnings() {
        assertEquals(taskWarnings.size(), 0, "Expected no warnings but got: " + StringUtils.join(taskWarnings, ","));
        return this;
    }

    private void initOutputFromSource(File source, String password) throws IOException {
        if (source.getName().toLowerCase().endsWith(SejdaFileExtensions.PDF_EXTENSION)) {
            this.outputDocument = PDFParser.parse(SeekableSources.seekableSourceFrom(source), password);
            assertNotNull(outputDocument);
        }
    }

    private void initOutputFromSource(SeekableSource source, String password) throws IOException {
        this.outputDocument = PDFParser.parse(source, password);
        assertNotNull(outputDocument);
    }

    @Override
    public void close() throws IOException {
        IOUtils.closeQuietly(streamOutput);
        this.streamOutput = null;
        IOUtils.closeQuietly(outputDocument);
        this.outputDocument = null;
        if (nonNull(fileOutput)) {
            if (fileOutput.isDirectory()) {
                Files.walkFileTree(fileOutput.toPath(), new SimpleFileVisitor<>() {
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        Files.delete(file);
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                        Files.delete(dir);
                        return FileVisitResult.CONTINUE;
                    }

                });
            }
            Files.deleteIfExists(fileOutput.toPath());
        }
        this.fileOutput = null;
    }

    public File getFileOutput() {
        return fileOutput;
    }

    public void assertSimilar(String filename, String resourcePath) throws IOException {
        //debug(FilenameUtils.getName(resourcePath));
        this.forPdfOutput(filename, actual -> new PixelCompareUtils().assertSimilar(actual, resourcePath));
    }

    public void assertSimilar(String resourcePath) {
        //debug(FilenameUtils.getName(resourcePath));
        this.forPdfOutput(actual -> new PixelCompareUtils().assertSimilar(actual, resourcePath));
    }

    public void assertSimilar(String resourcePath, double similarityThreshold) {
        //debug(FilenameUtils.getName(resourcePath));
        this.forPdfOutput(actual -> new PixelCompareUtils(similarityThreshold).assertSimilar(actual, resourcePath));
    }

    public void assertSimilar(String filename, String resourcePath, double similarityThreshold) throws IOException {
        //debug(FilenameUtils.getName(resourcePath));
        this.forPdfOutput(filename, actual -> new PixelCompareUtils(similarityThreshold).assertSimilar(actual, resourcePath));
    }

    public void debug(String filename) {
        File dest = new File(SystemUtils.getJavaIoTmpDir(), filename);
        debugTo(dest.getAbsolutePath());
        System.out.println(dest);
    }

    public void debugTo(String destPath) {
        try {
            this.forEachRawOutput(path -> {
                try {
                    FileUtils.copyFile(path.toFile(), new File(destPath));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy