Skip to content

Commit

Permalink
Merge branch 'master' of [email protected]:qmetry/qaf-support-openapi.gi…
Browse files Browse the repository at this point in the history
…t into HEAD
  • Loading branch information
cjayswal committed Jan 23, 2024
2 parents 84c06af + 25d3045 commit 788519b
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 40 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ support library for openapi to auto generate request call repository and bdd fro


To generate request call repository and BDD src, run `com.qmetry.qaf.automation.openapi.v3.CodeGeneratorCLI` with one or more specification URL as command line argument. If you want to use specification from local file system, you need to provide [file URL](https://en.wikipedia.org/wiki/File_URI_scheme).

<img src="https://user-images.githubusercontent.com/110619/153110503-35842bec-b955-494b-a1ee-38d88e164225.gif" width="800">
103 changes: 78 additions & 25 deletions src/com/qmetry/qaf/automation/openapi/v3/CodeGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ of this software and associated documentation files (the "Software"), to deal
*/
package com.qmetry.qaf.automation.openapi.v3;

import static com.qmetry.qaf.automation.core.ConfigurationManager.getBundle;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
Expand All @@ -40,6 +43,7 @@ of this software and associated documentation files (the "Software"), to deal
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.qmetry.qaf.automation.util.FileUtil;
import com.qmetry.qaf.automation.util.JSONUtil;
import com.qmetry.qaf.automation.util.StringUtil;

import io.swagger.v3.oas.models.OpenAPI;
Expand All @@ -50,6 +54,8 @@ of this software and associated documentation files (the "Software"), to deal
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.ParseOptions;

/**
* @author chirag.jayswal
Expand All @@ -60,7 +66,9 @@ public class CodeGenerator {
private String tmplKey;

private OpenAPI api;
private XMLConfiguration config;
private Map<String, Map<String, Object>> config;
private XMLConfiguration dataConfig;

private Map<String, String> globalHeaders;
private Gson gson;
private String specUrl;
Expand All @@ -77,19 +85,30 @@ public CodeGenerator(String specUrl, OpenAPI api) {
this.api = api;
this.specUrl = specUrl;
gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
config = new XMLConfiguration();
config.setDelimiterParsingDisabled(true);
config.setAttributeSplittingDisabled(true);
dataConfig = new XMLConfiguration();
dataConfig.setDelimiterParsingDisabled(true);
dataConfig.setAttributeSplittingDisabled(true);
config = new LinkedHashMap<String, Map<String, Object>>();
}

public void generate() throws ConfigurationException, IOException {
public List<String> generate() throws ConfigurationException, IOException {
String dest = getBundle().getString("autogenerated.file.location","resources/auto_generated/");
if(!dest.endsWith("/")) {dest=dest+"/";}

if(dest.indexOf("auto_generated")<0) {
dest = dest+"auto_generated/";
}
ArrayList<String> genratedFiles = new ArrayList<String>();
prefix = StringUtil.toCamelCaseIdentifier(api.getInfo().getTitle());
tmplKey = prefix + ".tmpl";


Map<String, Object> tmpl = new LinkedHashMap<String, Object>();
config.put(tmplKey, tmpl);

bddSrc = new StringBuffer("@Auto-generated \nFeature: " + api.getInfo().getTitle());
bddSrc.append("\n").append(api.getInfo().getDescription()).append("\n");

config.setProperty(tmplKey + ".baseUrl", api.getServers().get(0).getUrl());
tmpl.put("baseUrl", api.getServers().get(0).getUrl());
scanGlobalHeaders();

for (Entry<String, PathItem> entry : api.getPaths().entrySet()) {
Expand All @@ -100,13 +119,44 @@ public void generate() throws ConfigurationException, IOException {
}
}

config.setProperty(tmplKey + ".headers", globalHeaders);
tmpl.put("headers", globalHeaders);
Map<String, String> globalParams = new HashMap<String, String>();
globalParams.put("specUrl", specUrl);
config.setProperty(tmplKey + ".parameters", gson.toJson(globalParams));

config.save("resources/" + prefix + ".xml");
FileUtil.write(new File("scenarios/"+prefix + "SanitySuite.feature"), bddSrc, Charset.defaultCharset());
tmpl.put("parameters", globalParams);

String wscjFile = dest + prefix + ".wscj";
JSONUtil.writeJsonObjectToFile(wscjFile, config);
genratedFiles.add(wscjFile);

String resourceFile = dest + prefix + "-data.xml";
dataConfig.save(resourceFile);
genratedFiles.add(resourceFile);


String featureFile="scenarios/auto_generated/"+prefix + "SanitySuite.feature";
FileUtil.write(new File(featureFile), bddSrc, Charset.defaultCharset());
genratedFiles.add(featureFile);

return genratedFiles;
}

public static List<String> importWSC(String... specUrls){
ArrayList<String> genratedFiles = new ArrayList<String>();

for (String specUrl : specUrls) {
System.out.println("Processing: " + specUrl);
ParseOptions options = new ParseOptions();
options.setResolve(true);
options.setResolveFully(true);
OpenAPI api = new OpenAPIV3Parser().read(specUrl, null, options);
CodeGenerator codeGenerator = new CodeGenerator(specUrl, api);
try {
genratedFiles.addAll(codeGenerator.generate());
} catch (ConfigurationException | IOException e) {
e.printStackTrace();
}
}
return genratedFiles;
}

private void recordResponses(String key, HttpMethod method, Operation operation, Map<String, Object> params) {
Expand All @@ -116,9 +166,9 @@ private void recordResponses(String key, HttpMethod method, Operation operation,
boolean hasResponseBody = false;

for (Entry<String, ApiResponse> entry : operation.getResponses().entrySet()) {
config.addProperty(responseKey + "(-1).statusCode", entry.getKey());
dataConfig.addProperty(responseKey + "(-1).statusCode", entry.getKey());
ApiResponse response = entry.getValue();
config.addProperty(responseKey + ".recId", response.getDescription());
dataConfig.addProperty(responseKey + ".recId", response.getDescription());
Map<String, Object> recparams = new HashMap<String, Object>(params);

if (response.getContent() != null && response.getContent().entrySet() != null) {
Expand All @@ -127,7 +177,7 @@ private void recordResponses(String key, HttpMethod method, Operation operation,
hasResponseBody = true;
}
}
config.addProperty(responseKey + ".parameters", gson.toJson(recparams));
dataConfig.addProperty(responseKey + ".parameters", gson.toJson(recparams));

}

Expand All @@ -140,10 +190,13 @@ private void recordResponses(String key, HttpMethod method, Operation operation,
private Map<String, Object> recordReqCall(String path, HttpMethod method, Operation operation) {
String rcPrefix = prefix + "." + StringUtil.toCamelCaseIdentifier(operation.getOperationId()) + "."
+ method.name().toLowerCase();

Map<String, Object> reqCall = new LinkedHashMap<String, Object>();
config.put(rcPrefix, reqCall);

config.setProperty(rcPrefix + ".reference", tmplKey);
config.setProperty(rcPrefix + ".endPoint", path.replace("{", "${"));
config.setProperty(rcPrefix + ".method", method.name());
reqCall.put("reference", tmplKey);
reqCall.put("endPoint", path.replace("{", "${"));
reqCall.put("method", method.name());

List<Parameter> params = operation.getParameters();
Map<String, Object> parameters = new HashMap<String, Object>();
Expand Down Expand Up @@ -175,7 +228,7 @@ private Map<String, Object> recordReqCall(String path, HttpMethod method, Operat
break;
case "body":
parameters.put("body", "");
config.setProperty(rcPrefix + ".body", "${body}");
reqCall.put("body", "${body}");
break;
default:
parameters.put(paramName, example);
Expand All @@ -184,13 +237,13 @@ private Map<String, Object> recordReqCall(String path, HttpMethod method, Operat
}

if (!headers.isEmpty()) {
config.setProperty(rcPrefix + ".headers", gson.toJson(headers));
reqCall.put("headers", headers);
}
if (!queryParameters.isEmpty()) {
config.setProperty(rcPrefix + ".query-parameters", gson.toJson(queryParameters));
reqCall.put("query-parameters", queryParameters);
}
if (!formParameters.isEmpty()) {
config.setProperty(rcPrefix + ".form-parameters", gson.toJson(formParameters));
reqCall.put("form-parameters", formParameters);
}
}

Expand All @@ -204,12 +257,12 @@ private Map<String, Object> recordReqCall(String path, HttpMethod method, Operat
}
}
headers.put("Content-Type", contenEntry.getKey());
config.setProperty(rcPrefix + ".headers", gson.toJson(headers));
reqCall.put("headers", headers);
}
if (!formParameters.isEmpty()) {
config.setProperty(rcPrefix + ".form-parameters", gson.toJson(formParameters));
reqCall.put("form-parameters", formParameters);
} else {
config.setProperty(rcPrefix + ".body", "${body}");
reqCall.put("body", "${body}");
parameters.put("body", "");
}
}
Expand Down
18 changes: 4 additions & 14 deletions src/com/qmetry/qaf/automation/openapi/v3/CodeGeneratorCLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,10 @@ of this software and associated documentation files (the "Software"), to deal
import static com.qmetry.qaf.automation.core.ConfigurationManager.getBundle;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.configuration.ConfigurationException;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.ParseOptions;

/**
* This class can be run through command line to generate request call
* repository and BDD source from OpenAPI specification. Set URL of the
Expand Down Expand Up @@ -62,15 +58,9 @@ public static void main(String... args) throws ConfigurationException, IOExcepti
}
}

System.out.println("Open API Specifications to process: " + Arrays.asList(getBundle().getStringArray("openapi.specUrl")));
for (String specUrl : getBundle().getStringArray("openapi.specUrl")) {
System.out.println("Processing: " + specUrl);
ParseOptions options = new ParseOptions();
options.setResolve(true);
options.setResolveFully(true);
OpenAPI api = new OpenAPIV3Parser().read(specUrl, null, options);
CodeGenerator codeGenerator = new CodeGenerator(specUrl, api);
codeGenerator.generate();
List<String> res = CodeGenerator.importWSC(getBundle().getStringArray("openapi.specUrl"));
if(null!=res && !res.isEmpty()) {
System.out.println("Genereted files: " + res);
}
}
}
9 changes: 9 additions & 0 deletions src/com/qmetry/qaf/automation/step/openapi/OpenApiSteps.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ of this software and associated documentation files (the "Software"), to deal

import static com.qmetry.qaf.automation.core.ConfigurationManager.getBundle;

import java.util.List;

import com.atlassian.oai.validator.OpenApiInteractionValidator;
import com.atlassian.oai.validator.model.Request.Method;
import com.atlassian.oai.validator.model.Response;
import com.atlassian.oai.validator.model.SimpleResponse;
import com.atlassian.oai.validator.report.ValidationReport;
import com.atlassian.oai.validator.report.ValidationReport.Message;
import com.qmetry.qaf.automation.core.MessageTypes;
import com.qmetry.qaf.automation.openapi.v3.CodeGenerator;
import com.qmetry.qaf.automation.step.QAFTestStep;
import com.qmetry.qaf.automation.util.Reporter;
import com.qmetry.qaf.automation.ws.WsRequestBean;
Expand Down Expand Up @@ -67,6 +70,12 @@ public static boolean validateResponseSchema(Object requestCall) {
}
return !result.hasErrors();
}

@QAFTestStep(description = "import openapi spec {openapi-spec-file} into {dest}")
public static List<String> importOpenAPISpec(String file, String dest){
getBundle().setProperty("autogenerated.file.location",dest);
return CodeGenerator.importWSC(file);
}
private static OpenApiInteractionValidator getValidator(String specUrl){
String validatorKey = specUrl.replaceAll("\\W", "");
OpenApiInteractionValidator validator = (OpenApiInteractionValidator) getBundle().getObject(validatorKey);
Expand Down

0 comments on commit 788519b

Please sign in to comment.