Skip to content

Commit

Permalink
fix: Fix parsing swagger2 by API Catalog (#2876)
Browse files Browse the repository at this point in the history
* fix

* fix in case an error is thrown (i.e. class is not defined)

* basic test to verify the API doc service is parsing the document

* extend unit test for object verification
  • Loading branch information
pj892031 authored Apr 20, 2023
1 parent 895c11a commit cc45774
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 10 deletions.
5 changes: 3 additions & 2 deletions .run/spring-ApiCatalogApplication.run.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration name="spring-ApiCatalogApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
<configuration default="false" name="spring-ApiCatalogApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
<module name="api-layer.api-catalog-services.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="org.zowe.apiml.apicatalog.ApiCatalogApplication" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="SHORTEN_COMMAND_LINE" value="CLASSPATH_FILE" />
<additionalParameters>
<param>
<option name="enabled" value="true" />
Expand All @@ -14,4 +15,4 @@
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
</component>
3 changes: 2 additions & 1 deletion .run/spring-DiscoveryServiceApplication.run.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration name="spring-DiscoveryServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
<configuration default="false" name="spring-DiscoveryServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
<module name="api-layer.discovery-service.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="org.zowe.apiml.discovery.DiscoveryServiceApplication" />
<option name="ACTIVE_PROFILES" value="https" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="SHORTEN_COMMAND_LINE" value="CLASSPATH_FILE" />
<additionalParameters>
<param>
<option name="enabled" value="true" />
Expand Down
2 changes: 1 addition & 1 deletion api-catalog-services/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ dependencies {
annotationProcessor libraries.lombok
annotationProcessor libraries.spring_boot_configuration_processor

implementation group: 'io.swagger', name: 'swagger-models', version: '1.6.2'
implementation group: 'io.swagger', name: 'swagger-models', version: swaggerCoreVersion
implementation(libraries.spring_boot_starter_web) {
exclude group: "org.yaml", module: "snakeyaml"
exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-el"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ public String getDefaultApiDocForService(final String serviceId) {
CachedApiDocService.serviceApiDocs.put(new ApiDocCacheKey(serviceId, DEFAULT_API_KEY), apiDoc);
return apiDoc;
}
} catch (Exception e) {
log.debug("Exception updating default API doc in cache for '{}'.", serviceId, e);
} catch (Throwable t) {
log.debug("Exception updating default API doc in cache for '{}'.", serviceId, t);
}

// if no DS is available try to use cached data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.swagger.models.*;
import org.apache.commons.io.IOUtils;
import org.hamcrest.collection.IsMapContaining;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
Expand All @@ -22,6 +23,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.core.io.ClassPathResource;
import org.springframework.test.util.ReflectionTestUtils;
import org.zowe.apiml.apicatalog.services.cached.model.ApiDocInfo;
import org.zowe.apiml.config.ApiInfo;
Expand All @@ -33,13 +35,16 @@

import javax.validation.UnexpectedTypeException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
Expand Down Expand Up @@ -331,6 +336,35 @@ void givenApimlHiddenTag_thenShouldBeSameDescriptionAndPaths() {

assertThat(actualSwagger.getPaths(), is(dummySwaggerObject.getPaths()));
}

private void verifySwagger2(Swagger swagger) {
assertEquals("APIML test API", swagger.getInfo().getTitle());
assertEquals("Example of GET endpoint", swagger.getPaths().get("/api/v1/").getGet().getSummary());
assertEquals("exampleResponse200", swagger.getDefinitions().entrySet().iterator().next().getKey());
}

@Test
void givenInputFile_thenParseItCorrectly() throws IOException {
GatewayConfigProperties gatewayConfigProperties = GatewayConfigProperties.builder().scheme("https").hostname("localhost").build();
GatewayClient gatewayClient = new GatewayClient(gatewayConfigProperties);

AtomicReference<Swagger> swaggerHolder = new AtomicReference<>();
ApiDocV2Service apiDocV2Service = new ApiDocV2Service(gatewayClient) {
@Override
protected void updateExternalDoc(Swagger swagger, ApiDocInfo apiDocInfo) {
super.updateExternalDoc(swagger, apiDocInfo);
swaggerHolder.set(swagger);
}
};
String transformed = apiDocV2Service.transformApiDoc("serviceId", new ApiDocInfo(
mock(ApiInfo.class),
IOUtils.toString(new ClassPathResource("swagger/swagger2.json").getInputStream(), StandardCharsets.UTF_8),
mock(RoutedServices.class)
));
assertNotNull(transformed);
verifySwagger2(swaggerHolder.get());
}

}

private String convertSwaggerToJson(Swagger swagger) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.tags.Tag;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.test.util.ReflectionTestUtils;
import org.zowe.apiml.apicatalog.services.cached.model.ApiDocInfo;
import org.zowe.apiml.config.ApiInfo;
Expand All @@ -33,12 +35,15 @@

import javax.validation.UnexpectedTypeException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;

