diff --git a/src/main/java/com/endava/cats/fuzzer/fields/base/ExactValuesInFieldsFuzzer.java b/src/main/java/com/endava/cats/fuzzer/fields/base/ExactValuesInFieldsFuzzer.java index 74bd79f31..eb7eb7744 100644 --- a/src/main/java/com/endava/cats/fuzzer/fields/base/ExactValuesInFieldsFuzzer.java +++ b/src/main/java/com/endava/cats/fuzzer/fields/base/ExactValuesInFieldsFuzzer.java @@ -93,7 +93,8 @@ private String generateWithAdjustedLength(Schema schema, int adjustedLength) { int fromSchemaLengthAdjusted = (fromSchemaLength.intValue() > Integer.MAX_VALUE / 100 - adjustedLength) ? Integer.MAX_VALUE / 100 : fromSchemaLength.intValue(); int generatedStringLength = fromSchemaLengthAdjusted + adjustedLength; - String generated = StringGenerator.generateExactLength(pattern, generatedStringLength); + String generated = StringGenerator.generateExactLength(schema, pattern, generatedStringLength); + if (CatsModelUtils.isByteArraySchema(schema)) { return Base64.getEncoder().encodeToString(generated.getBytes(StandardCharsets.UTF_8)); } diff --git a/src/main/java/com/endava/cats/generator/format/impl/PasswordGenerator.java b/src/main/java/com/endava/cats/generator/format/impl/PasswordGenerator.java index 5a62c094f..978c6a92d 100644 --- a/src/main/java/com/endava/cats/generator/format/impl/PasswordGenerator.java +++ b/src/main/java/com/endava/cats/generator/format/impl/PasswordGenerator.java @@ -7,6 +7,7 @@ import jakarta.inject.Singleton; import java.util.List; +import java.util.Locale; /** * A generator class implementing interfaces for generating valid and invalid password data formats. @@ -21,7 +22,7 @@ public Object generate(Schema schema) { @Override public boolean appliesTo(String format, String propertyName) { - return "password".equalsIgnoreCase(format); + return "password".equalsIgnoreCase(format) || propertyName.toLowerCase(Locale.ROOT).endsWith("password"); } @Override diff --git a/src/main/java/com/endava/cats/generator/simple/RegexFlattener.java b/src/main/java/com/endava/cats/generator/simple/RegexFlattener.java new file mode 100644 index 000000000..4643e60a1 --- /dev/null +++ b/src/main/java/com/endava/cats/generator/simple/RegexFlattener.java @@ -0,0 +1,45 @@ +package com.endava.cats.generator.simple; + +/** + * Flattens a regex by simplifying character classes, quantifiers, and removing unnecessary parentheses. + */ +public abstract class RegexFlattener { + + + /** + * Flattens a regex by simplifying character classes, quantifiers, and removing unnecessary parentheses. + * + * @param regex the regex to flatten + * @return the flattened regex + */ + public static String flattenRegex(String regex) { + regex = simplifyCharacterClasses(regex); + regex = simplifyQuantifiers(regex); +// regex = useNonCapturingGroups(regex); + + return regex; + } + + public static String useNonCapturingGroups(String regex) { + return regex.replaceAll("\\((?!\\?:)(?=[^()]*\\|)", "(?:"); + } + + private static String simplifyCharacterClasses(String regex) { + regex = regex.replaceAll("\\[a-zA-Z0-9_\\]", "\\\\w"); + regex = regex.replaceAll("\\[0-9\\]", "\\\\d"); + regex = regex.replaceAll("\\[\\s\\t\\r\\n\\f\\]", "\\\\s"); + + regex = regex.replaceAll("\\[^\\\\d\\]", "\\\\D"); + regex = regex.replaceAll("\\[^\\\\w\\]", "\\\\W"); + regex = regex.replaceAll("\\[^\\\\s\\]", "\\\\S"); + + return regex; + } + + private static String simplifyQuantifiers(String regex) { + regex = regex.replaceAll("\\{0,1\\}", "?"); + regex = regex.replaceAll("\\{1,\\}", "+"); + regex = regex.replaceAll("\\{0,\\}", "*"); + return regex; + } +} diff --git a/src/main/java/com/endava/cats/generator/simple/StringGenerator.java b/src/main/java/com/endava/cats/generator/simple/StringGenerator.java index 874d2410f..1ccbb48f2 100755 --- a/src/main/java/com/endava/cats/generator/simple/StringGenerator.java +++ b/src/main/java/com/endava/cats/generator/simple/StringGenerator.java @@ -1,5 +1,6 @@ package com.endava.cats.generator.simple; +import com.endava.cats.util.CatsModelUtils; import com.endava.cats.util.CatsUtil; import com.github.curiousoddman.rgxgen.RgxGen; import io.github.ludovicianul.prettylogger.PrettyLogger; @@ -14,12 +15,19 @@ import org.springframework.util.CollectionUtils; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.Optional; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import static com.endava.cats.util.CatsModelUtils.isEmail; +import static com.endava.cats.util.CatsModelUtils.isPassword; +import static com.endava.cats.util.CatsModelUtils.isUri; + /** * Generates strings based on different criteria. */ @@ -49,6 +57,11 @@ public class StringGenerator { private static final Pattern LENGTH_INLINE_PATTERN = Pattern.compile("(\\^)?(\\[[^]]*]\\{\\d+}|\\(\\[[^]]*]\\{\\d+}\\)\\?)*(\\$)?"); + private static final String ALPHANUMERIC = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + private static final String[] DOMAINS = {"example", "cats", "google", "yahoo"}; + private static final String[] TLDS = {".com", ".net", ".org", ".io"}; + private static final String[] URI_SCHEMES = {"http", "https", "ftp", "file"}; + /** * Represents an empty string. */ @@ -127,12 +140,17 @@ public static String generateLargeString(int times) { * @param length the desired length * @return a generated value of exact length provided */ - public static String generateExactLength(String regex, int length) { + public static String generateExactLength(Schema schema, String regex, int length) { if (length <= 0) { return EMPTY; } - StringBuilder initialValue = new StringBuilder(StringGenerator.sanitize(generate(cleanPattern(regex), length, length))); + String stringFromComplexRegex = generateComplexRegex(schema, length); + if (stringFromComplexRegex != null) { + return stringFromComplexRegex; + } + + StringBuilder initialValue = new StringBuilder(StringGenerator.sanitize(generate(regex, length, length))); if (initialValue.isEmpty()) { return EMPTY; @@ -162,9 +180,10 @@ public static String generateExactLength(String regex, int length) { */ public static String generate(String pattern, int min, int max) { LOGGER.debug("Generate for pattern {} min {} max {}", pattern, min, max); - pattern = cleanPattern(pattern); + String cleanedPattern = cleanPattern(pattern); + String flattenedPattern = RegexFlattener.flattenRegex(cleanedPattern); - GeneratorParams generatorParams = new GeneratorParams(pattern, min, max); + GeneratorParams generatorParams = new GeneratorParams(flattenedPattern, min, max, cleanedPattern); String generatedWithRgxGenerator = callGenerateTwice(StringGenerator::generateUsingRgxGenerator, generatorParams); if (generatedWithRgxGenerator != null) { @@ -187,8 +206,8 @@ public static String generate(String pattern, int min, int max) { public static String callGenerateTwice(Function generator, GeneratorParams generatorParams) { try { String initialVersion = generator.apply(generatorParams); - if (initialVersion.matches(generatorParams.pattern)) { - LOGGER.debug("Generated value " + initialVersion + " matched " + generatorParams.pattern); + if (initialVersion.matches(generatorParams.originalPattern())) { + LOGGER.debug("Generated value " + initialVersion + " matched " + generatorParams.originalPattern()); return initialVersion; } } catch (Exception e) { @@ -196,9 +215,9 @@ public static String callGenerateTwice(Function generat } try { - String secondVersion = generator.apply(new GeneratorParams(removeLookaheadAssertions(generatorParams.pattern), generatorParams.min, generatorParams.max)); - if (secondVersion.matches(generatorParams.pattern)) { - LOGGER.debug("Generated value with lookaheads removed " + secondVersion + " matched " + generatorParams.pattern); + String secondVersion = generator.apply(new GeneratorParams(removeLookaheadAssertions(generatorParams.cleanedPattern()), generatorParams.min, generatorParams.max, generatorParams.originalPattern())); + if (secondVersion.matches(generatorParams.originalPattern())) { + LOGGER.debug("Generated value with lookaheads removed " + secondVersion + " matched " + generatorParams.originalPattern()); return secondVersion; } } catch (Exception e) { @@ -236,7 +255,8 @@ public static String cleanPattern(String pattern) { } private static String generateUsingRegexpGen(GeneratorParams generatorParams) { - String pattern = generatorParams.pattern; + String pattern = generatorParams.cleanedPattern(); + String originalPattern = generatorParams.originalPattern(); int min = generatorParams.min; int max = generatorParams.max; @@ -248,7 +268,7 @@ private static String generateUsingRegexpGen(GeneratorParams generatorParams) { } String generated = generator.generate(REGEXP_RANDOM_GEN, min, max); - if (generated.matches(pattern)) { + if (generated.matches(originalPattern)) { LOGGER.debug("Generated using REGEXP {} matches {}", generated, pattern); return generated; } @@ -259,7 +279,8 @@ private static String generateUsingRegexpGen(GeneratorParams generatorParams) { } private static String generateUsingCatsRegexGenerator(GeneratorParams generatorParams) { - String pattern = generatorParams.pattern; + String pattern = generatorParams.cleanedPattern(); + String originalPattern = generatorParams.originalPattern(); int min = generatorParams.min; int max = generatorParams.max; @@ -267,13 +288,13 @@ private static String generateUsingCatsRegexGenerator(GeneratorParams generatorP Pattern compiledPattern = Pattern.compile(pattern); String secondVersionBase = RegexGenerator.generate(compiledPattern, EMPTY, min, max); - if (secondVersionBase.matches(pattern)) { + if (secondVersionBase.matches(originalPattern)) { LOGGER.debug("Generated using CATS generator {} and matches {}", secondVersionBase, pattern); return secondVersionBase; } String generatedString = composeString(secondVersionBase, min, max); - if (generatedString.matches(pattern)) { + if (generatedString.matches(originalPattern)) { LOGGER.debug("Generated using CATS generator {} and matches {}", generatedString, pattern); return generatedString; } @@ -284,19 +305,20 @@ private static String generateUsingCatsRegexGenerator(GeneratorParams generatorP private static String generateUsingRgxGenerator(GeneratorParams generatorParams) { int attempts = 0; String generatedValue; - String pattern = generatorParams.pattern; + String pattern = generatorParams.cleanedPattern(); + String originalPattern = generatorParams.originalPattern(); int min = generatorParams.min; int max = generatorParams.max; try { do { generatedValue = new RgxGen(pattern).generate(); - if ((hasLengthInline(pattern) || isSetOfAlternatives(pattern) || (min <= 0 && max <= 0)) && generatedValue.matches(pattern)) { + if ((hasLengthInline(pattern) || isSetOfAlternatives(pattern) || (min <= 0 && max <= 0)) && generatedValue.matches(originalPattern)) { return generatedValue; } generatedValue = composeString(generatedValue, min, max); attempts++; - } while (attempts < MAX_ATTEMPTS_GENERATE && !generatedValue.matches(pattern)); + } while (attempts < MAX_ATTEMPTS_GENERATE && !generatedValue.matches(originalPattern)); } catch (Exception e) { LOGGER.debug("RGX generator failed, returning empty.", e); return ALPHANUMERIC_VALUE; @@ -443,6 +465,11 @@ public static String generateValueBasedOnMinMax(Schema property) { maxLength = minLength; } + String complexRegexGenerated = generateComplexRegex(property, Math.max(1, maxLength)); + if (complexRegexGenerated != null) { + return complexRegexGenerated; + } + return StringGenerator.generate(pattern, minLength, maxLength); } @@ -481,30 +508,103 @@ public static String removeLookaheadAssertions(String regex) { return regex; } + public static String generateFixedLengthEmail(int length) { + String domain = DOMAINS[CatsUtil.random().nextInt(DOMAINS.length)]; + String tld = TLDS[CatsUtil.random().nextInt(TLDS.length)]; + + int localPartLength = length - domain.length() - tld.length() - 1; // -1 for '@' + + StringBuilder localPart = new StringBuilder(); + for (int i = 0; i < localPartLength; i++) { + localPart.append(ALPHANUMERIC.charAt(CatsUtil.random().nextInt(ALPHANUMERIC.length()))); + } + + return localPart + "@" + domain + tld; + } + + public static String generateFixedLengthUri(int length) { + String scheme = URI_SCHEMES[CatsUtil.random().nextInt(URI_SCHEMES.length)]; + + String domain = DOMAINS[CatsUtil.random().nextInt(DOMAINS.length)]; + String tld = TLDS[CatsUtil.random().nextInt(TLDS.length)]; + + String fixedPart = scheme + "://" + domain + tld; + + int pathLength = length - fixedPart.length(); + if (pathLength <= 0) { + return fixedPart.substring(0, length); + } + + StringBuilder path = new StringBuilder(); + path.append("/"); + for (int i = 0; i < pathLength - 1; i++) { + path.append(ALPHANUMERIC.charAt(CatsUtil.random().nextInt(ALPHANUMERIC.length()))); + } + + return fixedPart + path; + } + + /** + * There are complex regexes which will fail to generate a string of a given length, especially for a fixed and large length. + * This is particularly true for email addresses and URIs where patterns can be quite complex. + * Sometimes, for large generated strings, the match of the generated string against the given regex will fail with StackOverflowError. + *

+ * This method tries to generate a string of a given length for such complex regexes. It only supports URIs and emails for now. + * + * @param schema the schema + * @param length the length + * @return a string of given length matching patterns schema + */ + private static String generateComplexRegex(Schema schema, int length) { + if (StringUtils.isBlank(schema.getPattern())) { + return null; + } + + String lowerField = Optional.ofNullable(schema.getExtensions()).orElse(Collections.emptyMap()).getOrDefault(CatsModelUtils.X_CATS_FIELD_NAME, "").toString().toLowerCase(Locale.ROOT); + String pattern = schema.getPattern(); + + if (isUri(pattern, lowerField)) { + return generateFixedLengthUri(length); + } + if (isEmail(pattern, lowerField)) { + return generateFixedLengthEmail(length); + } + if (isPassword(pattern, lowerField)) { + return "catsISC00l#" + RandomStringUtils.secure().nextPrint(length - 11); + } + + return null; + } + /** * A record that holds the parameters for the string generator. * - * @param pattern the pattern to check - * @param min the minimum length - * @param max the maximum length + * @param cleanedPattern the pattern to check + * @param min the minimum length + * @param max the maximum length + * @param originalPattern the original pattern */ - public record GeneratorParams(String pattern, int min, int max) { + public record GeneratorParams(String cleanedPattern, int min, int max, String originalPattern) { /** * Instantiates a new Generator params. * - * @param pattern the pattern - * @param min the min - * @param max the max + * @param cleanedPattern the pattern + * @param min the min + * @param max the max + * @param originalPattern the original pattern */ - public GeneratorParams(String pattern, int min, int max) { + public GeneratorParams(String cleanedPattern, int min, int max, String originalPattern) { this.min = min; this.max = max; + this.originalPattern = inlineLengthIfNeeded(originalPattern, min, max); + this.cleanedPattern = inlineLengthIfNeeded(cleanedPattern, min, max); + } + private String inlineLengthIfNeeded(String pattern, int min, int max) { if (!hasLength(pattern) && (min > 0 || max > 0)) { - this.pattern = pattern + "{" + min + "," + max + "}"; - } else { - this.pattern = pattern; + return pattern + "{" + min + "," + max + "}"; } + return pattern; } } } diff --git a/src/main/java/com/endava/cats/model/FuzzingData.java b/src/main/java/com/endava/cats/model/FuzzingData.java index 75b3e8279..450d25e9d 100644 --- a/src/main/java/com/endava/cats/model/FuzzingData.java +++ b/src/main/java/com/endava/cats/model/FuzzingData.java @@ -1,8 +1,8 @@ package com.endava.cats.model; import com.endava.cats.http.HttpMethod; -import com.endava.cats.util.JsonUtils; import com.endava.cats.util.CatsModelUtils; +import com.endava.cats.util.JsonUtils; import io.github.ludovicianul.prettylogger.PrettyLogger; import io.github.ludovicianul.prettylogger.PrettyLoggerFactory; import io.swagger.v3.oas.models.OpenAPI; @@ -163,6 +163,8 @@ private Set getFields(Schema schema, String prefix) { return catsFields.stream() .filter(catsField -> this.getRequestPropertyTypes().get(catsField.getName()) != null) + //this is a bit of a hack that might be abused in the future to include a full object as extension. currently it only holds the field name + .peek(catsField -> catsField.getSchema().addExtension(CatsModelUtils.X_CATS_FIELD_NAME, catsField.getName())) .collect(Collectors.toSet()); } diff --git a/src/main/java/com/endava/cats/util/CatsModelUtils.java b/src/main/java/com/endava/cats/util/CatsModelUtils.java index 7371a551a..fed5cf791 100644 --- a/src/main/java/com/endava/cats/util/CatsModelUtils.java +++ b/src/main/java/com/endava/cats/util/CatsModelUtils.java @@ -5,9 +5,13 @@ import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.parser.util.SchemaTypeUtil; +import org.apache.commons.lang3.StringUtils; import org.openapitools.codegen.utils.ModelUtils; +import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.Optional; import java.util.stream.Stream; /** @@ -15,6 +19,7 @@ * some particular conditions needed by CATS. */ public abstract class CatsModelUtils { + public static final String X_CATS_FIELD_NAME = "x-cats-field-name"; private CatsModelUtils() { //ntd @@ -142,4 +147,54 @@ public static String eliminateDuplicatePart(String input) { }) .orElse(""); } + + /** + * Checks if the field name contains a complex regex. For now, this is used to determine if the field is an email or a URI. + * This is of course not 100% accurate, but it's a good start. + * + * @param schema the schema to be checked + * @return true if the field name contains a complex regex, false otherwise + */ + public static boolean isComplexRegex(Schema schema) { + if (StringUtils.isBlank(schema.getPattern())) { + return false; + } + + String lowerField = Optional.ofNullable(schema.getExtensions()).orElse(Collections.emptyMap()).getOrDefault(X_CATS_FIELD_NAME, "").toString().toLowerCase(Locale.ROOT); + String pattern = schema.getPattern(); + return isEmail(pattern, lowerField) || isUri(pattern, lowerField) || isPassword(pattern, lowerField); + } + + /** + * Checks if the given combination or pattern and field name is a URI. + * + * @param pattern the pattern of the field + * @param lowerField the name of the field + * @return true if the field name contains a complex regex, false otherwise + */ + public static boolean isUri(String pattern, String lowerField) { + return (lowerField.contains("url") || lowerField.contains("uri")) && ("http://www.test.com".matches(pattern) || "https://www.test.com".matches(pattern)); + } + + /** + * Checks if the given combination or pattern and field name is an email. + * + * @param pattern the pattern of the field + * @param lowerField the name of the field + * @return true if the field name contains a complex regex, false otherwise + */ + public static boolean isEmail(String pattern, String lowerField) { + return lowerField.contains("email") && "test@test.com".matches(pattern); + } + + /** + * Checks if the given combination or pattern and field name is a password. + * + * @param pattern the pattern of the field + * @param lowerField the name of the field + * @return true if the field name contains a complex regex, false otherwise + */ + public static boolean isPassword(String pattern, String lowerField) { + return lowerField.contains("password") && "catsISc00l?!useIt#".matches(pattern); + } } diff --git a/src/test/java/com/endava/cats/generator/format/impl/PasswordGeneratorTest.java b/src/test/java/com/endava/cats/generator/format/impl/PasswordGeneratorTest.java index f62f6bc26..d5308bba6 100644 --- a/src/test/java/com/endava/cats/generator/format/impl/PasswordGeneratorTest.java +++ b/src/test/java/com/endava/cats/generator/format/impl/PasswordGeneratorTest.java @@ -23,6 +23,13 @@ void shouldApply(String format, boolean expected) { Assertions.assertThat(passwordGenerator.appliesTo(format, "")).isEqualTo(expected); } + @ParameterizedTest + @CsvSource({"password,true", "other,false"}) + void shouldApplyToPropertyName(String property, boolean expected) { + PasswordGenerator passwordGenerator = new PasswordGenerator(); + Assertions.assertThat(passwordGenerator.appliesTo("", property)).isEqualTo(expected); + } + @Test void givenAPasswordFormatGeneratorStrategy_whenGettingTheAlmostValidValue_thenTheValueIsReturnedAsExpected() { PasswordGenerator strategy = new PasswordGenerator(); diff --git a/src/test/java/com/endava/cats/generator/simple/StringGeneratorTest.java b/src/test/java/com/endava/cats/generator/simple/StringGeneratorTest.java index 76d6b25d3..51e939474 100644 --- a/src/test/java/com/endava/cats/generator/simple/StringGeneratorTest.java +++ b/src/test/java/com/endava/cats/generator/simple/StringGeneratorTest.java @@ -1,5 +1,6 @@ package com.endava.cats.generator.simple; +import com.endava.cats.util.CatsModelUtils; import io.quarkus.test.junit.QuarkusTest; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; @@ -114,7 +115,7 @@ void shouldSanitize(String input, String expected) { @ParameterizedTest @CsvSource(value = {"^\\+?[1-9]\\d{6,15}$;16", "[A-Z]+;20", "[A-Z0-9]{13,18};18", "[0-9]+;10", "^(?=[^\\s])(?=.*[^\\s]$)(?=^(?:(?!<|>|%3e|%3c).)*$).*$;2048", "M|F;1"}, delimiterString = ";") void shouldGenerateFixedLength(String pattern, int length) { - String fixedLengthGenerated = StringGenerator.generateExactLength(pattern, length); + String fixedLengthGenerated = StringGenerator.generateExactLength(new Schema(), pattern, length); Assertions.assertThat(fixedLengthGenerated).hasSize(length).matches(pattern); } @@ -148,7 +149,7 @@ void shouldGenerateLeftBoundaryForEnum() { @Test void shouldGenerateEmptyWhenLengthZero() { - String generated = StringGenerator.generateExactLength("^[A-Z]{3}$", 0); + String generated = StringGenerator.generateExactLength(new Schema(), "^[A-Z]{3}$", 0); Assertions.assertThat(generated).isEmpty(); } @@ -193,4 +194,42 @@ void shouldGenerateWhenPatternDoesNotHaveLengthButHasMinOrMax() { String generated = StringGenerator.generate("[A-Z]", 4, 4); Assertions.assertThat(generated).hasSize(4); } + + @Test + void shouldGenerateComplexEmailRegex() { + String regex = "^((([a-z]|\\d|[!#\\$%&'\\\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\\\+\\-\\/=\\?\\^`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+))|((\\x22)((((\\x20|\\x09)(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))(((\\x20|\\x09)(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|||~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+([a-z]+|\\d|-|\\.{0,1}|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])?([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))$"; + String generated = StringGenerator.generate(regex, 1, 6000); + Assertions.assertThat(generated).hasSizeBetween(1, 6000); + } + + @ParameterizedTest + @CsvSource(value = { + "[a-zA-Z0-9_]+@[a-zA-Z0-9_]+\\.[a-zA-Z]{2,4};\\w+@\\w+\\.[a-zA-Z]{2,4}", + "ab{1,}c{1}d{0,1};ab+c{1}d?"}, + delimiter = ';') + void shouldFlatten(String regex, String expected) { + String flattened = RegexFlattener.flattenRegex(regex); + Assertions.assertThat(flattened).isEqualTo(expected); + } + + @Test + void shouldGenerateEmail() { + Schema schema = new Schema<>(); + schema.addExtension(CatsModelUtils.X_CATS_FIELD_NAME, "email"); + schema.setPattern("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"); + String generated = StringGenerator.generateExactLength(schema, "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", 200); + + Assertions.assertThat(generated).hasSize(200); + } + + @Test + void shouldGenerateEmailBasedOnMixMax() { + Schema schema = new Schema<>(); + schema.setPattern("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"); + schema.setMaxLength(100); + schema.addExtension(CatsModelUtils.X_CATS_FIELD_NAME, "email"); + String generated = StringGenerator.generateValueBasedOnMinMax(schema); + + Assertions.assertThat(generated).hasSize(100); + } } diff --git a/src/test/java/com/endava/cats/util/CatsModeLUtilsTest.java b/src/test/java/com/endava/cats/util/CatsModeLUtilsTest.java index 1a9ea6268..599330dc1 100644 --- a/src/test/java/com/endava/cats/util/CatsModeLUtilsTest.java +++ b/src/test/java/com/endava/cats/util/CatsModeLUtilsTest.java @@ -1,6 +1,7 @@ package com.endava.cats.util; import io.quarkus.test.junit.QuarkusTest; +import io.swagger.v3.oas.models.media.Schema; import org.assertj.core.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -19,4 +20,24 @@ void shouldReturnSimpleReference(String ref, String expected) { Assertions.assertThat(result).isEqualTo(expected); } + + @ParameterizedTest + @CsvSource(value = {"email;[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,};true", + "email;[A-Z]+;false", + "uri;^(?:[a-z]+:)?//[^\\s]*;true", + "wrong;^(?:[a-z]+:)?//[^\\\\s]*;false", + "url;null;false", + "password;[a-zA-Z0-9._%+\\-\\!#\\?]+;true", + "wrong;[a-zA-Z0-9._%+\\-\\!#\\?]+;false", + "password;[a-zA-Z0-9._%+\\-\\!#]+;false", + "wrong;wrong;false"}, + delimiter = ';', nullValues = "null") + void shouldTestEmailAndUrlNameMatches(String name, String pattern, boolean expected) { + Schema schema = new Schema<>(); + schema.addExtension(CatsModelUtils.X_CATS_FIELD_NAME, name); + schema.setPattern(pattern); + boolean result = CatsModelUtils.isComplexRegex(schema); + + Assertions.assertThat(result).isEqualTo(expected); + } }