Open Osmyslitelny opened 1 year ago
I was wondering how to implement swagger coverage on spring mvc. I first thought to use an Mvc filter :
mockMvc = standaloneSetup(new PersonController()).addFilters(new SwaggerCoverageFilter()).build();
Do you have a piece of code to show how it could work?
I made an integration by implementing a ResultHandler
for Swagger V3 and using it into a TestConfiguration
.
The ResultHandler is largely inspired by https://github.com/viclovsky/swagger-coverage/blob/master/swagger-coverage-rest-assured/src/main/java/com/github/viclovsky/swagger/coverage/SwaggerCoverageV3RestAssured.java
The ResultHandler
implementation :
import com.github.viclovsky.swagger.coverage.CoverageOutputWriter;
import com.github.viclovsky.swagger.coverage.FileSystemOutputWriter;
import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.parameters.*;
import io.swagger.v3.oas.models.responses.*;
import io.swagger.v3.oas.models.servers.Server;
import org.apache.commons.lang3.StringUtils;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.web.servlet.HandlerMapping;
import java.net.URI;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.Map;
public class SwaggerCoverageResultHandler implements ResultHandler {
private CoverageOutputWriter writer;
public SwaggerCoverageResultHandler() {
this.writer = new FileSystemOutputWriter(Paths.get("target", OUTPUT_DIRECTORY));
}
@Override
public void handle(MvcResult result) throws Exception {
var request = result.getRequest();
var response = result.getResponse();
Operation operation = new Operation();
Map<String, String> pathAttributes = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
pathAttributes.forEach((n, v) -> operation.addParametersItem(new PathParameter().name(n).example(v)));
var queryString = request.getQueryString();
request.getParameterMap().keySet().stream()
.filter(name -> queryString.contains(name + "="))
.forEach((n) -> operation.addParametersItem(new QueryParameter().name(n).example(request.getParameter(n))));
for (Iterator<String> it = request.getHeaderNames().asIterator(); it.hasNext(); ) {
String headerName = it.next();
String headerValue = request.getHeader(headerName);
operation.addParametersItem(new HeaderParameter().name(headerName)
.example(headerValue));
}
if ("POST".equalsIgnoreCase(request.getMethod()) && request.getContentLength() > 0) {
MediaType mediaType = new MediaType();
mediaType.setSchema(new Schema());
request.getParameterMap().keySet().stream()
.filter(name -> !queryString.contains(name + "="))
.forEach((n) -> mediaType.getSchema().addProperties(n, new Schema().example(request.getParameter(n))));
operation.requestBody(
new RequestBody().content(new Content().addMediaType(request.getContentType(), mediaType)));
}
var apiResponse = new ApiResponse();
if (!StringUtils.isEmpty(response.getContentType())) {
apiResponse.content((new Content()).addMediaType(response.getContentType(), new MediaType()));
}
operation.responses((new ApiResponses())
.addApiResponse(String.valueOf(response.getStatus()), apiResponse));
PathItem pathItem = new PathItem();
pathItem.operation(PathItem.HttpMethod.valueOf(request.getMethod().toUpperCase()), operation);
OpenAPI openAPI = new OpenAPI()
.addServersItem(new Server().url(URI.create(request.getRequestURI()).getHost()))
.path(request.getRequestURI(), pathItem);
writer.write(openAPI);
}
}
The configuration:
import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcBuilderCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
@TestConfiguration
public class SwaggerCoverageTestConfiguration {
@Bean
SwaggerCoverageMockMvcBuilderCustomizer swaggerCoverageMockMvcBuilderCustomizer() {
return new SwaggerCoverageMockMvcBuilderCustomizer();
}
public static class SwaggerCoverageMockMvcBuilderCustomizer implements MockMvcBuilderCustomizer {
@Override
public void customize(ConfigurableMockMvcBuilder<?> builder) {
builder.alwaysDo(new SwaggerCoverageResultHandler());
}
}
}
The configuration can then be imported into tests:
@ImportAutoConfiguration({SwaggerCoverageTestConfiguration.class})
pathAttributes could be null but other code is work as expected
I'm submitting a ...
Do you need PR with spring mockMvc integration based on ResultHandler implementation? I'm not sure this case is very popular, so, before creating PR I decided create this issues.