class ApiDocV3ServiceTest {

Expand Down Expand Up @@ -212,6 +217,36 @@ void givenValidApiDoc_thenDontCapitalizeEnums() {
assertThat(actualContent, containsString("\"style\":\"form\""));
assertThat(actualContent, not(containsString("\"style\":\"FORM\"")));
}

private void verifyOpenApi3(OpenAPI openAPI) {
assertEquals("Sample of OpenAPI v3", openAPI.getInfo().getTitle());
assertEquals("Main server", openAPI.getServers().get(0).getDescription());
assertEquals("receive", openAPI.getPaths().get("/service/api/v1/endpoint").getPost().getOperationId());
assertNotNull(openAPI.getPaths().get("/service/api/v1/endpoint").getPost().getResponses().get("204"));
}

@Test
void givenInputFile_thenParseItCorrectly() throws IOException {
GatewayConfigProperties gatewayConfigProperties = GatewayConfigProperties.builder().scheme("https").hostname("localhost").build();
GatewayClient gatewayClient = new GatewayClient(gatewayConfigProperties);

AtomicReference<OpenAPI> openApiHolder = new AtomicReference<>();
ApiDocV3Service apiDocV3Service = new ApiDocV3Service(gatewayClient) {
@Override
protected void updateExternalDoc(OpenAPI openAPI, ApiDocInfo apiDocInfo) {
super.updateExternalDoc(openAPI, apiDocInfo);
openApiHolder.set(openAPI);
}
};
String transformed = apiDocV3Service.transformApiDoc("serviceId", new ApiDocInfo(
mock(ApiInfo.class),
IOUtils.toString(new ClassPathResource("swagger/openapi3.json").getInputStream(), StandardCharsets.UTF_8),
mock(RoutedServices.class)
));
assertNotNull(transformed);
verifyOpenApi3(openApiHolder.get());
}

}

private String convertOpenApiToJson(OpenAPI openApi) {
Expand Down
81 changes: 81 additions & 0 deletions api-catalog-services/src/test/resources/swagger/openapi3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"openapi": "3.0.1",
"info": {
"title": "Sample of OpenAPI v3",
"description": "For testing purposes - parse the doc file",
"version": "1.0.0"
},
"servers": [
{
"url": "https://localhost:8081/service/api/v1",
"description": "Main server"
}
],
"tags": [
{
"name": "tagName",
"description": "First tag in the list"
}
],
"paths": {
"/endpoint": {
"post": {
"tags": [
"Receive data"
],
"summary": "This endpoint returns a data",
"operationId": "receive",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Response",
"exampleSetFlag": false
}
}
}
},
"responses": {
"204": {
"description": "Successful - data were returned"
},
"400": {
"description": "Invalid request"
}
},
"security": [
{
"basicAuth": []
}
]
}
}
},
"components": {
"schemas": {
"Response": {
"title": "Response object",
"type": "object",
"properties": {
"output": {
"type": "string",
"description": "data",
"exampleSetFlag": false
}
},
"exampleSetFlag": false
}
},
"securitySchemes": {
"basicAuth": {
"type": "http",
"scheme": "basic"
},
"bearerAuth": {
"type": "http",
"scheme": "bearer"
}
},
"extensions": {}
}
}
74 changes: 74 additions & 0 deletions api-catalog-services/src/test/resources/swagger/swagger2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"swagger": "2.0",
"info": {
"description": "Example of Swagger for testing",
"version": "1.0",
"title": "APIML test API"
},
"basePath": "/api/v1",
"tags": [
{
"name": "firstTag"
}
],
"securityDefinitions": {
"BasicAuth": {
"type": "basic"
},
"BearerAuth": {
"type": "apiKey",
"in": "header",
"name": "Authorization"
}
},
"security": [
{
"BasicAuth": []
},
{
"BearerAuth": []
}
],
"paths": {
"/": {
"get": {
"tags": [
"List"
],
"summary": "Example of GET endpoint",
"description": "Only for testing",
"operationId": "getData",
"produces": [
"application/json"
],
"parameters": [],
"responses": {
"200": {
"description": "Successful operation",
"schema": {
"$ref": "#/definitions/exampleResponse200"
}
},
"500": {
"description": "Unexpected internal error. Contact your support"
}
}
}
}
},
"definitions": {
"exampleResponse200": {
"type": "object",
"required": [
"action"
],
"properties": {
"output": {
"type": "string",
"description": "Contains the sample response test",
"example": "success"
}
}
}
}
}
8 changes: 4 additions & 4 deletions gradle/versions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ ext {
springFoxVersion = '2.9.2'
springDocVersion = '1.6.9'
spring4Version = '4.3.7.RELEASE' // Used within PJE in tests
swagger3CoreVersion = '2.0.0'
swagger3ParserVersion = '2.1.9'
swaggerCoreVersion = '1.5.24'
swaggerInflector = '2.0.8'
swagger3CoreVersion = '2.2.8'
swagger3ParserVersion = '2.1.12'
swaggerCoreVersion = '1.6.10'
swaggerInflector = '2.0.9'
swaggerJerseyJaxrsVersion = '1.5.10'
thymeleafVersion = '3.0.15.RELEASE'
tomcatVersion = '9.0.69'
Expand Down

0 comments on commit cc45774

Please sign in to comment.