diff --git a/.agents/_TOC.md b/.agents/_TOC.md index 065df133a..83375f417 100644 --- a/.agents/_TOC.md +++ b/.agents/_TOC.md @@ -13,4 +13,4 @@ 11. [Advanced safety rules](advanced-safety-rules.md) 12. [Refactoring guidelines](refactoring-guidelines.md) 13. [Common tasks](common-tasks.md) -14. [Java to Kotlin conversion](java-kotlin-conversion.md) +14. [Java to Kotlin conversion](skills/java-to-kotlin/SKILL.md) diff --git a/.agents/quick-reference-card.md b/.agents/quick-reference-card.md index 6c25b9a7f..e2be69cb8 100644 --- a/.agents/quick-reference-card.md +++ b/.agents/quick-reference-card.md @@ -3,7 +3,6 @@ ``` 🔑 Key Information: - Kotlin/Java project with CQRS architecture -- Use ChatGPT for documentation, Codex for code generation, GPT-4o for complex analysis - Follow coding guidelines in Spine Event Engine docs - Always include tests with code changes - Version bump required for all PRs diff --git a/.agents/java-kotlin-conversion.md b/.agents/skills/java-to-kotlin/SKILL.md similarity index 88% rename from .agents/java-kotlin-conversion.md rename to .agents/skills/java-to-kotlin/SKILL.md index 95cf92954..d3abdc2f7 100644 --- a/.agents/java-kotlin-conversion.md +++ b/.agents/skills/java-to-kotlin/SKILL.md @@ -1,3 +1,11 @@ +--- +name: java-to-kotlin +description: > + Convert Java code to Kotlin, including Java API comments from Javadoc to KDoc. + Use when asked to migrate Java files, classes, methods, nullability semantics, + or common Java patterns into idiomatic Kotlin while preserving behavior. +--- + # 🪄 Converting Java code to Kotlin * Java code API comments are Javadoc format. diff --git a/.agents/skills/java-to-kotlin/agents/openai.yaml b/.agents/skills/java-to-kotlin/agents/openai.yaml new file mode 100644 index 000000000..252920fed --- /dev/null +++ b/.agents/skills/java-to-kotlin/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Java to Kotlin" + short_description: "Convert Java code to idiomatic Kotlin" + default_prompt: "Use $java-to-kotlin to convert Java code to Kotlin while preserving behavior, nullability, and API documentation wording." diff --git a/.github/workflows/build-on-ubuntu.yml b/.github/workflows/build-on-ubuntu.yml index f8c24933f..b571ce82f 100644 --- a/.github/workflows/build-on-ubuntu.yml +++ b/.github/workflows/build-on-ubuntu.yml @@ -1,10 +1,9 @@ -name: Build under Ubuntu +name: Build on Ubuntu on: push jobs: build: - name: Build under Ubuntu runs-on: ubuntu-latest steps: diff --git a/.github/workflows/build-on-windows.yml b/.github/workflows/build-on-windows.yml index 4e6b57f1f..37eba1be7 100644 --- a/.github/workflows/build-on-windows.yml +++ b/.github/workflows/build-on-windows.yml @@ -1,10 +1,9 @@ -name: Build under Windows +name: Build on Windows on: pull_request jobs: build: - name: Build under Windows runs-on: windows-latest steps: diff --git a/.github/workflows/ensure-reports-updated.yml b/.github/workflows/ensure-reports-updated.yml index fdd8b8e67..93531059e 100644 --- a/.github/workflows/ensure-reports-updated.yml +++ b/.github/workflows/ensure-reports-updated.yml @@ -8,8 +8,7 @@ on: - '**' jobs: - build: - name: Ensure license reports updated + check: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 858cebbcc..a7cde85e5 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -9,7 +9,6 @@ on: jobs: validation: - name: Gradle Wrapper Validation runs-on: ubuntu-latest steps: - name: Checkout latest code diff --git a/.github/workflows/increment-guard.yml b/.github/workflows/increment-guard.yml index 1993841a6..dd7df7137 100644 --- a/.github/workflows/increment-guard.yml +++ b/.github/workflows/increment-guard.yml @@ -9,8 +9,7 @@ on: - '**' jobs: - build: - name: Check version increment + check: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/remove-obsolete-artifacts-from-packages.yaml b/.github/workflows/remove-obsolete-artifacts-from-packages.yaml index fe8ad849d..f70617100 100644 --- a/.github/workflows/remove-obsolete-artifacts-from-packages.yaml +++ b/.github/workflows/remove-obsolete-artifacts-from-packages.yaml @@ -34,7 +34,7 @@ env: jobs: retrieve-package-names: - name: Retrieve the package names published from this repository + name: Retrieve package names runs-on: ubuntu-latest outputs: package-names: ${{ steps.request-package-names.outputs.package-names }} @@ -54,7 +54,7 @@ jobs: echo "package-names=$(<./package-names.json)" >> $GITHUB_OUTPUT delete-obsolete-artifacts: - name: Remove obsolete artifacts published from this repository to GitHub Packages + name: Delete obsolete artifacts needs: retrieve-package-names runs-on: ubuntu-latest strategy: diff --git a/.gitignore b/.gitignore index a3e0ec13b..5f85d295e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ # Internal tool directories. .fleet/ +.junie/memory/ # Kotlin temp directories. **/.kotlin/ diff --git a/.junie/guidelines.md b/.junie/guidelines.md index 459e28eca..5160f499e 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -10,7 +10,7 @@ Also follow the Junie-specific rules described below. ## Junie Assistance Tips -When working with Junie AI on the Spine Tool-Base project: +When working with Junie AI on the Spine family of projects: 1. **Project Navigation**: Use `search_project` to find relevant files and code segments. 2. **Code Understanding**: Request file structure with `get_file_structure` before editing. diff --git a/.junie/skills b/.junie/skills new file mode 120000 index 000000000..2b7a412b8 --- /dev/null +++ b/.junie/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 6496c646c..4184e9bf2 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -137,8 +137,6 @@ val koverVersion = "0.9.1" /** * The version of the Shadow Plugin. * - * `7.1.2` is the last version compatible with Gradle 7.x. Newer versions require Gradle v8.x. - * * @see Shadow Plugin releases */ val shadowVersion = "9.4.1" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/Dokka.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/Dokka.kt index 858731b3f..6b4822dcd 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/Dokka.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/Dokka.kt @@ -26,7 +26,6 @@ package io.spine.dependency.build -import io.spine.dependency.build.Dokka.GradlePlugin.id import io.spine.dependency.local.Spine // https://github.com/Kotlin/dokka diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt index dfdaf9535..2333e4ce7 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt @@ -46,7 +46,7 @@ object CoreJvmCompiler { /** * The version used to in the build classpath. */ - const val dogfoodingVersion = "2.0.0-SNAPSHOT.062" + const val dogfoodingVersion = "2.0.0-SNAPSHOT.063" /** * The version to be used for integration tests. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt index 275aa0070..05eb7bfaf 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt @@ -40,7 +40,7 @@ import io.spine.dependency.Dependency ) object Time : Dependency() { override val group = Spine.group - override val version = "2.0.0-SNAPSHOT.235" + override val version = "2.0.0-SNAPSHOT.236" private const val infix = "spine-time" fun lib(version: String): String = "$group:$infix:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt index a5ef40318..121d7aac2 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt @@ -36,7 +36,7 @@ object Validation { /** * The version of the Validation library artifacts. */ - const val version = "2.0.0-SNAPSHOT.413" + const val version = "2.0.0-SNAPSHOT.414" /** * The last version of Validation compatible with ProtoData. diff --git a/config b/config index cd6e86330..f24236f0a 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit cd6e86330c283e0414be7c318a4cd450dd3c6982 +Subproject commit f24236f0a897e9d6a101e60d1dd936317c7a3c06 diff --git a/dependencies.md b/dependencies.md index 843bea5ab..df15203e7 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine.tools:time-gradle-plugin:2.0.0-SNAPSHOT.236` +# Dependencies of `io.spine.tools:time-gradle-plugin:2.0.0-SNAPSHOT.237` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -1059,14 +1059,14 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using +This report was generated on **Fri May 01 17:48:31 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:time-testlib:2.0.0-SNAPSHOT.236` +# Dependencies of `io.spine.tools:time-testlib:2.0.0-SNAPSHOT.237` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -1869,14 +1869,14 @@ This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using +This report was generated on **Fri May 01 17:48:31 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-time:2.0.0-SNAPSHOT.236` +# Dependencies of `io.spine:spine-time:2.0.0-SNAPSHOT.237` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -2833,14 +2833,14 @@ This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using +This report was generated on **Fri May 01 17:48:31 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-time-java:2.0.0-SNAPSHOT.236` +# Dependencies of `io.spine:spine-time-java:2.0.0-SNAPSHOT.237` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -3643,14 +3643,14 @@ This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using +This report was generated on **Fri May 01 17:48:31 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-time-kotlin:2.0.0-SNAPSHOT.236` +# Dependencies of `io.spine:spine-time-kotlin:2.0.0-SNAPSHOT.237` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -4461,14 +4461,14 @@ This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using +This report was generated on **Fri May 01 17:48:31 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:time-validation:2.0.0-SNAPSHOT.236` +# Dependencies of `io.spine.tools:time-validation:2.0.0-SNAPSHOT.237` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -5590,14 +5590,14 @@ This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using +This report was generated on **Fri May 01 17:48:31 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-validation-tests:2.0.0-SNAPSHOT.236` +# Dependencies of `io.spine:spine-validation-tests:2.0.0-SNAPSHOT.237` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. @@ -6683,6 +6683,6 @@ This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Apr 28 13:46:33 WEST 2026** using +This report was generated on **Fri May 01 17:48:31 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/pom.xml b/pom.xml index 64877507f..8ba7a58fe 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine spine-time -2.0.0-SNAPSHOT.236 +2.0.0-SNAPSHOT.237 2015 @@ -53,10 +53,16 @@ all modules and does not describe the project structure per-subproject. 2.0.0-SNAPSHOT.387 compile + + io.spine + spine-time + 2.0.0-SNAPSHOT.236 + compile + io.spine spine-validation-jvm-runtime - 2.0.0-SNAPSHOT.413 + 2.0.0-SNAPSHOT.414 compile @@ -239,7 +245,7 @@ all modules and does not describe the project structure per-subproject. io.spine.tools core-jvm-plugins - 2.0.0-SNAPSHOT.062 + 2.0.0-SNAPSHOT.063 io.spine.tools @@ -251,10 +257,15 @@ all modules and does not describe the project structure per-subproject. spine-dokka-extensions 2.0.0-SNAPSHOT.7 + + io.spine.tools + time-validation + 2.0.0-SNAPSHOT.236 + io.spine.tools validation-java-bundle - 2.0.0-SNAPSHOT.413 + 2.0.0-SNAPSHOT.414 net.sourceforge.pmd diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/Fixtures.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/Fixtures.kt new file mode 100644 index 000000000..c6b304dfb --- /dev/null +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/Fixtures.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.tools.time.validation.java + +import com.google.protobuf.Timestamp +import com.google.protobuf.util.Durations.fromMillis +import com.google.protobuf.util.Timestamps +import io.spine.time.LocalDateTimes +import io.spine.time.LocalDateTime +import java.time.Instant +import java.time.LocalDateTime.ofInstant +import java.time.ZoneOffset.UTC + +/** + * Five hundred milliseconds. + * + * To shift the time into the past or future, we add or subtract a difference of this amount. + * + * There are two reasons for choosing 500 milliseconds: + * + * 1. The generated code uses `io.spine.base.Time.currentTime()` to get the current timestamp + * for comparison. In turn, this method relies on `io.spine.base.Time.SystemTimeProvider` + * by default, which has millisecond precision. + * 2. Adding too small amount of time to make the stamp denote "future" might be unreliable. + * As it could catch up `now` by the time `Time.currentTime()` is invoked. + */ +private const val HALF_OF_SECOND: Long = 500 + +internal object TemporalFixtures { + + fun pastTime(): LocalDateTime { + val current = Instant.now() // It is a UTC stamp. + return LocalDateTimes.of(ofInstant(current.minusMillis(HALF_OF_SECOND), UTC)) + } + + fun futureTime(): LocalDateTime { + val current = Instant.now() // It is a UTC stamp. + return LocalDateTimes.of(ofInstant(current.plusMillis(HALF_OF_SECOND), UTC)) + } +} + +internal object TimestampFixtures { + + fun pastTime(): Timestamp = + Timestamps.subtract(Timestamps.now(), fromMillis(HALF_OF_SECOND)) + + fun futureTime(): Timestamp = + Timestamps.add(Timestamps.now(), fromMillis(HALF_OF_SECOND)) +} diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenSpec.kt deleted file mode 100644 index 5f4115c37..000000000 --- a/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenSpec.kt +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright 2026, TeamDev. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package io.spine.tools.time.validation.java - -import io.spine.test.tools.validate.anySpineTemporal -import io.spine.test.tools.validate.anySpineTemporals -import io.spine.test.tools.validate.futureSpineTemporal -import io.spine.test.tools.validate.futureSpineTemporals -import io.spine.test.tools.validate.pastSpineTemporal -import io.spine.test.tools.validate.pastSpineTemporals -import io.spine.time.LocalDateTimes -import java.time.Instant -import java.time.LocalDateTime.ofInstant -import java.time.ZoneOffset.UTC -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import io.spine.time.LocalDateTime as SpineTimeLocalDateTime - -@DisplayName("If used with Spine `Temporal`, `(when)` constraint should") -internal class SpineTemporalWhenSpec { - - @Nested inner class - `when given a temporal denoting` { - - @Nested inner class - `the past` { - - @Test - fun `throw, if restricted to be in future`() = assertValidationFails { - futureSpineTemporal { - value = pastTime() - } - } - - @Test - fun `pass, if restricted to be in past`() = assertValidationPasses { - pastSpineTemporal { - value = pastTime() - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anySpineTemporal { - value = pastTime() - } - } - } - - @Nested inner class - `the future` { - - @Test - fun `throw, if restricted to be in past`() = assertValidationFails { - pastSpineTemporal { - value = futureTime() - } - } - - @Test - fun `pass, if restricted to be in future`() = assertValidationPasses { - futureSpineTemporal { - value = futureTime() - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anySpineTemporal { - value = futureTime() - } - } - } - } - - @Nested inner class - `when given several times` { - - @Nested inner class - `denoting only the past` { - - private val severalPastTimes = listOf(pastTime(), pastTime(), pastTime()) - - @Test - fun `throw, if restricted to be in future`() = assertValidationFails { - futureSpineTemporals { - value.addAll(severalPastTimes) - } - } - - @Test - fun `pass, if restricted to be in past`() = assertValidationPasses { - pastSpineTemporals { - value.addAll(severalPastTimes) - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anySpineTemporals { - value.addAll(severalPastTimes) - } - } - } - - @Nested inner class - `denoting only the future` { - - private val severalFutureTimes = listOf(futureTime(), futureTime(), futureTime()) - - @Test - fun `throw, if restricted to be in past`() = assertValidationFails { - pastSpineTemporals { - value.addAll(severalFutureTimes) - } - } - - @Test - fun `pass, if restricted to be in future`() = assertValidationPasses { - futureSpineTemporals { - value.addAll(severalFutureTimes) - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anySpineTemporals { - value.addAll(severalFutureTimes) - } - } - } - - @Nested inner class - `with a single past time within the future times` { - - private val severalFutureAndPast = listOf(futureTime(), pastTime(), futureTime()) - - @Test - fun `throw, if restricted to be in future`() = assertValidationFails { - futureSpineTemporals { - value.addAll(severalFutureAndPast) - } - } - - @Test - fun `throw, if restricted to be in past`() = assertValidationFails { - pastSpineTemporals { - value.addAll(severalFutureAndPast) - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anySpineTemporals { - value.addAll(severalFutureAndPast) - } - } - } - - @Nested inner class - `with a single future time within the past times` { - - private val severalPastAndFuture = listOf(pastTime(), futureTime(), pastTime()) - - @Test - fun `throw, if restricted to be in future`() = assertValidationFails { - futureSpineTemporals { - value.addAll(severalPastAndFuture) - } - } - - @Test - fun `throw, if restricted to be in past`() = assertValidationFails { - pastSpineTemporals { - value.addAll(severalPastAndFuture) - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anySpineTemporals { - value.addAll(severalPastAndFuture) - } - } - } - } -} - -private fun pastTime(): SpineTimeLocalDateTime { - val current = Instant.now() // It is a UTC stamp. - val past = current.minusMillis(HALF_OF_SECOND) - return LocalDateTimes.of(ofInstant(past, UTC)) -} - -private fun futureTime(): SpineTimeLocalDateTime { - val current = Instant.now() // It is a UTC stamp. - val past = current.plusMillis(HALF_OF_SECOND) - return LocalDateTimes.of(ofInstant(past, UTC)) -} - -/** - * Five hundred milliseconds. - * - * To shift the time into the past or future, we add or subtract a difference of this amount. - * - * There are two reasons for choosing 500 milliseconds: - * - * 1. The generated code uses `io.spine.base.Time.currentTime()` to get the current timestamp - * for comparison. In turn, this method relies on `io.spine.base.Time.SystemTimeProvider` - * by default, which has millisecond precision. - * 2. Adding too small amount of time to make the stamp denote "future" might be unreliable. - * As it could catch up `now` by the time `Time.currentTime()` is invoked. - */ -private const val HALF_OF_SECOND: Long = 500 diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalRepeatedWhenSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalRepeatedWhenSpec.kt new file mode 100644 index 000000000..83e55b15c --- /dev/null +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalRepeatedWhenSpec.kt @@ -0,0 +1,148 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.tools.time.validation.java + +import io.spine.test.tools.validate.anySpineTemporals +import io.spine.test.tools.validate.futureSpineTemporals +import io.spine.test.tools.validate.pastSpineTemporals +import io.spine.tools.time.validation.java.TemporalFixtures.futureTime +import io.spine.tools.time.validation.java.TemporalFixtures.pastTime +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +@DisplayName("If used with repeated `Temporal`, `(when)` constraint should") +internal class TemporalRepeatedWhenSpec { + + @Nested inner class + `denoting only the past` { + + private val severalPastTimes = listOf(pastTime(), pastTime(), pastTime()) + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureSpineTemporals { + value.addAll(severalPastTimes) + } + } + + @Test + fun `pass, if restricted to be in past`() = assertValidationPasses { + pastSpineTemporals { + value.addAll(severalPastTimes) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporals { + value.addAll(severalPastTimes) + } + } + } + + @Nested inner class + `denoting only the future` { + + private val severalFutureTimes = listOf(futureTime(), futureTime(), futureTime()) + + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastSpineTemporals { + value.addAll(severalFutureTimes) + } + } + + @Test + fun `pass, if restricted to be in future`() = assertValidationPasses { + futureSpineTemporals { + value.addAll(severalFutureTimes) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporals { + value.addAll(severalFutureTimes) + } + } + } + + @Nested inner class + `with a single past time within the future times` { + + private val severalFutureAndPast = listOf(futureTime(), pastTime(), futureTime()) + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureSpineTemporals { + value.addAll(severalFutureAndPast) + } + } + + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastSpineTemporals { + value.addAll(severalFutureAndPast) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporals { + value.addAll(severalFutureAndPast) + } + } + } + + @Nested inner class + `with a single future time within the past times` { + + private val severalPastAndFuture = listOf(pastTime(), futureTime(), pastTime()) + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureSpineTemporals { + value.addAll(severalPastAndFuture) + } + } + + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastSpineTemporals { + value.addAll(severalPastAndFuture) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporals { + value.addAll(severalPastAndFuture) + } + } + } +} diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalWhenMapSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalWhenMapSpec.kt new file mode 100644 index 000000000..e695479d6 --- /dev/null +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalWhenMapSpec.kt @@ -0,0 +1,121 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.tools.time.validation.java + +import io.spine.test.tools.validate.anySpineTemporalMap +import io.spine.test.tools.validate.futureSpineTemporalMap +import io.spine.test.tools.validate.pastSpineTemporalMap +import io.spine.tools.time.validation.java.TemporalFixtures.futureTime +import io.spine.tools.time.validation.java.TemporalFixtures.pastTime +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +@DisplayName("If used with a map of `Temporal` values, `(when)` constraint should") +internal class TemporalWhenMapSpec { + + @Nested inner class + `only the past` { + + private val severalPastTimes = mapOf("a" to pastTime(), "b" to pastTime()) + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureSpineTemporalMap { + value.putAll(severalPastTimes) + } + } + + @Test + fun `pass, if restricted to be in past`() = assertValidationPasses { + pastSpineTemporalMap { + value.putAll(severalPastTimes) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporalMap { + value.putAll(severalPastTimes) + } + } + } + + @Nested inner class + `only the future` { + + private val severalFutureTimes = mapOf("a" to futureTime(), "b" to futureTime()) + + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastSpineTemporalMap { + value.putAll(severalFutureTimes) + } + } + + @Test + fun `pass, if restricted to be in future`() = assertValidationPasses { + futureSpineTemporalMap { + value.putAll(severalFutureTimes) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporalMap { + value.putAll(severalFutureTimes) + } + } + } + + @Nested inner class + `a mix of past and future` { + + private val mixedTimes = mapOf("a" to futureTime(), "b" to pastTime()) + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureSpineTemporalMap { + value.putAll(mixedTimes) + } + } + + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastSpineTemporalMap { + value.putAll(mixedTimes) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporalMap { + value.putAll(mixedTimes) + } + } + } +} diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalWhenSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalWhenSpec.kt new file mode 100644 index 000000000..8ef9ed260 --- /dev/null +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalWhenSpec.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.tools.time.validation.java + +import io.spine.test.tools.validate.anySpineTemporal +import io.spine.test.tools.validate.futureSpineTemporal +import io.spine.test.tools.validate.pastSpineTemporal +import io.spine.tools.time.validation.java.TemporalFixtures.futureTime +import io.spine.tools.time.validation.java.TemporalFixtures.pastTime +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +@DisplayName("If used with `Temporal`, `(when)` constraint should") +internal class TemporalWhenSpec { + + @Nested inner class + `the past` { + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureSpineTemporal { + value = pastTime() + } + } + + @Test + fun `pass, if restricted to be in past`() = assertValidationPasses { + pastSpineTemporal { + value = pastTime() + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporal { + value = pastTime() + } + } + } + + @Nested inner class + `the future` { + + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastSpineTemporal { + value = futureTime() + } + } + + @Test + fun `pass, if restricted to be in future`() = assertValidationPasses { + futureSpineTemporal { + value = futureTime() + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporal { + value = futureTime() + } + } + } +} diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampRepeatedWhenSpec.kt similarity index 63% rename from tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenSpec.kt rename to tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampRepeatedWhenSpec.kt index b124f1423..9954a946e 100644 --- a/tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenSpec.kt +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampRepeatedWhenSpec.kt @@ -26,76 +26,17 @@ package io.spine.tools.time.validation.java -import com.google.protobuf.Duration -import com.google.protobuf.Timestamp -import com.google.protobuf.util.Durations.fromMillis -import com.google.protobuf.util.Timestamps -import io.spine.test.tools.validate.anyProtoTimestamp import io.spine.test.tools.validate.anyProtoTimestamps -import io.spine.test.tools.validate.futureProtoTimestamp import io.spine.test.tools.validate.futureProtoTimestamps -import io.spine.test.tools.validate.pastProtoTimestamp import io.spine.test.tools.validate.pastProtoTimestamps +import io.spine.tools.time.validation.java.TimestampFixtures.futureTime +import io.spine.tools.time.validation.java.TimestampFixtures.pastTime import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -@DisplayName("If used with Protobuf `Timestamp`, `(when)` constrain should") -internal class ProtoTimestampWhenSpec { - - @Nested inner class - `when given a timestamp denoting` { - - @Nested inner class - `the past` { - - @Test - fun `throw, if restricted to be in future`() = assertValidationFails { - futureProtoTimestamp { - value = pastTime() - } - } - - @Test - fun `pass, if restricted to be in past`() = assertValidationPasses { - pastProtoTimestamp { - value = pastTime() - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anyProtoTimestamp { - value = pastTime() - } - } - } - - @Nested inner class - `the future` { - - @Test - fun `throw, if restricted to be in past`() = assertValidationFails { - pastProtoTimestamp { - value = futureTime() - } - } - - @Test - fun `pass, if restricted to be in future`() = assertValidationPasses { - futureProtoTimestamp { - value = futureTime() - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anyProtoTimestamp { - value = futureTime() - } - } - } - } +@DisplayName("If used with repeated Protobuf `Timestamp`, `(when)` constraint should") +internal class TimestampRepeatedWhenSpec { @Nested inner class `when given several timestamps` { @@ -209,30 +150,3 @@ internal class ProtoTimestampWhenSpec { } } } - -private fun pastTime(): Timestamp { - val current = Timestamps.now() - val past = Timestamps.subtract(current, HALF_OF_SECONDS) - return past -} - -private fun futureTime(): Timestamp { - val current = Timestamps.now() - val future = Timestamps.add(current, HALF_OF_SECONDS) - return future -} - -/** - * Protobuf [Duration] of five hundred milliseconds. - * - * To shift the time into the past or future, we add or subtract a difference of this amount. - * - * There are two reasons for choosing 500 milliseconds: - * - * 1. The generated code uses `io.spine.base.Time.currentTime()` to get the current timestamp - * for comparison. In turn, this method relies on `io.spine.base.Time.SystemTimeProvider` - * by default, which has millisecond precision. - * 2. Adding too small amount of time to make the stamp denote "future" might be unreliable. - * As it could catch up `now` by the time `Time.currentTime()` is invoked. - */ -private val HALF_OF_SECONDS: Duration = fromMillis(500) diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampWhenMapSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampWhenMapSpec.kt new file mode 100644 index 000000000..630b65b17 --- /dev/null +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampWhenMapSpec.kt @@ -0,0 +1,125 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.tools.time.validation.java + +import io.spine.test.tools.validate.anyProtoTimestampMap +import io.spine.test.tools.validate.futureProtoTimestampMap +import io.spine.test.tools.validate.pastProtoTimestampMap +import io.spine.tools.time.validation.java.TimestampFixtures.futureTime +import io.spine.tools.time.validation.java.TimestampFixtures.pastTime +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +@DisplayName("If used with a map of Protobuf `Timestamp` values, `(when)` constraint should") +internal class TimestampWhenMapSpec { + + @Nested inner class + `when given a map with timestamps denoting` { + + @Nested inner class + `only the past` { + + private val severalPastTimes = mapOf("a" to pastTime(), "b" to pastTime()) + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureProtoTimestampMap { + value.putAll(severalPastTimes) + } + } + + @Test + fun `pass, if restricted to be in past`() = assertValidationPasses { + pastProtoTimestampMap { + value.putAll(severalPastTimes) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anyProtoTimestampMap { + value.putAll(severalPastTimes) + } + } + } + + @Nested inner class + `only the future` { + + private val severalFutureTimes = mapOf("a" to futureTime(), "b" to futureTime()) + + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastProtoTimestampMap { + value.putAll(severalFutureTimes) + } + } + + @Test + fun `pass, if restricted to be in future`() = assertValidationPasses { + futureProtoTimestampMap { + value.putAll(severalFutureTimes) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anyProtoTimestampMap { + value.putAll(severalFutureTimes) + } + } + } + + @Nested inner class + `a mix of past and future` { + + private val mixedTimes = mapOf("a" to futureTime(), "b" to pastTime()) + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureProtoTimestampMap { + value.putAll(mixedTimes) + } + } + + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastProtoTimestampMap { + value.putAll(mixedTimes) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anyProtoTimestampMap { + value.putAll(mixedTimes) + } + } + } + } +} diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampWhenSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampWhenSpec.kt new file mode 100644 index 000000000..1547c6f74 --- /dev/null +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampWhenSpec.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.tools.time.validation.java + +import io.spine.test.tools.validate.anyProtoTimestamp +import io.spine.test.tools.validate.futureProtoTimestamp +import io.spine.test.tools.validate.pastProtoTimestamp +import io.spine.tools.time.validation.java.TimestampFixtures.futureTime +import io.spine.tools.time.validation.java.TimestampFixtures.pastTime +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +@DisplayName("If used with Protobuf `Timestamp`, `(when)` constraint should") +internal class TimestampWhenSpec { + + @Nested inner class + `when given a timestamp denoting` { + + @Nested inner class + `the past` { + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureProtoTimestamp { + value = pastTime() + } + } + + @Test + fun `pass, if restricted to be in past`() = assertValidationPasses { + pastProtoTimestamp { + value = pastTime() + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anyProtoTimestamp { + value = pastTime() + } + } + } + + @Nested inner class + `the future` { + + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastProtoTimestamp { + value = futureTime() + } + } + + @Test + fun `pass, if restricted to be in future`() = assertValidationPasses { + futureProtoTimestamp { + value = futureTime() + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anyProtoTimestamp { + value = futureTime() + } + } + } + } +} diff --git a/tests/src/testFixtures/proto/spine/test/tools/validate/when_map.proto b/tests/src/testFixtures/proto/spine/test/tools/validate/when_map.proto new file mode 100644 index 000000000..0ce4d24bb --- /dev/null +++ b/tests/src/testFixtures/proto/spine/test/tools/validate/when_map.proto @@ -0,0 +1,69 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +syntax = "proto3"; + +package spine.test.tools.validate; + +import "spine/options.proto"; +import "spine/time_options.proto"; + +option (type_url_prefix) = "type.spine.io"; +option java_package = "io.spine.test.tools.validate"; +option java_outer_classname = "WhenMapProto"; +option java_multiple_files = true; + +import "google/protobuf/timestamp.proto"; +import "spine/time/time.proto"; + +// Tests `PAST` restriction with a map of Protobuf timestamps. +message PastProtoTimestampMap { + map value = 1 [(when).in = PAST]; +} + +// Tests `PAST` restriction with a map of Spine temporals. +message PastSpineTemporalMap { + map value = 1 [(when).in = PAST]; +} + +// Tests `FUTURE` restriction with a map of Protobuf timestamps. +message FutureProtoTimestampMap { + map value = 1 [(when).in = FUTURE]; +} + +// Tests `FUTURE` restriction with a map of Spine temporals. +message FutureSpineTemporalMap { + map value = 1 [(when).in = FUTURE]; +} + +// Tests that a map of Protobuf timestamps is not restricted when there's no option. +message AnyProtoTimestampMap { + map value = 1; +} + +// Tests that a map of Spine temporals is not restricted when there's no option. +message AnySpineTemporalMap { + map value = 1; +} diff --git a/validation/src/main/kotlin/io/spine/tools/time/validation/java/WhenGenerator.kt b/validation/src/main/kotlin/io/spine/tools/time/validation/java/WhenGenerator.kt index 34ef9f9d0..da910ad11 100644 --- a/validation/src/main/kotlin/io/spine/tools/time/validation/java/WhenGenerator.kt +++ b/validation/src/main/kotlin/io/spine/tools/time/validation/java/WhenGenerator.kt @@ -30,6 +30,7 @@ import io.spine.base.FieldPath import io.spine.server.query.select import io.spine.time.validation.Time.FUTURE import io.spine.tools.compiler.ast.TypeName +import io.spine.tools.compiler.ast.isMap import io.spine.tools.compiler.ast.isRepeatedMessage import io.spine.tools.compiler.ast.name import io.spine.tools.compiler.jvm.CodeBlock @@ -113,6 +114,15 @@ private class GenerateWhen( """.trimIndent() ) + fieldType.isMap -> + CodeBlock( + """ + for (var element : $fieldValue.values()) { + ${validateTime(ReadVar("element"))} + } + """.trimIndent() + ) + else -> unsupportedFieldType() }.run { SingleOptionCode(this) } diff --git a/validation/src/main/kotlin/io/spine/tools/time/validation/java/WhenOption.kt b/validation/src/main/kotlin/io/spine/tools/time/validation/java/WhenOption.kt index 4b267736f..eed6573a5 100644 --- a/validation/src/main/kotlin/io/spine/tools/time/validation/java/WhenOption.kt +++ b/validation/src/main/kotlin/io/spine/tools/time/validation/java/WhenOption.kt @@ -47,6 +47,7 @@ import io.spine.tools.compiler.ast.FieldType import io.spine.tools.compiler.ast.File import io.spine.tools.compiler.ast.event.FieldOptionDiscovered import io.spine.tools.compiler.ast.extractMessageType +import io.spine.tools.compiler.ast.isMap import io.spine.tools.compiler.ast.isRepeatedMessage import io.spine.tools.compiler.ast.name import io.spine.tools.compiler.ast.qualifiedName @@ -155,13 +156,13 @@ private fun checkFieldType(field: Field, typeSystem: TypeSystem, file: File): Ti } /** - * Analysis the given [fieldType], determining whether it represents + * Analyses the given [fieldType], determining whether it represents * the Protobuf [Timestamp] or Spine [Temporal]. * * For other field types, the method returns [TimeFieldType.TFT_UNKNOWN]. */ private fun TypeSystem.determineTimeType(fieldType: FieldType): TimeFieldType { - if (!fieldType.isMessage && !fieldType.isRepeatedMessage) { + if (!fieldType.isMessage && !fieldType.isRepeatedMessage && !fieldType.isMap) { return TFT_UNKNOWN } val messageType = fieldType.extractMessageType(typeSystem = this)?.name diff --git a/version.gradle.kts b/version.gradle.kts index 1df9dd0b8..388e257dc 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -27,4 +27,4 @@ /** * The version of this library for publishing. */ -val versionToPublish by extra("2.0.0-SNAPSHOT.236") +val versionToPublish by extra("2.0.0-SNAPSHOT.237")