From 025d91c82114045c7f6188d1254c4e9ae49e2fc1 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 30 Apr 2026 20:22:00 +0100 Subject: [PATCH 01/10] Bump version -> `2.0.0-SNAPSHOT.237` --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index 1df9dd0b..388e257d 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") From c39970b42ef918393353e15f71888c7d802fd75c Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 30 Apr 2026 20:46:59 +0100 Subject: [PATCH 02/10] Bump version -> `2.0.0-SNAPSHOT.237` --- .agents/_TOC.md | 2 +- .agents/quick-reference-card.md | 1 - .../java-to-kotlin/SKILL.md} | 8 ++++++ .../skills/java-to-kotlin/agents/openai.yaml | 4 +++ .github/workflows/build-on-ubuntu.yml | 3 +- .github/workflows/build-on-windows.yml | 3 +- .github/workflows/ensure-reports-updated.yml | 3 +- .../workflows/gradle-wrapper-validation.yml | 1 - .github/workflows/increment-guard.yml | 3 +- ...move-obsolete-artifacts-from-packages.yaml | 4 +-- .gitignore | 1 + .junie/guidelines.md | 2 +- .junie/skills | 1 + buildSrc/build.gradle.kts | 2 -- .../kotlin/io/spine/dependency/build/Dokka.kt | 1 - .../spine/dependency/local/CoreJvmCompiler.kt | 2 +- .../kotlin/io/spine/dependency/local/Time.kt | 2 +- .../io/spine/dependency/local/Validation.kt | 2 +- config | 2 +- dependencies.md | 28 +++++++++---------- pom.xml | 19 ++++++++++--- 21 files changed, 55 insertions(+), 39 deletions(-) rename .agents/{java-kotlin-conversion.md => skills/java-to-kotlin/SKILL.md} (88%) create mode 100644 .agents/skills/java-to-kotlin/agents/openai.yaml create mode 120000 .junie/skills diff --git a/.agents/_TOC.md b/.agents/_TOC.md index 065df133..83375f41 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 6c25b9a7..e2be69cb 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 95cf9295..d3abdc2f 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 00000000..252920fe --- /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 f8c24933..b571ce82 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 4e6b57f1..37eba1be 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 fdd8b8e6..93531059 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 858cebbc..a7cde85e 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 1993841a..dd7df713 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 fe8ad849..f7061710 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 a3e0ec13..5f85d295 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 459e28ec..5160f499 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 00000000..2b7a412b --- /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 6496c646..4184e9bf 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 858731b3..6b4822dc 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 dfdaf953..2333e4ce 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 275aa007..05eb7bfa 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 a5ef4031..121d7aac 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 cd6e8633..f24236f0 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit cd6e86330c283e0414be7c318a4cd450dd3c6982 +Subproject commit f24236f0a897e9d6a101e60d1dd936317c7a3c06 diff --git a/dependencies.md b/dependencies.md index 843bea5a..60d279fb 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 **Thu Apr 30 20:23:43 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 **Thu Apr 30 20:23:43 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 **Thu Apr 30 20:23:43 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 **Thu Apr 30 20:23:43 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 **Thu Apr 30 20:23:43 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 **Thu Apr 30 20:23:43 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 **Thu Apr 30 20:23:43 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 64877507..8ba7a58f 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 From 012f8d6bf9aa59b5186d5fdff2652292bcd38502 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 30 Apr 2026 21:14:09 +0100 Subject: [PATCH 03/10] Update build time --- dependencies.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dependencies.md b/dependencies.md index 60d279fb..68d95836 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1059,7 +1059,7 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using +This report was generated on **Thu Apr 30 21:13:52 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). @@ -1869,7 +1869,7 @@ This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using +This report was generated on **Thu Apr 30 21:13:52 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). @@ -2833,7 +2833,7 @@ This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using +This report was generated on **Thu Apr 30 21:13:52 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). @@ -3643,7 +3643,7 @@ This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using +This report was generated on **Thu Apr 30 21:13:52 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). @@ -4461,7 +4461,7 @@ This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using +This report was generated on **Thu Apr 30 21:13:52 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). @@ -5590,7 +5590,7 @@ This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using +This report was generated on **Thu Apr 30 21:13:52 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). @@ -6683,6 +6683,6 @@ This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 20:23:43 WEST 2026** using +This report was generated on **Thu Apr 30 21:13:52 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 From 6869d45f746049993cd0997325a14ca5492cfebb Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 30 Apr 2026 21:14:25 +0100 Subject: [PATCH 04/10] Handle map values with `(when)` --- .../java/ProtoTimestampWhenMapSpec.kt | 139 +++++++++++++++++ .../java/SpineTemporalWhenMapSpec.kt | 140 ++++++++++++++++++ .../spine/test/tools/validate/when_map.proto | 69 +++++++++ .../time/validation/java/WhenGenerator.kt | 10 ++ .../tools/time/validation/java/WhenOption.kt | 3 +- 5 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenMapSpec.kt create mode 100644 tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenMapSpec.kt create mode 100644 tests/src/testFixtures/proto/spine/test/tools/validate/when_map.proto diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenMapSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenMapSpec.kt new file mode 100644 index 00000000..47277344 --- /dev/null +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenMapSpec.kt @@ -0,0 +1,139 @@ +/* + * 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.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.anyProtoTimestampMap +import io.spine.test.tools.validate.futureProtoTimestampMap +import io.spine.test.tools.validate.pastProtoTimestampMap +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 ProtoTimestampWhenMapSpec { + + @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) + } + } + } + } +} + +private fun pastTime(): Timestamp { + val current = Timestamps.now() + return Timestamps.subtract(current, HALF_OF_SECOND) +} + +private fun futureTime(): Timestamp { + val current = Timestamps.now() + return Timestamps.add(current, HALF_OF_SECOND) +} + +private val HALF_OF_SECOND: Duration = fromMillis(500) diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenMapSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenMapSpec.kt new file mode 100644 index 00000000..85ec409e --- /dev/null +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenMapSpec.kt @@ -0,0 +1,140 @@ +/* + * 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.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 a map of Spine `Temporal` values, `(when)` constraint should") +internal class SpineTemporalWhenMapSpec { + + @Nested inner class + `when given a map with temporals 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 { + 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) + } + } + } + } +} + +private fun pastTime(): SpineTimeLocalDateTime { + val current = Instant.now() + return LocalDateTimes.of(ofInstant(current.minusMillis(HALF_OF_SECOND), UTC)) +} + +private fun futureTime(): SpineTimeLocalDateTime { + val current = Instant.now() + return LocalDateTimes.of(ofInstant(current.plusMillis(HALF_OF_SECOND), UTC)) +} + +private const val HALF_OF_SECOND: Long = 500 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 00000000..0ce4d24b --- /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 34ef9f9d..da910ad1 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 4b267736..54c76fc4 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 @@ -161,7 +162,7 @@ private fun checkFieldType(field: Field, typeSystem: TypeSystem, file: File): Ti * 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 From 2c2e8a2d500b779819339be08fed8725dd56cb0a Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 30 Apr 2026 21:15:43 +0100 Subject: [PATCH 05/10] Fix comment grammar --- .../kotlin/io/spine/tools/time/validation/java/WhenOption.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 54c76fc4..eed6573a 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 @@ -156,7 +156,7 @@ 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]. From 6d3e61d7bda3b9757ba1ee240a8df367718868b5 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 30 Apr 2026 21:33:16 +0100 Subject: [PATCH 06/10] Allocate fixtures by objects --- .../tools/time/validation/java/Fixtures.kt | 73 +++++++++++++++++++ .../java/ProtoTimestampWhenMapSpec.kt | 18 +---- .../validation/java/ProtoTimestampWhenSpec.kt | 33 +-------- .../java/SpineTemporalWhenMapSpec.kt | 19 +---- .../validation/java/SpineTemporalWhenSpec.kt | 34 +-------- 5 files changed, 81 insertions(+), 96 deletions(-) create mode 100644 tests/src/test/kotlin/io/spine/tools/time/validation/java/Fixtures.kt 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 00000000..c6b304df --- /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/ProtoTimestampWhenMapSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenMapSpec.kt index 47277344..264f6a11 100644 --- a/tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenMapSpec.kt +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenMapSpec.kt @@ -26,13 +26,11 @@ 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.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 @@ -125,15 +123,3 @@ internal class ProtoTimestampWhenMapSpec { } } } - -private fun pastTime(): Timestamp { - val current = Timestamps.now() - return Timestamps.subtract(current, HALF_OF_SECOND) -} - -private fun futureTime(): Timestamp { - val current = Timestamps.now() - return Timestamps.add(current, HALF_OF_SECOND) -} - -private val HALF_OF_SECOND: Duration = fromMillis(500) 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/ProtoTimestampWhenSpec.kt index b124f142..08ffb3aa 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/ProtoTimestampWhenSpec.kt @@ -26,16 +26,14 @@ 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 @@ -209,30 +207,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/SpineTemporalWhenMapSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenMapSpec.kt index 85ec409e..48cd57e1 100644 --- a/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenMapSpec.kt +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenMapSpec.kt @@ -29,14 +29,11 @@ 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.time.LocalDateTimes -import java.time.Instant -import java.time.LocalDateTime.ofInstant -import java.time.ZoneOffset.UTC +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 -import io.spine.time.LocalDateTime as SpineTimeLocalDateTime @DisplayName("If used with a map of Spine `Temporal` values, `(when)` constraint should") internal class SpineTemporalWhenMapSpec { @@ -126,15 +123,3 @@ internal class SpineTemporalWhenMapSpec { } } } - -private fun pastTime(): SpineTimeLocalDateTime { - val current = Instant.now() - return LocalDateTimes.of(ofInstant(current.minusMillis(HALF_OF_SECOND), UTC)) -} - -private fun futureTime(): SpineTimeLocalDateTime { - val current = Instant.now() - return LocalDateTimes.of(ofInstant(current.plusMillis(HALF_OF_SECOND), UTC)) -} - -private const val HALF_OF_SECOND: Long = 500 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 index 5f4115c3..4f35d88e 100644 --- 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 @@ -32,14 +32,11 @@ 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 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 -import io.spine.time.LocalDateTime as SpineTimeLocalDateTime @DisplayName("If used with Spine `Temporal`, `(when)` constraint should") internal class SpineTemporalWhenSpec { @@ -210,30 +207,3 @@ internal class SpineTemporalWhenSpec { } } } - -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 From 587b2a1cd97280b8d6e004d08bb6eb144c3bc7be Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 30 Apr 2026 21:36:02 +0100 Subject: [PATCH 07/10] Improve names of test suites --- .../{SpineTemporalWhenMapSpec.kt => TemporalWhenMapSpec.kt} | 2 +- .../java/{SpineTemporalWhenSpec.kt => TemporalWhenSpec.kt} | 2 +- .../{ProtoTimestampWhenMapSpec.kt => TimestampWhenMapSpec.kt} | 2 +- .../java/{ProtoTimestampWhenSpec.kt => TimestampWhenSpec.kt} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename tests/src/test/kotlin/io/spine/tools/time/validation/java/{SpineTemporalWhenMapSpec.kt => TemporalWhenMapSpec.kt} (99%) rename tests/src/test/kotlin/io/spine/tools/time/validation/java/{SpineTemporalWhenSpec.kt => TemporalWhenSpec.kt} (99%) rename tests/src/test/kotlin/io/spine/tools/time/validation/java/{ProtoTimestampWhenMapSpec.kt => TimestampWhenMapSpec.kt} (99%) rename tests/src/test/kotlin/io/spine/tools/time/validation/java/{ProtoTimestampWhenSpec.kt => TimestampWhenSpec.kt} (99%) diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenMapSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalWhenMapSpec.kt similarity index 99% rename from tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenMapSpec.kt rename to tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalWhenMapSpec.kt index 48cd57e1..eca973e4 100644 --- a/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenMapSpec.kt +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalWhenMapSpec.kt @@ -36,7 +36,7 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @DisplayName("If used with a map of Spine `Temporal` values, `(when)` constraint should") -internal class SpineTemporalWhenMapSpec { +internal class TemporalWhenMapSpec { @Nested inner class `when given a map with temporals denoting` { 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/TemporalWhenSpec.kt similarity index 99% rename from tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenSpec.kt rename to tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalWhenSpec.kt index 4f35d88e..07dfecab 100644 --- a/tests/src/test/kotlin/io/spine/tools/time/validation/java/SpineTemporalWhenSpec.kt +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalWhenSpec.kt @@ -39,7 +39,7 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @DisplayName("If used with Spine `Temporal`, `(when)` constraint should") -internal class SpineTemporalWhenSpec { +internal class TemporalWhenSpec { @Nested inner class `when given a temporal denoting` { diff --git a/tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenMapSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampWhenMapSpec.kt similarity index 99% rename from tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenMapSpec.kt rename to tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampWhenMapSpec.kt index 264f6a11..630b65b1 100644 --- a/tests/src/test/kotlin/io/spine/tools/time/validation/java/ProtoTimestampWhenMapSpec.kt +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampWhenMapSpec.kt @@ -36,7 +36,7 @@ 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 ProtoTimestampWhenMapSpec { +internal class TimestampWhenMapSpec { @Nested inner class `when given a map with timestamps denoting` { 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/TimestampWhenSpec.kt similarity index 99% 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/TimestampWhenSpec.kt index 08ffb3aa..97a3f423 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/TimestampWhenSpec.kt @@ -39,7 +39,7 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @DisplayName("If used with Protobuf `Timestamp`, `(when)` constrain should") -internal class ProtoTimestampWhenSpec { +internal class TimestampWhenSpec { @Nested inner class `when given a timestamp denoting` { From 531420edeee66e0e3817bffccf4d86438c5d7c69 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 30 Apr 2026 21:43:35 +0100 Subject: [PATCH 08/10] Split test suites by types of fields --- .../java/TemporalRepeatedWhenSpec.kt | 148 +++++++++++++++ .../validation/java/TemporalWhenMapSpec.kt | 2 +- .../time/validation/java/TemporalWhenSpec.kt | 175 +++--------------- .../java/TimestampRepeatedWhenSpec.kt | 152 +++++++++++++++ .../time/validation/java/TimestampWhenSpec.kt | 117 +----------- 5 files changed, 330 insertions(+), 264 deletions(-) create mode 100644 tests/src/test/kotlin/io/spine/tools/time/validation/java/TemporalRepeatedWhenSpec.kt create mode 100644 tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampRepeatedWhenSpec.kt 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 00000000..83e55b15 --- /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 index eca973e4..748bf41e 100644 --- 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 @@ -35,7 +35,7 @@ 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 Spine `Temporal` values, `(when)` constraint should") +@DisplayName("If used with a map of `Temporal` values, `(when)` constraint should") internal class TemporalWhenMapSpec { @Nested inner class 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 index 07dfecab..8ef9ed26 100644 --- 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 @@ -27,182 +27,63 @@ 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.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 Spine `Temporal`, `(when)` constraint should") +@DisplayName("If used with `Temporal`, `(when)` constraint should") internal class TemporalWhenSpec { @Nested inner class - `when given a temporal denoting` { + `the past` { - @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() - } + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureSpineTemporal { + 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 restricted to be in past`() = assertValidationPasses { + pastSpineTemporal { + value = pastTime() } + } - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anySpineTemporal { - value = futureTime() - } + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporal { + value = pastTime() } } } @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) - } - } - } + `the future` { - @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) - } + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastSpineTemporal { + value = futureTime() } } - @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) - } + @Test + fun `pass, if restricted to be in future`() = assertValidationPasses { + futureSpineTemporal { + value = futureTime() } } - @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) - } + @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/TimestampRepeatedWhenSpec.kt b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampRepeatedWhenSpec.kt new file mode 100644 index 00000000..9954a946 --- /dev/null +++ b/tests/src/test/kotlin/io/spine/tools/time/validation/java/TimestampRepeatedWhenSpec.kt @@ -0,0 +1,152 @@ +/* + * 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.anyProtoTimestamps +import io.spine.test.tools.validate.futureProtoTimestamps +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 repeated Protobuf `Timestamp`, `(when)` constraint should") +internal class TimestampRepeatedWhenSpec { + + @Nested inner class + `when given several timestamps` { + + @Nested inner class + `denoting only the past` { + + private val severalPastTimes = listOf(pastTime(), pastTime(), pastTime()) + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureProtoTimestamps { + value.addAll(severalPastTimes) + } + } + + @Test + fun `pass, if restricted to be in past`() = assertValidationPasses { + pastProtoTimestamps { + value.addAll(severalPastTimes) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anyProtoTimestamps { + 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 { + pastProtoTimestamps { + value.addAll(severalFutureTimes) + } + } + + @Test + fun `pass, if restricted to be in future`() = assertValidationPasses { + futureProtoTimestamps { + value.addAll(severalFutureTimes) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anyProtoTimestamps { + value.addAll(severalFutureTimes) + } + } + } + + @Nested inner class + `with a single past stamp within the future stamps` { + + private val severalFutureAndPast = listOf(futureTime(), pastTime(), futureTime()) + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureProtoTimestamps { + value.addAll(severalFutureAndPast) + } + } + + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastProtoTimestamps { + value.addAll(severalFutureAndPast) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anyProtoTimestamps { + value.addAll(severalFutureAndPast) + } + } + } + + @Nested inner class + `with a single future stamp within the past stamps` { + + private val severalPastAndFuture = listOf(pastTime(), futureTime(), pastTime()) + + @Test + fun `throw, if restricted to be in future`() = assertValidationFails { + futureProtoTimestamps { + value.addAll(severalPastAndFuture) + } + } + + @Test + fun `throw, if restricted to be in past`() = assertValidationFails { + pastProtoTimestamps { + value.addAll(severalPastAndFuture) + } + } + + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anyProtoTimestamps { + value.addAll(severalPastAndFuture) + } + } + } + } +} 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 index 97a3f423..1547c6f7 100644 --- 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 @@ -27,18 +27,15 @@ package io.spine.tools.time.validation.java 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") +@DisplayName("If used with Protobuf `Timestamp`, `(when)` constraint should") internal class TimestampWhenSpec { @Nested inner class @@ -94,116 +91,4 @@ internal class TimestampWhenSpec { } } } - - @Nested inner class - `when given several timestamps` { - - @Nested inner class - `denoting only the past` { - - private val severalPastTimes = listOf(pastTime(), pastTime(), pastTime()) - - @Test - fun `throw, if restricted to be in future`() = assertValidationFails { - futureProtoTimestamps { - value.addAll(severalPastTimes) - } - } - - @Test - fun `pass, if restricted to be in past`() = assertValidationPasses { - pastProtoTimestamps { - value.addAll(severalPastTimes) - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anyProtoTimestamps { - 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 { - pastProtoTimestamps { - value.addAll(severalFutureTimes) - } - } - - @Test - fun `pass, if restricted to be in future`() = assertValidationPasses { - futureProtoTimestamps { - value.addAll(severalFutureTimes) - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anyProtoTimestamps { - value.addAll(severalFutureTimes) - } - } - } - - @Nested inner class - `with a single past stamp within the future stamps` { - - private val severalFutureAndPast = listOf(futureTime(), pastTime(), futureTime()) - - @Test - fun `throw, if restricted to be in future`() = assertValidationFails { - futureProtoTimestamps { - value.addAll(severalFutureAndPast) - } - } - - @Test - fun `throw, if restricted to be in past`() = assertValidationFails { - pastProtoTimestamps { - value.addAll(severalFutureAndPast) - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anyProtoTimestamps { - value.addAll(severalFutureAndPast) - } - } - } - - @Nested inner class - `with a single future stamp within the past stamps` { - - private val severalPastAndFuture = listOf(pastTime(), futureTime(), pastTime()) - - @Test - fun `throw, if restricted to be in future`() = assertValidationFails { - futureProtoTimestamps { - value.addAll(severalPastAndFuture) - } - } - - @Test - fun `throw, if restricted to be in past`() = assertValidationFails { - pastProtoTimestamps { - value.addAll(severalPastAndFuture) - } - } - - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anyProtoTimestamps { - value.addAll(severalPastAndFuture) - } - } - } - } } From 5268c2386f2dd4f400a7fb6885474c2945475b3a Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 30 Apr 2026 21:46:00 +0100 Subject: [PATCH 09/10] Avoid unnecessary nesting of test functions --- .../validation/java/TemporalWhenMapSpec.kt | 108 +++++++++--------- 1 file changed, 52 insertions(+), 56 deletions(-) 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 index 748bf41e..e695479d 100644 --- 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 @@ -39,86 +39,82 @@ import org.junit.jupiter.api.Test internal class TemporalWhenMapSpec { @Nested inner class - `when given a map with temporals denoting` { + `only the past` { - @Nested inner class - `only the past` { + private val severalPastTimes = mapOf("a" to pastTime(), "b" to pastTime()) - 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 `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 restricted to be in past`() = assertValidationPasses { + pastSpineTemporalMap { + value.putAll(severalPastTimes) } + } - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anySpineTemporalMap { - value.putAll(severalPastTimes) - } + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporalMap { + value.putAll(severalPastTimes) } } + } - @Nested inner class - `only the future` { + @Nested inner class + `only the future` { - private val severalFutureTimes = mapOf("a" to futureTime(), "b" to futureTime()) + 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 `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 restricted to be in future`() = assertValidationPasses { + futureSpineTemporalMap { + value.putAll(severalFutureTimes) } + } - @Test - fun `pass, if not restricted at all`() = assertValidationPasses { - anySpineTemporalMap { - 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` { + @Nested inner class + `a mix of past and future` { - private val mixedTimes = mapOf("a" to futureTime(), "b" to pastTime()) + 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 future`() = assertValidationFails { + futureSpineTemporalMap { + value.putAll(mixedTimes) } + } - @Test - fun `throw, if restricted to be in past`() = assertValidationFails { - pastSpineTemporalMap { - 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) - } + @Test + fun `pass, if not restricted at all`() = assertValidationPasses { + anySpineTemporalMap { + value.putAll(mixedTimes) } } } From 353da29a8a80520a5942b75daf5725dee8403426 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 1 May 2026 17:49:45 +0100 Subject: [PATCH 10/10] Update build time to force re-build of hanged CI --- dependencies.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dependencies.md b/dependencies.md index 68d95836..df15203e 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1059,7 +1059,7 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 21:13:52 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). @@ -1869,7 +1869,7 @@ This report was generated on **Thu Apr 30 21:13:52 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 21:13:52 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). @@ -2833,7 +2833,7 @@ This report was generated on **Thu Apr 30 21:13:52 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 21:13:52 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). @@ -3643,7 +3643,7 @@ This report was generated on **Thu Apr 30 21:13:52 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 21:13:52 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). @@ -4461,7 +4461,7 @@ This report was generated on **Thu Apr 30 21:13:52 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 21:13:52 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). @@ -5590,7 +5590,7 @@ This report was generated on **Thu Apr 30 21:13:52 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 21:13:52 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). @@ -6683,6 +6683,6 @@ This report was generated on **Thu Apr 30 21:13:52 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Apr 30 21:13:52 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