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

org.kiwiproject.beta.test.servlet.KiwiServletMocks Maven / Gradle / Ivy

package org.kiwiproject.beta.test.servlet;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.google.common.annotations.Beta;
import jakarta.servlet.http.HttpServletRequest;
import lombok.experimental.UtilityClass;
import org.kiwiproject.beta.servlet.KiwiServletRequests;
import org.mockito.ArgumentMatcher;

import javax.security.auth.x500.X500Principal;
import java.security.Principal;
import java.security.cert.X509Certificate;

/**
 * Static utilities to create Mockito-based mocks for servlet API code.
 */
@UtilityClass
@Beta
public class KiwiServletMocks {

    /**
     * Create a mock {@link HttpServletRequest} with the given subject distinguished name.
     *
     * @param dn the subject distinguished name
     * @return a mocked HttpServletRequest with the given subject distinguished name
     * @implNote Don't inline the 'certificate' in the thenReturn() call on the mock request.
     * For some reason that I have not fully investigated, Mockito gets really upset and throws
     * a {@link org.mockito.exceptions.misusing.UnfinishedStubbingException}.
     */
    public static HttpServletRequest mockHttpServletRequestWithCertificate(String dn) {
        var request = mock(HttpServletRequest.class);
        var certificate = mockX509Certificate(dn);
        when(request.getAttribute(KiwiServletRequests.X509_CERTIFICATE_ATTRIBUTE))
                .thenReturn(new X509Certificate[]{certificate});
        return request;
    }

    /**
     * Create a mock {@link HttpServletRequest} that contains no X509 certificate.
     *
     * @return a mocked HttpServletRequest with no X.509 certificate
     * @implNote This is not strictly necessary since Mockito will return null for methods that
     * return a reference type if not provided any expectations.
     * But it makes test code more explicit about the intent of the code, so that's why this exists.
     */
    public static HttpServletRequest mockHttpServletRequestWithNoCertificate() {
        var request = mock(HttpServletRequest.class);
        when(request.getAttribute(KiwiServletRequests.X509_CERTIFICATE_ATTRIBUTE)).thenReturn(null);
        return request;
    }

    /**
     * Create a mock {@link X509Certificate} with the given subject distinguished name.
     *
     * @param dn the subject distinguished name
     * @return a mocked X509Certificate with the given subject distinguished name
     * @implNote Has to mock the {@link Principal} returned by {@link X509Certificate#getSubjectDN()} so
     * this actually creates two mocks. Also, since {@link X509Certificate#getSubjectX500Principal()} returns
     * an instance of the final class {@link X500Principal}, we can't mock it and instead a "real"
     * instance of {@link X500Principal} having the given distinguished name is returned. Also see
     * StackOverflow entry
     * regarding the getSubjectDN method being "denigrated". And, Java 16 deprecated {@link X509Certificate#getSubjectDN()}
     * though (as of this writing on Nov. 8, 2022) not for removal.
     */
    @SuppressWarnings("deprecation")
    public static X509Certificate mockX509Certificate(String dn) {
        var cert = mock(X509Certificate.class);
        var principal = mock(Principal.class);

        when(cert.getSubjectDN()).thenReturn(principal);
        when(principal.getName()).thenReturn(dn);

        var x500Principal = new X500Principal(dn);
        when(cert.getSubjectX500Principal()).thenReturn(x500Principal);

        return cert;
    }

    /**
     * Argument matcher that matches a certificate array containing exactly one {@link X509Certificate}
     * having the given subject DN. Uses the {@link X509Certificate#getSubjectDN()} to obtain the
     * {@link Principal} and then matches against {@link Principal#getName()}.
     *
     * @param subjectDn the subject distinguished name
     * @return a Mockito argument matcher for an array of X509Certificate objects
     */
    @SuppressWarnings("deprecation")
    public static ArgumentMatcher matchesExpectedCertArrayBySubjectDN(String subjectDn) {

        return certs -> {
            assertThat(certs)
                    .extracting(cert -> cert.getSubjectDN().getName())
                    .containsExactly(subjectDn);

            return true;
        };
    }

    /**
     * Argument matcher that matches a certificate having the given subject DN.
     *
     * @param subjectDn the subject distinguished name
     * @return a Mockito argument matcher for an array of X509Certificate objects
     */
    @SuppressWarnings("deprecation")
    public static ArgumentMatcher matchesExpectedCertBySubjectDN(String subjectDn) {

        return cert -> {
            assertThat(cert.getSubjectDN().getName()).isEqualTo(subjectDn);

            return true;
        };
    }

    /**
     * Argument matcher that matches a certificate array containing exactly one {@link X509Certificate}
     * having an {@link X500Principal} with the given name. Uses {@link X509Certificate#getSubjectX500Principal()}
     * and to obtain the {@link X500Principal} and then matches against {@link X500Principal#getName()}.
     *
     * @param name the name for the X500Principal
     * @return a Mockito argument matcher for an array of X509Certificate objects
     */
    public static ArgumentMatcher matchesCertArrayByX500PrincipalName(String name) {

        return object -> {
            assertThat(object).isInstanceOf(X509Certificate[].class);

            // Create an X500Principal to compare against, so that differences in whitespace are ignored.
            // X500Principal removes whitespace between components such that "CN=John Doe, OU=Test Org"
            // becomes "CN=John Doe,OU=Test Org".
            var x500Principal = new X500Principal(name);
            assertThat(object)
                    .extracting(cert -> cert.getSubjectX500Principal().getName())
                    .containsExactly(x500Principal.getName());

            return true;
        };
    }

    /**
     * Argument matcher that matches a certificate having an {@link X500Principal} with the given name.
     *
     * @param name the name for the X500Principal
     * @return a Mockito argument matcher for an array of X509Certificate objects
     */
    public static ArgumentMatcher matchesExpectedCertByX500PrincipalName(String name) {

        return cert -> {
            // Create an X500Principal to compare against, so that differences in whitespace are ignored.
            // X500Principal removes whitespace between components such that "CN=John Doe, OU=Test Org"
            // becomes "CN=John Doe,OU=Test Org".
            var x500Principal = new X500Principal(name);
            assertThat(cert.getSubjectX500Principal().getName()).isEqualTo(x500Principal.getName());

            return true;
        };
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy