Делаем хранилище доверенных сертификатов частью вашего Maven-проекта с API-тестами на Java

В моей предыдущей статье мы говорили об основных концепциях тестирования API в Java с использованием HTTPs-соединений. Мы выяснили как создается хранилище доверенных сертификатов (trust store), содержащее сертификат, возвращаемый нашим тестовым сервисом. Таким образом, мы научились избегать ошибок вида "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target". Теперь мы взглянем на более оформленное решение, которое будет включать в себя использование таких фреймворков как Maven и JUnit5. Ключевая проблема, которую мы будем решать - это добавление нашего trust store в структуру проекта, так чтобы тесты всегда использовали бы одно и то же хранилище, где бы они не запускались.

Описание примера: простой Мавен-проект с API-тестами, работающими по HTTPs-протоколу

Мы взглянем на пример, который очень похож на то, что я использовал в своей предыдущей статье. Но на этот раз мы создадим корректно стилизованный тестовый проект, который будет использовать Maven и JUnit вместо простого запуска всего в main-методе. Итак, наш pom.xml будет выглядеть так:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>click.webelement</groupId>
    <artifactId>ssl-api-tests</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.5.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

А вот как будет выглядеть наш тестовый класс:

package click.webelement.https;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

public class ApiWithSSLTest {

    private final static int OK_CODE = 200;

    @Test
    public void callHttpsEndpoint() throws IOException {

        HttpURLConnection connection = (HttpURLConnection) new URL("https://webelement.click").openConnection();
        Assertions.assertEquals(OK_CODE, connection.getResponseCode(), "Response code should be successful");

    }

}

Так как мы собираемся сконцентрироваться на том как добавить доверенное хранилище в тестовый проект, а не на том, как создать такое хранилище, я предполагаю, что вы уже его подготовили. Если нет, добро пожаловать в мою предыдущую статью, где я описываю необходимые шаги.

Добавляем доверенное хранилище сертификатов в Maven-проект

Первое, что необходимо сделать - это положить файл хранилища в папку resources вашего Maven-проекта (так как мы говорим о тестировании, и скоуп депенденси JUnit - test, мы будем хранить весь код и ресурсы в папке src/test). Теперь нам надо получить путь к нашему хранилищу. Этот путь может быть разным в разных средах, однако существует способ получить текущий путь к любому ресурсу вашего проекта в рантайме, используя следующий подход.

Получить URI ресурса:

URI trustStoreURI = ApiWithSSLTest.class.getClassLoader().getResource(TRUST_STORE_FILE).toURI();

Транслировать полученный URI в путь к файлу:

String trustStoreFilePath = new File(trustStoreURI).getAbsolutePath();

Таким образом, совместив эти шаги и добавив их в код, мы получим следующий тестовый класс:

package click.webelement.https;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

public class ApiWithSSLTest {

    private final static String TRUST_STORE_FILE = "mytruststore";
    private final static String TRUST_STORE_PASSWORD = "mystorepass";
    private final static int OK_CODE = 200;

    @BeforeAll
    public static void setUpAll() throws URISyntaxException {
        URI trustStoreURI = ApiWithSSLTest.class.getClassLoader().getResource(TRUST_STORE_FILE).toURI();
        System.setProperty("javax.net.ssl.trustStore", new File(trustStoreURI).getAbsolutePath());
        System.setProperty("javax.net.ssl.trustStorePassword", TRUST_STORE_PASSWORD);
    }

    @Test
    public void callHttpsEndpoint() throws IOException {
        HttpURLConnection connection = (HttpURLConnection) new URL("https://webelement.click").openConnection();
        Assertions.assertEquals(OK_CODE, connection.getResponseCode(), "Response code should be successful");
    }

}

Теперь наш тест привязан к тому хранилищу сертификатов, которое находится в файле src/test/resources/mytruststore. Это означает, что, будучи частью CI пайплайна код ваших тестов скомпилируется вместе с вложенным в ресурсы хранилищем, после чего в рантайме настроится на это хранилище независимо от того в какой среде, процесс будет исполняться.

Если после прочтения у вас всё ещё остались вопросы, присылайте их мне используя эту форму обратной связи. Я постараюсь ответить вам напрямую, а также скорректировать статью, опираясь на ваши отзывы.