-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(#1175): added random generator framework
Framework was added in favour of OpenApiTestDataGenerator implementation
- Loading branch information
Thorsten Schlathoelter
committed
Jul 7, 2024
1 parent
8ef6181
commit 8f4bcb5
Showing
37 changed files
with
2,627 additions
and
1,046 deletions.
There are no files selected for viewing
600 changes: 18 additions & 582 deletions
600
...rs/citrus-openapi/src/main/java/org/citrusframework/openapi/OpenApiTestDataGenerator.java
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
...citrus-openapi/src/main/java/org/citrusframework/openapi/random/RandomArrayGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package org.citrusframework.openapi.random; | ||
|
||
import io.apicurio.datamodels.openapi.models.OasSchema; | ||
import java.util.concurrent.ThreadLocalRandom; | ||
import org.citrusframework.openapi.model.OasModelHelper; | ||
|
||
/** | ||
* A generator for producing random arrays based on an OpenAPI schema. This class extends the | ||
* {@link RandomGenerator} and provides a specific implementation for generating random arrays | ||
* with constraints defined in the schema. | ||
* | ||
* <p>The generator supports arrays with items of a single schema type. If the array's items have | ||
* different schemas, an {@link UnsupportedOperationException} will be thrown.</p>s | ||
* | ||
*/ | ||
public class RandomArrayGenerator extends RandomGenerator { | ||
|
||
@Override | ||
public boolean handles(OasSchema other) { | ||
return OasModelHelper.isArrayType(other); | ||
} | ||
|
||
@Override | ||
void generate(RandomContext randomContext, OasSchema schema) { | ||
Object items = schema.items; | ||
|
||
if (items instanceof OasSchema itemsSchema) { | ||
createRandomArrayValueWithSchemaItem(randomContext, schema, itemsSchema); | ||
} else { | ||
throw new UnsupportedOperationException( | ||
"Random array creation for an array with items having different schema is currently not supported!"); | ||
} | ||
} | ||
|
||
private static void createRandomArrayValueWithSchemaItem(RandomContext randomContext, | ||
OasSchema schema, | ||
OasSchema itemsSchema) { | ||
|
||
Number minItems = schema.minItems != null ? schema.minItems : 1; | ||
Number maxItems = schema.maxItems != null ? schema.maxItems : 9; | ||
|
||
int nItems = ThreadLocalRandom.current() | ||
.nextInt(minItems.intValue(), maxItems.intValue() + 1); | ||
|
||
randomContext.getRandomModelBuilder().array(() -> { | ||
for (int i = 0; i < nItems; i++) { | ||
randomContext.generate(itemsSchema); | ||
} | ||
}); | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
...us-openapi/src/main/java/org/citrusframework/openapi/random/RandomCompositeGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package org.citrusframework.openapi.random; | ||
|
||
import static org.springframework.util.CollectionUtils.isEmpty; | ||
|
||
import io.apicurio.datamodels.openapi.models.OasSchema; | ||
import io.apicurio.datamodels.openapi.v3.models.Oas30Schema; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.ThreadLocalRandom; | ||
import org.citrusframework.openapi.model.OasModelHelper; | ||
|
||
/** | ||
* A generator for producing random composite schemas based on an OpenAPI schema. This class extends | ||
* the {@link RandomGenerator} and provides a specific implementation for generating composite schemas | ||
* with constraints defined in the schema. | ||
* | ||
* <p>The generator supports composite schemas, which include `allOf`, `anyOf`, and `oneOf` constructs.</p> | ||
*/ | ||
public class RandomCompositeGenerator extends RandomGenerator { | ||
|
||
@Override | ||
public boolean handles(OasSchema other) { | ||
return OasModelHelper.isCompositeSchema(other); | ||
} | ||
|
||
@Override | ||
void generate(RandomContext randomContext, OasSchema schema) { | ||
|
||
if (!isEmpty(schema.allOf)) { | ||
createAllOff(randomContext, schema); | ||
} else if (schema instanceof Oas30Schema oas30Schema && !isEmpty(oas30Schema.anyOf)) { | ||
createAnyOf(randomContext, oas30Schema); | ||
} else if (schema instanceof Oas30Schema oas30Schema && !isEmpty(oas30Schema.oneOf)) { | ||
createOneOf(randomContext, oas30Schema.oneOf); | ||
} | ||
} | ||
|
||
private static void createOneOf(RandomContext randomContext, List<OasSchema> schemas) { | ||
int schemaIndex = ThreadLocalRandom.current().nextInt(schemas.size()); | ||
randomContext.getRandomModelBuilder().object(() -> | ||
randomContext.generate(schemas.get(schemaIndex))); | ||
} | ||
|
||
private static void createAnyOf(RandomContext randomContext, Oas30Schema schema) { | ||
|
||
randomContext.getRandomModelBuilder().object(() -> { | ||
boolean anyAdded = false; | ||
for (OasSchema oneSchema : schema.anyOf) { | ||
if (ThreadLocalRandom.current().nextBoolean()) { | ||
randomContext.generate(oneSchema); | ||
anyAdded = true; | ||
} | ||
} | ||
|
||
// Add at least one | ||
if (!anyAdded) { | ||
createOneOf(randomContext, schema.anyOf); | ||
} | ||
}); | ||
} | ||
|
||
private static Map<String, Object> createAllOff(RandomContext randomContext, OasSchema schema) { | ||
Map<String, Object> allOf = new HashMap<>(); | ||
|
||
randomContext.getRandomModelBuilder().object(() -> { | ||
for (OasSchema oneSchema : schema.allOf) { | ||
randomContext.generate(oneSchema); | ||
} | ||
}); | ||
|
||
return allOf; | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
.../citrus-openapi/src/main/java/org/citrusframework/openapi/random/RandomConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package org.citrusframework.openapi.random; | ||
|
||
import static org.citrusframework.openapi.OpenApiConstants.FORMAT_DATE; | ||
import static org.citrusframework.openapi.OpenApiConstants.FORMAT_DATE_TIME; | ||
import static org.citrusframework.openapi.OpenApiConstants.FORMAT_UUID; | ||
import static org.citrusframework.openapi.OpenApiConstants.TYPE_BOOLEAN; | ||
import static org.citrusframework.openapi.OpenApiConstants.TYPE_STRING; | ||
import static org.citrusframework.openapi.random.RandomGenerator.ANY; | ||
import static org.citrusframework.openapi.random.RandomGenerator.NULL_GENERATOR; | ||
import static org.citrusframework.openapi.random.RandomGeneratorBuilder.builder; | ||
|
||
import io.apicurio.datamodels.openapi.models.OasSchema; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
/** | ||
* Configuration class that initializes and manages a list of random generators | ||
* for producing random data based on an OpenAPI schema. This class is a singleton | ||
* and provides a static instance {@code RANDOM_CONFIGURATION} for global access. | ||
*/ | ||
public class RandomConfiguration { | ||
|
||
private static final String EMAIL_PATTERN = "[a-z]{5,15}\\.?[a-z]{5,15}\\@[a-z]{5,15}\\.[a-z]{2}"; | ||
private static final String URI_PATTERN = "((http|https)://[a-zA-Z0-9-]+(\\.[a-zA-Z]{2,})+(/[a-zA-Z0-9-]+){1,6})|(file:///[a-zA-Z0-9-]+(/[a-zA-Z0-9-]+){1,6})"; | ||
private static final String HOSTNAME_PATTERN = "(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])"; | ||
private static final String IPV4_PATTERN = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"; | ||
private static final String IPV6_PATTERN = "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"; | ||
|
||
private final List<RandomGenerator> randomGenerators; | ||
|
||
public static final RandomConfiguration RANDOM_CONFIGURATION = new RandomConfiguration(); | ||
|
||
private RandomConfiguration() { | ||
List<RandomGenerator> generators = new ArrayList<>(); | ||
|
||
// Note that the order of generators in the list is relevant, as the list is traversed from start to end, to find the first matching generator for a schema, and some generators match for less significant schemas. | ||
generators.add(new RandomEnumGenerator()); | ||
generators.add(builder(TYPE_STRING, FORMAT_DATE).build((randomContext, schema) -> randomContext.getRandomModelBuilder().appendSimpleQuoted("citrus:currentDate('yyyy-MM-dd')"))); | ||
generators.add(builder(TYPE_STRING, FORMAT_DATE_TIME).build((randomContext, schema) -> randomContext.getRandomModelBuilder().appendSimpleQuoted("citrus:currentDate('yyyy-MM-dd'T'hh:mm:ssZ')"))); | ||
generators.add(builder(TYPE_STRING, FORMAT_UUID).build((randomContext, schema) -> randomContext.getRandomModelBuilder().appendSimpleQuoted("citrus:randomUUID()"))); | ||
generators.add(builder(TYPE_STRING, "email").build((randomContext, schema) -> randomContext.getRandomModelBuilder().appendSimpleQuoted("citrus:randomPattern('"+EMAIL_PATTERN+"')"))); | ||
generators.add(builder(TYPE_STRING, "uri").build((randomContext, schema) -> randomContext.getRandomModelBuilder().appendSimpleQuoted("citrus:randomPattern('"+URI_PATTERN+"')"))); | ||
generators.add(builder(TYPE_STRING, "hostname").build((randomContext, schema) -> randomContext.getRandomModelBuilder().appendSimpleQuoted("citrus:randomPattern('"+HOSTNAME_PATTERN+"')"))); | ||
generators.add(builder(TYPE_STRING, "ipv4").build((randomContext, schema) -> randomContext.getRandomModelBuilder().appendSimpleQuoted("citrus:randomPattern('"+IPV4_PATTERN+"')"))); | ||
generators.add(builder(TYPE_STRING,"ipv6").build((randomContext, schema) -> randomContext.getRandomModelBuilder().appendSimpleQuoted("citrus:randomPattern('"+IPV6_PATTERN+"')"))); | ||
generators.add(builder().withType(TYPE_STRING).withPattern(ANY).build((randomContext, schema) -> randomContext.getRandomModelBuilder().appendSimpleQuoted("citrus:randomPattern('"+schema.pattern+"')"))); | ||
generators.add(builder().withType(TYPE_BOOLEAN).build((randomContext, schema) -> randomContext.getRandomModelBuilder().appendSimple("citrus:randomEnumValue('true', 'false')"))); | ||
generators.add(new RandomStringGenerator()); | ||
generators.add(new RandomCompositeGenerator()); | ||
generators.add(new RandomNumberGenerator()); | ||
generators.add(new RandomObjectGenerator()); | ||
generators.add(new RandomArrayGenerator()); | ||
|
||
randomGenerators = Collections.unmodifiableList(generators); | ||
} | ||
|
||
public RandomGenerator getGenerator(OasSchema oasSchema) { | ||
return randomGenerators.stream().filter(generator -> generator.handles(oasSchema)) | ||
.findFirst() | ||
.orElse(NULL_GENERATOR); | ||
} | ||
} |
Oops, something went wrong.