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

org.sejda.tests.TestUtils Maven / Gradle / Ivy

There is a newer version: 5.1.6
Show newest version
/*
 * Copyright 2022 Sober Lemur S.a.s. di Vacondio Andrea and Sejda BV
 * 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;

import jakarta.validation.Configuration;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.apache.commons.io.FilenameUtils;
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;
import org.sejda.commons.util.IOUtils;
import org.sejda.commons.util.StringUtils;
import org.sejda.model.encryption.CipherBasedEncryptionAtRest;
import org.sejda.model.encryption.EncryptionAtRestPolicy;
import org.sejda.model.exception.TaskIOException;
import org.sejda.model.input.FileSource;
import org.sejda.model.input.PdfFileSource;
import org.sejda.model.input.PdfStreamSource;
import org.sejda.model.input.StreamSource;
import org.sejda.model.input.TaskSource;
import org.sejda.model.parameter.base.TaskParameters;
import org.sejda.sambox.pdmodel.PDDocument;
import org.sejda.sambox.pdmodel.PDPage;
import org.sejda.sambox.pdmodel.common.PDPageLabelRange;
import org.sejda.sambox.pdmodel.common.PDPageLabels;
import org.sejda.sambox.pdmodel.common.PDRectangle;
import org.sejda.sambox.pdmodel.interactive.annotation.PDAnnotationLink;
import org.sejda.sambox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
import org.sejda.sambox.text.PDFTextStripper;
import org.sejda.sambox.text.PDFTextStripperByArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.awt.Rectangle;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;

import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static org.sejda.commons.util.RequireUtils.requireNotBlank;
import static org.sejda.commons.util.StringUtils.normalizeLineEndings;

/**
 * Test utilities
 *
 * @author Andrea Vacondio
 */
public final class TestUtils {

    private static final Logger LOG = LoggerFactory.getLogger(TestUtils.class);
    private static final NotInstanceOf NOT_INSTANCE_OF = new NotInstanceOf();

    private TestUtils() {
        // util
    }

    /**
     * Sets the given property to the given instance at the given value.
     *
     * @param instance
     * @param propertyName
     * @param propertyValue
     */
    public static void setProperty(Object instance, String propertyName, Object propertyValue) {
        Field field;
        try {
            field = instance.getClass().getDeclaredField(propertyName);
            field.setAccessible(true);
            field.set(instance, propertyValue);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new IllegalStateException(String.format("Unable to set field %s", propertyName), e);
        }
    }

    public static void assertInvalidParameters(TaskParameters parameters) {
        Validator VALIDATOR = new ValidatorHolder().getValidator();
        Set> violations = VALIDATOR.validate(parameters);
        for (ConstraintViolation violation : violations) {
            LOG.debug("{}: {}", violation.getPropertyPath(), violation.getMessage());
        }
        assertFalse(violations.isEmpty());
    }

    public static void assertValidParameters(TaskParameters parameters) {
        Validator VALIDATOR = new ValidatorHolder().getValidator();
        Set> violations = VALIDATOR.validate(parameters);
        assertFalse(violations.isEmpty());
    }

    private static class ValidatorHolder {

        private Validator validator;

        private ValidatorHolder() {
            Configuration validationConfig = Validation.byDefaultProvider().configure();
            validationConfig.ignoreXmlConfiguration();
            validationConfig.messageInterpolator(new ParameterMessageInterpolator());
            ValidatorFactory factory = validationConfig.buildValidatorFactory();
            validator = factory.getValidator();
        }

        public Validator getValidator() {
            return validator;
        }

    }

    /**
     * Class used to test instance of returning false.
     *
     * @author Andrea Vacondio
     */
    private static final class NotInstanceOf {
        // nothing
    }

    /**
     * Test that the equals and hashCode implementations respect the general rules being reflexive, transitive and symmetric.
     *
     * @param 
     * @param eq1  equal instance
     * @param eq2  equal instance
     * @param eq3  equal instance
     * @param diff not equal instance
     */
    public static  void testEqualsAndHashCodes(T eq1, T eq2, T eq3, T diff) {
        // null safe
        assertNotEquals(null, eq1);

        // not instance of
        assertNotEquals(eq1, NOT_INSTANCE_OF);

        // reflexive
        assertEquals(eq1, eq1);
        assertEquals(eq1.hashCode(), eq1.hashCode());

        // symmetric
        assertEquals(eq1, eq2);
        assertEquals(eq2, eq1);
        assertEquals(eq1.hashCode(), eq2.hashCode());
        assertNotEquals(eq2, diff);
        assertNotEquals(diff, eq2);
        assertNotEquals(diff.hashCode(), eq2.hashCode());

        // transitive
        assertEquals(eq1, eq2);
        assertEquals(eq2, eq3);
        assertEquals(eq1, eq3);
        assertEquals(eq1.hashCode(), eq2.hashCode());
        assertEquals(eq2.hashCode(), eq3.hashCode());
        assertEquals(eq1.hashCode(), eq3.hashCode());
    }

