Skip to content

Commit

Permalink
481 accept date and time as string in ISO 8601 format for ChronoLocal…
Browse files Browse the repository at this point in the history
…DateTime 2 (#552)

#481 added String parameter instead of a ChronoLocalDateTime
  • Loading branch information
rkiselev committed Aug 19, 2020
1 parent def1fc9 commit ae4a36f
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,103 @@ fun <T : ChronoLocalDateTime<out ChronoLocalDate>> Expect<T>.isAfterOrEqual(
fun <T : ChronoLocalDateTime<out ChronoLocalDate>> Expect<T>.isEqual(
expected: ChronoLocalDateTime<*>
): Expect<T> = _logicAppend { isEqual(expected) }

/**
* Expects that the subject of the assertion (a [ChronoLocalDateTime])
* is before the [expected] [LocalDateTime] given as [String] in (modified) ISO 8601 format.
* The string will be converted to a LocalDateTime according to ISO 8601 but with a slight deviation.
* The alternative notation (e.g. 20200401120001 instead of 2020-04-01T12:00:01) is not supported and we accept a
* date without time in which case 00:00:00 is used. Following the supported formats from the longest to the shortest:
* yyyy-mm-ddThh:mm:ss.sss (up to 9 digits for nanoseconds)
* yyyy-mm-ddThh:mm:ss
* yyyy-mm-ddThh:mm
* yyyy-mm-dd
*
* @return An [Expect] for the current subject of the assertion.
* @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct.
*
* @since 0.13.0
*/
fun <T : ChronoLocalDateTime<out ChronoLocalDate>> Expect<T>.isBefore(
expected: String
): Expect<T> = _logicAppend { isBefore(expected) }

/**
* Expects that the subject of the assertion (a [ChronoLocalDateTime])
* is before the [expected] [LocalDateTime] given as [String] in (modified) ISO 8601 format.
* The string will be converted to a LocalDateTime according to ISO 8601 but with a slight deviation.
* The alternative notation (e.g. 20200401120001 instead of 2020-04-01T12:00:01) is not supported and we accept a
* date without time in which case 00:00:00 is used. Following the supported formats from the longest to the shortest:
* yyyy-mm-ddThh:mm:ss.sss (up to 9 digits for nanoseconds)
* yyyy-mm-ddThh:mm:ss
* yyyy-mm-ddThh:mm
* yyyy-mm-dd
*
* @return An [Expect] for the current subject of the assertion.
* @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct.
*
* @since 0.13.0
*/
fun <T : ChronoLocalDateTime<out ChronoLocalDate>> Expect<T>.isBeforeOrEqual(
expected: String
): Expect<T> = _logicAppend { isBeforeOrEqual(expected) }

/**
* Expects that the subject of the assertion (a [ChronoLocalDateTime])
* is before the [expected] [LocalDateTime] given as [String] in (modified) ISO 8601 format.
* The string will be converted to a LocalDateTime according to ISO 8601 but with a slight deviation.
* The alternative notation (e.g. 20200401120001 instead of 2020-04-01T12:00:01) is not supported and we accept a date
* without time in which case 00:00:00 is used. Following the supported formats from the longest to the shortest:
* yyyy-mm-ddThh:mm:ss.sss (up to 9 digits for nanoseconds)
* yyyy-mm-ddThh:mm:ss
* yyyy-mm-ddThh:mm
* yyyy-mm-dd
*
* @return An [Expect] for the current subject of the assertion.
* @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct.
*
* @since 0.13.0
*/
fun <T : ChronoLocalDateTime<out ChronoLocalDate>> Expect<T>.isAfter(
expected: String
): Expect<T> = _logicAppend { isAfter(expected) }

/**
* Expects that the subject of the assertion (a [ChronoLocalDateTime])
* is before the [expected] [LocalDateTime] given as [String] in (modified) ISO 8601 format.
* The string will be converted to a LocalDateTime according to ISO 8601 but with a slight deviation.
* The alternative notation (e.g. 20200401120001 instead of 2020-04-01T12:00:01) is not supported and we accept a date
* without time in which case 00:00:00 is used. Following the supported formats from the longest to the shortest:
* yyyy-mm-ddThh:mm:ss.sss (up to 9 digits for nanoseconds)
* yyyy-mm-ddThh:mm:ss
* yyyy-mm-ddThh:mm
* yyyy-mm-dd
*
* @return An [Expect] for the current subject of the assertion.
* @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct.
*
* @since 0.13.0
*/
fun <T : ChronoLocalDateTime<out ChronoLocalDate>> Expect<T>.isAfterOrEqual(
expected: String
): Expect<T> = _logicAppend { isAfterOrEqual(expected) }

/**
* Expects that the subject of the assertion (a [ChronoLocalDateTime])
* is before the [expected] [LocalDateTime] given as [String] in (modified) ISO 8601 format.
* The string will be converted to a LocalDateTime according to ISO 8601 but with a slight deviation.
* The alternative notation (e.g. 20200401120001 instead of 2020-04-01T12:00:01) is not supported and we accept a date
* without time in which case 00:00:00 is used. Following the supported formats from the longest to the shortest:
* yyyy-mm-ddThh:mm:ss.sss (up to 9 digits for nanoseconds)
* yyyy-mm-ddThh:mm:ss
* yyyy-mm-ddThh:mm
* yyyy-mm-dd
*
* @return An [Expect] for the current subject of the assertion.
* @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct.
*
* @since 0.13.0
*/
fun <T : ChronoLocalDateTime<out ChronoLocalDate>> Expect<T>.isEqual(
expected: String
): Expect<T> = _logicAppend { isEqual(expected) }
Original file line number Diff line number Diff line change
@@ -1,46 +1,179 @@
package ch.tutteli.atrium.api.fluent.en_GB

import ch.tutteli.atrium.api.verbs.internal.expect
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.reporting.AtriumError
import ch.tutteli.atrium.specs.fun1
import ch.tutteli.atrium.specs.notImplemented
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.chrono.ChronoLocalDate
import java.time.chrono.ChronoLocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeFormatter.ISO_DATE_TIME
import java.time.format.DateTimeParseException

class ChronoLocalDateTimeAssertionSpec : Spek({
include(ChronoLocalDateTimeSpec)
include(StringSpec)
listOf(
"20200101010101111",
"20200101010101",
"202001010101",
"2020010101",
"2020-1-01T01:01",
"2020-01-1T01:01",
"20-01-01T01:01" ,
"2020-01-01T1:01",
"2020-01-01T01:1",
"2020-01-01T01:01:1",
"2020-01-01t01:01:01"
).forEach{ value ->
val now = expect(LocalDateTime.now())
describe("wrong string format") {
it("isBefore throws an DateTimeParseException") {
expect {
now.isBefore(value)
}.toThrow<DateTimeParseException> { messageContains("could not be parsed") }
}
it("isBeforeOrEqual throws an DateTimeParseException") {
expect {
now.isBeforeOrEqual(value)
}.toThrow<DateTimeParseException> { messageContains("could not be parsed") }
}
it("isAfter throws an DateTimeParseException") {
expect {
now.isAfter(value)
}.toThrow<DateTimeParseException> { messageContains("could not be parsed") }
}
it("isAfterOrEqual throws an DateTimeParseException") {
expect {
now.isAfterOrEqual(value)
}.toThrow<DateTimeParseException> { messageContains("could not be parsed") }
}
it("isEqual throws an DateTimeParseException") {
expect {
now.isEqual(value)
}.toThrow<DateTimeParseException> { messageContains("could not be parsed") }
}
}
}

listOf(
"2020-01-01T01:01:01",
"2020-01-01T01:01",
"2020-01-01"
).forEach{value ->
val nowLocalDateTime = LocalDateTime.now()
val now = expect(nowLocalDateTime)
val before = LocalDateTime.now()
describe("allowed shortcuts") {
it("isBefore throws an AtriumError") {
expect {
now.isBefore(value)
}.toThrow<AtriumError> { messageContains("is before: $value") }
}
it("isBefore doesn't throw any Error") {
expect {
now.isBefore(before.format(ISO_DATE_TIME))
}.notToThrow()
}
it("isBeforeOrEqual doesn't throw any Error") {
expect {
now.isBeforeOrEqual(nowLocalDateTime.format(ISO_DATE_TIME))
}.notToThrow()
}
it("isBeforeOrEqual throws an AtriumError") {
expect {
now.isBeforeOrEqual(value)
}.toThrow<AtriumError> { messageContains("is before or equal: $value") }
}
it("isAfter doesn't throw any Error") {
expect {
now.isAfter(value)
}.notToThrow()
}
it("isAfter throws an AtriumError") {
expect {
now.isAfter(before.format(ISO_DATE_TIME))
}.toThrow<AtriumError> { messageContains("is after: ${before.format(ISO_DATE_TIME)}") }
}
it("isAfterOrEqual doesn't throw any Error") {
expect {
now.isAfterOrEqual(nowLocalDateTime.format(ISO_DATE_TIME))
}.notToThrow()
}
it("isAfterOrEqual doesn't throw any Error") {
expect {
now.isAfterOrEqual(value)
}.notToThrow()
}
it("isAfterOrEqual throws an AtriumError") {
expect {
now.isAfterOrEqual(before.format(ISO_DATE_TIME))
}.toThrow<AtriumError> { messageContains("is after or equal: ${before.format(ISO_DATE_TIME)}") }
}
it("isEqual throws an AtriumError") {
expect {
now.isEqual(value)
}.toThrow<AtriumError> { messageContains("is equal to: $value") }
}
it("isEqual doesn't throw any error") {
expect {
now.isEqual(nowLocalDateTime.format(ISO_DATE_TIME))
}.notToThrow()
}
}
}
}) {
object ChronoLocalDateTimeSpec : ch.tutteli.atrium.specs.integration.ChronoLocalDateTimeAssertionSpec(
fun1(Expect<ChronoLocalDateTime<*>>::isBefore),
fun1(Expect<ChronoLocalDateTime<*>>::isBeforeOrEqual),
fun1(Expect<ChronoLocalDateTime<*>>::isAfter),
fun1(Expect<ChronoLocalDateTime<*>>::isAfterOrEqual),
fun1(Expect<ChronoLocalDateTime<*>>::isEqual)
fun1<ChronoLocalDateTime<*>, ChronoLocalDateTime<*>>(Expect<ChronoLocalDateTime<*>>::isBefore),
fun1<ChronoLocalDateTime<*>, ChronoLocalDateTime<*>>(Expect<ChronoLocalDateTime<*>>::isBeforeOrEqual),
fun1<ChronoLocalDateTime<*>, ChronoLocalDateTime<*>>(Expect<ChronoLocalDateTime<*>>::isAfter),
fun1<ChronoLocalDateTime<*>, ChronoLocalDateTime<*>>(Expect<ChronoLocalDateTime<*>>::isAfterOrEqual),
fun1<ChronoLocalDateTime<*>, ChronoLocalDateTime<*>>(Expect<ChronoLocalDateTime<*>>::isEqual)
)

object StringSpec : ch.tutteli.atrium.specs.integration.ChronoLocalDateTimeAssertionSpec(
fun1(Companion::isBefore),
//TODO #481 introduce more companions
fun1(Expect<ChronoLocalDateTime<*>>::isBeforeOrEqual),
fun1(Expect<ChronoLocalDateTime<*>>::isAfter),
fun1(Expect<ChronoLocalDateTime<*>>::isAfterOrEqual),
fun1(Expect<ChronoLocalDateTime<*>>::isEqual)
fun1(Companion::isBeforeOrEqual),
fun1(Companion::isAfter),
fun1(Companion::isAfterOrEqual),
fun1(Companion::isEqual)
)

companion object {
fun isBefore(
expect: Expect<ChronoLocalDateTime<*>>,
expected: ChronoLocalDateTime<*>
): Expect<ChronoLocalDateTime<*>> =
//TODO #481 turn into string in ISO format
//expect.isBefore(expected.format(DateTimeFormatter.ISO_DATE_TIME))
expect.isBefore(expected)
}
expect.isBefore(expected.format(ISO_DATE_TIME))

fun isBeforeOrEqual(
expect: Expect<ChronoLocalDateTime<*>>,
expected: ChronoLocalDateTime<*>
): Expect<ChronoLocalDateTime<*>> =
expect.isBeforeOrEqual(expected.format(ISO_DATE_TIME))

fun isAfter(
expect: Expect<ChronoLocalDateTime<*>>,
expected: ChronoLocalDateTime<*>
): Expect<ChronoLocalDateTime<*>> =
expect.isAfter(expected.format(ISO_DATE_TIME))

fun isAfterOrEqual(
expect: Expect<ChronoLocalDateTime<*>>,
expected: ChronoLocalDateTime<*>
): Expect<ChronoLocalDateTime<*>> =
expect.isAfterOrEqual(expected.format(ISO_DATE_TIME))

fun isEqual(
expect: Expect<ChronoLocalDateTime<*>>,
expected: ChronoLocalDateTime<*>
): Expect<ChronoLocalDateTime<*>> =
expect.isEqual(expected.format(ISO_DATE_TIME))
}

@Suppress("unused", "UNUSED_VALUE")
private fun ambiguityTest() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import ch.tutteli.atrium.assertions.Assertion
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.domain.creating.ChronoLocalDateTimeAssertions
import ch.tutteli.atrium.domain.robstoll.lib.creating.*
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.chrono.ChronoLocalDate
import java.time.chrono.ChronoLocalDateTime

Expand Down

0 comments on commit ae4a36f

Please sign in to comment.