OpenApiResourceIT.java
package io.swagger.v3.jaxrs2.it;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.restassured.http.ContentType;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.jaxrs2.annotations.AbstractAnnotationTest;
import org.testng.SkipException;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.io.IOException;
import static io.restassured.RestAssured.given;
import static org.testng.Assert.assertTrue;
/**
* <p>
* An functional integration test that runs during maven's integration-test phase,
* uses RestAssured to define REST API tests, and Jetty's Maven plugin to serve a simple
* sample app just prior to the integration-test phase starting.
*/
public class OpenApiResourceIT extends AbstractAnnotationTest {
private static final String EXPECTED_JSON = "{\n" +
" \"openapi\": \"3.0.1\",\n" +
" \"paths\": {\n" +
" \"/cars/all\": {\n" +
" \"get\": {\n" +
" \"tags\": [\n" +
" \"cars\"\n" +
" ],\n" +
" \"description\": \"Return whole car\",\n" +
" \"operationId\": \"getAll\",\n" +
" \"responses\": {\n" +
" \"200\": {\n" +
" \"content\": {\n" +
" \"application/json\": {\n" +
" \"schema\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"$ref\": \"#/components/schemas/Car\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"/cars/summary\": {\n" +
" \"get\": {\n" +
" \"tags\": [\n" +
" \"cars\"\n" +
" ],\n" +
" \"description\": \"Return car summaries\",\n" +
" \"operationId\": \"getSummaries\",\n" +
" \"responses\": {\n" +
" \"200\": {\n" +
" \"content\": {\n" +
" \"application/json\": {\n" +
" \"schema\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"$ref\": \"#/components/schemas/Car_Summary\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"/cars/detail\": {\n" +
" \"get\": {\n" +
" \"tags\": [\n" +
" \"cars\"\n" +
" ],\n" +
" \"description\": \"Return car detail\",\n" +
" \"operationId\": \"getDetails\",\n" +
" \"responses\": {\n" +
" \"200\": {\n" +
" \"content\": {\n" +
" \"application/json\": {\n" +
" \"schema\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"$ref\": \"#/components/schemas/Car_Detail\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"/cars/sale\": {\n" +
" \"get\": {\n" +
" \"tags\": [\n" +
" \"cars\"\n" +
" ],\n" +
" \"operationId\": \"getSaleSummaries\",\n" +
" \"responses\": {\n" +
" \"default\": {\n" +
" \"description\": \"default response\",\n" +
" \"content\": {\n" +
" \"application/json\": {\n" +
" \"schema\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"$ref\": \"#/components/schemas/Car_Summary-or-Sale\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"/files/upload\": {\n" +
" \"post\": {\n" +
" \"operationId\": \"uploadFile\",\n" +
" \"requestBody\": {\n" +
" \"content\": {\n" +
" \"multipart/form-data\": {\n" +
" \"schema\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"fileIdRenamed\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"fileRenamed\": {\n" +
" \"type\": \"string\",\n" +
" \"format\": \"binary\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"responses\": {\n" +
" \"default\": {\n" +
" \"description\": \"default response\",\n" +
" \"content\": {\n" +
" \"application/json\": {\n" +
" \n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"/files/attach\": {\n" +
" \"put\": {\n" +
" \"operationId\": \"putFile\",\n" +
" \"requestBody\": {\n" +
" \"content\": {\n" +
" \"application/octet-stream\": {\n" +
" \"schema\": {\n" +
" \"type\": \"string\",\n" +
" \"format\": \"binary\"\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"responses\": {\n" +
" \"default\": {\n" +
" \"description\": \"default response\",\n" +
" \"content\": {\n" +
" \"application/json\": {\n" +
" \n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"/users/add\": {\n" +
" \"post\": {\n" +
" \"operationId\": \"addUser\",\n" +
" \"requestBody\": {\n" +
" \"content\": {\n" +
" \"application/x-www-form-urlencoded\": {\n" +
" \"schema\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"id\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"name\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"gender\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"responses\": {\n" +
" \"default\": {\n" +
" \"description\": \"default response\",\n" +
" \"content\": {\n" +
" \"application/json\": {\n" +
" \n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"/widgets/{widgetId}\": {\n" +
" \"get\": {\n" +
" \"tags\": [\n" +
" \"widgets\"\n" +
" ],\n" +
" \"summary\": \"Find pet by ID\",\n" +
" \"description\": \"Returns a pet when ID <= 10. ID > 10 or nonintegers will simulate API error conditions\",\n" +
" \"operationId\": \"getWidget\",\n" +
" \"parameters\": [\n" +
" {\n" +
" \"name\": \"widgetId\",\n" +
" \"in\": \"path\",\n" +
" \"required\": true,\n" +
" \"schema\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" }\n" +
" ],\n" +
" \"responses\": {\n" +
" \"200\": {\n" +
" \"description\": \"Returns widget with matching id\",\n" +
" \"content\": {\n" +
" \"application/json\": {\n" +
" \"schema\": {\n" +
" \"$ref\": \"#/components/schemas/Widget\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" \"components\": {\n" +
" \"schemas\": {\n" +
" \"Tire_Detail\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"condition\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"brand\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"Car\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"model\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"tires\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"$ref\": \"#/components/schemas/Tire\"\n" +
" }\n" +
" },\n" +
" \"price\": {\n" +
" \"type\": \"integer\",\n" +
" \"format\": \"int32\"\n" +
" },\n" +
" \"color\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"manufacture\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"Car_Summary-or-Sale\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"model\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"price\": {\n" +
" \"type\": \"integer\",\n" +
" \"format\": \"int32\"\n" +
" },\n" +
" \"color\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"manufacture\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"Car_Detail\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"model\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"tires\": {\n" +
" \"type\": \"array\",\n" +
" \"items\": {\n" +
" \"$ref\": \"#/components/schemas/Tire_Detail\"\n" +
" }\n" +
" },\n" +
" \"color\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"manufacture\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"Widget\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"a\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"b\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"id\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"Car_Summary\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"model\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"color\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"manufacture\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"Tire\": {\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"condition\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"brand\": {\n" +
" \"type\": \"string\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
private static final String EXPECTED_YAML = "openapi: 3.0.1\n" +
"paths:\n" +
" /cars/all:\n" +
" get:\n" +
" tags:\n" +
" - cars\n" +
" description: Return whole car\n" +
" operationId: getAll\n" +
" responses:\n" +
" \"200\":\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" type: array\n" +
" items:\n" +
" $ref: \"#/components/schemas/Car\"\n" +
" /cars/detail:\n" +
" get:\n" +
" tags:\n" +
" - cars\n" +
" description: Return car detail\n" +
" operationId: getDetails\n" +
" responses:\n" +
" \"200\":\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" type: array\n" +
" items:\n" +
" $ref: \"#/components/schemas/Car_Detail\"\n" +
" /cars/sale:\n" +
" get:\n" +
" tags:\n" +
" - cars\n" +
" operationId: getSaleSummaries\n" +
" responses:\n" +
" default:\n" +
" description: default response\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" type: array\n" +
" items:\n" +
" $ref: \"#/components/schemas/Car_Summary-or-Sale\"\n" +
" /cars/summary:\n" +
" get:\n" +
" tags:\n" +
" - cars\n" +
" description: Return car summaries\n" +
" operationId: getSummaries\n" +
" responses:\n" +
" \"200\":\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" type: array\n" +
" items:\n" +
" $ref: \"#/components/schemas/Car_Summary\"\n" +
" /files/attach:\n" +
" put:\n" +
" operationId: putFile\n" +
" requestBody:\n" +
" content:\n" +
" application/octet-stream:\n" +
" schema:\n" +
" type: string\n" +
" format: binary\n" +
" responses:\n" +
" default:\n" +
" description: default response\n" +
" content:\n" +
" application/json: {}\n" +
" /files/upload:\n" +
" post:\n" +
" operationId: uploadFile\n" +
" requestBody:\n" +
" content:\n" +
" multipart/form-data:\n" +
" schema:\n" +
" type: object\n" +
" properties:\n" +
" fileIdRenamed:\n" +
" type: string\n" +
" fileRenamed:\n" +
" type: string\n" +
" format: binary\n" +
" responses:\n" +
" default:\n" +
" description: default response\n" +
" content:\n" +
" application/json: {}\n" +
" /users/add:\n" +
" post:\n" +
" operationId: addUser\n" +
" requestBody:\n" +
" content:\n" +
" application/x-www-form-urlencoded:\n" +
" schema:\n" +
" type: object\n" +
" properties:\n" +
" gender:\n" +
" type: string\n" +
" id:\n" +
" type: string\n" +
" name:\n" +
" type: string\n" +
" responses:\n" +
" default:\n" +
" description: default response\n" +
" content:\n" +
" application/json: {}\n" +
" /widgets/{widgetId}:\n" +
" get:\n" +
" tags:\n" +
" - widgets\n" +
" summary: Find pet by ID\n" +
" description: Returns a pet when ID <= 10. ID > 10 or nonintegers will simulate\n" +
" API error conditions\n" +
" operationId: getWidget\n" +
" parameters:\n" +
" - name: widgetId\n" +
" in: path\n" +
" required: true\n" +
" schema:\n" +
" type: string\n" +
" responses:\n" +
" \"200\":\n" +
" description: Returns widget with matching id\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" $ref: \"#/components/schemas/Widget\"\n" +
"components:\n" +
" schemas:\n" +
" Car:\n" +
" type: object\n" +
" properties:\n" +
" color:\n" +
" type: string\n" +
" manufacture:\n" +
" type: string\n" +
" model:\n" +
" type: string\n" +
" price:\n" +
" type: integer\n" +
" format: int32\n" +
" tires:\n" +
" type: array\n" +
" items:\n" +
" $ref: \"#/components/schemas/Tire\"\n" +
" Car_Detail:\n" +
" type: object\n" +
" properties:\n" +
" color:\n" +
" type: string\n" +
" manufacture:\n" +
" type: string\n" +
" model:\n" +
" type: string\n" +
" tires:\n" +
" type: array\n" +
" items:\n" +
" $ref: \"#/components/schemas/Tire_Detail\"\n" +
" Car_Summary:\n" +
" type: object\n" +
" properties:\n" +
" color:\n" +
" type: string\n" +
" manufacture:\n" +
" type: string\n" +
" model:\n" +
" type: string\n" +
" Car_Summary-or-Sale:\n" +
" type: object\n" +
" properties:\n" +
" color:\n" +
" type: string\n" +
" manufacture:\n" +
" type: string\n" +
" model:\n" +
" type: string\n" +
" price:\n" +
" type: integer\n" +
" format: int32\n" +
" Tire:\n" +
" type: object\n" +
" properties:\n" +
" brand:\n" +
" type: string\n" +
" condition:\n" +
" type: string\n" +
" Tire_Detail:\n" +
" type: object\n" +
" properties:\n" +
" brand:\n" +
" type: string\n" +
" condition:\n" +
" type: string\n" +
" Widget:\n" +
" type: object\n" +
" properties:\n" +
" a:\n" +
" type: string\n" +
" b:\n" +
" type: string\n" +
" id:\n" +
" type: string\n";
private static final int jettyPort = System.getProperties().containsKey("jetty.port") ? Integer.parseInt(System.getProperty("jetty.port")): -1;
@BeforeMethod
public void checkJetty() {
if (jettyPort == -1) {
throw new SkipException("Jetty not configured");
}
}
@Test
public void testSwaggerJson() throws Exception {
final String actualBody = given()
.port(jettyPort)
.log().all()
.when()
.get("/openapi.json")
.then()
.log().all()
.assertThat()
.statusCode(200)
.contentType(ContentType.JSON)
.extract()
.response().body().asString();
compareAsJson(formatJson(actualBody), EXPECTED_JSON);
}
@Test
public void testSwaggerJsonUsingAcceptHeader() throws Exception {
final String actualBody = given()
.port(jettyPort)
.log().all()
.accept(ContentType.JSON)
.when()
.get("/openapi")
.then()
.log().all()
.assertThat()
.statusCode(200)
.contentType(ContentType.JSON)
.extract().response().body().asString();
compareAsJson(formatJson(actualBody), EXPECTED_JSON);
}
@Test
public void testSwaggerYaml() throws Exception {
final String actualBody = given()
.port(jettyPort)
.log().all()
.when()
.get("/openapi.yaml")
.then()
.log().all()
.assertThat()
.statusCode(200)
.contentType("application/yaml")
.extract().response().body().asString();
compareAsYaml(formatYaml(actualBody), EXPECTED_YAML);
}
@Test
public void testSwaggerYamlUsingAcceptHeader() throws Exception {
final String actualBody = given()
.port(jettyPort)
.log().all()
.accept("application/yaml")
.when()
.get("/openapi")
.then()
.log().all()
.assertThat()
.statusCode(200)
.contentType("application/yaml")
.extract().response().body().asString();
compareAsYaml(formatYaml(actualBody), EXPECTED_YAML);
}
@Test
public void testYamlOpenAPI31() throws Exception {
final String actualBody = given()
.port(jettyPort)
.log().all()
.accept("application/yaml")
.when()
.get("/oas/openapi31")
.then()
.log().all()
.assertThat()
.statusCode(200)
.contentType("application/yaml")
.extract().response().body().asString();
assertTrue(actualBody.contains("openapi: 3.1.0"));
}
@Test
public void testServletOpenAPI31() throws Exception {
final String actualBody = given()
.port(jettyPort)
.log().all()
.when()
.get("/oas/openapi.yaml")
.then()
.log().all()
.assertThat()
.statusCode(200)
.contentType("application/yaml")
.extract().response().body().asString();
assertTrue(actualBody.contains("openapi: 3.1.0"));
}
@Test
public void testYamlOpenAPI31WithBootstrapServlet() throws Exception {
final String actualBody = given()
.port(jettyPort)
.log().all()
.when()
.get("/bootstrap")
.then()
.log().all()
.assertThat()
.statusCode(200)
.extract().response().body().asString();
assertTrue(actualBody.contains("openapi: 3.1.0"));
}
private String formatYaml(String source) throws IOException {
return Yaml.mapper().configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
.writerWithDefaultPrettyPrinter()
.writeValueAsString(Yaml.mapper().readValue(source, Object.class));
}
private String formatJson(String source) throws IOException {
return Json.mapper().configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
.writerWithDefaultPrettyPrinter()
.writeValueAsString(Json.mapper().readValue(source, Object.class));
}
}