IdRequestLimitsTest.java

/*
 * Copyright © 2023 Mark Raynsford <code@io7m.com> https://www.io7m.com
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package com.io7m.idstore.tests.server.service.reqlimit;

import com.io7m.idstore.server.service.reqlimit.IdRequestLimitExceeded;
import com.io7m.idstore.server.service.reqlimit.IdRequestLimits;
import com.io7m.idstore.tests.IdFakeServletInputStream;
import com.io7m.idstore.tests.server.service.IdServiceContract;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.io.ByteArrayInputStream;
import java.security.SecureRandom;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public final class IdRequestLimitsTest
  extends IdServiceContract<IdRequestLimits>
{
  private IdRequestLimits limits;

  @Override
  protected IdRequestLimits createInstanceA()
  {
    return new IdRequestLimits(
      "size %d is too large"::formatted
    );
  }

  @Override
  protected IdRequestLimits createInstanceB()
  {
    return new IdRequestLimits(
      "size %d is not small enough"::formatted
    );
  }

  @BeforeEach
  public void setup()
  {
    this.limits = new IdRequestLimits("size %d is too large"::formatted);
  }

  /**
   * It's only possible to read the limited data, even if the request specifies
   * fewer bytes than are actually provided.
   *
   * @throws Exception On errors
   */

  @Test
  public void testLimitNotExceeded()
    throws Exception
  {
    final var request =
      Mockito.mock(HttpServletRequest.class);

    final var data = new byte[200];
    SecureRandom.getInstanceStrong().nextBytes(data);
    final var slice = new byte[20];
    System.arraycopy(data, 0, slice, 0, 20);

    final var realStream =
      new ByteArrayInputStream(data);

    Mockito.when(Integer.valueOf(request.getContentLength()))
      .thenReturn(Integer.valueOf(20));

    final var stream =
      new IdFakeServletInputStream(realStream);

    Mockito.when(request.getInputStream())
      .thenReturn(stream);

    final var input =
      this.limits.boundedMaximumInput(request, 100);

    assertArrayEquals(
      slice,
      input.readAllBytes()
    );
  }

  /**
   * Exceeding the size limit is not allowed.
   *
   * @throws Exception On errors
   */

  @Test
  public void testLimitExceeded()
    throws Exception
  {
    final var request =
      Mockito.mock(HttpServletRequest.class);
    Mockito.when(Integer.valueOf(request.getContentLength()))
      .thenReturn(Integer.valueOf(101));

    final var ex =
      assertThrows(IdRequestLimitExceeded.class, () -> {
        this.limits.boundedMaximumInput(request, 100);
      });

    assertEquals(101L, ex.sizeProvided());
    assertEquals(100L, ex.sizeLimit());
    assertEquals("size 101 is too large", ex.getMessage());
  }

  /**
   * It's possible to read the full data if no limit is provided.
   *
   * @throws Exception On errors
   */

  @Test
  public void testLimitUnlimited()
    throws Exception
  {
    final var request =
      Mockito.mock(HttpServletRequest.class);

    final var data = new byte[200];
    SecureRandom.getInstanceStrong().nextBytes(data);

    final var realStream =
      new ByteArrayInputStream(data);

    Mockito.when(Integer.valueOf(request.getContentLength()))
      .thenReturn(Integer.valueOf(-1));

    final var stream =
      new IdFakeServletInputStream(realStream);

    Mockito.when(request.getInputStream())
      .thenReturn(stream);

    final var input =
      this.limits.boundedMaximumInput(request, 200);

    assertArrayEquals(
      data,
      input.readAllBytes()
    );
  }
}