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

Improved support for time #1633

Merged
merged 2 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
23 changes: 23 additions & 0 deletions core/src/main/kotlin/io/specmatic/core/pattern/ISO8601.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.specmatic.core.pattern

import io.specmatic.core.value.StringValue
import java.time.LocalTime
import java.time.format.DateTimeFormatter

class ISO8601 {
companion object{
fun validatedStringValue(time: String): StringValue {
return try {
LocalTime.parse(time, DateTimeFormatter.ISO_TIME)
StringValue(time)
} catch (e: Exception) {
throw ContractException("Error while parsing $time as per ISO 8601: ${e.message}")
}
}

val currentTime: String get() =
LocalTime.now().format(
DateTimeFormatter.ISO_TIME
)
}
}
26 changes: 0 additions & 26 deletions core/src/main/kotlin/io/specmatic/core/pattern/RFC8601.kt

This file was deleted.

16 changes: 9 additions & 7 deletions core/src/main/kotlin/io/specmatic/core/pattern/TimePattern.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,23 @@ object TimePattern : Pattern, ScalarType {
}
}

override fun generate(resolver: Resolver): StringValue = StringValue(RFC8601.currentTime())
override fun generate(resolver: Resolver): StringValue = StringValue(ISO8601.currentTime)

override fun newBasedOn(row: Row, resolver: Resolver): Sequence<ReturnValue<Pattern>> = sequenceOf(HasValue(this))

override fun newBasedOn(resolver: Resolver): Sequence<TimePattern> = sequenceOf(this)

override fun negativeBasedOn(row: Row, resolver: Resolver, config: NegativePatternConfiguration): Sequence<ReturnValue<Pattern>> {
override fun negativeBasedOn(
row: Row,
resolver: Resolver,
config: NegativePatternConfiguration
): Sequence<ReturnValue<Pattern>> {
return scalarAnnotation(this, sequenceOf(NullPattern))
}

override fun parse(value: String, resolver: Resolver): StringValue =
attempt {
RFC8601.parse(value)
StringValue(value)
}
override fun parse(value: String, resolver: Resolver): StringValue {
return ISO8601.validatedStringValue(value)
}

override fun encompasses(otherPattern: Pattern, thisResolver: Resolver, otherResolver: Resolver, typeStack: TypeStack): Result {
return encompasses(this, otherPattern, thisResolver, otherResolver, typeStack)
Expand Down
57 changes: 20 additions & 37 deletions core/src/test/kotlin/io/specmatic/core/pattern/TimePatternTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,15 @@ import org.junit.jupiter.params.provider.CsvSource

class TimePatternTest {
@Test
fun testValidTimeMatch() {
val timeString = "10:05:59"
val result = TimePattern.matches(StringValue(timeString), Resolver())
assertTrue(result is Result.Success)
}

@Test
fun testInvalidTimeMatch() {
val timeString = "invalid-time"
val result = TimePattern.matches(StringValue(timeString), Resolver())
assertTrue(result is Result.Failure)
}

@Test
fun testGeneratedTimeIsValid() {
fun `should be able to generate a time`() {
val generated = TimePattern.generate(Resolver())
println(generated)
val result = TimePattern.matches(generated, Resolver())
assertTrue(result is Result.Success)
}

@Test
fun testParseValidTime() {
val parsed = TimePattern.parse("23:59:59", Resolver())
assertEquals("23:59:59", parsed.string)
}

@Test
fun testNewBasedOn() {
fun `should generate new time values for test`() {
val row = Row()
val patterns = TimePattern.newBasedOn(row, Resolver()).map { it.value }.toList()

Expand All @@ -55,23 +35,26 @@ class TimePatternTest {

@ParameterizedTest
@CsvSource(
"01:01:01, true",
"a23:59:59, false",
"235959, true",
"01:01:01Z, true",
"010101Z, true",
"01:01:01+05:30, true",
"01:01:01+05:30d, false",
"010101+0530, true",
"010101d+0530, false",
"010101+0530d, false",
"01:01:01-01:00, true",
"01:01:01b-01:00a, false",
"010101-0100, true",
"010101c-0100d, false",
"01:01:01, valid",
"a23:59:59, invalid",
"01:01:01Z, valid",
"01:01:01T, invalid",
"01:01:01+05:30, valid",
"01:01:01+05:30d, invalid",
"01:01:01-01:00, valid",
"01:01:01b-01:00a, invalid",
"not-a-time, invalid",
"aa:bb:cc, invalid"
)
fun `RFC 6801 regex should validate time`(time: String, isValid: Boolean) {
fun `RFC 6801 regex should validate time`(time: String, validity: String) {
val result = TimePattern.matches(StringValue(time), Resolver())

val isValid = when(validity) {
"valid" -> true
"invalid" -> false
else -> IllegalArgumentException("Unknown validity: $validity")
}

assertEquals(isValid, result is Result.Success)
}
}