LoggingInterceptorTest.java

/*
 * Copyright (c) 2016, 2024 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.logging;

import org.mockito.stubbing.Answer;

import javax.ws.rs.core.MediaType;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Random;

import org.junit.jupiter.api.Test;
import static org.glassfish.jersey.logging.LoggingFeature.Verbosity.HEADERS_ONLY;
import static org.glassfish.jersey.logging.LoggingFeature.Verbosity.PAYLOAD_ANY;
import static org.glassfish.jersey.logging.LoggingFeature.Verbosity.PAYLOAD_TEXT;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
import static javax.ws.rs.core.MediaType.TEXT_HTML_TYPE;

/**
 * @author Ondrej Kosatka
 */
public class LoggingInterceptorTest {

    //
    // isReadable
    //

    @Test
    public void testReadableTypeTestSubWild() {
        assertTrue(LoggingInterceptor.isReadable(new MediaType("text", "*")));
    }

    @Test
    public void testReadableTypeTestSubSomething() {
        assertTrue(LoggingInterceptor.isReadable(new MediaType("text", "something")));
    }

    @Test
    public void testReadableTypeAppSubJson() {
        assertTrue(LoggingInterceptor.isReadable(new MediaType("application", "json")));
    }

    @Test
    public void testReadableTypeApplicationSubVndApiJson() {
        assertTrue(LoggingInterceptor.isReadable(new MediaType("application", "vnd.api+json")));
    }

    @Test
    public void testReadableTypeAppSubBinary() {
        assertFalse(LoggingInterceptor.isReadable(new MediaType("application", "octet-stream")));
    }

    @Test
    public void testReadableTypeAppSubUnknown() {
        assertFalse(LoggingInterceptor.isReadable(new MediaType("application", "unknown")));
    }

    @Test
    public void testReadableTypeUnknownSubUnknown() {
        assertFalse(LoggingInterceptor.isReadable(new MediaType("unknown", "unknown")));
    }

    //
    // printEntity
    //

    @Test
    public void testVerbosityTextPrintTextEntity() {
        assertTrue(LoggingInterceptor.printEntity(PAYLOAD_TEXT, TEXT_HTML_TYPE));
    }

    @Test
    public void testVerbosityTextPrintBinaryEntity() {
        assertFalse(LoggingInterceptor.printEntity(PAYLOAD_TEXT, APPLICATION_OCTET_STREAM_TYPE));
    }

    @Test
    public void testVerbosityAnyPrintTextEntity() {
        assertTrue(LoggingInterceptor.printEntity(PAYLOAD_ANY, TEXT_HTML_TYPE));
    }

    @Test
    public void testVerbosityAnyPrintBinaryEntity() {
        assertTrue(LoggingInterceptor.printEntity(PAYLOAD_ANY, APPLICATION_OCTET_STREAM_TYPE));
    }

    @Test
    public void testVerbosityHeadersPrintTextEntity() {
        assertFalse(LoggingInterceptor.printEntity(HEADERS_ONLY, TEXT_HTML_TYPE));
    }

    @Test
    public void testVerbosityHeadersPrintBinaryEntity() {
        assertFalse(LoggingInterceptor.printEntity(HEADERS_ONLY, APPLICATION_OCTET_STREAM_TYPE));
    }

    //
    // logInboundEntity
    //

    @Test
    public void testLogInboundEntityMockedStream() throws Exception {
        int maxEntitySize = 20;
        LoggingInterceptor loggingInterceptor = new LoggingInterceptor(LoggingFeature.builder().maxEntitySize(maxEntitySize)) {};

        StringBuilder buffer = new StringBuilder();
        InputStream stream = mock(InputStream.class);
        when(stream.markSupported()).thenReturn(true);

        when(stream.read(any(), eq(0), eq(maxEntitySize + 1)))
                .thenAnswer(chunk(4, 'a'));
        when(stream.read(any(), eq(4), eq(maxEntitySize + 1 - 4)))
                .thenAnswer(chunk(3, 'b'));
        when(stream.read(any(), eq(7), eq(maxEntitySize + 1 - 7)))
                .thenAnswer(chunk(5, 'c'));
        when(stream.read(any(), eq(12), eq(maxEntitySize + 1 - 12)))
                .thenReturn(-1);

        loggingInterceptor.logInboundEntity(buffer, stream, StandardCharsets.UTF_8);

        assertEquals("aaaabbbccccc\n", buffer.toString());
        verify(stream).mark(maxEntitySize + 1);
        verify(stream).reset();
    }

    private Answer<?> chunk(int size, char filler) {
        return invocation -> {
            byte[] buf = invocation.getArgument(0, byte[].class);
            int offset = invocation.getArgument(1, Integer.class);
            Arrays.fill(buf, offset, offset + size, (byte) filler);
            return size;
        };
    }

    @Test
    public void testLogInboundEntityRealStream() throws Exception {
        int maxEntitySize = 2000;
        String inputString = getRandomString(maxEntitySize * 2);

        LoggingInterceptor loggingInterceptor = new LoggingInterceptor(LoggingFeature.builder().maxEntitySize(maxEntitySize)) {};
        StringBuilder buffer = new StringBuilder();
        InputStream stream = new ByteArrayInputStream(inputString.getBytes());

        loggingInterceptor.logInboundEntity(buffer, stream, StandardCharsets.UTF_8);

        assertEquals(inputString.substring(0, maxEntitySize) + "...more...\n", buffer.toString());
    }

    private static String getRandomString(int length) {
        final String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 _";
        StringBuilder result = new StringBuilder();

        while (length > 0) {
            Random rand = new Random();
            result.append(characters.charAt(rand.nextInt(characters.length())));
            length--;
        }
        return result.toString();
    }
}