diff --git a/pom.xml b/pom.xml index c9658480..0d17fd46 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,7 @@ kafka-testing junit5-testing zerocode-maven-archetype + zerocode-openapi diff --git a/zerocode-openapi/README.md b/zerocode-openapi/README.md new file mode 100644 index 00000000..c992ad1c --- /dev/null +++ b/zerocode-openapi/README.md @@ -0,0 +1,55 @@ +# Zerocode OpenAPI Generator (POC) + +This module (`zerocode-openapi`) is a proof of concept (POC) of a generator of Zerocode scenarios from [OpenAPI](https://swagger.io/docs/specification/v3_0/about/) specifications. + +This generator provides a single entry point `generateAll` +in the `or.jsmart.zerocode.openapi.ScenarioGenerator` class, that takes two arguments: +- File name or url where the OpenAPI specification is located. +- A folder where the generated scenarios will be placed. + +The output is a set of scenarios (one for each path in the OpenAPI specification), each containing a step for each operation. Scenario files take the name of the path. Symbols and non ascii characters are replaced by underscore, except brackets (used for path parameters) + +## Tests + +- Unit Tests: Exercise simple scenarios. Result comparison is made between the generated scenarios (in target) and the expected scenarios (in resources). Html files with the differences are stored in the target folder for offline checking. +- Integration Test: Scenarios generated from the [Swagger Petstore](https://github.com/swagger-api/swagger-petstore) specification, that is the demonstrator of this POC. Result comparison is made as in unit tests using soft assertions. An additional manual performance test was made using the OpenAPI specification of the GitHub API (631 scenarios). +- End to End Tests: Manual tests to verify that the generated scenarios can run against the real Swagger Petstore backend. Test scripts are provided in the E2eTest class. + +## Supported features + +Currently supported features are enumerated below in the order stated in https://swagger.io/docs/specification/v3_0/about/: + +- Media Types: + - The schema of the content is the one defined by the `application/json media` type (if present). + - If not, the first media type found is used, and the corresponding `Content-Type` header is set. +- Operations: Each scenario can generate steps for the `POST` `GET` `PUT` `PATCH` `HEAD` `OPTIONS` `TRACE` and `DELETE` operations defined in the path. +- Parameters and Serialization: + - Primitive Query Parameters: Added to `request.queryParameters`. + - Array Query Parameters: Serialized and url encoded in the path. Supports: + - `form` (default), `spaceDelimited` and `pipeDelimited` formats. + - `explode` (true by default). + - Primitive Path Parameters: Serialized and url encoded in the path. + - Primitive Header Parameters: Added to `request.headers`, `explode` is not supported +- Request Body: As indicated in Media Types. +- Responses: Generates an assertion for the first `2xx` response, if any. Response content is ignored. +- Data Models: Primitive values are generated by Zerocode tokens unless explicitly indicated: + - Primitive data Types: `string`, `number`, `integer`, `boolean` (randomly generated). + - Formats: String `date` and `date-time`. + - Enums: Of primitive data. Values are randomly generated. + - Arrays and Objects: as defined by their schemas. + - Maps: The `additionalProperties` keyword generates maps of primitive and non primitive. Free-Form is not supported +- References: `$ref` is handled by the swagger parser. + +## Zerocode enhancements + +All scenarios generated for the swagger petstore API generate valid requests, but there are a number of possible improvements to enhance the data generation: + +- *[Bug] Random strings are not random in a step*. All values generated by the RANDOM.STRING token have the same value inside each step. As workaround, a random number is appended to the random string. +- *Add a token to generate a random value among a set of values*. The ONE.OF token is only supported in asserts. If implemented to generate values in the request body, it would be used to generate enums and booleans without needing random generator at the time of writing the scenarios. Note that this should be able to generate both strings and non strings (quoted and unquoted values, including boolean). +- *Send unquoted numbers in requests*. The value generated for random numbers is a string. This could cause problems problems in the backend to accept these values depending on its serialization approach. +- *Add tokens to generate random date and datetime*. Currently the date/time values generate the current date, that leads to repeated values in a step. +- *Support Zulu timezone designator when generating dates*. Step execution fails if Z is included into the datetime format (it also fails with milliseconds). +- *Add tokens to generate numbers with decimals*. Current workaround concatenates two numbers and dot to include decimals, but this is sent as a string, not as a number +- *Allow ONE.OF to check the response status*. Lowest priority as having more than one 2xx possible responses should be infrequent. Currently the generators adds an assertion for the first success response code that is found. + + \ No newline at end of file diff --git a/zerocode-openapi/pom.xml b/zerocode-openapi/pom.xml new file mode 100644 index 00000000..f6cb4b71 --- /dev/null +++ b/zerocode-openapi/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + zerocode-tdd-parent + org.jsmart + 1.3.45-SNAPSHOT + + + zerocode-openapi + + jar + POC - Zerocode test generation from OpenAPI specifications + Proof of concept - Generation of Zerocde scenarios and steps from the paths and operations of an OpenAPI specification + + + 1.8 + + + + + zerocode-tdd + org.jsmart + ${project.version} + + + + io.swagger.parser.v3 + swagger-parser + 2.1.23 + + + junit + junit + + + io.github.javiertuya + visual-assert + 2.5.1 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + + diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/DataGeneratorFactory.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/DataGeneratorFactory.java new file mode 100644 index 00000000..ebc29932 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/DataGeneratorFactory.java @@ -0,0 +1,32 @@ +package org.jsmart.zerocode.openapi; + +import org.jsmart.zerocode.openapi.types.DataGenerator; +import org.jsmart.zerocode.openapi.types.ArrayGenerator; +import org.jsmart.zerocode.openapi.types.BooleanGenerator; +import org.jsmart.zerocode.openapi.types.IntegerGenerator; +import org.jsmart.zerocode.openapi.types.NumberGenerator; +import org.jsmart.zerocode.openapi.types.ObjectGenerator; +import org.jsmart.zerocode.openapi.types.StringGenerator; + +import io.swagger.v3.oas.models.media.Schema; + +public class DataGeneratorFactory implements IDataGeneratorFactory { + + public DataGenerator getItem(String name, Schema schema) { + if ("integer".equals(schema.getType())) { + return new IntegerGenerator(name, schema); + } else if ("number".equals(schema.getType())) { + return new NumberGenerator(name, schema); + } else if ("string".equals(schema.getType())) { + return new StringGenerator(name, schema); + } else if ("boolean".equals(schema.getType())) { + return new BooleanGenerator(name, schema); + } else if ("array".equals(schema.getType())) { + return new ArrayGenerator(name, schema, this); // requires factory to create objects + } else if ("object".equals(schema.getType())) { + return new ObjectGenerator(name, schema, this); + } + throw new RuntimeException( + String.format("OpenAPI schema type %s not allowed, property: %s", schema.getType(), name)); + } +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/IDataGeneratorFactory.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/IDataGeneratorFactory.java new file mode 100644 index 00000000..04364d13 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/IDataGeneratorFactory.java @@ -0,0 +1,11 @@ +package org.jsmart.zerocode.openapi; + +import org.jsmart.zerocode.openapi.types.DataGenerator; + +import io.swagger.v3.oas.models.media.Schema; + +public interface IDataGeneratorFactory { + + DataGenerator getItem(String name, Schema schema); + +} \ No newline at end of file diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/ParameterSerializer.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/ParameterSerializer.java new file mode 100644 index 00000000..cee7e618 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/ParameterSerializer.java @@ -0,0 +1,140 @@ +package org.jsmart.zerocode.openapi; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.jsmart.zerocode.openapi.types.DataGenerator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import io.swagger.v3.oas.models.parameters.Parameter; + +/** + * OpenAPI supports parameters at several places + * https://swagger.io/docs/specification/v3_0/describing-parameters/ + * Currently: query string, path and header parameters are supported (no cookie params) + * + * There are multiple formats for manage non primitive values: + * https://swagger.io/docs/specification/v3_0/serialization/ + * Currently: the supported parameters are + * - path: primitive + * - query string: primitive, array (Styles delimited by comma, space and pipe, others ignored) + * - headers: primitive + * + * Error handling: Warn and ignore if a data type or style is not supported + */ +public class ParameterSerializer { + + private static final org.slf4j.Logger LOGGER = getLogger(ParameterSerializer.class); + + public String serializePathParams(String path, List oaParams) { + for (Parameter oaParam : filterParams(oaParams, "path")) { + if (!isObject(oaParam) && !isArray(oaParam)) { + DataGeneratorFactory factory = new DataGeneratorFactory(); + JsonNode value = factory.getItem(oaParam.getName(), oaParam.getSchema()).setRequireUrlEncode(true).generateJsonValue(); + path = path.replace("{" + oaParam.getName() + "}", value.asText()); + } else { + LOGGER.warn("Non primitive path parameters are not supported yet"); + } + } + return path; + } + + public JsonNode getQueryParams(List oaParams) { + ObjectNode params = new ObjectMapper().createObjectNode(); + for (Parameter oaParam : filterParams(oaParams, "query")) { + // Only primitive params are in the header, other types are set in the path + if (!isObject(oaParam) && !isArray(oaParam)) { + DataGeneratorFactory factory = new DataGeneratorFactory(); + JsonNode value = factory.getItem(oaParam.getName(), oaParam.getSchema()).generateJsonValue(); + params.set(oaParam.getName(), value); + } + } + return params; + } + + public String serializeQueryParams(String path, List oaParams) { + List queryString = new ArrayList<>(); + for (Parameter oaParam : filterParams(oaParams, "query")) { + if (isArray(oaParam)) { + String params = getArrayQueryParams(oaParam); + if (params != null) + queryString.add(params); + } else if (isObject(oaParam)) { + LOGGER.warn("Object query parameters are not supported yet"); + } // primitive are not serialized in the path + } + // For now, generation is quite simple, not using any url template library + // Assuming the OpenAPI spec has no query strings in the url + if (!queryString.isEmpty()) + path = path + "?" + String.join("&", queryString); + return path; + } + + private String getArrayQueryParams(Parameter oaParam) { + DataGeneratorFactory factory = new DataGeneratorFactory(); + DataGenerator generator = factory.getItem(oaParam.getName(), oaParam.getSchema()).setRequireUrlEncode(true); + String paramName = generator.encodeIfRequired(oaParam.getName()); // values will be encoded when generated + String arraySeparator = getArraySeparator(oaParam.getStyle()); + if (arraySeparator == null) { + LOGGER.warn("Array query parameter style {} is not supported yet", oaParam.getStyle().toString()); + return null; // to not add any parameter + } + + JsonNode jsonArray = generator.generateJsonValue(); + List items = new ArrayList<>(); + for (JsonNode value : jsonArray) { + if (oaParam.getExplode()) + items.add(paramName + "=" + value.textValue()); + else + items.add(value.textValue()); + } + if (oaParam.getExplode()) + return String.join("&", items); + else + return paramName + "=" + String.join(arraySeparator, items); + } + + private String getArraySeparator(Parameter.StyleEnum style) { + if (style == Parameter.StyleEnum.FORM) + return ","; + else if (style == Parameter.StyleEnum.SPACEDELIMITED) + return "%20"; + else if (style == Parameter.StyleEnum.PIPEDELIMITED) + return "|"; + else + return null; + } + + public JsonNode getHeaderParams(List oaParams) { + ObjectNode params = new ObjectMapper().createObjectNode(); + for (Parameter oaParam : filterParams(oaParams, "header")) { + // Specification has only the "simple" style, no considering explode + // If considering explode eventually, refactor with getArrayQueryParams + DataGeneratorFactory factory = new DataGeneratorFactory(); + JsonNode value = factory.getItem(oaParam.getName(), oaParam.getSchema()).generateJsonValue(); + params.set(oaParam.getName(), value); + } + return params; + } + + private List filterParams(List oaParams, String in) { + if (oaParams == null) + return new ArrayList<>(); + return oaParams.stream().filter(oaParam -> in.equals(oaParam.getIn())).collect(Collectors.toList()); + } + + private boolean isObject(Parameter oaParam) { + return "object".equals(oaParam.getSchema().getType()); + } + + private boolean isArray(Parameter oaParam) { + return "array".equals(oaParam.getSchema().getType()); + } + +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/ScenarioGenerator.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/ScenarioGenerator.java new file mode 100644 index 00000000..98084b67 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/ScenarioGenerator.java @@ -0,0 +1,94 @@ +package org.jsmart.zerocode.openapi; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.jsmart.zerocode.core.domain.ScenarioSpec; +import org.jsmart.zerocode.core.domain.Step; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.parser.OpenAPIV3Parser; +import io.swagger.v3.parser.core.models.ParseOptions; + +public class ScenarioGenerator { + + private static final org.slf4j.Logger LOGGER = getLogger(ScenarioGenerator.class); + + /** + * Generates Zerocode scenarios from an OpenAPI specification. + * The name of the resulting scenarios is the name of the path + * (other characters different from alphanumeric, brackets, dots and dashes are replaced by _). + * Each scenario contains a step for each operation defined in the specification. + * + * @param oaSpecLocation Yaml or Json file or url with the OpenAPI specification. + * @param outFolder Folder where the scenarios are generated. + */ + public void generateAll(String oaSpecLocation, String outFolder) { + OpenAPI openApi = parseOpenApi(oaSpecLocation); + Map oaPaths = openApi.getPaths(); + for (Entry oaPath : oaPaths.entrySet()) { + LOGGER.info("Generating scenario for path: {}", oaPath.getKey()); + generateScenario(oaPath.getKey(), oaPath.getValue(), outFolder); + } + } + + private void generateScenario(String path, PathItem oaPathValue, String outFolder) { + List steps = new ArrayList<>(); + generateStep(path, "POST", oaPathValue.getPost(), steps); + generateStep(path, "GET", oaPathValue.getGet(), steps); + generateStep(path, "PUT", oaPathValue.getPut(), steps); + generateStep(path, "PATCH", oaPathValue.getPatch(), steps); + generateStep(path, "HEAD", oaPathValue.getHead(), steps); + generateStep(path, "OPTIONS", oaPathValue.getOptions(), steps); + generateStep(path, "TRACE", oaPathValue.getTrace(), steps); + generateStep(path, "DELETE", oaPathValue.getDelete(), steps); + + ScenarioSpec scenario = new ScenarioSpec(null, false, path + " - Test scenario", steps, null, null); + writeScenario(scenario, outFolder, path); + } + + private OpenAPI parseOpenApi(String location) { + LOGGER.info("Parsing OpenAPI specification file: {}", location); + ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolve(true); + parseOptions.setResolveFully(true); // for refs + return new OpenAPIV3Parser().read(location, null, parseOptions); + } + + private void generateStep(String path, String method, Operation oaOperation, List outSteps) { + if (oaOperation == null) + return; + try { + StepGenerator generator=new StepGenerator(); + Step step = generator.parseOperation(path, method, oaOperation); + outSteps.add(step); + } catch (RuntimeException e) { + // Prevents the whole scenario failure if some step fails (error, not supported feature, etc.) + LOGGER.error("Failed step generation for {} {}", method, path, e); + } + } + + private void writeScenario(ScenarioSpec scenario, String outFolder, String name) { + try { + String scenarioStr = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(scenario); + String fileName = name.replaceAll("[^A-Za-z0-9 \\{\\}\\-]", "_") + ".json"; + LOGGER.info("Writing scenario to file: {}", fileName); + FileUtils.writeStringToFile(new File(FilenameUtils.concat(outFolder, fileName)), scenarioStr, "UTF-8"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/StepGenerator.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/StepGenerator.java new file mode 100644 index 00000000..005f6625 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/StepGenerator.java @@ -0,0 +1,121 @@ +package org.jsmart.zerocode.openapi; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.jsmart.zerocode.core.domain.Step; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.MediaType; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.RequestBody; +import io.swagger.v3.oas.models.responses.ApiResponse; + +public class StepGenerator { + + private static final org.slf4j.Logger LOGGER = getLogger(StepGenerator.class); + + /** + * Generates a Zerocode Step object for a given path and http method + * from an OpenAPI operation object + */ + public Step parseOperation(String path, String method, Operation oaOperation) { + LOGGER.info("Generating step: {} {}", method, path); + + // Path params must be serialized in the path + ParameterSerializer serializer = new ParameterSerializer(); + String url = serializer.serializePathParams(path, oaOperation.getParameters()); + + // Non primitive query params must be serialized in the path + url = serializer.serializeQueryParams(url, oaOperation.getParameters()); + + LOGGER.info(" Generated url: {}", url); + + ObjectNode request = new ObjectMapper().createObjectNode(); + + // Primitive query params are added to the request + JsonNode queryParams = serializer.getQueryParams(oaOperation.getParameters()); + if (!queryParams.isEmpty()) + request.set("queryParams", queryParams); + + // Header params are also added to the request, but not added immediatly + // because after reading the requests, some additional media type headers may be added + JsonNode headerParams = serializer.getHeaderParams(oaOperation.getParameters()); + + JsonNode body = generateRequestBody(oaOperation.getRequestBody(), headerParams); + if (!headerParams.isEmpty()) // + request.set("headers", headerParams); + // empty object does not generates body, but does for primitive types (e.g. string) + if (!(body.isEmpty() && body.isObject())) + request.set("body", body); + + LOGGER.info(" Generated request: {}", request); + + // Create assert for the 2xx response codes only + ObjectNode assertions = new ObjectMapper().createObjectNode(); + List responseCodes = generateResponses(oaOperation.getResponses()); + LOGGER.info(" Generated response: {}", responseCodes); + // Although not frequent, a response could contain more than one 2xx, + // but $ONE.OF can't be use to assert the response. Takes the first (if any) + if (!responseCodes.isEmpty()) + assertions.put("status", Integer.valueOf(responseCodes.get(0))); + + // Zerocode Step with the minimum of attributes + Step step = new Step(null, null, path + " - " + oaOperation.getSummary(), + method, null, url, request, null, null, + assertions, null, null, false); + return step; + } + + private JsonNode generateRequestBody(RequestBody oaBody, JsonNode currentHeaders) { + if (oaBody == null) + return new ObjectMapper().createObjectNode(); + // By default, application/json is used (not required to set in headers), and it is the + // request body returned (this will exclude other that appear before, eg. application/xml) + // But if none is found, returns the first one that finds and adds the media type to the headers + Entry fallbackMediaType = null; + for (Entry oaItem : oaBody.getContent().entrySet()) { + if (fallbackMediaType == null) + fallbackMediaType = oaItem; //keep the first. + if ("application/json".equals(oaItem.getKey())) { // ignore other media types + Schema oaSchema = oaItem.getValue().getSchema(); + return generateJsonBody(oaSchema); + } + } + // at this point, no json body was found + if (fallbackMediaType == null) { // no mediatype found + return new ObjectMapper().createObjectNode(); + } else { // there is a fallaback, add header and return the body + ((ObjectNode) currentHeaders).put("Content-Type", fallbackMediaType.getKey().toString()); + Schema oaSchema = fallbackMediaType.getValue().getSchema(); + return generateJsonBody(oaSchema); + } + } + + private JsonNode generateJsonBody(Schema oaSchema) { + DataGeneratorFactory factory = new DataGeneratorFactory(); + if (oaSchema.getType() == null) // request body does not set the type if it is an object + oaSchema.setType("object"); + return factory.getItem("body", oaSchema).generateJsonValue(); //actual generation of data + } + + private List generateResponses(Map oaResponses) { + List responseCodes = new ArrayList<>(); + if (oaResponses == null) + return responseCodes; + for (Entry oaResponse : oaResponses.entrySet()) { + if (oaResponse.getKey().startsWith("2")) + responseCodes.add(oaResponse.getKey()); + } + return responseCodes; + } + +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/ArrayGenerator.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/ArrayGenerator.java new file mode 100644 index 00000000..41d9bfa5 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/ArrayGenerator.java @@ -0,0 +1,32 @@ +package org.jsmart.zerocode.openapi.types; + +import org.jsmart.zerocode.openapi.IDataGeneratorFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + +import io.swagger.v3.oas.models.media.Schema; + +public class ArrayGenerator extends DataGenerator { + private static final int ARRAY_NUM_ITEMS_GENERATED = 2; + + private IDataGeneratorFactory factory; + private Schema items; + + public ArrayGenerator(String name, Schema schema, IDataGeneratorFactory factory) { + super(name, schema); + this.factory = factory; + items = schema.getItems(); + } + + @Override + public JsonNode generateJsonValue() { + ArrayNode allObjects = new ObjectMapper().createArrayNode(); + for (int i = 0; i < ARRAY_NUM_ITEMS_GENERATED; i++) { //propagates required encoding to the array values + DataGenerator item = factory.getItem(name, items).setRequireUrlEncode(this.requireUrlEncode); + allObjects.add(item.generateJsonValue()); + } + return allObjects; + } +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/BooleanGenerator.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/BooleanGenerator.java new file mode 100644 index 00000000..52b5aa39 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/BooleanGenerator.java @@ -0,0 +1,19 @@ +package org.jsmart.zerocode.openapi.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.swagger.v3.oas.models.media.Schema; + +public class BooleanGenerator extends PrimitiveGenerator { + public BooleanGenerator(String name, Schema schema) { + super(name, schema); + } + + @Override + public JsonNode generateJsonValue() { + int value = new RandomGeneratorWithSeed().nextInt(0, 1); + return new ObjectMapper().createObjectNode().booleanNode(value == 1); + } + +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/DataGenerator.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/DataGenerator.java new file mode 100644 index 00000000..16a82f99 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/DataGenerator.java @@ -0,0 +1,56 @@ +package org.jsmart.zerocode.openapi.types; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +import com.fasterxml.jackson.databind.JsonNode; + +import io.swagger.v3.oas.models.media.Schema; + +/** + * Base class for all data type generators given their OpenAPI Schema + * DataGenerator + * PrimitiveGenerator + * IntegerGenerator + * NumberGenerator + * StringGenerator (including date and date-time formats) + * BooleanGenerator + * Object Generator + * ArrayGenerator + */ +public abstract class DataGenerator { + protected String name; + protected Schema schema; + // Indicates if values need url encoding, used for query and path parameters. + // If true, the method encodeIfRequired will return the appropriate encoding + protected boolean requireUrlEncode; + + public DataGenerator(String name, Schema schema) { + this.name = name; + this.schema = schema; + this.requireUrlEncode = false; + } + + /** + * Generates a value to include in a Zerocode step for the appropriate schema data type. + */ + public abstract JsonNode generateJsonValue(); + + /** + * Sets the generator to require (or not) url encoded values when encodeIfRequired is called + */ + public DataGenerator setRequireUrlEncode(boolean value) { + this.requireUrlEncode = value; + return this; + } + + public String encodeIfRequired(String value) { + try { + return requireUrlEncode ? URLEncoder.encode(value, "UTF-8") : value; + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/IntegerGenerator.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/IntegerGenerator.java new file mode 100644 index 00000000..3b20ea02 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/IntegerGenerator.java @@ -0,0 +1,21 @@ +package org.jsmart.zerocode.openapi.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.swagger.v3.oas.models.media.Schema; + +public class IntegerGenerator extends PrimitiveGenerator { + public IntegerGenerator(String name, Schema schema) { + super(name, schema); + } + + @Override + public JsonNode generateJsonValue() { + if (hasEnum()) + return new ObjectMapper().createObjectNode().numberNode(Long.valueOf(getEnumItem().toString())); + else // Note that numbers are generated as strings + return new ObjectMapper().createObjectNode().textNode("${RANDOM.NUMBER:8}"); + } + +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/NumberGenerator.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/NumberGenerator.java new file mode 100644 index 00000000..371cddfd --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/NumberGenerator.java @@ -0,0 +1,21 @@ +package org.jsmart.zerocode.openapi.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.swagger.v3.oas.models.media.Schema; + +public class NumberGenerator extends PrimitiveGenerator { + public NumberGenerator(String name, Schema schema) { + super(name, schema); + } + + @Override + public JsonNode generateJsonValue() { + if (hasEnum()) + return new ObjectMapper().createObjectNode().numberNode(Double.valueOf(getEnumItem().toString())); + else + return new ObjectMapper().createObjectNode().textNode("${RANDOM.NUMBER:8}.${RANDOM.NUMBER:4}"); + } + +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/ObjectGenerator.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/ObjectGenerator.java new file mode 100644 index 00000000..e0767260 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/ObjectGenerator.java @@ -0,0 +1,46 @@ +package org.jsmart.zerocode.openapi.types; + +import java.util.Map.Entry; + +import org.jsmart.zerocode.openapi.IDataGeneratorFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import io.swagger.v3.oas.models.media.Schema; + +public class ObjectGenerator extends DataGenerator { + private static final int MAP_NUM_ITEMS_GENERATED = 2; + + private IDataGeneratorFactory factory; + + public ObjectGenerator(String name, Schema schema, IDataGeneratorFactory factory) { + super(name, schema); + this.factory = factory; + } + + @SuppressWarnings("rawtypes") + @Override + public JsonNode generateJsonValue() { + ObjectNode allObjects = new ObjectMapper().createObjectNode(); + // Can have the standard properties or additional properties (map) + if (schema.getProperties() != null) { + for (Entry prop : schema.getProperties().entrySet()) { + DataGenerator item = factory.getItem(prop.getKey(), prop.getValue()); + JsonNode node = item.generateJsonValue(); + allObjects.set(item.name, node); + } + } else if (schema.getAdditionalProperties() != null) { + // Each additional property has a string key and a value according to the given schema + Schema valueSchema = (Schema) schema.getAdditionalProperties(); + for (int i = 0; i < MAP_NUM_ITEMS_GENERATED; i++) { + String key = this.name + "Key" + i; // could be random + JsonNode value = factory.getItem(key, valueSchema).generateJsonValue(); + allObjects.set(key, value); + } + } + + return allObjects; + } +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/PrimitiveGenerator.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/PrimitiveGenerator.java new file mode 100644 index 00000000..cc83d734 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/PrimitiveGenerator.java @@ -0,0 +1,30 @@ +package org.jsmart.zerocode.openapi.types; + +import java.util.List; + +import io.swagger.v3.oas.models.media.Schema; + +public abstract class PrimitiveGenerator extends DataGenerator { + + public PrimitiveGenerator(String name, Schema schema) { + super(name, schema); + } + + protected boolean hasEnum() { + return schema.getEnum() != null; + } + + protected Object getEnumItem() { + return getOneOf(schema.getEnum()); + // There is no token like "$ONE.OF:[San Francisco, New York, Seattle]", only for asserts + } + + protected Object getOneOf(List values) { + int position = new RandomGeneratorWithSeed().nextInt(0, values.size() - 1); + return values.get(position); + } + + protected String getFormat() { + return schema.getFormat(); + } +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/RandomGeneratorWithSeed.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/RandomGeneratorWithSeed.java new file mode 100644 index 00000000..5230dcd1 --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/RandomGeneratorWithSeed.java @@ -0,0 +1,26 @@ +package org.jsmart.zerocode.openapi.types; + +import java.util.Random; + +/** + * Generates a random number, with the possibility to set a seed. + * The standard random generator of ZeroCode uses the ThreadLocalRandom + * that does not allow to set a seed, making the test not repeatable + */ +public class RandomGeneratorWithSeed { + + private static Random random; + + public RandomGeneratorWithSeed() { + if (random == null) + random = new Random(); + } + + public void setSeed(long seed) { + random.setSeed(seed); + } + + public int nextInt(int origin, int bound) { + return Math.abs(random.nextInt()) % (bound - origin + 1); + } +} diff --git a/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/StringGenerator.java b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/StringGenerator.java new file mode 100644 index 00000000..824886de --- /dev/null +++ b/zerocode-openapi/src/main/java/org/jsmart/zerocode/openapi/types/StringGenerator.java @@ -0,0 +1,31 @@ +package org.jsmart.zerocode.openapi.types; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.swagger.v3.oas.models.media.Schema; + +public class StringGenerator extends PrimitiveGenerator { + public StringGenerator(String name, Schema schema) { + super(name, schema); + } + + @Override + public JsonNode generateJsonValue() { + return new ObjectMapper().createObjectNode() // only urlencode the enum values + .textNode(hasEnum() ? encodeIfRequired(getEnumItem().toString()) : getRandomToken()); + } + + private String getRandomToken() { + // Note that we are always generating the current date/time value, that can lead to repeated values in a step + if ("date-time".equals(getFormat())) + return "${LOCAL.DATETIME.NOW:yyyy-MM-dd'T'HH:mm:ss}"; + else if ("date".equals(getFormat())) + return "${LOCAL.DATE.NOW:yyyy-MM-dd}"; + else // for string and other formats fallback + // RANDOM.STRING always returns the same value inside an step. + // Workaround: add a number to make values different + return "${RANDOM.STRING:12}${RANDOM.NUMBER:4}"; + } + +} diff --git a/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/BaseTest.java b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/BaseTest.java new file mode 100644 index 00000000..db626d88 --- /dev/null +++ b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/BaseTest.java @@ -0,0 +1,66 @@ +package org.jsmart.zerocode.openapi; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.junit.Before; + +import giis.visualassert.Framework; +import giis.visualassert.SoftVisualAssert; +import giis.visualassert.VisualAssert; + +/** + * Base class for all unit and integration tests. + * Tests place the generated scenario files at target folder + * and compare then against the expected scenario files at test resources folder + * using the assertScenario included in this class. + */ +public class BaseTest { + + protected String ymlFolder = ""; // OpenApi specification files + protected String outFolder = ""; // Scenarios generated by the tests + protected String bmkFolder = ""; // Expected scenarios + + @Before + public void setUp() { //should be overriden by integration tests + ymlFolder = "src/test/resources/unit_test_files"; + outFolder = "target/unit_test_generated_files"; + bmkFolder = "src/test/resources/unit_test_generated_files"; + } + + /** + * Compare the expected vs the actual contents of a scenario file. + * In addition to the ability to compare the differences in failed tests + * from the development environment, an html file that highlights the differences + * is created at the target folder for offline checking. + */ + protected void assertScenario(String scenarioName) throws IOException { + VisualAssert va = new VisualAssert() + .setSoftDifferences(false).setFramework(Framework.JUNIT4); + String fileName = scenarioName + ".json"; + String expected = FileUtils.readFileToString(new File(FilenameUtils.concat(bmkFolder, fileName)), "UTF-8"); + String actual = FileUtils.readFileToString(new File(FilenameUtils.concat(outFolder, fileName)), "UTF-8"); + va.assertEquals(expected, actual, "Diffs in scenario " + scenarioName); + } + + /** + * Soft Assert to compare the expected vs the actual contents of all scenario files generated + * from an OpenAPI specification. + * Each test failure stores the diff file in target as well as an aggregate file with all differences. + */ + protected void assertAllScenarios(String folderName) throws IOException { + SoftVisualAssert va = new SoftVisualAssert() + .setSoftDifferences(false).setCallStackLength(2).setFramework(Framework.JUNIT4); + Collection files = FileUtils.listFiles(new File(outFolder), new String[] { "json" }, false); + for (File file : files) { + String fileName = file.getName(); + String expected = FileUtils.readFileToString(new File(FilenameUtils.concat(bmkFolder, fileName)), "UTF-8"); + String actual = FileUtils.readFileToString(new File(FilenameUtils.concat(outFolder, fileName)), "UTF-8"); + va.assertEquals(expected, actual, "Diffs in file " + fileName, "DIFF" + fileName + ".html"); + } + va.assertAll("ALL_DIFFS.html"); + } +} diff --git a/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/DataTypesTest.java b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/DataTypesTest.java new file mode 100644 index 00000000..b1b75bc1 --- /dev/null +++ b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/DataTypesTest.java @@ -0,0 +1,49 @@ +package org.jsmart.zerocode.openapi; + +import java.io.IOException; + +import org.jsmart.zerocode.openapi.types.RandomGeneratorWithSeed; +import org.junit.Test; + +public class DataTypesTest extends BaseTest { + + @Test + public void testSchemaOtherDataTypesAndFormats() throws IOException { + // int and string where tested with step scenarios, here testing number and dates + new RandomGeneratorWithSeed().setSeed(9876543); // to allow repeatable tests for boolean + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-other-types.yml", outFolder); + assertScenario("_testOtherTypes"); + } + + @Test + public void testSchemaAdditionalProperties() throws IOException { + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-additional-properties.yml", outFolder); + assertScenario("_testAdditionalProperties"); + } + + @Test + public void testSchemaEnumInQueryPathParamsAndBody() throws IOException { + new RandomGeneratorWithSeed().setSeed(9876543); // to allow repeatable tests + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-enum.yml", outFolder); + assertScenario("_testEnum_{pathEnum}"); + } + + @Test + public void testSchemaEnumWithUrlEncode() throws IOException { + new RandomGeneratorWithSeed().setSeed(9876543); + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-enum-urlencode.yml", outFolder); + assertScenario("_testEnumUrlencode_{pathEnum}"); + } + + @Test + public void testSchemaArraysInBody() throws IOException { + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-array.yml", outFolder); + assertScenario("_testArray"); + } + +} diff --git a/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/E2eTest.java b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/E2eTest.java new file mode 100644 index 00000000..f08571ff --- /dev/null +++ b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/E2eTest.java @@ -0,0 +1,53 @@ +package org.jsmart.zerocode.openapi; + +import org.jsmart.zerocode.core.domain.Scenario; +import org.jsmart.zerocode.core.domain.TargetEnv; +import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * End to End test to run a sample of the generated scenarios against the real swagger petstore api. + * The tests are for manual execution only. See the setup and the test script below. + */ +@Ignore +@TargetEnv("e2e_test_files/petstore.properties") +@RunWith(ZeroCodeUnitRunner.class) +public class E2eTest { + + /* + Setup: + - Clone and run the swagger petstore using the scripts provided in the resources/e2e_test_files folder + - Run the integration tests (that will place the generated scenarios to use in this test in the target folder) + - Comment the Ignore annotation and follow the scripts below (uncomment a scenario in each test). + + Pet endpoints: + - Run test _pet.json. Verify the first (POST): request/response. Save the pet id. The second (PUT) should fail + Edit file and set the PUT pet id to the saved value and change the name. Run test. Verify all responses + - Edit _pet_{petId} and set the url id to the saved value in all 3 operation. Set the queryParams to some value in first operation. + Run the test. Verify all responses. Run again, first step should fail because the pet was deleted + - Run _pet_findByStatus.json. Verify the response has only pets with the same status (sold) + - Edit _pet_{petId}_uploadImage.json, set the id to 5 (created on startup) and run. + Verify the response has a new item in photoUrls: /tmp/inflectorNNNNNN.tmp + From inside the container, type this file. Its value must be equal to the body sent in the request + + Other endpoints: + - Run _store_inventory.json. Verify the responses + - Run _store_order.json. Verify responses. ShipDate must be the current datetime +00:00 + (note that zulu format is not supported, dates are generated as localdate) + - Run _user_createWithList.json. Verify responses + */ + @Test + @Scenario("target/integration_test_generated_files/_pet.json") + //@Scenario("target/integration_test_generated_files/_pet_{petId}.json") + //@Scenario("target/integration_test_generated_files/_pet_findByStatus.json") + //@Scenario("target/integration_test_generated_files/_pet_{petId}_uploadImage.json") + + //@Scenario("target/integration_test_generated_files/_store_inventory.json") + //@Scenario("target/integration_test_generated_files/_store_order.json") + //@Scenario("target/integration_test_generated_files/_user_createWithList.json") + public void testE2eScenario() throws Exception { + } + +} diff --git a/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/IntegrationTest.java b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/IntegrationTest.java new file mode 100644 index 00000000..70e0f3f9 --- /dev/null +++ b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/IntegrationTest.java @@ -0,0 +1,32 @@ +package org.jsmart.zerocode.openapi; + +import java.io.IOException; + +import org.jsmart.zerocode.openapi.types.RandomGeneratorWithSeed; +import org.junit.Before; +import org.junit.Test; + +public class IntegrationTest extends BaseTest { + + @Before + public void setUp() { + ymlFolder = "src/test/resources/integration_test_files"; + outFolder = "target/integration_test_generated_files"; + bmkFolder = "src/test/resources/integration_test_generated_files"; + } + + // https://github.com/swagger-api/swagger-petstore + // Swagger Petstore OpenAPI 3.0 release 1.0.19 + // https://github.com/swagger-api/swagger-petstore/archive/refs/tags/swagger-petstore-v3-1.0.19.zip + @Test + public void testSwaggerPetstoreIntegration() throws IOException { + new RandomGeneratorWithSeed().setSeed(9876543); // to allow repeatable tests + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/openapi.yaml", outFolder); + assertAllScenarios("petstore_all.html"); + } + + // To check performance, use the GitHub Api specification: https://github.com/github/rest-api-description + // https://raw.githubusercontent.com/github/rest-api-description/refs/heads/main/descriptions/ghes-3.14/ghes-3.14.json + // Time to generate: between 7 and 11 seconds, 631 scenarios +} diff --git a/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/ParametersTest.java b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/ParametersTest.java new file mode 100644 index 00000000..637940fe --- /dev/null +++ b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/ParametersTest.java @@ -0,0 +1,53 @@ +package org.jsmart.zerocode.openapi; + +import java.io.IOException; + +import org.jsmart.zerocode.openapi.types.RandomGeneratorWithSeed; +import org.junit.Test; + +public class ParametersTest extends BaseTest { + + @Test + public void testQueryParamsWithFormStyleAndUrlEncoding() throws IOException { + new RandomGeneratorWithSeed().setSeed(98765432); // to allow repeatable tests for enum + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-query-param-form.yml", outFolder); + assertScenario("_testQueryParamForm"); + } + + @Test + public void testQueryParamsWithOtherStyleAndUnsupported() throws IOException { + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-query-param-other.yml", outFolder); + assertScenario("_testQueryParamOther"); + } + + @Test + public void testPathParamsWithUrlEncodingAndUnsupported() throws IOException { + new RandomGeneratorWithSeed().setSeed(98765432); // to allow repeatable tests for enum + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-path-param.yml", outFolder); + assertScenario("_testPathParam_{par Enum}_array_{parArray}_object{parObject}"); + } + + @Test + public void testHeaderParams() throws IOException { + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-header-param.yml", outFolder); + assertScenario("_testHeaderParam"); + } + + @Test + public void testHeaderFromMediatype() throws IOException { + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-header-mediatype.yml", outFolder); + assertScenario("_testHeaderMediatype"); + } + + @Test + public void testHeaderParamsAndMediatype() throws IOException { + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-header-param-mediatype.yml", outFolder); + assertScenario("_testHeaderParamMediatype"); + } +} diff --git a/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/StepGenerationTest.java b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/StepGenerationTest.java new file mode 100644 index 00000000..ec55c06f --- /dev/null +++ b/zerocode-openapi/src/test/java/org/jsmart/zerocode/openapi/StepGenerationTest.java @@ -0,0 +1,44 @@ +package org.jsmart.zerocode.openapi; + +import java.io.IOException; + +import org.junit.Test; + +public class StepGenerationTest extends BaseTest { + + @Test + public void testFull_WithQueryPathParamsAndBody() throws IOException { + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-full.yml", outFolder); + assertScenario("_testFull_{pparam1}_more_{pparam2}"); + } + + @Test + public void testEmpty_WithOperationOnly() throws IOException { + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-empty.yml", outFolder); + assertScenario("_testEmpty"); + } + + @Test + public void testFailedStep_DoesNotBlockGeneration() throws IOException { + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-failed-step.yml", outFolder); + assertScenario("_testFailedStep"); + } + + @Test + public void testStringBody() throws IOException { + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-string-body.yml", outFolder); + assertScenario("_testStringBody"); + } + + @Test + public void testArrayBody() throws IOException { + ScenarioGenerator parser = new ScenarioGenerator(); + parser.generateAll(ymlFolder + "/oa-array-body.yml", outFolder); + assertScenario("_testArrayBody"); + } + +} diff --git a/zerocode-openapi/src/test/resources/e2e_test_files/petstore-clone.sh b/zerocode-openapi/src/test/resources/e2e_test_files/petstore-clone.sh new file mode 100644 index 00000000..6b3266f4 --- /dev/null +++ b/zerocode-openapi/src/test/resources/e2e_test_files/petstore-clone.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Gets the source of the swagger petstore. +# After this, execute the command to run the container +SCRIPT_DIR=$(readlink -f $0 | xargs dirname) +echo "run command from directory: $SCRIPT_DIR" +cd $SCRIPT_DIR/../../../.. +mkdir target +cd target +pwd +git clone https://github.com/swagger-api/swagger-petstore --branch swagger-petstore-v3-1.0.19 diff --git a/zerocode-openapi/src/test/resources/e2e_test_files/petstore-run.sh b/zerocode-openapi/src/test/resources/e2e_test_files/petstore-run.sh new file mode 100644 index 00000000..da3ae87d --- /dev/null +++ b/zerocode-openapi/src/test/resources/e2e_test_files/petstore-run.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# run the container with the swagger petstore api at port 8080 +# Before executing this, run the clone command to have the sources in the target/swagger-petstore folder. +SCRIPT_DIR=$(readlink -f $0 | xargs dirname) +echo "run command from directory: $SCRIPT_DIR" +cd $SCRIPT_DIR/../../../../target/swagger-petstore +pwd +mvn package -ntp -DskipTests=true +docker build -t swagger-petstore . +docker stop swagger-petstore && docker rm swagger-petstore +docker run -d -p 8080:8080 --name swagger-petstore swagger-petstore diff --git a/zerocode-openapi/src/test/resources/e2e_test_files/petstore.properties b/zerocode-openapi/src/test/resources/e2e_test_files/petstore.properties new file mode 100644 index 00000000..9581ab4c --- /dev/null +++ b/zerocode-openapi/src/test/resources/e2e_test_files/petstore.properties @@ -0,0 +1,9 @@ +# Swagger petstore api endpoint running in local +restful.application.endpoint.host=http://localhost +restful.application.endpoint.port=8080 +restful.application.endpoint.context=/api/v3 +# See the swagger ui at http://localhost:8080 + +# If not running in local, the swagger ui and api endpoints can be found at: +# https://petstore3.swagger.io +# https://petstore3.swagger.io/api/v3 diff --git a/zerocode-openapi/src/test/resources/integration_test_files/openapi.yaml b/zerocode-openapi/src/test/resources/integration_test_files/openapi.yaml new file mode 100644 index 00000000..12158ce1 --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_files/openapi.yaml @@ -0,0 +1,819 @@ +openapi: 3.0.2 +servers: + - url: /v3 +info: + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + version: 1.0.19 + title: Swagger Petstore - OpenAPI 3.0 + termsOfService: 'http://swagger.io/terms/' + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: 'http://swagger.io' + - name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: 'http://swagger.io' + - name: user + description: Operations about user +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + responses: + '200': + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + description: Create a new pet in the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + responses: + '200': + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + description: Update an existent pet in the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + - petstore_auth: + - 'write:pets' + - 'read:pets' + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + description: '' + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + x-swagger-router-controller: OrderController + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: Place a new order in the store + operationId: placeOrder + x-swagger-router-controller: OrderController + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + '405': + description: Invalid input + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + x-swagger-router-controller: OrderController + description: >- + For valid response try integer IDs with value <= 5 or > 10. Other values + will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + x-swagger-router-controller: OrderController + description: >- + For valid response try integer IDs with value < 1000. Anything above + 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + description: Created user object + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: 'Creates list of users with given input array' + x-swagger-router-controller: UserController + operationId: createUsersWithListInput + responses: + '200': + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + default: + description: successful operation + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + '200': + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + parameters: [] + responses: + default: + description: successful operation + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Update user + x-swagger-router-controller: UserController + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that needs to be updated + required: true + schema: + type: string + responses: + default: + description: successful operation + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + schemas: + Order: + x-swagger-router-model: io.swagger.petstore.model.Order + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + example: approved + complete: + type: boolean + xml: + name: order + type: object + Customer: + properties: + id: + type: integer + format: int64 + example: 100000 + username: + type: string + example: fehguy + address: + type: array + items: + $ref: '#/components/schemas/Address' + xml: + wrapped: true + name: addresses + xml: + name: customer + type: object + Address: + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: 94301 + xml: + name: address + type: object + Category: + x-swagger-router-model: io.swagger.petstore.model.Category + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + type: object + User: + x-swagger-router-model: io.swagger.petstore.model.User + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: 12345 + phone: + type: string + example: 12345 + userStatus: + type: integer + format: int32 + example: 1 + description: User Status + xml: + name: user + type: object + Tag: + x-swagger-router-model: io.swagger.petstore.model.Tag + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + type: object + Pet: + x-swagger-router-model: io.swagger.petstore.model.Pet + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + xml: + name: tag + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + type: object + ApiResponse: + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + type: object + requestBodies: + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: 'https://petstore.swagger.io/oauth/authorize' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet.json new file mode 100644 index 00000000..7c9f76a4 --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet.json @@ -0,0 +1,61 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/pet - Test scenario", + "steps" : [ { + "name" : "/pet - Add a new pet to the store", + "operation" : "POST", + "method" : "POST", + "url" : "/pet", + "request" : { + "body" : { + "id" : "${RANDOM.NUMBER:8}", + "name" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "category" : { + "id" : "${RANDOM.NUMBER:8}", + "name" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, + "photoUrls" : [ "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" ], + "tags" : [ { + "id" : "${RANDOM.NUMBER:8}", + "name" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, { + "id" : "${RANDOM.NUMBER:8}", + "name" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + } ], + "status" : "sold" + } + }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + }, { + "name" : "/pet - Update an existing pet", + "operation" : "PUT", + "method" : "PUT", + "url" : "/pet", + "request" : { + "body" : { + "id" : "${RANDOM.NUMBER:8}", + "name" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "category" : { + "id" : "${RANDOM.NUMBER:8}", + "name" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, + "photoUrls" : [ "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" ], + "tags" : [ { + "id" : "${RANDOM.NUMBER:8}", + "name" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, { + "id" : "${RANDOM.NUMBER:8}", + "name" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + } ], + "status" : "sold" + } + }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_findByStatus.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_findByStatus.json new file mode 100644 index 00000000..9b923243 --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_findByStatus.json @@ -0,0 +1,19 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/pet/findByStatus - Test scenario", + "steps" : [ { + "name" : "/pet/findByStatus - Finds Pets by status", + "operation" : "GET", + "method" : "GET", + "url" : "/pet/findByStatus", + "request" : { + "queryParams" : { + "status" : "sold" + } + }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_findByTags.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_findByTags.json new file mode 100644 index 00000000..fe8b2492 --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_findByTags.json @@ -0,0 +1,15 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/pet/findByTags - Test scenario", + "steps" : [ { + "name" : "/pet/findByTags - Finds Pets by tags", + "operation" : "GET", + "method" : "GET", + "url" : "/pet/findByTags?tags=${RANDOM.STRING:12}${RANDOM.NUMBER:4}&tags=${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "request" : { }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_{petId}.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_{petId}.json new file mode 100644 index 00000000..2921542c --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_{petId}.json @@ -0,0 +1,40 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/pet/{petId} - Test scenario", + "steps" : [ { + "name" : "/pet/{petId} - Updates a pet in the store with form data", + "operation" : "POST", + "method" : "POST", + "url" : "/pet/${RANDOM.NUMBER:8}", + "request" : { + "queryParams" : { + "name" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "status" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + } + }, + "assertions" : { }, + "ignoreStep" : false + }, { + "name" : "/pet/{petId} - Find pet by ID", + "operation" : "GET", + "method" : "GET", + "url" : "/pet/${RANDOM.NUMBER:8}", + "request" : { }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + }, { + "name" : "/pet/{petId} - Deletes a pet", + "operation" : "DELETE", + "method" : "DELETE", + "url" : "/pet/${RANDOM.NUMBER:8}", + "request" : { + "headers" : { + "api_key" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + } + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_{petId}_uploadImage.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_{petId}_uploadImage.json new file mode 100644 index 00000000..8af78b3b --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_pet_{petId}_uploadImage.json @@ -0,0 +1,23 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/pet/{petId}/uploadImage - Test scenario", + "steps" : [ { + "name" : "/pet/{petId}/uploadImage - uploads an image", + "operation" : "POST", + "method" : "POST", + "url" : "/pet/${RANDOM.NUMBER:8}/uploadImage", + "request" : { + "queryParams" : { + "additionalMetadata" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, + "headers" : { + "Content-Type" : "application/octet-stream" + }, + "body" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_store_inventory.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_store_inventory.json new file mode 100644 index 00000000..ba16293f --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_store_inventory.json @@ -0,0 +1,15 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/store/inventory - Test scenario", + "steps" : [ { + "name" : "/store/inventory - Returns pet inventories by status", + "operation" : "GET", + "method" : "GET", + "url" : "/store/inventory", + "request" : { }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_store_order.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_store_order.json new file mode 100644 index 00000000..5ff92a05 --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_store_order.json @@ -0,0 +1,24 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/store/order - Test scenario", + "steps" : [ { + "name" : "/store/order - Place an order for a pet", + "operation" : "POST", + "method" : "POST", + "url" : "/store/order", + "request" : { + "body" : { + "id" : "${RANDOM.NUMBER:8}", + "petId" : "${RANDOM.NUMBER:8}", + "quantity" : "${RANDOM.NUMBER:8}", + "shipDate" : "${LOCAL.DATETIME.NOW:yyyy-MM-dd'T'HH:mm:ss}", + "status" : "placed", + "complete" : false + } + }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_store_order_{orderId}.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_store_order_{orderId}.json new file mode 100644 index 00000000..25352192 --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_store_order_{orderId}.json @@ -0,0 +1,23 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/store/order/{orderId} - Test scenario", + "steps" : [ { + "name" : "/store/order/{orderId} - Find purchase order by ID", + "operation" : "GET", + "method" : "GET", + "url" : "/store/order/${RANDOM.NUMBER:8}", + "request" : { }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + }, { + "name" : "/store/order/{orderId} - Delete purchase order by ID", + "operation" : "DELETE", + "method" : "DELETE", + "url" : "/store/order/${RANDOM.NUMBER:8}", + "request" : { }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_user.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_user.json new file mode 100644 index 00000000..5c53137b --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_user.json @@ -0,0 +1,24 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/user - Test scenario", + "steps" : [ { + "name" : "/user - Create user", + "operation" : "POST", + "method" : "POST", + "url" : "/user", + "request" : { + "body" : { + "id" : "${RANDOM.NUMBER:8}", + "username" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "firstName" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "lastName" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "email" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "password" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "phone" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "userStatus" : "${RANDOM.NUMBER:8}" + } + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_createWithList.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_createWithList.json new file mode 100644 index 00000000..f11dbd0d --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_createWithList.json @@ -0,0 +1,35 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/user/createWithList - Test scenario", + "steps" : [ { + "name" : "/user/createWithList - Creates list of users with given input array", + "operation" : "POST", + "method" : "POST", + "url" : "/user/createWithList", + "request" : { + "body" : [ { + "id" : "${RANDOM.NUMBER:8}", + "username" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "firstName" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "lastName" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "email" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "password" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "phone" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "userStatus" : "${RANDOM.NUMBER:8}" + }, { + "id" : "${RANDOM.NUMBER:8}", + "username" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "firstName" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "lastName" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "email" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "password" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "phone" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "userStatus" : "${RANDOM.NUMBER:8}" + } ] + }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_login.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_login.json new file mode 100644 index 00000000..de90adaf --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_login.json @@ -0,0 +1,20 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/user/login - Test scenario", + "steps" : [ { + "name" : "/user/login - Logs user into the system", + "operation" : "GET", + "method" : "GET", + "url" : "/user/login", + "request" : { + "queryParams" : { + "username" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "password" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + } + }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_logout.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_logout.json new file mode 100644 index 00000000..57243ea3 --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_logout.json @@ -0,0 +1,13 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/user/logout - Test scenario", + "steps" : [ { + "name" : "/user/logout - Logs out current logged in user session", + "operation" : "GET", + "method" : "GET", + "url" : "/user/logout", + "request" : { }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_{username}.json b/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_{username}.json new file mode 100644 index 00000000..dc3e6b9b --- /dev/null +++ b/zerocode-openapi/src/test/resources/integration_test_generated_files/_user_{username}.json @@ -0,0 +1,42 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/user/{username} - Test scenario", + "steps" : [ { + "name" : "/user/{username} - Get user by user name", + "operation" : "GET", + "method" : "GET", + "url" : "/user/${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "request" : { }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + }, { + "name" : "/user/{username} - Update user", + "operation" : "PUT", + "method" : "PUT", + "url" : "/user/${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "request" : { + "body" : { + "id" : "${RANDOM.NUMBER:8}", + "username" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "firstName" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "lastName" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "email" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "password" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "phone" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "userStatus" : "${RANDOM.NUMBER:8}" + } + }, + "assertions" : { }, + "ignoreStep" : false + }, { + "name" : "/user/{username} - Delete user", + "operation" : "DELETE", + "method" : "DELETE", + "url" : "/user/${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "request" : { }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-additional-properties.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-additional-properties.yml new file mode 100644 index 00000000..0ef35853 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-additional-properties.yml @@ -0,0 +1,27 @@ +openapi: "3.0.0" +paths: + /testAdditionalProperties: + post: + summary: Additional properties in body, primitive and non primitive + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Maps" +components: + schemas: + Maps: + properties: + primitiveMap: + type: object + additionalProperties: + type: string + objectMap: + type: object + additionalProperties: + type: object + properties: + prop1: + type: integer + prop2: + type: string diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-array-body.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-array-body.yml new file mode 100644 index 00000000..ce72563d --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-array-body.yml @@ -0,0 +1,12 @@ +openapi: "3.0.0" +paths: + /testArrayBody: + get: + summary: Body is not an object (array) + requestBody: + content: + application/json: + schema: + type: array + items: + type: string diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-array.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-array.yml new file mode 100644 index 00000000..19d84b5d --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-array.yml @@ -0,0 +1,33 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Array generation +paths: + /testArray: + post: + summary: Array of primitives and objects + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/TestArray" +components: + schemas: + TestArray: + properties: + arrPrimInt: + type: array + items: + type: integer + arrPrimString: + type: array + items: + type: string + arrObj: + type: array + items: + type: object + properties: + value: + type: string + \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-empty.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-empty.yml new file mode 100644 index 00000000..55f3de32 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-empty.yml @@ -0,0 +1,5 @@ +openapi: "3.0.0" +paths: + /testEmpty: + get: + summary: Neither params nor body diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-enum-urlencode.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-enum-urlencode.yml new file mode 100644 index 00000000..b1dc5265 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-enum-urlencode.yml @@ -0,0 +1,32 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Enum generation +paths: + /testEnumUrlencode/{pathEnum}: + post: + summary: Enum generation urlencoded only in path params + parameters: + - in: path + name: pathEnum + schema: + type: string + enum: ["p1 []@!$&'()*+,;=-_.", "p2 []@!$&'()*+,;=-_."] + - in: query + name: queryEnum + schema: + type: string + enum: ["q1 []@!$&'()*+,;=-_.", "q2 []@!$&'()*+,;=-_."] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/TestEnum" +components: + schemas: + TestEnum: + properties: + strEnum: + type: string + enum: ["b1 []@!$&'()*+,;=-_.", "b2 []@!$&'()*+,;=-_."] + \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-enum.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-enum.yml new file mode 100644 index 00000000..d9cd2422 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-enum.yml @@ -0,0 +1,35 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Enum generation +paths: + /testEnum/{pathEnum}: + post: + summary: Enum generation in body and params + parameters: + - in: path + name: pathEnum + schema: + type: string + enum: [penum1, penum2] + - in: query + name: queryEnum + schema: + type: string + enum: [qenum1, qenum2] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/TestEnum" +components: + schemas: + TestEnum: + properties: + intEnum: + type: integer + enum: [1, 3, 5, 7, 11] + strEnum: + type: string + enum: [red, green, blue, yellow, black] + \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-failed-step.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-failed-step.yml new file mode 100644 index 00000000..ac871e3c --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-failed-step.yml @@ -0,0 +1,30 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Generation continues if some step fails +paths: + /testFailedStep: + post: + summary: First step fails + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/TestKo" + get: + summary: Second step des not fail + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/TestOk" +components: + schemas: + TestOk: + properties: + okField: + type: string + TestKo: + properties: + koField: + type: strong diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-full.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-full.yml new file mode 100644 index 00000000..13a6e85c --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-full.yml @@ -0,0 +1,65 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Basic types, params and body +paths: + /testFull/{pparam1}/more/{pparam2}: + post: + summary: Basic types, in params and body + parameters: + - in: path + name: pparam1 + schema: + type: string + - in: path + name: pparam2 + schema: + type: integer + - in: query + name: qparam1 + schema: + type: integer + - in: query + name: qparam2 + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/TestBasic" + application/xml: + schema: + $ref: "#/components/schemas/TestBasicXml" + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/TestBasic" + "201": + description: Successful operation (other) + content: + application/json: + schema: + $ref: "#/components/schemas/TestBasic" + "400": + description: Unsuccessful operation +components: + schemas: + TestBasic: + properties: + stringField: + type: string + intField: + type: integer + objField: + type: object + properties: + value: + type: string + TestBasicXml: + properties: + xmlField: + type: string diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-header-mediatype.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-header-mediatype.yml new file mode 100644 index 00000000..bbbc201e --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-header-mediatype.yml @@ -0,0 +1,14 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Header from media type +paths: + /testHeaderMediatype: + get: + summary: Header from mediatype + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary # no standard format, handled as string diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-header-param-mediatype.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-header-param-mediatype.yml new file mode 100644 index 00000000..ff3775e6 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-header-param-mediatype.yml @@ -0,0 +1,19 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Header parameters +paths: + /testHeaderParamMediatype: + get: + summary: Header parameters and mediatype header + parameters: + - in: header + name: headerParam + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-header-param.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-header-param.yml new file mode 100644 index 00000000..6ffe522f --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-header-param.yml @@ -0,0 +1,13 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Header parameters +paths: + /testHeaderParam: + get: + summary: Header parameters + parameters: + - in: header + name: headerParam + schema: + type: string diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-other-types.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-other-types.yml new file mode 100644 index 00000000..2088480b --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-other-types.yml @@ -0,0 +1,28 @@ +openapi: "3.0.0" +paths: + /testOtherTypes: + post: + summary: Other types in body, different from integer, string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/OtherTypes" +components: + schemas: + OtherTypes: + properties: + numberField: + type: number + booleanField1: + type: boolean + booleanField2: + type: boolean + booleanField3: + type: boolean + dateField: + type: string + format: date + dateTimeField: + type: string + format: date-time diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-path-param.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-path-param.yml new file mode 100644 index 00000000..d0921208 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-path-param.yml @@ -0,0 +1,29 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Path parameters +paths: + /testPathParam/{par Enum}/array/{parArray}/object{parObject}: + get: + summary: Path parameters + parameters: + # test url encoding on name and values + - in: path + name: "par Enum" + schema: + type: string + enum: ["item$one", "item$two", "item$three", "item$four" , "item$five"] + # non primitive are not supported + - in: path + name: parArray + schema: + type: array + items: + type: string + - in: path + name: parObject + schema: + type: object + properties: + - name: value + type: string diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-query-param-form.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-query-param-form.yml new file mode 100644 index 00000000..c127f052 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-query-param-form.yml @@ -0,0 +1,32 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Array in query params, form format +paths: + /testQueryParamForm: + get: + summary: Query parameters, form style + parameters: + - in: query + name: parExpl + explode: true + schema: + type: array + items: + type: string + - in: query + name: parNoExpl + explode: false + schema: + type: array + items: + type: integer + # test url encoding on name and values + - in: query + name: "param Enum" + explode: true + schema: + type: array + items: + type: string + enum: ["item$one", "item$two", "item$three", "item$four" , "item$five"] diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-query-param-other.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-query-param-other.yml new file mode 100644 index 00000000..8fc31474 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-query-param-other.yml @@ -0,0 +1,45 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Array in query params, other formats +paths: + /testQueryParamOther: + get: + summary: Query parameters, other formats and not supported features + parameters: + - in: query + name: parSpace + explode: false + style: spaceDelimited + schema: + type: array + items: + type: string + - in: query + name: parPipe + explode: false + style: pipeDelimited + schema: + type: array + items: + type: integer + # Others formats not supported (not generated and warning) + - in: query + name: parDeepObject + explode: false + style: deepObject + schema: + type: array + items: + type: object + properties: + value: + type: string + - in: query + name: parObject + schema: + type: object + properties: + value: + type: string + diff --git a/zerocode-openapi/src/test/resources/unit_test_files/oa-string-body.yml b/zerocode-openapi/src/test/resources/unit_test_files/oa-string-body.yml new file mode 100644 index 00000000..6daaf870 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_files/oa-string-body.yml @@ -0,0 +1,10 @@ +openapi: "3.0.0" +paths: + /testStringBody: + get: + summary: Body is not an object (string) + requestBody: + content: + application/json: + schema: + type: string diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testAdditionalProperties.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testAdditionalProperties.json new file mode 100644 index 00000000..08955d34 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testAdditionalProperties.json @@ -0,0 +1,30 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testAdditionalProperties - Test scenario", + "steps" : [ { + "name" : "/testAdditionalProperties - Additional properties in body, primitive and non primitive", + "operation" : "POST", + "method" : "POST", + "url" : "/testAdditionalProperties", + "request" : { + "body" : { + "primitiveMap" : { + "primitiveMapKey0" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "primitiveMapKey1" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, + "objectMap" : { + "objectMapKey0" : { + "prop1" : "${RANDOM.NUMBER:8}", + "prop2" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, + "objectMapKey1" : { + "prop1" : "${RANDOM.NUMBER:8}", + "prop2" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + } + } + } + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testArray.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testArray.json new file mode 100644 index 00000000..8c778c91 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testArray.json @@ -0,0 +1,23 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testArray - Test scenario", + "steps" : [ { + "name" : "/testArray - Array of primitives and objects", + "operation" : "POST", + "method" : "POST", + "url" : "/testArray", + "request" : { + "body" : { + "arrPrimInt" : [ "${RANDOM.NUMBER:8}", "${RANDOM.NUMBER:8}" ], + "arrPrimString" : [ "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" ], + "arrObj" : [ { + "value" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, { + "value" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + } ] + } + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testArrayBody.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testArrayBody.json new file mode 100644 index 00000000..eb248ae3 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testArrayBody.json @@ -0,0 +1,15 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testArrayBody - Test scenario", + "steps" : [ { + "name" : "/testArrayBody - Body is not an object (array)", + "operation" : "GET", + "method" : "GET", + "url" : "/testArrayBody", + "request" : { + "body" : [ "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" ] + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testEmpty.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testEmpty.json new file mode 100644 index 00000000..2326c896 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testEmpty.json @@ -0,0 +1,13 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testEmpty - Test scenario", + "steps" : [ { + "name" : "/testEmpty - Neither params nor body", + "operation" : "GET", + "method" : "GET", + "url" : "/testEmpty", + "request" : { }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testEnumUrlencode_{pathEnum}.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testEnumUrlencode_{pathEnum}.json new file mode 100644 index 00000000..d780e2a2 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testEnumUrlencode_{pathEnum}.json @@ -0,0 +1,20 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testEnumUrlencode/{pathEnum} - Test scenario", + "steps" : [ { + "name" : "/testEnumUrlencode/{pathEnum} - Enum generation urlencoded only in path params", + "operation" : "POST", + "method" : "POST", + "url" : "/testEnumUrlencode/p1+%5B%5D%40%21%24%26%27%28%29*%2B%2C%3B%3D-_.", + "request" : { + "queryParams" : { + "queryEnum" : "q1 []@!$&'()*+,;=-_." + }, + "body" : { + "strEnum" : "b2 []@!$&'()*+,;=-_." + } + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testEnum_{pathEnum}.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testEnum_{pathEnum}.json new file mode 100644 index 00000000..f012b02b --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testEnum_{pathEnum}.json @@ -0,0 +1,21 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testEnum/{pathEnum} - Test scenario", + "steps" : [ { + "name" : "/testEnum/{pathEnum} - Enum generation in body and params", + "operation" : "POST", + "method" : "POST", + "url" : "/testEnum/penum1", + "request" : { + "queryParams" : { + "queryEnum" : "qenum1" + }, + "body" : { + "intEnum" : 5, + "strEnum" : "yellow" + } + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testFailedStep.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testFailedStep.json new file mode 100644 index 00000000..603d639d --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testFailedStep.json @@ -0,0 +1,17 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testFailedStep - Test scenario", + "steps" : [ { + "name" : "/testFailedStep - Second step des not fail", + "operation" : "GET", + "method" : "GET", + "url" : "/testFailedStep", + "request" : { + "body" : { + "okField" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + } + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testFull_{pparam1}_more_{pparam2}.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testFull_{pparam1}_more_{pparam2}.json new file mode 100644 index 00000000..feebd9f8 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testFull_{pparam1}_more_{pparam2}.json @@ -0,0 +1,27 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testFull/{pparam1}/more/{pparam2} - Test scenario", + "steps" : [ { + "name" : "/testFull/{pparam1}/more/{pparam2} - Basic types, in params and body", + "operation" : "POST", + "method" : "POST", + "url" : "/testFull/${RANDOM.STRING:12}${RANDOM.NUMBER:4}/more/${RANDOM.NUMBER:8}", + "request" : { + "queryParams" : { + "qparam1" : "${RANDOM.NUMBER:8}", + "qparam2" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, + "body" : { + "stringField" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "intField" : "${RANDOM.NUMBER:8}", + "objField" : { + "value" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + } + } + }, + "assertions" : { + "status" : 200 + }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testHeaderMediatype.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testHeaderMediatype.json new file mode 100644 index 00000000..ca9e6039 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testHeaderMediatype.json @@ -0,0 +1,18 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testHeaderMediatype - Test scenario", + "steps" : [ { + "name" : "/testHeaderMediatype - Header from mediatype", + "operation" : "GET", + "method" : "GET", + "url" : "/testHeaderMediatype", + "request" : { + "headers" : { + "Content-Type" : "application/octet-stream" + }, + "body" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testHeaderParam.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testHeaderParam.json new file mode 100644 index 00000000..06e27915 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testHeaderParam.json @@ -0,0 +1,17 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testHeaderParam - Test scenario", + "steps" : [ { + "name" : "/testHeaderParam - Header parameters", + "operation" : "GET", + "method" : "GET", + "url" : "/testHeaderParam", + "request" : { + "headers" : { + "headerParam" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + } + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testHeaderParamMediatype.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testHeaderParamMediatype.json new file mode 100644 index 00000000..917a1f51 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testHeaderParamMediatype.json @@ -0,0 +1,19 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testHeaderParamMediatype - Test scenario", + "steps" : [ { + "name" : "/testHeaderParamMediatype - Header parameters and mediatype header", + "operation" : "GET", + "method" : "GET", + "url" : "/testHeaderParamMediatype", + "request" : { + "headers" : { + "headerParam" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}", + "Content-Type" : "application/octet-stream" + }, + "body" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testOtherTypes.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testOtherTypes.json new file mode 100644 index 00000000..956bec3e --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testOtherTypes.json @@ -0,0 +1,22 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testOtherTypes - Test scenario", + "steps" : [ { + "name" : "/testOtherTypes - Other types in body, different from integer, string", + "operation" : "POST", + "method" : "POST", + "url" : "/testOtherTypes", + "request" : { + "body" : { + "numberField" : "${RANDOM.NUMBER:8}.${RANDOM.NUMBER:4}", + "booleanField1" : false, + "booleanField2" : false, + "booleanField3" : true, + "dateField" : "${LOCAL.DATE.NOW:yyyy-MM-dd}", + "dateTimeField" : "${LOCAL.DATETIME.NOW:yyyy-MM-dd'T'HH:mm:ss}" + } + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testPathParam_{par Enum}_array_{parArray}_object{parObject}.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testPathParam_{par Enum}_array_{parArray}_object{parObject}.json new file mode 100644 index 00000000..4d80901c --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testPathParam_{par Enum}_array_{parArray}_object{parObject}.json @@ -0,0 +1,13 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testPathParam/{par Enum}/array/{parArray}/object{parObject} - Test scenario", + "steps" : [ { + "name" : "/testPathParam/{par Enum}/array/{parArray}/object{parObject} - Path parameters", + "operation" : "GET", + "method" : "GET", + "url" : "/testPathParam/item%24two/array/{parArray}/object{parObject}", + "request" : { }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testQueryParamForm.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testQueryParamForm.json new file mode 100644 index 00000000..0bb0e397 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testQueryParamForm.json @@ -0,0 +1,13 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testQueryParamForm - Test scenario", + "steps" : [ { + "name" : "/testQueryParamForm - Query parameters, form style", + "operation" : "GET", + "method" : "GET", + "url" : "/testQueryParamForm?parExpl=${RANDOM.STRING:12}${RANDOM.NUMBER:4}&parExpl=${RANDOM.STRING:12}${RANDOM.NUMBER:4}&parNoExpl=${RANDOM.NUMBER:8},${RANDOM.NUMBER:8}¶m+Enum=item%24two¶m+Enum=item%24five", + "request" : { }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testQueryParamOther.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testQueryParamOther.json new file mode 100644 index 00000000..b98a43cf --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testQueryParamOther.json @@ -0,0 +1,13 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testQueryParamOther - Test scenario", + "steps" : [ { + "name" : "/testQueryParamOther - Query parameters, other formats and not supported features", + "operation" : "GET", + "method" : "GET", + "url" : "/testQueryParamOther?parSpace=${RANDOM.STRING:12}${RANDOM.NUMBER:4}%20${RANDOM.STRING:12}${RANDOM.NUMBER:4}&parPipe=${RANDOM.NUMBER:8}|${RANDOM.NUMBER:8}", + "request" : { }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file diff --git a/zerocode-openapi/src/test/resources/unit_test_generated_files/_testStringBody.json b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testStringBody.json new file mode 100644 index 00000000..17dc75f2 --- /dev/null +++ b/zerocode-openapi/src/test/resources/unit_test_generated_files/_testStringBody.json @@ -0,0 +1,15 @@ +{ + "ignoreStepFailures" : false, + "scenarioName" : "/testStringBody - Test scenario", + "steps" : [ { + "name" : "/testStringBody - Body is not an object (string)", + "operation" : "GET", + "method" : "GET", + "url" : "/testStringBody", + "request" : { + "body" : "${RANDOM.STRING:12}${RANDOM.NUMBER:4}" + }, + "assertions" : { }, + "ignoreStep" : false + } ] +} \ No newline at end of file