    private static Cipher getCipher(int mode) {
        String salt = "9qZGubQY4B6Ra7GU5ZN9";
        String key = "MjxHL4QHjWqQt2qfYN6Z1whe6VJvJKfk3xfDBZJCgv0fqdksKkHhbrWy7Lqj9qNEZwA";
        return getCipher(salt, key, mode);
    }

    public static EncryptionAtRestPolicy getEncryptionAtRestPolicy() {
        return new CipherBasedEncryptionAtRest(TestUtils::getCipher);
    }

    private static File encryptedAtRestFile(TaskSource source) throws IOException {
        return encryptedAtRest(source.getSeekableSource().asNewInputStream(), source.getName());
    }

    private static File encryptedAtRest(InputStream in, String name) {
        try {
            File file = org.sejda.model.util.IOUtils.createTemporaryBufferWithName(name);
            OutputStream out = getEncryptionAtRestPolicy().encrypt(new FileOutputStream(file));
            IOUtils.copy(in, out);
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);

            return file;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static StreamSource encryptedAtRest(StreamSource source) throws IOException {
        File file = encryptedAtRestFile(source);
        StreamSource result = StreamSource.newInstance(new FileInputStream(file), source.getName());
        result.setEncryptionAtRestPolicy(getEncryptionAtRestPolicy());
        return result;
    }

    public static FileSource encryptedAtRest(FileSource source) throws IOException {
        File file = encryptedAtRest(source.getSeekableSource().asNewInputStream(), source.getName());
        FileSource result = FileSource.newInstance(file);
        result.setEncryptionAtRestPolicy(getEncryptionAtRestPolicy());
        return result;
    }

    public static PdfStreamSource encryptedAtRest(PdfStreamSource source) throws IOException {
        File file = encryptedAtRestFile(source);
        PdfStreamSource result = PdfStreamSource.newInstanceWithPassword(new FileInputStream(file), source.getName(),
                source.getPassword());
        result.setEncryptionAtRestPolicy(getEncryptionAtRestPolicy());
        return result;
    }

    public static PdfFileSource encryptedAtRest(PdfFileSource source) throws IOException {
        File file = encryptedAtRestFile(source);
        PdfFileSource result = PdfFileSource.newInstanceWithPassword(file, source.getPassword());
        result.setEncryptionAtRestPolicy(getEncryptionAtRestPolicy());
        return result;
    }


    public static String getPageText(PDPage page) throws IOException {
        PDFTextStripperByArea textStripper = new PDFTextStripperByArea();
        PDRectangle pageSize = page.getCropBox();
        Rectangle cropBoxRectangle = new Rectangle(0, 0, (int) pageSize.getWidth(), (int) pageSize.getHeight());
        if (page.getRotation() == 90 || page.getRotation() == 270) {
            cropBoxRectangle = new Rectangle(0, 0, (int) pageSize.getHeight(), (int) pageSize.getWidth());
        }
        textStripper.setSortByPosition(true);
        textStripper.addRegion("area1", cropBoxRectangle);
        textStripper.extractRegions(page);
        return textStripper.getTextForRegion("area1");
    }

    public static void withPageText(PDPage page, Consumer callback) {
        try {
            callback.accept(getPageText(page));
        } catch (IOException e) {
            fail(e.getMessage());
        }
    }

    public static String getPageTextNormalized(PDPage page) throws IOException {
        return normalizeLineEndings(getPageText(page));
    }

    public static String getDocTextNormalized(PDDocument doc) throws IOException {
        return normalizeLineEndings(new PDFTextStripper().getText(doc));
    }

    public static void assertPageText(PDPage page, String text) {
        withPageText(page, pageText -> assertEquals(text, pageText.replaceAll("[^A-Za-z0-9]", "")));
    }

    public static void assertPageTextExact(PDPage page, String text) {
        withPageText(page, pageText -> assertEquals(text, pageText));
    }

    public static void assertPageTextExactLines(PDPage page, String text) {
        withPageText(page, pageText -> assertEquals(normalizeLineEndings(text), normalizeLineEndings(pageText)));
    }

    public static void assertDocTextExactLines(PDDocument doc, String text) throws IOException {
        assertEquals((text), getDocTextNormalized(doc));
    }

    public static void assertPageTextContains(PDPage page, String text) {
        withPageText(page, pageText -> {
            pageText = StringUtils.normalizeWhitespace(pageText);
            // ignores whitespace
            pageText = pageText.replaceAll("\\s", "");
            assertThat(pageText, containsString(text.replaceAll("\\s", "")));
        });
    }

    public static void assertPageTextDoesNotContain(PDPage page, String text) {
        withPageText(page, pageText -> {
            pageText = StringUtils.normalizeWhitespace(pageText);
            // ignores whitespace
            pageText = pageText.replaceAll("\\s", "");
            assertThat(pageText, not(containsString(text.replaceAll("\\s", ""))));
        });
    }

    public static  java.util.List getAnnotationsOf(PDPage page, Class clazz) {
        return iteratorToList(
                page.getAnnotations().stream().filter(clazz::isInstance).map(a -> (T) a).iterator());
    }

    public static  List iteratorToList(Iterator iterator) {
        List result = new ArrayList<>();
        while (iterator.hasNext()) {
            result.add(iterator.next());
        }
        return result;
    }

    public static void assertPDRectanglesEqual(PDRectangle expected, PDRectangle actual) {
        assertEquals(expected.getLowerLeftX(), actual.getLowerLeftX(), 0.1);
        assertEquals(expected.getLowerLeftY(), actual.getLowerLeftY(), 0.1);
        assertEquals( expected.getWidth(), actual.getWidth(), 0.1);
        assertEquals( expected.getHeight(), actual.getHeight(), 0.1);
    }

    public static void assertPageDestination(PDAnnotationLink link, PDPage expectedPage) throws IOException {
        PDPage actualPage = ((PDPageDestination) link.getDestination()).getPage();
        assertEquals(expectedPage, actualPage);
    }

    public static void assertPageLabelIndexesAre(PDPageLabels labels, Integer... expected) {
        assertThat(labels.getLabels().keySet(), is(new HashSet<>(Arrays.asList(expected))));
    }

    public static void assertPageLabelRangeIs(PDPageLabels labels, int startPage, PDPageLabelRange expected) {
        PDPageLabelRange actual = labels.getPageLabelRange(startPage);
        assertNotNull( actual, "No page label range found at index: " + startPage + ". " + labels.getLabels().keySet());
        assertThat("Difference at index: " + startPage, actual.getCOSObject().toString(),
                is(expected.getCOSObject().toString()));
    }

    public static void assertPageLabelRangeIsDefault(PDPageLabels labels, int startPage) {
        PDPageLabelRange defaultLabel = new PDPageLabelRange();
        defaultLabel.setStyle(PDPageLabelRange.STYLE_DECIMAL);
        assertPageLabelRangeIs(labels, startPage, defaultLabel);
    }

    public static void assertPageLabelRangeIs(PDPageLabels labels, int startPage, String style) {
        assertPageLabelRangeIs(labels, startPage, new PDPageLabelRange(style, null, null));
    }

    public static void assertPageLabelRangeIs(PDPageLabels labels, int startPage, String style, String prefix,
            Integer start) {
        assertPageLabelRangeIs(labels, startPage, new PDPageLabelRange(style, prefix, start));
    }

    private static SecretKeySpec keyToSpec(String salt, String key) throws NoSuchAlgorithmException {
        byte[] keyBytes = (salt + key).getBytes(StandardCharsets.UTF_8);
        MessageDigest sha = MessageDigest.getInstance("SHA-1");
        keyBytes = sha.digest(keyBytes);
        keyBytes = Arrays.copyOf(keyBytes, 16);
        return new SecretKeySpec(keyBytes, "AES");
    }

    public static Cipher getCipher(String salt, String key, int mode) {
        try {
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(mode, keyToSpec(salt, key));

            return cipher;
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    public static PdfStreamSource shortInput() {
        return PdfStreamSource.newInstanceNoPassword(TestUtils.class.getResourceAsStream("/pdf/short-test-file.pdf"),
                "short-test-file.pdf");
    }

    public static PdfStreamSource regularInput() {
        return PdfStreamSource.newInstanceNoPassword(TestUtils.class.getResourceAsStream("/pdf/test-pdf.pdf"),
                "test-file.pdf");
    }

    public static PdfStreamSource mediumInput() {
        return PdfStreamSource.newInstanceNoPassword(TestUtils.class.getResourceAsStream("/pdf/medium_test.pdf"),
                "medium-test-file.pdf");
    }

    public static PdfStreamSource largeInput() {
        return PdfStreamSource.newInstanceNoPassword(TestUtils.class.getResourceAsStream("/pdf/large_test.pdf"),
                "large-test-file.pdf");
    }

    public static PdfStreamSource largeOutlineInput() {
        return PdfStreamSource.newInstanceNoPassword(TestUtils.class.getResourceAsStream("/pdf/large_outline.pdf"),
                "large-outline-test-file.pdf");
    }

    public static PdfStreamSource encryptedInput() {
        return PdfStreamSource.newInstanceWithPassword(
                TestUtils.class.getResourceAsStream("/pdf/encrypted_AES128_user_pwd.pdf"), "encrypted-test-file.pdf",
                "test");
    }

    public static PdfStreamSource formInput() {
        return PdfStreamSource.newInstanceNoPassword(
                TestUtils.class.getResourceAsStream("/pdf/forms/two_pages_form.pdf"), "test-form.pdf");
    }

    public static PdfStreamSource stronglyEncryptedInput() {
        return PdfStreamSource.newInstanceWithPassword(
                TestUtils.class.getResourceAsStream("/pdf/encrypted_AES256_user_pwd.pdf"),
                "strongly-encrypted-test-file.pdf", "test");
    }

    public static PdfStreamSource customInput(String path) {
        return customInput(path, randomAlphanumeric(16) + ".pdf");
    }

    public static PdfStreamSource customInput(String path, String name) {
        requireNotBlank(name, "Name cannot be blank");
        return PdfStreamSource.newInstanceNoPassword(TestUtils.class.getClassLoader().getResourceAsStream(path), name);
    }

    public static PdfFileSource customInputAsFileSource(String path) {
        String filename = new File(path).getName();
        return customInputAsFileSource(path, filename);
    }

    public static PdfFileSource customInputAsFileSource(String path, String filename) {
        requireNotBlank(filename, "Name cannot be blank");
        return PdfFileSource.newInstanceNoPassword(
                streamToTmpFile(TestUtils.class.getClassLoader().getResourceAsStream(path), filename));
    }

    public static PdfFileSource customInput(PDDocument doc, String name) {
        try {
            File tmp = org.sejda.model.util.IOUtils.createTemporaryBufferWithName(name);
            doc.writeTo(tmp);
            return PdfFileSource.newInstanceNoPassword(tmp);
        } catch (TaskIOException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static PdfStreamSource customEncryptedInput(String path, String password) {
        return PdfStreamSource.newInstanceWithPassword(TestUtils.class.getClassLoader().getResourceAsStream(path),
                randomAlphanumeric(16) + ".pdf", password);
    }

    public static StreamSource customNonPdfInputAsStreamSource(String path) {
        String extension = FilenameUtils.getExtension(path);
        String filename = new File(path).getName();
        return customNonPdfInputAsStreamSource(path, filename);
    }

    public static FileSource customNonPdfInput(String path) {
        // default to file source, as stream sources have issues with reading the image multiple times
        return customNonPdfInputAsFileSource(path);
    }

    public static FileSource customNonPdfInput(String path, String filename) {
        // default to file source, as stream sources have issues with reading the image multiple times
        return customNonPdfInputAsFileSource(path, filename);
    }

    public static StreamSource customNonPdfInputAsStreamSource(String path, String filename) {
        requireNotBlank(filename, "Name cannot be blank");
        return StreamSource.newInstance(TestUtils.class.getClassLoader().getResourceAsStream(path), filename);
    }

    public static FileSource customNonPdfInputAsFileSource(String path) {
        String filename = new File(path).getName();
        return customNonPdfInputAsFileSource(path, filename);
    }

    public static FileSource customNonPdfInputAsFileSource(String path, String filename) {
        requireNotBlank(filename, "Name cannot be blank");
        return FileSource.newInstance(
                streamToTmpFile(TestUtils.class.getClassLoader().getResourceAsStream(path), filename));
    }

    public static File streamToTmpFile(InputStream in, String filename) {
        try {
            File tmp = org.sejda.model.util.IOUtils.createTemporaryBufferWithName(filename);
            OutputStream out = new BufferedOutputStream(new FileOutputStream(tmp));
            IOUtils.copy(in, out);
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
            return tmp;
        } catch (IOException | TaskIOException ex) {
            throw new RuntimeException(ex);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy