From 2daf3fcb5fe8d8f53dad2ff433d41945ec8d8157 Mon Sep 17 00:00:00 2001 From: Balogh Csaba Date: Mon, 21 Nov 2022 21:11:19 +0100 Subject: [PATCH] feat(core): #3 - Investigate RESTAssured integration (#22) - added and implemented openapi-extender-restassured module - added quarkus sample with openapi-extender-restassured module --- openapi-extender-restassured/pom.xml | 33 ++++ .../exception/OpenApiFilterException.java | 29 ++++ .../filter/OpenApiRequestFilter.java | 98 ++++++++++++ .../filter/OpenApiResponseFilter.java | 96 ++++++++++++ pom.xml | 8 + samples/quarkus-with-restassured/pom.xml | 84 ++++++++++ .../dto/ErrorResponse.java | 44 ++++++ .../dto/UserRequest.java | 72 +++++++++ .../dto/UserResponse.java | 44 ++++++ .../rest/ExampleRest.java | 61 ++++++++ .../rest/ExampleRestIT.java | 146 ++++++++++++++++++ 11 files changed, 715 insertions(+) create mode 100644 openapi-extender-restassured/pom.xml create mode 100644 openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/exception/OpenApiFilterException.java create mode 100644 openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/filter/OpenApiRequestFilter.java create mode 100644 openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/filter/OpenApiResponseFilter.java create mode 100644 samples/quarkus-with-restassured/pom.xml create mode 100644 samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/ErrorResponse.java create mode 100644 samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/UserRequest.java create mode 100644 samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/UserResponse.java create mode 100644 samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/rest/ExampleRest.java create mode 100644 samples/quarkus-with-restassured/src/test/java/org/rodnalsol/sample/quarkuswithrestassured/rest/ExampleRestIT.java diff --git a/openapi-extender-restassured/pom.xml b/openapi-extender-restassured/pom.xml new file mode 100644 index 0000000..6b59578 --- /dev/null +++ b/openapi-extender-restassured/pom.xml @@ -0,0 +1,33 @@ + + + + openapi-extender-parent + org.rodnansol + 999-SNAPSHOT + + 4.0.0 + + openapi-extender-restassured + OpenAPI Extender - REST Assured + + + 8 + 8 + UTF-8 + + + + + org.rodnansol + openapi-extender-resource-generator + compile + + + io.rest-assured + rest-assured + compile + + + diff --git a/openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/exception/OpenApiFilterException.java b/openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/exception/OpenApiFilterException.java new file mode 100644 index 0000000..ecdfcbe --- /dev/null +++ b/openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/exception/OpenApiFilterException.java @@ -0,0 +1,29 @@ +package org.rodnalsol.openapi.extender.restassured.exception; + +/** + * Exception for OpenAPI filters. + * + * @author csaba.balogh + * @since 0.3.0 + */ +public class OpenApiFilterException extends RuntimeException { + + /** + * Constructs a new {@link OpenApiFilterException} with the specified detail message. + * + * @param message the detail message. + */ + public OpenApiFilterException(String message) { + super(message); + } + + /** + * Constructs a new {@link OpenApiFilterException} with the specified detail message and cause. + * + * @param message the detail message. + * @param cause the cause. + */ + public OpenApiFilterException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/filter/OpenApiRequestFilter.java b/openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/filter/OpenApiRequestFilter.java new file mode 100644 index 0000000..ab1a336 --- /dev/null +++ b/openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/filter/OpenApiRequestFilter.java @@ -0,0 +1,98 @@ +package org.rodnalsol.openapi.extender.restassured.filter; + +import io.restassured.filter.Filter; +import io.restassured.filter.FilterContext; +import io.restassured.response.Response; +import io.restassured.specification.FilterableRequestSpecification; +import io.restassured.specification.FilterableResponseSpecification; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.rodnalsol.openapi.extender.restassured.exception.OpenApiFilterException; +import org.rodnansol.generator.ReportParams; +import org.rodnansol.generator.RequestBodyExampleFileOutputResourceGenerator; + +/** + * Rest assured filter class for generating OpenAPI request example. + * + * @author csaba.balogh + * @since 0.3.0 + */ +public class OpenApiRequestFilter implements Filter { + + /** + * The OpenAPI request example generator. + */ + private final RequestBodyExampleFileOutputResourceGenerator requestBodyExampleFileOutputResourceGenerator; + + /** + * The operation's name. + */ + private final String operation; + + /** + * The name of the OpenAPI request example. + */ + private final String name; + + /** + * The description of the OpenAPI request example. + */ + private final String description; + + /** + * Creates a new {@link OpenApiRequestFilter} with the specified operation and name. + * The description will be null and the output directory will be {@link RequestBodyExampleFileOutputResourceGenerator#DEFAULT_OUTPUT_DIRECTORY}. + * + * @param operation the operation's name. + * @param name the name of the OpenAPI request example. + */ + public OpenApiRequestFilter(String operation, String name) { + this(operation, name, null, RequestBodyExampleFileOutputResourceGenerator.DEFAULT_OUTPUT_DIRECTORY); + } + + /** + * Creates a new {@link OpenApiRequestFilter} with the specified operation, name and description. + * The output directory will be {@link RequestBodyExampleFileOutputResourceGenerator#DEFAULT_OUTPUT_DIRECTORY}. + * + * @param operation the operation's name. + * @param name the name of the OpenAPI request example. + * @param description the description of the OpenAPI request example. + */ + public OpenApiRequestFilter(String operation, String name, String description) { + this(operation, name, description, RequestBodyExampleFileOutputResourceGenerator.DEFAULT_OUTPUT_DIRECTORY); + } + + /** + * Creates a new {@link OpenApiRequestFilter} with the specified operation, name, description and output directory. + * + * @param operation the operation's name. + * @param name the name of the OpenAPI request example. + * @param description the description of the OpenAPI request example. + * @param outputDirectory the output directory of the OpenAPI request example. + */ + public OpenApiRequestFilter(String operation, String name, String description, String outputDirectory) { + this.operation = operation; + this.name = name; + this.description = description; + this.requestBodyExampleFileOutputResourceGenerator = new RequestBodyExampleFileOutputResourceGenerator(outputDirectory); + } + + @Override + public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec, FilterContext ctx) { + try { + Response response = ctx.next(requestSpec, responseSpec); + generateResources(requestSpec, response); + return response; + } catch (Exception e) { + throw new OpenApiFilterException("Error during documenting response", e); + } + } + + private void generateResources(FilterableRequestSpecification requestSpec, Response response) throws IOException { + byte[] requestBodyAsByteArray = requestSpec.getBody().toString().getBytes(StandardCharsets.UTF_8); + ReportParams params = new ReportParams(operation, name, response.getStatusCode(), requestSpec.getContentType(), requestBodyAsByteArray); + params.setDescription(description); + requestBodyExampleFileOutputResourceGenerator.generateResources(params); + } +} diff --git a/openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/filter/OpenApiResponseFilter.java b/openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/filter/OpenApiResponseFilter.java new file mode 100644 index 0000000..a080342 --- /dev/null +++ b/openapi-extender-restassured/src/main/java/org/rodnalsol/openapi/extender/restassured/filter/OpenApiResponseFilter.java @@ -0,0 +1,96 @@ +package org.rodnalsol.openapi.extender.restassured.filter; + +import io.restassured.filter.Filter; +import io.restassured.filter.FilterContext; +import io.restassured.response.Response; +import io.restassured.specification.FilterableRequestSpecification; +import io.restassured.specification.FilterableResponseSpecification; +import java.io.IOException; + +import org.rodnalsol.openapi.extender.restassured.exception.OpenApiFilterException; +import org.rodnansol.generator.ApiResponseExampleFileOutputResourceGenerator; +import org.rodnansol.generator.ReportParams; + +/** + * Rest assured filter class for generating OpenAPI response example. + * + * @author csaba.balogh + * @since 0.3.0 + */ +public class OpenApiResponseFilter implements Filter { + + /** + * The OpenAPI response example generator. + */ + private final ApiResponseExampleFileOutputResourceGenerator apiResponseExampleFileOutputResourceGenerator; + + /** + * The operation's name. + */ + private final String operation; + + /** + * The name of the OpenAPI response example. + */ + private final String name; + + /** + * The description of the OpenAPI response example. + */ + private final String description; + + /** + * Creates a new {@link OpenApiResponseFilter} with the specified operation and name. + * The description will be null and the output directory will be {@link ApiResponseExampleFileOutputResourceGenerator#DEFAULT_OUTPUT_DIRECTORY}. + * + * @param operation the operation's name. + * @param name the name of the OpenAPI response example. + */ + public OpenApiResponseFilter(String operation, String name) { + this(operation, name, null, ApiResponseExampleFileOutputResourceGenerator.DEFAULT_OUTPUT_DIRECTORY); + } + + /** + * Creates a new {@link OpenApiResponseFilter} with the specified operation, name and description. + * The output directory will be {@link ApiResponseExampleFileOutputResourceGenerator#DEFAULT_OUTPUT_DIRECTORY}. + * + * @param operation the operation's name. + * @param name the name of the OpenAPI response example. + * @param description the description of the OpenAPI response example. + */ + public OpenApiResponseFilter(String operation, String name, String description) { + this(operation, name, description, ApiResponseExampleFileOutputResourceGenerator.DEFAULT_OUTPUT_DIRECTORY); + } + + /** + * Creates a new {@link OpenApiResponseFilter} with the specified operation, name, description and output directory. + * + * @param operation the operation's name. + * @param name the name of the OpenAPI response example. + * @param description the description of the OpenAPI response example. + * @param outputDirectory the output directory of the OpenAPI response example. + */ + public OpenApiResponseFilter(String operation, String name, String description, String outputDirectory) { + this.operation = operation; + this.name = name; + this.description = description; + this.apiResponseExampleFileOutputResourceGenerator = new ApiResponseExampleFileOutputResourceGenerator(outputDirectory); + } + + @Override + public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec, FilterContext ctx) { + try { + Response response = ctx.next(requestSpec, responseSpec); + generateResources(response); + return response; + } catch (Exception e) { + throw new OpenApiFilterException("Error during documenting response", e); + } + } + + private void generateResources(Response response) throws IOException { + ReportParams params = new ReportParams(operation, name, response.getStatusCode(), response.getContentType(), response.asByteArray()); + params.setDescription(description); + apiResponseExampleFileOutputResourceGenerator.generateResources(params); + } +} diff --git a/pom.xml b/pom.xml index 0de4ba6..2baecad 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ openapi-extender-resource-generator openapi-extender-spring-test openapi-extender-springdoc + openapi-extender-restassured @@ -43,6 +44,7 @@ 0.8.7 10.3.3 3.22.0 + 5.2.0 @@ -179,6 +181,12 @@ ${junit-jupiter-params.version} provided + + io.rest-assured + rest-assured + ${rest-assured.version} + provided + diff --git a/samples/quarkus-with-restassured/pom.xml b/samples/quarkus-with-restassured/pom.xml new file mode 100644 index 0000000..da275e1 --- /dev/null +++ b/samples/quarkus-with-restassured/pom.xml @@ -0,0 +1,84 @@ + + + + openapi-extender-parent + org.rodnansol + 999-SNAPSHOT + ../../pom.xml + + 4.0.0 + + quarkus-with-restassured + + + 17 + 17 + UTF-8 + + 2.13.0.Final + + + + + + io.quarkus.platform + quarkus-bom + ${quarkus.platform.version} + pom + import + + + + + + + org.rodnansol + openapi-extender-restassured + 999-SNAPSHOT + test + + + io.quarkus + quarkus-resteasy-jackson + + + io.quarkus + quarkus-resteasy-jaxb + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + io.quarkus + quarkus-jaxb + + + io.quarkus + quarkus-junit5 + test + + + + + + + io.quarkus.platform + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + + diff --git a/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/ErrorResponse.java b/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/ErrorResponse.java new file mode 100644 index 0000000..989e232 --- /dev/null +++ b/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/ErrorResponse.java @@ -0,0 +1,44 @@ +package org.rodnalsol.sample.quarkuswithrestassured.dto; + +/** + * Example DTO for representing error response. + * + * @author csaba.balogh + * @since 0.3.0 + */ +public class ErrorResponse { + + /** + * Message. + */ + private String message; + + /** + * Cause. + */ + private String cause; + + public ErrorResponse() { + } + + public ErrorResponse(String message, String cause) { + this.message = message; + this.cause = cause; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getCause() { + return cause; + } + + public void setCause(String cause) { + this.cause = cause; + } +} diff --git a/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/UserRequest.java b/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/UserRequest.java new file mode 100644 index 0000000..818804b --- /dev/null +++ b/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/UserRequest.java @@ -0,0 +1,72 @@ +package org.rodnalsol.sample.quarkuswithrestassured.dto; + +/** + * Example DTO for representing user request. + * + * @author csaba.balogh + * @since 0.3.0 + */ +public class UserRequest { + + /** + * Username. + */ + private String username; + + /** + * Password. + */ + private String password; + + /** + * Password confirmation. + */ + private String passwordConfirmation; + + /** + * Full name. + */ + private String fullName; + + public UserRequest() { + } + + public UserRequest(String username, String password, String passwordConfirmation, String fullName) { + this.username = username; + this.password = password; + this.passwordConfirmation = passwordConfirmation; + this.fullName = fullName; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPasswordConfirmation() { + return passwordConfirmation; + } + + public void setPasswordConfirmation(String passwordConfirmation) { + this.passwordConfirmation = passwordConfirmation; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } +} diff --git a/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/UserResponse.java b/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/UserResponse.java new file mode 100644 index 0000000..757f7c3 --- /dev/null +++ b/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/dto/UserResponse.java @@ -0,0 +1,44 @@ +package org.rodnalsol.sample.quarkuswithrestassured.dto; + +/** + * Example DTO for representing user response. + * + * @author csaba.balogh + * @since 0.3.0 + */ +public class UserResponse { + + /** + * Username. + */ + private String username; + + /** + * Full name. + */ + private String fullName; + + public UserResponse() { + } + + public UserResponse(String username, String fullName) { + this.username = username; + this.fullName = fullName; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } +} diff --git a/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/rest/ExampleRest.java b/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/rest/ExampleRest.java new file mode 100644 index 0000000..8611fdc --- /dev/null +++ b/samples/quarkus-with-restassured/src/main/java/org/rodnalsol/sample/quarkuswithrestassured/rest/ExampleRest.java @@ -0,0 +1,61 @@ +package org.rodnalsol.sample.quarkuswithrestassured.rest; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.rodnalsol.sample.quarkuswithrestassured.dto.ErrorResponse; +import org.rodnalsol.sample.quarkuswithrestassured.dto.UserRequest; +import org.rodnalsol.sample.quarkuswithrestassured.dto.UserResponse; + +/** + * Example REST for openapi-extender-restassured module. + * + * @author csaba.balogh + * @since 0.3.0 + */ +@Path("/example") +public class ExampleRest { + + @GET + @Path("/user/{id}") + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML, MediaType.APPLICATION_XML}) + public Response getUser(@PathParam("id") String userId) { + return switch (userId) { + case "BAD_REQUEST" -> Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Bad request", "Cause bad request")) + .build(); + case "INTERNAL_SERVER_ERROR" -> Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(new ErrorResponse("Internal server error", "Cause internal server error")) + .build(); + default -> Response.ok(new UserResponse("joe", "Joe Big")) + .build(); + }; + } + + @POST + @Path("/user") + @Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML, MediaType.APPLICATION_XML}) + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML, MediaType.APPLICATION_XML}) + public Response postUser(UserRequest userRequest) { + if (!userRequest.getPassword().equals(userRequest.getPasswordConfirmation())) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Passwords must match", "Cause passwords not match")) + .build(); + } + if (userRequest.getUsername().equals("bob")) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Username already exists", "Cause username already exists")) + .build(); + } + return Response.status(Response.Status.CREATED) + .entity(new UserResponse(userRequest.getUsername(), userRequest.getFullName())) + .build(); + } + +} diff --git a/samples/quarkus-with-restassured/src/test/java/org/rodnalsol/sample/quarkuswithrestassured/rest/ExampleRestIT.java b/samples/quarkus-with-restassured/src/test/java/org/rodnalsol/sample/quarkuswithrestassured/rest/ExampleRestIT.java new file mode 100644 index 0000000..c460aca --- /dev/null +++ b/samples/quarkus-with-restassured/src/test/java/org/rodnalsol/sample/quarkuswithrestassured/rest/ExampleRestIT.java @@ -0,0 +1,146 @@ +package org.rodnalsol.sample.quarkuswithrestassured.rest; + +import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.restassured.RestAssured; +import io.restassured.filter.log.RequestLoggingFilter; +import io.restassured.filter.log.ResponseLoggingFilter; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestClassOrder; +import org.junit.jupiter.api.TestMethodOrder; +import org.rodnalsol.openapi.extender.restassured.filter.OpenApiRequestFilter; +import org.rodnalsol.openapi.extender.restassured.filter.OpenApiResponseFilter; +import org.rodnalsol.sample.quarkuswithrestassured.dto.UserRequest; + +/** + * Integration test for {@link ExampleRest}. + * + * @author csaba.balogh + * @since 0.3.0 + */ +@QuarkusIntegrationTest +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +class ExampleRestIT { + + @Nested + @Order(1) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class GetUserIT { + + @Test + @Order(1) + void getUserOk() { + RestAssured + .given() + .filter(new RequestLoggingFilter()) + .filter(new ResponseLoggingFilter()) + .filter(new OpenApiResponseFilter("getUser", "Standard response")) + .pathParam("id", "GOOD") + .contentType(MediaType.APPLICATION_JSON) + .when() + .get("/example/user/{id}") + .then() + .statusCode(Response.Status.OK.getStatusCode()); + } + + @Test + @Order(2) + void getUserBadRequest() { + RestAssured + .given() + .filter(new RequestLoggingFilter()) + .filter(new ResponseLoggingFilter()) + .filter(new OpenApiResponseFilter("getUser", "Bad request")) + .pathParam("id", "BAD_REQUEST") + .contentType(MediaType.APPLICATION_JSON) + .when() + .get("/example/user/{id}") + .then() + .statusCode(Response.Status.BAD_REQUEST.getStatusCode()); + } + + @Test + @Order(3) + void getUserInternalServerError() { + RestAssured + .given() + .filter(new RequestLoggingFilter()) + .filter(new ResponseLoggingFilter()) + .filter(new OpenApiResponseFilter("getUser", "Internal server error")) + .pathParam("id", "INTERNAL_SERVER_ERROR") + .contentType(MediaType.APPLICATION_JSON) + .when() + .get("/example/user/{id}") + .then() + .statusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + } + + @Nested + @Order(2) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class PostUserIT { + + @Test + @Order(1) + void postUserOk() { + UserRequest userRequest = new UserRequest("joe", "password123", "password123", "Joe Big"); + RestAssured + .given() + .filter(new RequestLoggingFilter()) + .filter(new ResponseLoggingFilter()) + .filter(new OpenApiRequestFilter("postUser", "Standard request")) + .filter(new OpenApiResponseFilter("postUser", "Standard response")) + .contentType(MediaType.APPLICATION_JSON) + .body(userRequest) + .when() + .post("/example/user") + .then() + .statusCode(Response.Status.CREATED.getStatusCode()); + } + + @Test + @Order(2) + void postUserPasswordsDoesNotMatch() { + UserRequest userRequest = new UserRequest("joe", "password", "password123", "Joe Big"); + RestAssured + .given() + .filter(new RequestLoggingFilter()) + .filter(new ResponseLoggingFilter()) + .filter(new OpenApiRequestFilter("postUser", "Passwords does not match")) + .filter(new OpenApiResponseFilter("postUser", "Passwords does not match")) + .contentType(MediaType.APPLICATION_JSON) + .body(userRequest) + .when() + .post("/example/user") + .then() + .statusCode(Response.Status.BAD_REQUEST.getStatusCode()); + } + + @Test + @Order(3) + void postUserUsernameAlreadyExists() { + UserRequest userRequest = new UserRequest("bob", "password123", "password123", "Joe Big"); + RestAssured + .given() + .filter(new RequestLoggingFilter()) + .filter(new ResponseLoggingFilter()) + .filter(new OpenApiRequestFilter("postUser", "Username already exists")) + .filter(new OpenApiResponseFilter("postUser", "Username already exists")) + .contentType(MediaType.APPLICATION_JSON) + .body(userRequest) + .when() + .post("/example/user") + .then() + .statusCode(Response.Status.BAD_REQUEST.getStatusCode()); + } + } + +}