You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/01/31 11:57:19 UTC

[isis] branch master updated: ISIS-2952: impl. support f. declarative InteractionContext specification

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new aaa70ec  ISIS-2952: impl. support f. declarative InteractionContext specification
aaa70ec is described below

commit aaa70ec47614dbdcdfe2a4ae272080ce50df208b
Author: Andi Huber <ah...@apache.org>
AuthorDate: Mon Jan 31 12:56:35 2022 +0100

    ISIS-2952: impl. support f. declarative InteractionContext specification
---
 .../org/apache/isis/applib/clock/VirtualClock.java |  2 +-
 .../progmodel/ProgrammingModelConstants.java       | 29 +++++++
 .../testdomain/interact/CustomContextTest.java     | 91 ++++++++++++++++++++++
 .../applib/IsisInteractionHandler.java             | 11 ++-
 .../testing/integtestsupport/applib/_Helper.java   | 13 ++++
 .../applib/annotation/TestWith.java                | 74 ++++++++++++++++++
 .../applib/annotation/TestWithUtils.java           | 54 +++++++++++++
 7 files changed, 270 insertions(+), 4 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/clock/VirtualClock.java b/api/applib/src/main/java/org/apache/isis/applib/clock/VirtualClock.java
index 9a638ac..5dda8a5 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/clock/VirtualClock.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/clock/VirtualClock.java
@@ -142,7 +142,7 @@ public interface VirtualClock extends Serializable {
     }
 
     static VirtualClock frozenAt(@NonNull final java.time.LocalDateTime frozenAt) {
-        return frozenAt(Instant.from(frozenAt));
+        return frozenAt(Instant.from(frozenAt.atZone(ZoneId.systemDefault())));
     }
 
     static VirtualClock frozenAt(@NonNull final java.time.OffsetDateTime frozenAt) {
diff --git a/core/config/src/main/java/org/apache/isis/core/config/progmodel/ProgrammingModelConstants.java b/core/config/src/main/java/org/apache/isis/core/config/progmodel/ProgrammingModelConstants.java
index b62bbe6..cb8b7d4 100644
--- a/core/config/src/main/java/org/apache/isis/core/config/progmodel/ProgrammingModelConstants.java
+++ b/core/config/src/main/java/org/apache/isis/core/config/progmodel/ProgrammingModelConstants.java
@@ -24,6 +24,8 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.Collection;
 import java.util.Map;
 import java.util.function.Function;
@@ -122,6 +124,33 @@ public final class ProgrammingModelConstants {
         }
     }
 
