Skip to content

Commit

Permalink
add tests defining behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
pboos committed Nov 22, 2023
1 parent 0055dd4 commit 3d4d388
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.getyourguide.openapi.validation.filter;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.tomcat.util.http.fileupload.IOUtils;

/**
* A class to provide the ability to read a {@link jakarta.servlet.ServletRequest}'s body multiple
* times. via https://stackoverflow.com/a/36619972/2257038 and https://stackoverflow.com/a/30748533/2257038
*/
public class MultiReadHttpServletRequestWrapper extends HttpServletRequestWrapper {

private ByteArrayOutputStream cachedBytes;

/**
* Construct a new multi-read wrapper.
*
* @param request to wrap around
*/
public MultiReadHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}

@Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null) {
cacheInputStream();
}

return new CachedServletInputStream(cachedBytes.toByteArray());
}

@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

private void cacheInputStream() throws IOException {
/* Cache the inputstream in order to read it multiple times. For
* convenience, I use apache.commons IOUtils
*/
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}

/* An inputstream which reads the cached request body */
private static class CachedServletInputStream extends ServletInputStream {
private final ByteArrayInputStream buffer;

public CachedServletInputStream(byte[] contents) {
this.buffer = new ByteArrayInputStream(contents);
}

@Override
public int read() throws IOException {
return buffer.read();
}

@Override
public boolean isFinished() {
return buffer.available() == 0;
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener(ReadListener listener) {
throw new UnsupportedOperationException("Not implemented");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.getyourguide.openapi.validation.integration;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.getyourguide.openapi.validation.test.TestViolationLogger;
import java.util.Optional;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest(properties = {
"openapi.validation.should-fail-on-request-violation=true",
"openapi.validation.should-fail-on-response-violation=true",
})
@AutoConfigureMockMvc
@ExtendWith(SpringExtension.class)
public class FailOnViolationIntegrationTest {

@Autowired
private MockMvc mockMvc;

@Autowired
private TestViolationLogger openApiViolationLogger;

@BeforeEach
public void setup() {
openApiViolationLogger.clearViolations();
}

@Test
public void whenInvalidRequestThenReturn400AndNoViolationLogged() throws Exception {
mockMvc.perform(post("/test").content("{ \"value\": 1 }").contentType(MediaType.APPLICATION_JSON))
.andExpectAll(
status().is4xxClientError(),
content().string(Matchers.blankOrNullString())
);
Thread.sleep(100);

assertEquals(0, openApiViolationLogger.getViolations().size());

// TODO check that controller did not execute (inject controller here and have addition method to check & clear at setup)
// TODO check that something else gets logged?
}

@Test
public void whenInvalidResponseThenReturn500AndViolationLogged() throws Exception {
mockMvc.perform(get("/test").queryParam("value", "invalid-response-value!"))
.andExpectAll(
status().is5xxServerError(),
content().string(Matchers.blankOrNullString())
);
Thread.sleep(100);

assertEquals(1, openApiViolationLogger.getViolations().size());
var violation = openApiViolationLogger.getViolations().get(0);
assertEquals("validation.response.body.schema.pattern", violation.getRule());
assertEquals(Optional.of(200), violation.getResponseStatus());
assertEquals(Optional.of("/value"), violation.getInstance());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.getyourguide.openapi.validation.integration;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.getyourguide.openapi.validation.test.TestViolationLogger;
import java.util.Optional;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest(properties = {
"openapi.validation.should-fail-on-request-violation=true",
"openapi.validation.should-fail-on-response-violation=true",
})
@AutoConfigureMockMvc
@ExtendWith(SpringExtension.class)
public class FailOnViolationIntegrationTest {

@Autowired
private WebTestClient webTestClient;

@Autowired
private TestViolationLogger openApiViolationLogger;

@BeforeEach
public void setup() {
openApiViolationLogger.clearViolations();
}

@Test
public void whenInvalidRequestThenReturn400AndNoViolationLogged() throws Exception {
webTestClient
.post().uri("/test")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{ \"value\": 1 }")
.exchange()
.expectStatus().is4xxClientError()
.expectBody().isEmpty();
Thread.sleep(100);

assertEquals(0, openApiViolationLogger.getViolations().size());

// TODO check that controller did not execute (inject controller here and have addition method to check & clear at setup)
// TODO check that something else gets logged?
}

@Test
public void whenInvalidResponseThenReturn500AndViolationLogged() throws Exception {
webTestClient
.get().uri("/test?value=invalid-response-value!")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().is5xxServerError()
.expectBody().isEmpty();
Thread.sleep(100);

assertEquals(1, openApiViolationLogger.getViolations().size());
var violation = openApiViolationLogger.getViolations().get(0);
assertEquals("validation.response.body.schema.pattern", violation.getRule());
assertEquals(Optional.of(200), violation.getResponseStatus());
assertEquals(Optional.of("/value"), violation.getInstance());
}
}

0 comments on commit 3d4d388

Please sign in to comment.