-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[plugins/openapi-karate] adds OpenAPIKarateGenerator
- Loading branch information
Showing
19 changed files
with
507 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Spring WebTestClient Generator | ||
> 👉 ZenWave360 Helps You Create Software Easy to Understand | ||
Generates test for KarateDSL based on OpenAPI and Arazzo specifications. | ||
|
||
```shell | ||
jbang zw -p io.zenwave360.sdk.plugins.OpenAPIKaratePlugin \ | ||
specFile=src/main/resources/model/openapi.yml \ | ||
targetFolder=src/test/java \ | ||
testsPackage=io.zenwave360.example.adapters.web.tests \ | ||
groupBy=service | ||
``` | ||
|
||
```shell | ||
jbang zw -p io.zenwave360.sdk.plugins.OpenAPIKaratePlugin \ | ||
specFile=src/main/resources/model/openapi.yml \ | ||
targetFolder=src/test/java \ | ||
testsPackage=io.zenwave360.example.adapters.web.tests \ | ||
groupBy=businessFlow \ | ||
businessFlowTestName=CustomerCRUDTest \ | ||
operationIds=createCustomer,getCustomer,updateCustomer,deleteCustomer | ||
``` | ||
|
||
## Options | ||
|
||
| **Option** | **Description** | **Type** | **Default** | **Values** | | ||
|--------------------------------|------------------------------------------------------------------------------|-------------|----------------------------------------------------------|-------------------------------------------| | ||
| `specFile` | API Specification File | URI | | | | ||
| `targetFolder` | Target folder to generate code to. If left empty, it will print to stdout. | File | | | | ||
| `basePackage` | Applications base package | String | | | | ||
| `testsPackage` | Package name for generated tests | String | {{basePackage}}.adapters.web.tests | | | ||
| `groupBy` | Generate test classes grouped by | GroupByType | service | service, operation, partial, businessFlow | | ||
| `operationIds` | OpenAPI operationIds to generate code for | List | [] | | | ||
| `businessFlowTestName` | Business Flow Test name | String | | | | ||
|
||
## Getting Help | ||
|
||
```shell | ||
jbang zw -p io.zenwave360.sdk.plugins.OpenAPIKaratePlugin --help | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>io.github.zenwave360.zenwave-sdk</groupId> | ||
<artifactId>plugins-parent</artifactId> | ||
<version>1.7.0-SNAPSHOT</version> | ||
</parent> | ||
<name>${project.groupId}:${project.artifactId}</name> | ||
<groupId>io.github.zenwave360.zenwave-sdk.plugins</groupId> | ||
<artifactId>openapi-karate</artifactId> | ||
<packaging>jar</packaging> | ||
|
||
</project> |
153 changes: 153 additions & 0 deletions
153
plugins/openapi-karate/src/main/java/io/zenwave360/sdk/plugins/OpenAPIKarateGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package io.zenwave360.sdk.plugins; | ||
|
||
import java.util.*; | ||
import java.util.stream.Collectors; | ||
|
||
import io.zenwave360.sdk.generators.JsonSchemaToJsonFaker; | ||
import io.zenwave360.sdk.options.WebFlavorType; | ||
import io.zenwave360.sdk.utils.JSONPath; | ||
import io.zenwave360.sdk.utils.NamingUtils; | ||
import org.apache.commons.lang3.ObjectUtils; | ||
import org.apache.commons.lang3.StringUtils; | ||
|
||
import io.zenwave360.sdk.doc.DocumentedOption; | ||
import io.zenwave360.sdk.generators.AbstractOpenAPIGenerator; | ||
import io.zenwave360.sdk.parsers.Model; | ||
import io.zenwave360.sdk.templating.HandlebarsEngine; | ||
import io.zenwave360.sdk.templating.TemplateEngine; | ||
import io.zenwave360.sdk.templating.TemplateInput; | ||
import io.zenwave360.sdk.templating.TemplateOutput; | ||
|
||
import static io.zenwave360.sdk.templating.OutputFormatType.GERKIN; | ||
import static io.zenwave360.sdk.templating.OutputFormatType.JAVA; | ||
import static org.apache.commons.lang3.ObjectUtils.firstNonNull; | ||
|
||
public class OpenAPIKarateGenerator extends AbstractOpenAPIGenerator { | ||
|
||
enum GroupByType { | ||
service, operation, businessFlow | ||
} | ||
|
||
public String apiProperty = "api"; | ||
|
||
@DocumentedOption(description = "Package name for generated Karate tests") | ||
public String testsPackage = "{{basePackage}}.adapters.web"; | ||
|
||
public boolean simpleDomainPackaging = false; | ||
|
||
@DocumentedOption(description = "Generate features grouped by", required = true) | ||
public GroupByType groupBy = GroupByType.service; | ||
|
||
@DocumentedOption(description = "Business Flow Feature name") | ||
public String businessFlowTestName; | ||
|
||
private HandlebarsEngine handlebarsEngine = new HandlebarsEngine(); | ||
|
||
private JsonSchemaToJsonFaker jsonSchemaToJsonFaker = new JsonSchemaToJsonFaker(); | ||
|
||
private final String prefix = "io/zenwave360/sdk/plugins/OpenAPIKarateGenerator/"; | ||
private final TemplateInput partialTemplate = new TemplateInput(prefix + "partials/Operation.feature", "src/test/resources/{{asPackageFolder testsPackage}}/Operation.feature"); | ||
// private final TemplateInput testSetTemplate = new TemplateInput(prefix + "ControllersTestSet.java", "{{asPackageFolder testsPackage}}/ControllersTestSet.java").withMimeType(JAVA); | ||
|
||
private final TemplateInput businessFlowTestTemplate = new TemplateInput(prefix + "BusinessFlowTest.feature", "src/test/resources/{{asPackageFolder testsPackage}}/{{businessFlowTestName}}.feature").withMimeType(GERKIN); | ||
private final TemplateInput serviceTestTemplate = new TemplateInput(prefix + "Service.feature", "src/test/resources/{{asPackageFolder testsPackage}}/{{serviceName}}.feature").withMimeType(GERKIN); | ||
private final TemplateInput operationTestTemplate = new TemplateInput(prefix + "Operation.feature", "src/test/resources/{{asPackageFolder testsPackage}}/{{serviceName}}/{{asJavaTypeName operationId}}.feature").withMimeType(GERKIN); | ||
|
||
@Override | ||
public void onPropertiesSet() { | ||
if(basePackage == null) { | ||
basePackage = testsPackage; | ||
} | ||
if (simpleDomainPackaging) { | ||
testsPackage = "{{basePackage}}"; | ||
} | ||
} | ||
|
||
public TemplateEngine getTemplateEngine() { | ||
return handlebarsEngine; | ||
} | ||
|
||
Model getApiModel(Map<String, Object> contextModel) { | ||
return (Model) contextModel.get(apiProperty); | ||
} | ||
|
||
@Override | ||
public List<TemplateOutput> generate(Map<String, Object> contextModel) { | ||
List<TemplateOutput> templateOutputList = new ArrayList<>(); | ||
Model apiModel = getApiModel(contextModel); | ||
Map<String, List<Map<String, Object>>> operationsByTag = getOperationsGroupedByTag(apiModel); | ||
|
||
if (groupBy == GroupByType.businessFlow) { | ||
List<Map<String, Object>> operations = getOperationsByOperationIds(apiModel, operationIds); | ||
templateOutputList.add(generateTemplateOutput(contextModel, businessFlowTestTemplate, null, operations)); | ||
} | ||
|
||
if (groupBy == GroupByType.service || groupBy == GroupByType.operation) { | ||
|
||
List<String> includedTestNames = new ArrayList<>(); | ||
List<String> includedImports = new ArrayList<>(); | ||
|
||
for (Map.Entry<String, List<Map<String, Object>>> entry : operationsByTag.entrySet()) { | ||
String serviceName = apiServiceName(entry.getKey()); | ||
if(groupBy == GroupByType.service) { | ||
includedTestNames.add(serviceName); | ||
templateOutputList.add(generateTemplateOutput(contextModel, serviceTestTemplate, serviceName, entry.getValue())); | ||
} else { | ||
List<Map<String, Object>> operations = entry.getValue(); | ||
includedTestNames.addAll(operations.stream().map(o -> NamingUtils.asJavaTypeName((String) o.get("operationId"))).collect(Collectors.toList())); | ||
includedImports.add(serviceName); | ||
for (Map<String, Object> operation : operations) { | ||
templateOutputList.add(generateTemplateOutput(contextModel, operationTestTemplate, serviceName, List.of(operation))); | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
return templateOutputList; | ||
} | ||
|
||
{ | ||
handlebarsEngine.getHandlebars().registerHelper("requestExample", (operation, options) -> { | ||
return jsonSchemaToJsonFaker.generateExampleAsJson((Map) operation); | ||
}); | ||
handlebarsEngine.getHandlebars().registerHelper("karatePath", (operation, options) -> { | ||
String path = JSONPath.get(operation, "x--path"); | ||
return String.join("", "'", path, "'") | ||
.replace("{", "', pathParams.") | ||
.replace("}", ", '") | ||
.replace(", ''", ""); | ||
}); | ||
|
||
handlebarsEngine.getHandlebars().registerHelper("queryParams", (operation, options) | ||
-> JSONPath.get(operation, "parameters", Collections.<Map>emptyList()).stream().filter(p -> "query" .equals(p.get("in"))).collect(Collectors.toList())); | ||
|
||
handlebarsEngine.getHandlebars().registerHelper("pathParams", (operation, options) | ||
-> JSONPath.get(operation, "parameters", Collections.<Map>emptyList()).stream().filter(p -> "path" .equals(p.get("in"))).collect(Collectors.toList())); | ||
|
||
handlebarsEngine.getHandlebars().registerHelper("paramsExample", (params, options) -> { | ||
return ((Collection<Map>) params).stream() | ||
.map(p -> p.get("name") + ": " + firstNonNull(p.get("example"), jsonSchemaToJsonFaker.generateExample((Map<String, Object>) p.get("schema")))) | ||
.collect(Collectors.joining(", ")); | ||
}); | ||
|
||
} | ||
|
||
private String apiServiceName(String tag) { | ||
return NamingUtils.asJavaTypeName(tag) + "Api"; | ||
} | ||
|
||
public TemplateOutput generateTemplateOutput(Map<String, Object> contextModel, TemplateInput template, String serviceName, List<Map<String, Object>> operations) { | ||
Map<String, Object> model = new HashMap<>(); | ||
model.putAll(this.asConfigurationMap()); | ||
model.put("context", contextModel); | ||
model.put("openapi", getApiModel(contextModel)); | ||
model.put("serviceName", serviceName); | ||
model.put("operations", operations); | ||
if(operations != null && operations.size() == 1) { | ||
model.put("operationId", operations.get(0).get("operationId")); | ||
model.put("operation", operations.get(0)); | ||
} | ||
return getTemplateEngine().processTemplate(model, template).get(0); | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
plugins/openapi-karate/src/main/java/io/zenwave360/sdk/plugins/OpenAPIKaratePlugin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package io.zenwave360.sdk.plugins; | ||
|
||
import io.zenwave360.sdk.Plugin; | ||
import io.zenwave360.sdk.doc.DocumentedPlugin; | ||
import io.zenwave360.sdk.parsers.DefaultYamlParser; | ||
import io.zenwave360.sdk.processors.OpenApiProcessor; | ||
import io.zenwave360.sdk.writers.TemplateFileWriter; | ||
import io.zenwave360.sdk.writers.TemplateStdoutWriter; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import static io.zenwave360.sdk.plugins.OpenAPIKarateGenerator.GroupByType.businessFlow; | ||
|
||
@DocumentedPlugin(value = "Generates test for SpringMVC or Spring WebFlux using WebTestClient based on OpenAPI specification.", shortCode = "spring-webtestclient") | ||
public class OpenAPIKaratePlugin extends Plugin { | ||
|
||
private Logger log = LoggerFactory.getLogger(getClass()); | ||
public OpenAPIKaratePlugin() { | ||
super(); | ||
withChain(DefaultYamlParser.class, OpenApiProcessor.class, OpenAPIKarateGenerator.class, /* JavaFormatter.class, */ TemplateFileWriter.class); | ||
} | ||
|
||
@Override | ||
public <T extends Plugin> T processOptions() { | ||
|
||
if(hasOption("groupBy", businessFlow) && !hasOption("businessFlowTestName")) { | ||
log.info("Business flow test name option 'businessFlowTestName' not provided. Printing to stdout."); | ||
replaceInChain(TemplateFileWriter.class, TemplateStdoutWriter.class); | ||
withOption("businessFlowTestName", "BusinessFlowTest"); | ||
} | ||
withOption("DefaultYamlParser.specFile", StringUtils.firstNonBlank((String) getOptions().get("openapiFile"), this.getSpecFile())); | ||
return (T) this; | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
...n/resources/io/zenwave360/sdk/plugins/OpenAPIKarateGenerator/BusinessFlowTest.feature.hbs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
@openapi-file={{openapiFile}} | ||
Feature: {{businessFlowTestName}} | ||
|
||
Background: | ||
* url baseUrl | ||
# * def auth = { username: '', password: '' } | ||
* def authHeader = call read('classpath:karate-auth.js') auth | ||
* configure headers = authHeader || {} | ||
|
||
@business-flow | ||
@operationId={{operationIds}} | ||
Scenario: {{businessFlowTestName}} | ||
|
||
{{#each operations as |operation| ~}} | ||
# {{operation.operationId}} | ||
{{~> (partial 'partials/Operation.feature')}} | ||
|
||
{{/each~}} |
15 changes: 15 additions & 0 deletions
15
...src/main/resources/io/zenwave360/sdk/plugins/OpenAPIKarateGenerator/Operation.feature.hbs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
@openapi-file={{openapiFile}} | ||
Feature: {{operationId}} | ||
|
||
Background: | ||
* url baseUrl | ||
# * def auth = { username: '', password: '' } | ||
* def authHeader = call read('classpath:karate-auth.js') auth | ||
* configure headers = authHeader || {} | ||
|
||
{{#each operations as |operation| ~}} | ||
@operationId={{operationId}} | ||
Scenario: {{operationId}} | ||
{{> (partial 'partials/Operation.feature')}} | ||
|
||
{{/each~}} |
15 changes: 15 additions & 0 deletions
15
...e/src/main/resources/io/zenwave360/sdk/plugins/OpenAPIKarateGenerator/Service.feature.hbs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
@openapi-file={{openapiFile}} | ||
Feature: {{serviceName}} | ||
|
||
Background: | ||
* url baseUrl | ||
# * def auth = { username: '', password: '' } | ||
* def authHeader = call read('classpath:karate-auth.js') auth | ||
* configure headers = authHeader || {} | ||
|
||
{{#each operations as |operation| ~}} | ||
@operationId={{operationId}} | ||
Scenario: {{operationId}} | ||
{{> (partial 'partials/Operation.feature')}} | ||
|
||
{{/each~}} |
21 changes: 21 additions & 0 deletions
21
...resources/io/zenwave360/sdk/plugins/OpenAPIKarateGenerator/partials/Operation.feature.hbs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{{~#if (pathParams operation)}} | ||
* def pathParams = { {{{paramsExample (pathParams operation)}}} } | ||
{{~/if}} | ||
Given path {{{karatePath operation}}} | ||
{{~#if (queryParams operation)}} | ||
And def queryParams = {{{paramsExample (queryParams operation)}}} | ||
{{~/if}} | ||
{{~#if operation.x--request-dto}} | ||
And request | ||
""" | ||
{{{requestExample operation.x--request-schema}}} | ||
""" | ||
{{~/if}} | ||
When method {{operation.x--httpVerb}} | ||
Then status {{operation.x--response.x--statusCode}} | ||
{{~#if operation.x--response}} | ||
* def {{operation.operationId}}Response = response | ||
{{~#if operation.x--response.x--response-schema}} | ||
* def {{asInstanceName operation.x--response.x--response-dto}}Id = response.id | ||
{{~/if}} | ||
{{~/if}} |
Oops, something went wrong.