Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pattern Validation - Max Length Issue Fix #1426

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 19 additions & 23 deletions core/src/main/kotlin/io/specmatic/core/pattern/StringPattern.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,36 +67,27 @@ data class StringPattern (
return JSONArrayValue(valueList)
}

private val randomStringLength: Int =
private val patternMinLength: Int =
when {
minLength != null && 5 < minLength -> minLength
maxLength != null && 5 > maxLength -> maxLength
minLength != null && minLength > 0 -> minLength
maxLength != null && maxLength < 5 -> 1
samyakOO7 marked this conversation as resolved.
Show resolved Hide resolved
else -> 5
}

override fun generate(resolver: Resolver): Value {
val defaultExample: Value? = resolver.resolveExample(example, this)

if (regex != null) {
if(defaultExample == null)
return StringValue(Generex(regex.removePrefix("^").removeSuffix("$")).random(randomStringLength))

val defaultExampleMatchResult = matches(defaultExample, resolver)

if(defaultExampleMatchResult.isSuccess())
return defaultExample

throw ContractException("Schema example ${defaultExample.toStringLiteral()} does not match pattern $regex")
}

if(defaultExample != null) {
if(defaultExample !is StringValue)
throw ContractException("Schema example ${defaultExample.toStringLiteral()} is not a string")
override fun generate(resolver: Resolver): Value {
val defaultExample = resolver.resolveExample(example, this)

return defaultExample
defaultExample?.let {
if (matches(it, resolver).isSuccess()) {
return it
}
throw ContractException("Schema example ${it.toStringLiteral()} does not match pattern $regex")
samyakOO7 marked this conversation as resolved.
Show resolved Hide resolved
}

return StringValue(randomString(randomStringLength))
return regex?.let {
val regexWithoutCaretAndDollar = regex.removePrefix("^").removeSuffix("$")
StringValue(generateFromRegex(regexWithoutCaretAndDollar, patternMinLength, maxLength))
} ?: StringValue(randomString(patternMinLength))
}

override fun newBasedOn(row: Row, resolver: Resolver): Sequence<ReturnValue<Pattern>> {
Expand Down Expand Up @@ -157,6 +148,11 @@ data class StringPattern (

override val pattern: Any = "(string)"
override fun toString(): String = pattern.toString()

private fun generateFromRegex(regexWithoutCaretAndDollar: String, minLength: Int, maxLength: Int?): String =
maxLength?.let {
Generex(regexWithoutCaretAndDollar).random(minLength, it)
samyakOO7 marked this conversation as resolved.
Show resolved Hide resolved
} ?: Generex(regexWithoutCaretAndDollar).random(minLength)
}

fun randomString(length: Int = 5): String {
Expand Down
47 changes: 37 additions & 10 deletions core/src/test/kotlin/io/specmatic/core/pattern/StringPatternTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,28 +72,47 @@ internal class StringPatternTest {

companion object {
@JvmStatic
fun lengthTestValues(): Stream<Arguments> {
fun minLengthMaxLengthAndExpectedLength(): Stream<Arguments> {
return Stream.of(
Arguments.of(null, 10, 5),
Arguments.of(null, 4, 4),
Arguments.of(1, 10, 5),
Arguments.of(1, 4, 4),
Arguments.of(1, 5, 5),
Arguments.of(null, 4, 1),
Arguments.of(1, 10, 1),
Arguments.of(5, 10, 5),
Arguments.of(6, 10, 6),
Arguments.of(6, null, 6),
Arguments.of(3, null, 5),
Arguments.of(null, null, 5)
)
}

@JvmStatic
fun regexMinLengthAndMaxLengthAndExpectedLength(): Stream<Arguments> {
return Stream.of(
Arguments.of("^[a-z]*\$", null, null, 5),
Arguments.of("^[a-z0-9]{6,}\$", 3, 10, 6),
Arguments.of(null, 1, 10, 1),
)
}
}

@ParameterizedTest
@MethodSource("lengthTestValues")
fun `generate string value of appropriate length matching minLength and maxLength parameters`(min: Int?, max: Int?, length: Int) {
@MethodSource("minLengthMaxLengthAndExpectedLength")
fun `generate string value as per minLength and maxLength`(min: Int?, max: Int?, expectedLengthOfGeneratedValue: Int) {
samyakOO7 marked this conversation as resolved.
Show resolved Hide resolved
val result = StringPattern(minLength = min, maxLength = max).generate(Resolver()) as StringValue
val generatedLength = result.string.length

assertThat(result.string.length).isEqualTo(length)
assertThat(generatedLength).isGreaterThanOrEqualTo(expectedLengthOfGeneratedValue)
max?.let { assertThat(generatedLength).isLessThanOrEqualTo(it) }
}

@ParameterizedTest
@MethodSource("regexMinLengthAndMaxLengthAndExpectedLength")
fun `generate string value as per regex in conjunction with minLength and maxLength`(regex: String?, min: Int?, max: Int?, expectedLength: Int) {
samyakOO7 marked this conversation as resolved.
Show resolved Hide resolved
val result = StringPattern(minLength = min, maxLength = max, regex = regex).generate(Resolver()) as StringValue
val generatedString = result.string
val generatedLength = generatedString.length

assertThat(generatedLength).isGreaterThanOrEqualTo(expectedLength)
max?.let { assertThat(generatedLength).isLessThanOrEqualTo(it) }
regex?.let { assertThat(generatedString).matches(regex) }
}

@Test
Expand Down Expand Up @@ -240,4 +259,12 @@ internal class StringPatternTest {
fun `string pattern encompasses email`() {
assertThat(StringPattern().encompasses(EmailPattern(), Resolver(), Resolver())).isInstanceOf(Result.Success::class.java)
}

@Test
fun `should fail to generate string when maxLength is less than minLength`() {
harikrishnan83 marked this conversation as resolved.
Show resolved Hide resolved
val exception = assertThrows<IllegalArgumentException> {
StringPattern(minLength = 6, maxLength = 4)
}
assertThat(exception.message).isEqualTo("maxLength cannot be less than minLength")
}
}