+    // -- CANONICAL DATE-TIME PARSING/FORMATTING
+
+    @RequiredArgsConstructor
+    public enum DateTimeFormat {
+        /**
+         * Format: "yyyy-MM-dd HH:mm:ss"
+         * eg. {@literal "2010-01-01 13:02:04"}
+         */
+        CANONICAL(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+
+        // while this enum only has a single value, we just provide a (quasi) static method here
+        public LocalDateTime parseDateTime(final String dateTimeLiteral) {
+            return LocalDateTime.parse(dateTimeLiteral, dtf);
+        }
+
+        // while this enum only has a single value, we just provide a (quasi) static method here
+        public String formatDateTime(final LocalDateTime dateTime) {
+            return dtf.format(dateTime);
+        }
+
+        // -- HELPER
+
+        private final DateTimeFormatter dtf;
+
+    }
+
+
     // -- MIXIN CONSTRUCTION
 
     public enum MixinConstructor {
diff --git a/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/CustomContextTest.java b/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/CustomContextTest.java
new file mode 100644
index 0000000..32ef64d
--- /dev/null
+++ b/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/CustomContextTest.java
@@ -0,0 +1,91 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.testdomain.interact;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.Locale;
+
+import javax.inject.Inject;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
+import org.apache.isis.core.config.presets.IsisPresets;
+import org.apache.isis.core.config.progmodel.ProgrammingModelConstants.DateTimeFormat;
+import org.apache.isis.testdomain.conf.Configuration_headless;
+import org.apache.isis.testdomain.model.interaction.Configuration_usingInteractionDomain;
+import org.apache.isis.testing.integtestsupport.applib.IsisIntegrationTestAbstract;
+import org.apache.isis.testing.integtestsupport.applib.annotation.TestWith;
+
+import lombok.val;
+
+@SpringBootTest(
+        classes = {
+                Configuration_headless.class,
+                Configuration_usingInteractionDomain.class
+        },
+        properties = {
+        })
+@TestPropertySource({
+    IsisPresets.SilenceMetaModel,
+    IsisPresets.SilenceProgrammingModel
+})
+class CustomContextTest extends IsisIntegrationTestAbstract {
+
+    @Inject InteractionService interactionService;
+
+    @Test
+    @TestWith()
+    void shouldRunWithDefaultContext() {
+
+        val iaCtx = interactionService.currentInteractionContextElseFail();
+
+        assertTrue(iaCtx.getUser().isSystem());
+        assertEquals(Locale.getDefault(), iaCtx.getLocale().getLanguageLocale());
+        assertTrue(
+                Duration
+                    .between(LocalDateTime.now(), iaCtx.getClock().nowAsLocalDateTime())
+                    .abs()
+                    .toSeconds() <= 5L);
+    }
+
+
+    @Test
+    @TestWith(
+            userName = "sven",
+            localeName = "fr",
+            frozenDateTime = "2010-01-01 13:02:04")
+    void shouldRunWithCustomContext() {
+
+        val iaCtx = interactionService.currentInteractionContextElseFail();
+
+        assertEquals("sven", iaCtx.getUser().getName());
+        assertEquals(Locale.FRANCE.getLanguage(), iaCtx.getLocale().getLanguageLocale().getLanguage());
+        assertEquals(
+                DateTimeFormat.CANONICAL.parseDateTime("2010-01-01 13:02:04"),
+                iaCtx.getClock().nowAsLocalDateTime());
+    }
+
+}
diff --git a/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/IsisInteractionHandler.java b/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/IsisInteractionHandler.java
index 031bfbd..899bb36 100644
--- a/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/IsisInteractionHandler.java
+++ b/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/IsisInteractionHandler.java
@@ -30,13 +30,18 @@ import org.apache.isis.applib.services.iactnlayer.InteractionService;
 public class IsisInteractionHandler implements BeforeEachCallback, AfterEachCallback {
 
     @Override
-    public void beforeEach(ExtensionContext extensionContext) throws Exception {
+    public void beforeEach(final ExtensionContext extensionContext) throws Exception {
         _Helper.getInteractionFactory(extensionContext)
-        .ifPresent(InteractionService::openInteraction);
+        .ifPresent(interactionService->
+            _Helper
+                .getCustomInteractionContext(extensionContext)
+                .ifPresentOrElse(
+                        customInteractionContext->interactionService.openInteraction(customInteractionContext),
+                        interactionService::openInteraction));
     }
 
     @Override
-    public void afterEach(ExtensionContext extensionContext) throws Exception {
+    public void afterEach(final ExtensionContext extensionContext) throws Exception {
         _Helper.getInteractionFactory(extensionContext)
         .ifPresent(InteractionService::closeInteractionLayers);
     }
diff --git a/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/_Helper.java b/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/_Helper.java
index 8c93098..949f905 100644
--- a/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/_Helper.java
+++ b/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/_Helper.java
@@ -23,8 +23,12 @@ import java.util.Optional;
 import org.junit.jupiter.api.extension.ExtensionContext;
 
 import org.apache.isis.applib.services.exceprecog.ExceptionRecognizerService;
+import org.apache.isis.applib.services.iactnlayer.InteractionContext;
 import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.applib.services.registry.ServiceRegistry;
+import org.apache.isis.commons.internal.reflection._Annotations;
+import org.apache.isis.testing.integtestsupport.applib.annotation.TestWith;
+import org.apache.isis.testing.integtestsupport.applib.annotation.TestWithUtils;
 
 class _Helper {
 
@@ -35,6 +39,15 @@ class _Helper {
         .map(IsisIntegrationTestAbstract::getServiceRegistry);
     }
 
+    /**
+     * Eg. as declared on test method via {@link TestWith}.
+     */
+    static Optional<InteractionContext> getCustomInteractionContext(final ExtensionContext extensionContext) {
+        return extensionContext.getTestMethod()
+        .flatMap(testMethod->_Annotations.synthesize(testMethod, TestWith.class))
+        .map(TestWithUtils::toInteractionContext);
+    }
+
     // -- SHORTCUTS
 
     static Optional<InteractionService> getInteractionFactory(final ExtensionContext extensionContext) {
diff --git a/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/annotation/TestWith.java b/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/annotation/TestWith.java
new file mode 100644
index 0000000..d094f4a
--- /dev/null
+++ b/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/annotation/TestWith.java
@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.testing.integtestsupport.applib.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Locale;
+
+import org.apache.isis.applib.services.iactnlayer.InteractionContext;
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
+
+/**
+ * Offers an alternative to {@link InteractionService}'s imperative approach to switch
+ * the {@link InteractionContext} for a given block of <i>JUnit</i> test code.
+ * <p>
+ * Example:<br>
+ * <pre>
+ * &#64;Test
+ * &#64;TestWith(
+ *     userName = "sven",
+ *     localeName = "en",
+ *     frozenDateTime = "2010-01-01 13:02:04")
+ * void test() {
+ *     // ...
+ * }
+ * </pre>
+ */
+@Inherited
+@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TestWith {
+
+    /**
+     * If empty, defaults to the system user (with elevated privileges).
+     */
+    String userName()
+        default ""; // unspecified
+
+    /**
+     * The {@link Locale} language tag. Eg. "en", "fr", "de", ...
+     * If empty, defaults to current system locale.
+     * @see Locale#forLanguageTag(String)
+     */
+    String localeName()
+        default ""; // unspecified
+
+    /**
+     * If empty, defaults to current system time.
+     * Format: "yyyy-MM-dd HH:mm:ss"
+     * eg. {@literal "2010-01-01 13:02:04"}
+     */
+    String frozenDateTime()
+        default ""; // unspecified
+
+}
diff --git a/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/annotation/TestWithUtils.java b/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/annotation/TestWithUtils.java
new file mode 100644
index 0000000..d1813b8
--- /dev/null
+++ b/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/annotation/TestWithUtils.java
@@ -0,0 +1,54 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.testing.integtestsupport.applib.annotation;
+
+import java.util.Locale;
+
+import org.apache.isis.applib.clock.VirtualClock;
+import org.apache.isis.applib.locale.UserLocale;
+import org.apache.isis.applib.services.iactnlayer.InteractionContext;
+import org.apache.isis.applib.services.user.UserMemento;
+import org.apache.isis.commons.internal.base._Strings;
+import org.apache.isis.core.config.progmodel.ProgrammingModelConstants.DateTimeFormat;
+
+import lombok.val;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class TestWithUtils {
+
+    public InteractionContext toInteractionContext(final TestWith testWith) {
+        val user = _Strings.isNotEmpty(testWith.userName())
+                ? UserMemento.ofName(testWith.userName())
+                : UserMemento.system();
+
+        val mainLocale = _Strings.isNotEmpty(testWith.localeName())
+                ? Locale.forLanguageTag(testWith.localeName())
+                : Locale.getDefault();
+
+        val virtualClock = _Strings.isNotEmpty(testWith.frozenDateTime())
+                ? VirtualClock.frozenAt(DateTimeFormat.CANONICAL.parseDateTime(testWith.frozenDateTime()))
+                : VirtualClock.system();
+
+        return InteractionContext.ofUserWithSystemDefaults(user)
+                .withLocale(UserLocale.valueOf(mainLocale))
+                .withClock(virtualClock);
+    }
+
+}