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

danhaywood pushed a commit to branch CAUSEWAY-3676
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 6b99d52c70fb2ffafdf89dbf6983925b6331b897
Author: danhaywood <[email protected]>
AuthorDate: Thu Mar 14 10:55:51 2024 +0000

    CAUSEWAY-3676: adds tests for graphql with different scope
---
 bom/pom.xml                                        |   5 +
 .../pages/sections/causeway.viewer.graphql.adoc    |   8 +
 .../viewer/graphql/model/context/Context.java      |   7 +-
 .../viewer/graphql/model/domain/ElementCustom.java |  22 +++
 .../domain/common/query/CommonDomainObject.java    |  17 --
 .../domain/common/query/CommonDomainService.java   |   2 +
 .../common/query/CommonTopLevelQueryAbstract.java  |  20 +-
 .../domain/common/query/ObjectFeatureUtils.java    |   2 +-
 .../domain/rich/mutation/RichTopLevelMutation.java |   1 +
 .../simple/mutation/SimpleTopLevelMutation.java    |   6 +-
 .../graphql/model/types/TypeMapperDefault.java     |  40 ++--
 viewers/graphql/pom.xml                            |   1 +
 viewers/graphql/test2/pom.xml                      | 101 ++++++++++
 .../viewer/graphql/viewer/test2/.gitignore         |   2 +
 .../viewer/test2/domain/UniversityModule.java      |  32 ++++
 .../viewer/test2/domain/calc/Calculator.java       | 178 ++++++++++++++++++
 .../graphql/viewer/test2/domain/calc/Month.java    |  27 +++
 .../viewer/test2/domain/calc/Month_Test.java       |  14 ++
 .../viewer/test2/domain/dept/Department.java       | 207 +++++++++++++++++++++
 .../test2/domain/dept/DepartmentRepository.java    |  60 ++++++
 .../viewer/test2/domain/dept/Departments.java      |  63 +++++++
 .../graphql/viewer/test2/domain/dept/DeptHead.java | 137 ++++++++++++++
 .../test2/domain/dept/DeptHeadRepository.java      |  71 +++++++
 .../viewer/test2/domain/dept/DeptHeads.java        |  53 ++++++
 .../graphql/viewer/test2/domain/dept/Grade.java    |  30 +++
 .../graphql/viewer/test2/domain/dept/People.java   |  36 ++++
 .../graphql/viewer/test2/domain/dept/Person.java   |  17 ++
 .../graphql/viewer/test2/domain/dept/Staff.java    |  61 ++++++
 .../viewer/test2/domain/dept/StaffMember.java      | 119 ++++++++++++
 .../test2/domain/dept/StaffMemberRepository.java   |  72 +++++++
 .../viewer/test2/e2e/Abstract_IntegTest.java       |  61 ++++++
 ...alculator_IntegTest.each.add_big_decimals._.gql |  24 +++
 ...r_IntegTest.each.add_big_decimals.approved.json |  17 ++
 ...alculator_IntegTest.each.add_big_integers._.gql |  11 ++
 ...r_IntegTest.each.add_big_integers.approved.json |  13 ++
 ...ulator_IntegTest.each.add_double_wrappers._.gql |  11 ++
 ...ntegTest.each.add_double_wrappers.approved.json |  13 ++
 .../Calculator_IntegTest.each.add_doubles._.gql    |  11 ++
 ...ulator_IntegTest.each.add_doubles.approved.json |  13 ++
 ...culator_IntegTest.each.add_float_wrappers._.gql |  11 ++
 ...IntegTest.each.add_float_wrappers.approved.json |  13 ++
 .../Calculator_IntegTest.each.add_floats._.gql     |  11 ++
 ...culator_IntegTest.each.add_floats.approved.json |  13 ++
 ...lator_IntegTest.each.add_integer_wrappers._.gql |  11 ++
 ...tegTest.each.add_integer_wrappers.approved.json |  13 ++
 .../Calculator_IntegTest.each.add_integers._.gql   |  11 ++
 ...lator_IntegTest.each.add_integers.approved.json |  13 ++
 .../Calculator_IntegTest.each.boolean_and_1._.gql  |  11 ++
 ...ator_IntegTest.each.boolean_and_1.approved.json |  13 ++
 .../Calculator_IntegTest.each.boolean_and_2._.gql  |  11 ++
 ...ator_IntegTest.each.boolean_and_2.approved.json |  13 ++
 .../Calculator_IntegTest.each.boolean_not._.gql    |  11 ++
 ...ulator_IntegTest.each.boolean_not.approved.json |  13 ++
 .../Calculator_IntegTest.each.boolean_or_1._.gql   |  11 ++
 ...lator_IntegTest.each.boolean_or_1.approved.json |  13 ++
 .../Calculator_IntegTest.each.boolean_or_2._.gql   |  11 ++
 ...lator_IntegTest.each.boolean_or_2.approved.json |  13 ++
 .../calc/Calculator_IntegTest.each.concat._.gql    |  11 ++
 .../Calculator_IntegTest.each.concat.approved.json |  13 ++
 ...lator_IntegTest.each.jdk8_local_plus_days._.gql |  11 ++
 ...tegTest.each.jdk8_local_plus_days.approved.json |  13 ++
 ...st.each.jdk8_local_plus_hours_and_minutes._.gql |  11 ++
 ...jdk8_local_plus_hours_and_minutes.approved.json |  13 ++
 ...k8_offset_plus_days_and_hours_and_minutes._.gql |  11 ++
 ...t_plus_days_and_hours_and_minutes.approved.json |  13 ++
 ...t.each.jdk8_offset_plus_hours_and_minutes._.gql |  11 ++
 ...dk8_offset_plus_hours_and_minutes.approved.json |  13 ++
 ...dk8_zoned_plus_days_and_hours_and_minutes._.gql |  11 ++
 ...d_plus_days_and_hours_and_minutes.approved.json |  13 ++
 ...lator_IntegTest.each.joda_local_plus_days._.gql |  11 ++
 ...tegTest.each.joda_local_plus_days.approved.json |  13 ++
 ...st.each.joda_local_plus_hours_and_minutes._.gql |  11 ++
 ...joda_local_plus_hours_and_minutes.approved.json |  13 ++
 .../Calculator_IntegTest.each.joda_plus_days._.gql |  11 ++
 ...tor_IntegTest.each.joda_plus_days.approved.json |  13 ++
 .../Calculator_IntegTest.each.next_month._.gql     |  11 ++
 ...culator_IntegTest.each.next_month.approved.json |  13 ++
 ...Calculator_IntegTest.each.scenario_concat._.gql |  20 ++
 ...or_IntegTest.each.scenario_concat.approved.json |  22 +++
 .../Calculator_IntegTest.each.some_locale._.gql    |  11 ++
 ...ulator_IntegTest.each.some_locale.approved.json |  13 ++
 .../calc/Calculator_IntegTest.each.some_url._.gql  |  11 ++
 ...alculator_IntegTest.each.some_url.approved.json |  13 ++
 .../calc/Calculator_IntegTest.each.some_uuid._.gql |  11 ++
 ...lculator_IntegTest.each.some_uuid.approved.json |  13 ++
 .../test2/e2e/calc/Calculator_IntegTest.java       |  41 ++++
 .../viewer/test2/schema/PrintSchemaIntegTest.java  |  66 +++++++
 .../viewer/test2/schema/VerifySchemaIntegTest.java |  76 ++++++++
 .../src/test/resources/application-test.properties |  30 +++
 .../src/test/resources/junit-platform.properties   |   2 +
 90 files changed, 2319 insertions(+), 51 deletions(-)

diff --git a/bom/pom.xml b/bom/pom.xml
index dce9bcbfb3..deef864a80 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -527,6 +527,11 @@ It is therefore a copy of org.apache:apache, with 
customisations clearly identif
                 <artifactId>causeway-viewer-graphql-test</artifactId>
                 <version>2.0.0-SNAPSHOT</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.causeway.viewer</groupId>
+                <artifactId>causeway-viewer-graphql-test2</artifactId>
+                <version>2.0.0-SNAPSHOT</version>
+            </dependency>
             <dependency>
                 <groupId>org.apache.causeway.viewer</groupId>
                 <artifactId>causeway-viewer-graphql-viewer</artifactId>
diff --git 
a/core/config/src/main/adoc/modules/config/pages/sections/causeway.viewer.graphql.adoc
 
b/core/config/src/main/adoc/modules/config/pages/sections/causeway.viewer.graphql.adoc
index 8be5edaac9..0c57594ecc 100644
--- 
a/core/config/src/main/adoc/modules/config/pages/sections/causeway.viewer.graphql.adoc
+++ 
b/core/config/src/main/adoc/modules/config/pages/sections/causeway.viewer.graphql.adoc
@@ -11,6 +11,14 @@ include::../section-hooks/causeway.viewer.graphql~pre.adoc[]
 |Property
 |Default
 |Description
+|
+[[causeway.viewer.graphql.api-scope]]
+causeway.viewer.graphql.api-scope
+
+| 
+| Which domain objects to include the GraphQL schema.
+
+
 |
 [[causeway.viewer.graphql.api-variant]]
 causeway.viewer.graphql. +
diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/context/Context.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/context/Context.java
index a76300f777..0ae9fa904d 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/context/Context.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/context/Context.java
@@ -90,10 +90,15 @@ public class Context {
     }
 
     public List<ObjectSpecification> objectSpecifications(final 
Predicate<ObjectSpecification> predicate) {
+        val includeEntities = 
causewayConfiguration.getViewer().getGraphql().getApiScope() == 
CausewayConfiguration.Viewer.Graphql.ApiScope.ALL;
         return specificationLoader.snapshotSpecifications()
                 .filter(x -> x.getCorrespondingClass().getPackage() != 
Either.class.getPackage())   // exclude the 
org.apache_causeway.commons.functional
                 .distinct((a, b) -> 
a.getLogicalTypeName().equals(b.getLogicalTypeName()))
-                .filter(x -> x.isEntityOrViewModelOrAbstract() || 
x.getBeanSort().isManagedBeanContributing())
+                .filter(x ->
+                           x.isViewModel()
+                        || x.isEntity() && includeEntities
+                        || x.getBeanSort().isManagedBeanContributing()
+                )
                 .filter(predicate)
                 
.sorted(Comparator.comparing(HasLogicalType::getLogicalTypeName))
                 .toList();
diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/ElementCustom.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/ElementCustom.java
index fb5c815bb5..5711455595 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/ElementCustom.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/ElementCustom.java
@@ -25,6 +25,11 @@ import graphql.schema.GraphQLObjectType;
 import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
 import static graphql.schema.GraphQLObjectType.newObject;
 
+import org.apache.causeway.core.config.CausewayConfiguration;
+import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociation;
+
 import org.springframework.lang.Nullable;
 
 import org.apache.causeway.viewer.graphql.model.context.Context;
@@ -152,4 +157,21 @@ public abstract class ElementCustom
         return FieldCoordinates.coordinates(gqlObjectType, fieldName);
     }
 
+    protected boolean inApiScope(ObjectAction act) {
+        if (graphqlConfiguration.getApiScope() == 
CausewayConfiguration.Viewer.Graphql.ApiScope.ALL) {
+            return true;
+        }
+        val returnType = act.getElementType();
+        return returnType.isViewModelOrValueOrVoid() &&
+                
act.getParameterTypes().stream().allMatch(ObjectSpecification::isViewModelOrValue);
+    }
+
+    protected boolean inApiScope(final ObjectAssociation objAssoc) {
+        if (graphqlConfiguration.getApiScope() == 
CausewayConfiguration.Viewer.Graphql.ApiScope.ALL) {
+            return true;
+        }
+        return objAssoc.getElementType().isViewModelOrValue();
+    }
+
+
 }
diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainObject.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainObject.java
index 52c7a8d15f..807f96a8f5 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainObject.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainObject.java
@@ -37,7 +37,6 @@ import 
org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 import org.apache.causeway.core.metamodel.spec.feature.MixedIn;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociation;
-import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
 import org.apache.causeway.viewer.graphql.model.context.Context;
 import org.apache.causeway.viewer.graphql.model.domain.Environment;
 import org.apache.causeway.viewer.graphql.model.domain.Element;
@@ -174,22 +173,6 @@ public class CommonDomainObject
                 .forEach(act -> 
actions.add(addChildFieldFor(schemaStrategy.newAction(this, act, context))));
     }
 
-    private boolean inApiScope(ObjectAction act) {
-        if (graphqlConfiguration.getApiScope() == 
CausewayConfiguration.Viewer.Graphql.ApiScope.ALL) {
-            return true;
-        }
-        val returnType = act.getElementType();
-        return returnType.isViewModelOrValueOrVoid() &&
-               
act.getParameterTypes().stream().allMatch(ObjectSpecification::isViewModelOrValue);
-    }
-
-    private boolean inApiScope(final ObjectAssociation objAssoc) {
-        if (graphqlConfiguration.getApiScope() == 
CausewayConfiguration.Viewer.Graphql.ApiScope.ALL) {
-            return true;
-        }
-        return objAssoc.getElementType().isViewModelOrValue();
-    }
-
     @SuppressWarnings("unused")
     private ActionScope determineActionScope() {
         return 
context.causewaySystemEnvironment.getDeploymentType().isProduction()
diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainService.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainService.java
index be21351628..362b91eaf8 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainService.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonDomainService.java
@@ -89,7 +89,9 @@ public class CommonDomainService
     private void addActions() {
 
         val apiVariant = 
context.causewayConfiguration.getViewer().getGraphql().getApiVariant();
+        val apiScope = 
context.causewayConfiguration.getViewer().getGraphql().getApiVariant();
         objectSpecification.streamActions(context.getActionScope(), 
MixedIn.INCLUDED)
+                .filter(this::inApiScope)
                 .filter(objectAction -> 
objectAction.getSemantics().isSafeInNature() ||
                         apiVariant != 
CausewayConfiguration.Viewer.Graphql.ApiVariant.QUERY_ONLY    // the other 
variants have an entry for all actions.
                 )
diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonTopLevelQueryAbstract.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonTopLevelQueryAbstract.java
index f2b6454043..d37fdaace0 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonTopLevelQueryAbstract.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/CommonTopLevelQueryAbstract.java
@@ -23,7 +23,6 @@ import java.util.List;
 
 import graphql.schema.DataFetchingEnvironment;
 
-import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 import org.apache.causeway.viewer.graphql.model.context.Context;
 import org.apache.causeway.viewer.graphql.model.domain.Element;
@@ -49,18 +48,16 @@ public abstract class CommonTopLevelQueryAbstract
         super(schemaStrategy.getSchemaType().name() + "Schema", context);
         this.schemaStrategy = schemaStrategy;
 
-        boolean includeEntities = 
context.causewayConfiguration.getViewer().getGraphql().getApiScope() == 
CausewayConfiguration.Viewer.Graphql.ApiScope.ALL;
         context.objectSpecifications().forEach(objectSpec -> {
             switch (objectSpec.getBeanSort()) {
 
                 case ABSTRACT:
                 case VIEW_MODEL:
-                    addDomainObject(objectSpec, schemaStrategy, context);
-                    break;
                 case ENTITY:
-                    if (includeEntities) {
-                        addDomainObject(objectSpec, schemaStrategy, context);
-                    }
+                    val gqlvDomainObject = 
schemaStrategy.domainObjectFor(objectSpec, context);
+                    addChildField(gqlvDomainObject.newField());
+                    domainObjects.add(gqlvDomainObject);
+                    break;
 
             }
         });
@@ -78,15 +75,6 @@ public abstract class CommonTopLevelQueryAbstract
         });
     }
 
-    private void addDomainObject(
-            final ObjectSpecification objectSpec,
-            final SchemaStrategy schemaStrategy,
-            final Context context) {
-        val gqlvDomainObject = schemaStrategy.domainObjectFor(objectSpec, 
context);
-        addChildField(gqlvDomainObject.newField());
-        domainObjects.add(gqlvDomainObject);
-    }
-
     public static List<ObjectSpecification> superclassesOf(final 
ObjectSpecification objectSpecification) {
         val superclasses = new ArrayList<ObjectSpecification>();
         ObjectSpecification superclass = objectSpecification.superclass();
diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ObjectFeatureUtils.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ObjectFeatureUtils.java
index fecaae4647..2a6d1814ce 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ObjectFeatureUtils.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/common/query/ObjectFeatureUtils.java
@@ -39,7 +39,7 @@ import lombok.val;
 @UtilityClass
 public class ObjectFeatureUtils {
 
-    public static Optional<Object> asPojo(
+    static Optional<Object> asPojo(
             final ObjectSpecification elementType,
             final Object argumentValueObj,
             final Environment environment,
diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/mutation/RichTopLevelMutation.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/mutation/RichTopLevelMutation.java
index 6676ef098f..f9649b8664 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/mutation/RichTopLevelMutation.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/rich/mutation/RichTopLevelMutation.java
@@ -53,6 +53,7 @@ public class RichTopLevelMutation
 
         objectSpecifications.forEach(objectSpec -> {
             objectSpec.streamActions(context.getActionScope(), 
MixedIn.INCLUDED)
+                    .filter(this::inApiScope)
                     .filter(x -> ! x.getSemantics().isSafeInNature())
                     .forEach(objectAction -> addAction(objectSpec, 
objectAction));
             objectSpec.streamProperties(MixedIn.INCLUDED)
diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/simple/mutation/SimpleTopLevelMutation.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/simple/mutation/SimpleTopLevelMutation.java
index e5671a732e..372263c0be 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/simple/mutation/SimpleTopLevelMutation.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/simple/mutation/SimpleTopLevelMutation.java
@@ -51,15 +51,17 @@ public class SimpleTopLevelMutation
         }
         val objectSpecifications = context.objectSpecifications();
 
-        objectSpecifications.forEach(objectSpec -> {
+        objectSpecifications
+                .forEach(objectSpec -> {
             objectSpec.streamActions(context.getActionScope(), 
MixedIn.INCLUDED)
+                    .filter(this::inApiScope)
                     .filter(x -> ! x.getSemantics().isSafeInNature())
                     .forEach(objectAction -> addAction(objectSpec, 
objectAction));
             objectSpec.streamProperties(MixedIn.INCLUDED)
                     .filter(property -> ! property.isAlwaysHidden())
+                    .filter(this::inApiScope)
                     .filter(property -> 
property.containsFacet(PropertySetterFacet.class))
                     .forEach(property -> addProperty(objectSpec, property));
-
         });
 
         buildObjectType();
diff --git 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/TypeMapperDefault.java
 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/TypeMapperDefault.java
index 7e315870f1..622dd65fba 100644
--- 
a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/TypeMapperDefault.java
+++ 
b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/types/TypeMapperDefault.java
@@ -95,27 +95,36 @@ public class TypeMapperDefault implements TypeMapper {
             final OneToOneFeature oneToOneFeature,
             final SchemaType schemaType) {
         ObjectSpecification otoaObjectSpec = oneToOneFeature.getElementType();
+
         switch (otoaObjectSpec.getBeanSort()) {
 
             case VIEW_MODEL:
-            case ENTITY:
+                return typeRefPossiblyOptional(oneToOneFeature, schemaType, 
otoaObjectSpec);
 
-                GraphQLTypeReference fieldTypeRef = 
typeRef(TypeNames.objectTypeNameFor(otoaObjectSpec, schemaType));
-                return oneToOneFeature.isOptional()
-                        ? fieldTypeRef
-                        : nonNull(fieldTypeRef);
+            case ENTITY:
+                return typeRefPossiblyOptional(oneToOneFeature, schemaType, 
otoaObjectSpec);
 
             case VALUE:
-
-                GraphQLOutputType scalarType = 
outputTypeFor(otoaObjectSpec.getCorrespondingClass());
-
-                return oneToOneFeature.isOptional()
-                        ? scalarType
-                        : nonNull(scalarType);
+                return scalarTypePossiblyOptional(oneToOneFeature, 
otoaObjectSpec);
         }
         return null;
     }
 
+    private static GraphQLOutputType typeRefPossiblyOptional(OneToOneFeature 
oneToOneFeature, SchemaType schemaType, ObjectSpecification otoaObjectSpec) {
+        GraphQLTypeReference fieldTypeRef = 
typeRef(TypeNames.objectTypeNameFor(otoaObjectSpec, schemaType));
+        return oneToOneFeature.isOptional()
+                ? fieldTypeRef
+                : nonNull(fieldTypeRef);
+    }
+
+    private GraphQLOutputType scalarTypePossiblyOptional(OneToOneFeature 
oneToOneFeature, ObjectSpecification otoaObjectSpec) {
+        GraphQLOutputType scalarType = 
outputTypeFor(otoaObjectSpec.getCorrespondingClass());
+        return oneToOneFeature.isOptional()
+                ? scalarType
+                : nonNull(scalarType);
+    }
+
+
     @Override
     @Nullable
     public GraphQLOutputType outputTypeFor(
@@ -124,9 +133,10 @@ public class TypeMapperDefault implements TypeMapper {
 
         switch (objectSpecification.getBeanSort()){
             case ABSTRACT:
-            case ENTITY:
             case VIEW_MODEL:
                 return 
typeRef(TypeNames.objectTypeNameFor(objectSpecification, schemaType));
+            case ENTITY:
+                return 
typeRef(TypeNames.objectTypeNameFor(objectSpecification, schemaType));
 
             case VALUE:
                 return 
outputTypeFor(objectSpecification.getCorrespondingClass());
@@ -179,9 +189,10 @@ public class TypeMapperDefault implements TypeMapper {
         val elementObjectSpec = oneToOneFeature.getElementType();
         switch (elementObjectSpec.getBeanSort()) {
             case ABSTRACT:
-            case ENTITY:
             case VIEW_MODEL:
                 return typeRef(TypeNames.inputTypeNameFor(elementObjectSpec, 
schemaType));
+            case ENTITY:
+                return typeRef(TypeNames.inputTypeNameFor(elementObjectSpec, 
schemaType));
 
             case VALUE:
                 return inputTypeFor(elementObjectSpec.getCorrespondingClass());
@@ -208,9 +219,10 @@ public class TypeMapperDefault implements TypeMapper {
             final SchemaType schemaType){
         switch (elementType.getBeanSort()) {
             case ABSTRACT:
-            case ENTITY:
             case VIEW_MODEL:
                 return typeRef(TypeNames.inputTypeNameFor(elementType, 
schemaType));
+            case ENTITY:
+                return typeRef(TypeNames.inputTypeNameFor(elementType, 
schemaType));
 
             case VALUE:
                 return inputTypeFor(elementType.getCorrespondingClass());
diff --git a/viewers/graphql/pom.xml b/viewers/graphql/pom.xml
index 726c6b7ae0..cd12a944cb 100644
--- a/viewers/graphql/pom.xml
+++ b/viewers/graphql/pom.xml
@@ -52,6 +52,7 @@
                <module>model</module>
                <module>testsupport</module>
                <module>test</module>
+               <module>test2</module>
                <module>viewer</module>
        </modules>
 
diff --git a/viewers/graphql/test2/pom.xml b/viewers/graphql/test2/pom.xml
new file mode 100644
index 0000000000..9492ba36f1
--- /dev/null
+++ b/viewers/graphql/test2/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+
+       <modelVersion>4.0.0</modelVersion>
+
+       <parent>
+               <groupId>org.apache.causeway.viewer</groupId>
+               <artifactId>causeway-viewer-graphql</artifactId>
+               <version>2.0.0-SNAPSHOT</version>
+       </parent>
+
+       <artifactId>causeway-viewer-graphql-test2</artifactId>
+       <name>Apache Causeway Viewer - GraphQL (Test 2)</name>
+
+       <properties>
+               
<jar-plugin.automaticModuleName>org.apache.causeway.viewer.graphql.test2</jar-plugin.automaticModuleName>
+               
<git-plugin.propertiesDir>org/apache/causeway/viewer/graphql/test2</git-plugin.propertiesDir>
+        <maven.install.skip>true</maven.install.skip>
+        <maven.deploy.skip>true</maven.deploy.skip>
+
+               <!-- as a minimum requirement of
+                   org.springframework.graphql:spring-graphql-test:1.2.4 -->
+        <maven.compiler.release>17</maven.compiler.release>
+
+
+    </properties>
+
+    <build>
+               <testResources>
+                       <testResource>
+                               <directory>src/test/resources</directory>
+                               <filtering>true</filtering>
+                       </testResource>
+                       <testResource>
+                               <directory>src/test/java</directory>
+                               <filtering>false</filtering>
+                       </testResource>
+               </testResources>
+       </build>
+       <dependencies>
+
+               <!-- TESTING -->
+
+        <dependency>
+            <groupId>org.apache.causeway.viewer</groupId>
+            <artifactId>causeway-viewer-graphql-testsupport</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.causeway.persistence</groupId>
+            <artifactId>causeway-persistence-jpa-eclipselink</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-test-autoconfigure</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.gavlyukovskiy</groupId>
+            <artifactId>datasource-proxy-spring-boot-starter</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.vertical-blank</groupId>
+            <artifactId>sql-formatter</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+
+</project>
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/.gitignore
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/.gitignore
new file mode 100644
index 0000000000..837d57bea7
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/.gitignore
@@ -0,0 +1,2 @@
+*.received.gql
+*.received.json
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/UniversityModule.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/UniversityModule.java
new file mode 100644
index 0000000000..777e6b93a0
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/UniversityModule.java
@@ -0,0 +1,32 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.domain;
+
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+@Configuration
+@ComponentScan
+@EnableJpaRepositories
+@EntityScan(basePackageClasses = {UniversityModule.class})
+public class UniversityModule  {
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/calc/Calculator.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/calc/Calculator.java
new file mode 100644
index 0000000000..55777d325b
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/calc/Calculator.java
@@ -0,0 +1,178 @@
+package org.apache.causeway.viewer.graphql.viewer.test2.domain.calc;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.URL;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.ZonedDateTime;
+import java.util.Locale;
+import java.util.UUID;
+
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.causeway.applib.annotation.Action;
+import org.apache.causeway.applib.annotation.DomainService;
+import org.apache.causeway.applib.annotation.NatureOfService;
+import org.apache.causeway.applib.annotation.Optionality;
+import org.apache.causeway.applib.annotation.Parameter;
+import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.annotation.SemanticsOf;
+
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+
+@Named("university.calc.Calculator")
+@DomainService(nature= NatureOfService.BOTH)
+@Priority(PriorityPrecedence.EARLY)
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
+public class Calculator {
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public byte addBytes(byte x, byte y) {
+        return (byte)(x+y);
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public int addByteWrappers(Byte x, @Parameter(optionality = 
Optionality.OPTIONAL) Byte y) {
+        return y != null ? x+y : x;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public short addShorts(short x, short y) {
+        return (short)(x+y);
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Short addShortWrappers(Short x, @Parameter(optionality = 
Optionality.OPTIONAL) Short y) {
+        return y != null ? (short)(x+y) : x;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public int addIntegers(int x, int y) {
+        return x+y;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public int addIntegerWrappers(Integer x, @Parameter(optionality = 
Optionality.OPTIONAL) Integer y) {
+        return y != null ? x+y : x;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public double addDoubles(double x, double y) {
+        return x+y;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Double addDoubleWrappers(Double x, @Parameter(optionality = 
Optionality.OPTIONAL) Double y) {
+        return y != null ? x+y : x;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public float addFloats(float x, float y) {
+        return x+y;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Float addFloatWrappers(Float x, @Parameter(optionality = 
Optionality.OPTIONAL) Float y) {
+        return y != null ? (float)(x+y) : x;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public BigInteger addBigIntegers(BigInteger x, @Parameter(optionality = 
Optionality.OPTIONAL) BigInteger y) {
+        return y != null ? x.add(y) : x;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public BigDecimal addBigDecimals(BigDecimal x, @Parameter(optionality = 
Optionality.OPTIONAL) BigDecimal y) {
+        return y != null ? x.add(y) : x;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public LocalDate jdk8LocalPlusDays(LocalDate date, int numDays) {
+        return date.plusDays(numDays);
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public OffsetDateTime jdk8OffsetPlusDaysAndHoursAndMinutes(OffsetDateTime 
dateTime, int numDays, int numHours, int numMinutes) {
+        return 
dateTime.plusDays(numDays).plusHours(numHours).plusMinutes(numMinutes);
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public ZonedDateTime jdk8ZonedPlusDaysAndHoursAndMinutes(ZonedDateTime 
dateTime, int numDays, int numHours, int numMinutes) {
+        return 
dateTime.plusDays(numDays).plusHours(numHours).plusMinutes(numMinutes);
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public OffsetTime jdk8OffsetPlusHoursAndMinutes(OffsetTime time, int 
numHours, int numMinutes) {
+        return time.plusHours(numHours).plusMinutes(numMinutes);
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public LocalTime jdk8LocalPlusHoursAndMinutes(LocalTime time, int 
numHours, int numMinutes) {
+        return time.plusHours(numHours).plusMinutes(numMinutes);
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public org.joda.time.LocalDate jodaLocalPlusDays(org.joda.time.LocalDate 
date, int numDays) {
+        return date.plusDays(numDays);
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public org.joda.time.DateTime 
jodaPlusDaysAndHoursAndMinutes(org.joda.time.DateTime dateTime, int numDays, 
int numHours, int numMinutes) {
+        return 
dateTime.plusDays(numDays).plusHours(numHours).plusMinutes(numMinutes);
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public org.joda.time.LocalTime 
jodaLocalPlusHoursAndMinutes(org.joda.time.LocalTime time, int numHours, int 
numMinutes) {
+        return time.plusHours(numHours).plusMinutes(numMinutes);
+    }
+
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public boolean and(boolean x, boolean y) {
+        return x & y;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public boolean or(boolean x, boolean y) {
+        return x | y;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public boolean not(boolean x) {
+        return !x;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Month nextMonth(Month month) {
+        return month.nextMonth();
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public String concat(String prefix, @Parameter(optionality = 
Optionality.OPTIONAL) String suffix) {
+        return prefix + suffix;
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public UUID someUuid() {
+        return UUID.fromString("91be0d2d-1752-4962-ad2c-89a7ef73a656");
+    }
+
+    @SneakyThrows
+    @Action(semantics = SemanticsOf.SAFE)
+    public URL someUrl() {
+        return new URL("https://causeway.apache.org";);
+    }
+
+    @SneakyThrows
+    @Action(semantics = SemanticsOf.SAFE)
+    public Locale someLocale() {
+        return Locale.UK;
+    }
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/calc/Month.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/calc/Month.java
new file mode 100644
index 0000000000..223aa75595
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/calc/Month.java
@@ -0,0 +1,27 @@
+package org.apache.causeway.viewer.graphql.viewer.test2.domain.calc;
+
+public enum Month {
+    JANUARY,
+    FEBRUARY,
+    MARCH,
+    APRIL,
+    MAY,
+    JUNE,
+    JULY,
+    AUGUST,
+    SEPTEMBER,
+    OCTOBER,
+    NOVEMBER,
+    DECEMBER,
+    ;
+
+    public Month nextMonth() {
+        int currentMonthOrdinal = this.ordinal();
+        int nextMonthOrdinal = (currentMonthOrdinal + 1) % 
Month.values().length;
+        return Month.values()[nextMonthOrdinal];
+    }
+
+    public String toString() {
+        return "Month of " + ("" + name().charAt(0)).toUpperCase() + 
(name().substring(1).toLowerCase());
+    }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/calc/Month_Test.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/calc/Month_Test.java
new file mode 100644
index 0000000000..4bb21cbceb
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/calc/Month_Test.java
@@ -0,0 +1,14 @@
+package org.apache.causeway.viewer.graphql.viewer.test2.domain.calc;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class Month_Test {
+
+    @Test
+    public void testNextMonth() {
+        assertThat(Month.JANUARY.nextMonth()).isEqualTo(Month.FEBRUARY);
+        assertThat(Month.DECEMBER.nextMonth()).isEqualTo(Month.JANUARY);
+    }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Department.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Department.java
new file mode 100644
index 0000000000..a99ae0c2af
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Department.java
@@ -0,0 +1,207 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+
+import org.apache.causeway.applib.annotation.Action;
+import org.apache.causeway.applib.annotation.ActionLayout;
+import org.apache.causeway.applib.annotation.Bounding;
+import org.apache.causeway.applib.annotation.Collection;
+import org.apache.causeway.applib.annotation.DomainObject;
+import org.apache.causeway.applib.annotation.DomainObjectLayout;
+import org.apache.causeway.applib.annotation.Editing;
+import org.apache.causeway.applib.annotation.Nature;
+import org.apache.causeway.applib.annotation.Property;
+import org.apache.causeway.applib.annotation.SemanticsOf;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.val;
+
+@Entity
+@Table(
+        schema = "public",
+        name = "Department"
+)
[email protected]("university.dept.Department")
+@NoArgsConstructor
+@DomainObject(nature = Nature.ENTITY, bounding = Bounding.BOUNDED)
+@DomainObjectLayout(describedAs = "University department specializing in a 
field of study")
+public class Department implements Comparable<Department> {
+
+
+    public Department(String name, DeptHead deptHead) {
+        this.name = name;
+        this.deptHead = deptHead;
+    }
+
+    @Id
+    @GeneratedValue
+    private Long id;
+
+    @Getter @Setter
+    private String name;
+    @Action(semantics = SemanticsOf.IDEMPOTENT)
+    public class changeName {
+
+        public Department act(final String newName){
+            setName(newName);
+            return Department.this;
+        }
+
+        public String default0Act(){
+            return getName();
+        }
+
+        public String validate0Act(String name) {
+            if (name.contains("!")) {
+                return "Name cannot contain '!' character";
+            }
+            return null;
+        }
+    }
+
+
+
+    @Getter @Setter
+    @Property(editing = Editing.ENABLED) // yes, I know: this duplicates the 
functionality of changeDeptHead action
+    @OneToOne(optional = true)
+    @JoinColumn(name = "deptHead_id")
+    private DeptHead deptHead;
+
+    // overriding the default via @DomainObject to filter out the current dept 
head.
+    public List<DeptHead> autoCompleteDeptHead(String search) {
+        return deptHeadRepository.findByNameContaining(search)
+                .stream()
+                .filter(x -> x != getDeptHead())
+                .collect(Collectors.toList());
+    }
+
+
+    @Action(semantics = SemanticsOf.IDEMPOTENT)
+    @ActionLayout(associateWith = "deptHead")
+    public class changeDeptHead {
+        public Department act(DeptHead newDeptHead) {
+            setDeptHead(newDeptHead);
+            return Department.this;
+        }
+        public DeptHead default0Act() {
+            return getDeptHead();
+        }
+        public String validate0Act(DeptHead newDeptHead) {
+            if (newDeptHead == getDeptHead()) {
+                return "Same as current";
+            }
+            return null;
+        }
+    }
+
+
+    @OneToMany(mappedBy = "department")
+    private Set<StaffMember> staffMembers = new TreeSet<>();
+
+    // because the ordering seems not to be deterministic?
+    @Collection
+    public List<StaffMember> getStaffMembers() {
+        return staffMembers.stream().sorted().collect(Collectors.toList());
+    }
+
+    @Action(semantics = SemanticsOf.IDEMPOTENT)
+    @ActionLayout(associateWith = "staffMembers")
+    public class addStaffMember {
+
+        public Department act(StaffMember staffMember) {
+            val department = Department.this;
+
+            department.staffMembers.add(staffMember);
+            staffMember.setDepartment(department);
+            return department;
+        }
+    }
+
+    @Action(semantics = SemanticsOf.IDEMPOTENT)
+    @ActionLayout(associateWith = "staffMembers")
+    public class addStaffMembers {
+
+        public Department act(List<StaffMember> staffMembers) {
+            val department = Department.this;
+
+            staffMembers.forEach(sm -> sm.setDepartment(department));
+            department.staffMembers.addAll(staffMembers);
+
+            return department;
+        }
+        public List<StaffMember> choices0Act() {
+            val choices = new ArrayList<>(staffMemberRepository.findAll());
+            choices.removeAll(getStaffMembers());
+            return choices;
+        }
+
+        @Inject
+        StaffMemberRepository staffMemberRepository;
+    }
+
+
+    @Action(semantics = SemanticsOf.IDEMPOTENT)
+    @ActionLayout(associateWith = "staffMembers")
+    @RequiredArgsConstructor
+    public class removeStaffMember {
+
+        public Department act(StaffMember staffMember) {
+            val department = Department.this;
+
+            department.getStaffMembers().add(staffMember);
+            staffMember.setDepartment(department);
+            return department;
+        }
+        public List<StaffMember> choices0Act() {
+            val department = Department.this;
+            return department.getStaffMembers()
+                        .stream()
+                        .sorted(Comparator.comparing(StaffMember::getName))
+                        .collect(Collectors.toList());
+        }
+    }
+
+    @Override
+    public int compareTo(final Department o) {
+        return Comparator.comparing(Department::getName).compare(this, o);
+    }
+
+    @Inject @Transient private DeptHeadRepository deptHeadRepository;
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DepartmentRepository.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DepartmentRepository.java
new file mode 100644
index 0000000000..d2cae7b405
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DepartmentRepository.java
@@ -0,0 +1,60 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Repository;
+
+import org.apache.causeway.applib.services.repository.RepositoryService;
+
+@Repository
+public class DepartmentRepository {
+
+    @Inject private RepositoryService repositoryService;
+
+    public Department create(final String name, @Nullable final DeptHead 
deptHead) {
+        Department department = new Department(name, deptHead);
+        repositoryService.persistAndFlush(department);
+        return department;
+    }
+
+    public List<Department> findAll() {
+        return repositoryService.allInstances(Department.class).stream()
+                .sorted()
+                .collect(Collectors.toList());
+    }
+
+    public void removeAll(){
+        repositoryService.removeAll(Department.class);
+    }
+
+    public Department findByName(final String name){
+        return findAll().stream()
+                .filter(dept -> dept.getName().equals(name))
+                .sorted()
+                .findFirst()
+                .orElse(null);
+    }
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Departments.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Departments.java
new file mode 100644
index 0000000000..d1e05b87a1
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Departments.java
@@ -0,0 +1,63 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.springframework.lang.Nullable;
+
+import org.apache.causeway.applib.annotation.Action;
+import org.apache.causeway.applib.annotation.DomainService;
+import org.apache.causeway.applib.annotation.NatureOfService;
+import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.annotation.SemanticsOf;
+
+import lombok.RequiredArgsConstructor;
+
+@Named("university.dept.Departments")
+@DomainService(
+        nature=NatureOfService.BOTH)
[email protected](PriorityPrecedence.EARLY)
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
+public class Departments {
+
+    final DepartmentRepository departmentRepository;
+
+    @Action(semantics = SemanticsOf.NON_IDEMPOTENT)
+    public Department createDepartment(
+            final String name,
+            @Nullable final DeptHead deptHead
+    ){
+        return departmentRepository.create(name, deptHead);
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public List<Department> findAllDepartments(){
+        return departmentRepository.findAll();
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Department findDepartmentByName(final String name){
+        return departmentRepository.findByName(name);
+    }
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DeptHead.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DeptHead.java
new file mode 100644
index 0000000000..1c79cb31d0
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DeptHead.java
@@ -0,0 +1,137 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+
+import org.apache.causeway.applib.annotation.Action;
+import org.apache.causeway.applib.annotation.DomainObject;
+import org.apache.causeway.applib.annotation.DomainObjectLayout;
+import org.apache.causeway.applib.annotation.Nature;
+import org.apache.causeway.applib.annotation.Property;
+import org.apache.causeway.applib.annotation.SemanticsOf;
+
+import static org.apache.causeway.applib.annotation.Editing.ENABLED;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Entity
+@Table(
+        schema = "public",
+        name = "DeptHead"
+)
[email protected]("university.dept.DeptHead")
+@DomainObject(
+        nature = Nature.ENTITY,
+        autoCompleteRepository = DeptHeadRepository.class,
+        autoCompleteMethod = "findByNameContaining"
+)
+@DomainObjectLayout(describedAs = "Departmental head, responsible for 
curriculum, research, funding and staff")
+@NoArgsConstructor
+public class DeptHead extends Person implements Comparable<DeptHead>  {
+
+    public DeptHead(String name) {
+        this.name = name;
+    }
+
+    @Id
+    @GeneratedValue
+    private Long id;
+
+    @Getter @Setter
+    @Column(unique=true)
+    private String name;
+
+    @Getter @Setter
+    @Property(editing = ENABLED)
+    @OneToOne(optional = true)
+    @JoinColumn(name = "department_id")
+    private Department department;
+
+    public List<Department> choicesDepartment() {
+        return departmentRepository.findAll();
+    }
+
+
+
+    @Action(semantics = SemanticsOf.IDEMPOTENT)
+    public class changeName {
+
+        public DeptHead act(final String newName){
+            setName(newName);
+            return DeptHead.this;
+        }
+
+        public String default0Act(){
+            return getName();
+        }
+
+        public String validateAct(String name) {
+            if (name.contains("!")) {
+                return "Name cannot contain '!' character";
+            }
+            return null;
+        }
+    }
+
+
+
+    @Action(semantics = SemanticsOf.IDEMPOTENT)
+    public class changeDepartment {
+
+        public DeptHead act(final Department department){
+            setDepartment(department);
+            return DeptHead.this;
+        }
+
+        public List<Department> choices0Act(){
+            return departmentRepository.findAll().stream().
+                    filter(d -> d != getDepartment()).
+                    collect(Collectors.toList());
+        }
+
+        public String validateAct(final Department department){
+            if (getDepartment() == department) return "Already there";
+            return null;
+        }
+    }
+
+    @Override
+    public int compareTo(final DeptHead o) {
+        return Comparator.comparing(DeptHead::getName).compare(this, o);
+    }
+
+    @Inject @Transient DepartmentRepository departmentRepository;
+
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DeptHeadRepository.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DeptHeadRepository.java
new file mode 100644
index 0000000000..e9fa922342
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DeptHeadRepository.java
@@ -0,0 +1,71 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Repository;
+
+import org.apache.causeway.applib.services.repository.RepositoryService;
+
+@Repository
+public class DeptHeadRepository {
+
+    @Inject private RepositoryService repositoryService;
+
+    public DeptHead create(final String name, @Nullable final Department 
department) {
+        DeptHead deptHead = new DeptHead(name);
+        if (department != null) {
+            deptHead.setDepartment(department);
+            department.setDeptHead(deptHead);
+        }
+        repositoryService.persistAndFlush(deptHead);
+        return deptHead;
+    }
+
+    public List<DeptHead> findAll() {
+        return repositoryService.allInstances(DeptHead.class).stream()
+                .sorted()
+                .collect(Collectors.toList());
+    }
+
+    public void removeAll(){
+        repositoryService.removeAll(DeptHead.class);
+    }
+
+    public DeptHead findByName(final String name){
+        return findAll().stream()
+                .filter(deptHead -> deptHead.getName().equals(name))
+                .sorted()
+                .findFirst()
+                .orElse(null);
+    }
+
+    public List<DeptHead> findByNameContaining(final String name){
+        return findAll().stream()
+                .filter(deptHead -> deptHead.getName().contains(name))
+                .sorted()
+                .collect(Collectors.toList());
+    }
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DeptHeads.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DeptHeads.java
new file mode 100644
index 0000000000..0eea767761
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/DeptHeads.java
@@ -0,0 +1,53 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.causeway.applib.annotation.Action;
+import org.apache.causeway.applib.annotation.DomainService;
+import org.apache.causeway.applib.annotation.NatureOfService;
+import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.annotation.SemanticsOf;
+
+import lombok.RequiredArgsConstructor;
+
+@Named("university.dept.DeptHeads")
+@DomainService(nature=NatureOfService.BOTH)
[email protected](PriorityPrecedence.EARLY)
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
+public class DeptHeads {
+
+    final DeptHeadRepository deptHeadRepository;
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public List<DeptHead> findAllHeads(){
+        return deptHeadRepository.findAll();
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public DeptHead findHeadByName(final String name){
+        return deptHeadRepository.findByName(name);
+    }
+
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Grade.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Grade.java
new file mode 100644
index 0000000000..356ae5e605
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Grade.java
@@ -0,0 +1,30 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+public enum Grade  {
+
+    PROFESSOR,
+    READER,
+    SENIOR_LECTURER,
+    LECTURER,
+    RESEARCH_ASSISTANT,
+    ;
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/People.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/People.java
new file mode 100644
index 0000000000..ded65ad6f1
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/People.java
@@ -0,0 +1,36 @@
+package org.apache.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.causeway.applib.annotation.Action;
+import org.apache.causeway.applib.annotation.DomainService;
+import org.apache.causeway.applib.annotation.NatureOfService;
+import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.annotation.SemanticsOf;
+
+import lombok.RequiredArgsConstructor;
+
+@Named("university.dept.People")
+@DomainService(
+        nature= NatureOfService.BOTH)
[email protected](PriorityPrecedence.EARLY)
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
+public class People {
+
+    private final StaffMemberRepository staffMemberRepository;
+    private final DeptHeadRepository deptHeadRepository;
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Person findNamed(String name) {
+        return 
Optional.ofNullable((Person)staffMemberRepository.findByName(name))
+                .orElse(deptHeadRepository.findByName(name));
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public String nameOf(Person person) {
+        return person.getName();
+    }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Person.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Person.java
new file mode 100644
index 0000000000..9a0b96d847
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Person.java
@@ -0,0 +1,17 @@
+package org.apache.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+import javax.inject.Named;
+import javax.persistence.MappedSuperclass;
+
+import org.apache.causeway.applib.annotation.DomainObject;
+import org.apache.causeway.applib.annotation.Nature;
+import org.apache.causeway.applib.annotation.Property;
+
+@MappedSuperclass
+@Named("university.dept.Person")
+@DomainObject(nature = Nature.NOT_SPECIFIED)
+public abstract class Person {
+
+    @Property
+    public abstract String getName();
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Staff.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Staff.java
new file mode 100644
index 0000000000..14d8dea9c6
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/Staff.java
@@ -0,0 +1,61 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.causeway.applib.annotation.Action;
+import org.apache.causeway.applib.annotation.DomainService;
+import org.apache.causeway.applib.annotation.NatureOfService;
+import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.annotation.SemanticsOf;
+
+import lombok.RequiredArgsConstructor;
+
+@Named("university.dept.Staff")
+@DomainService(
+        nature=NatureOfService.BOTH)
[email protected](PriorityPrecedence.EARLY)
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
+public class Staff {
+
+    final StaffMemberRepository staffMemberRepository;
+
+    @Action(semantics = SemanticsOf.NON_IDEMPOTENT)
+    public StaffMember createStaffMember(
+            final String name,
+            final Department department
+    ){
+        return staffMemberRepository.create(name, department);
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public List<StaffMember> findAllStaffMembers(){
+        return staffMemberRepository.findAll();
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public StaffMember findStaffMemberByName(final String name){
+        return staffMemberRepository.findByName(name);
+    }
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/StaffMember.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/StaffMember.java
new file mode 100644
index 0000000000..be226367e2
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/StaffMember.java
@@ -0,0 +1,119 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+import java.util.Comparator;
+
+import javax.persistence.AttributeOverride;
+import javax.persistence.AttributeOverrides;
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.causeway.applib.annotation.DomainObject;
+import org.apache.causeway.applib.annotation.DomainObjectLayout;
+import org.apache.causeway.applib.annotation.Editing;
+import org.apache.causeway.applib.annotation.Nature;
+import org.apache.causeway.applib.annotation.Optionality;
+import org.apache.causeway.applib.annotation.Property;
+import org.apache.causeway.applib.annotation.PropertyLayout;
+import org.apache.causeway.applib.value.Blob;
+import org.apache.causeway.persistence.jpa.applib.types.BlobJpaEmbeddable;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Entity
+@Table(
+        schema = "public",
+        name = "StaffMember"
+)
[email protected]("university.dept.StaffMember")
+@DomainObject(nature = Nature.ENTITY, autoCompleteRepository = 
StaffMemberRepository.class, autoCompleteMethod = "findByNameMatching")
+@DomainObjectLayout(describedAs = "Staff member of a university department, 
responsible for delivering lectures, tutorials, exam invigilation and candidate 
interviews")
+@NoArgsConstructor
+public class StaffMember extends Person implements Comparable<StaffMember> {
+
+    public StaffMember(
+            final String name,
+            final Department department,
+            final Grade grade) {
+        this.name = name;
+        this.department = department;
+        this.grade = grade;
+    }
+
+    @Id
+    @GeneratedValue
+    private Long id;
+
+    @Getter @Setter
+    @Property(editing = Editing.ENABLED)
+    private String name;
+    public String validateName(String proposedName) {
+        if(proposedName.contains("!")) {
+            return "Name cannot contain '!' character";
+        }
+        return null;
+    }
+
+    @Getter @Setter
+    @Property
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "department_id")
+    private Department department;
+
+
+    @Getter @Setter
+    @Property(editing = Editing.ENABLED)
+    private Grade grade;
+
+
+    @AttributeOverrides({
+            @AttributeOverride(name="name",    
column=@Column(name="photo_name")),
+            
@AttributeOverride(name="mimeType",column=@Column(name="photo_mimeType")),
+            @AttributeOverride(name="bytes",   
column=@Column(name="photo_bytes"))
+    })
+    @Embedded
+    private BlobJpaEmbeddable photo;
+
+    @Property(optionality = Optionality.OPTIONAL)
+    @PropertyLayout(fieldSetId = "content", sequence = "1")
+    public Blob getPhoto() {
+        return BlobJpaEmbeddable.toBlob(photo);
+    }
+    public void setPhoto(final Blob photo) {
+        this.photo = BlobJpaEmbeddable.fromBlob(photo);
+    }
+
+
+
+
+
+    @Override
+    public int compareTo(final StaffMember o) {
+        return Comparator.comparing(StaffMember::getName).compare(this, o);
+    }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/StaffMemberRepository.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/StaffMemberRepository.java
new file mode 100644
index 0000000000..09b1687cd8
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/domain/dept/StaffMemberRepository.java
@@ -0,0 +1,72 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.domain.dept;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.springframework.stereotype.Repository;
+
+import org.apache.causeway.applib.services.repository.RepositoryService;
+import org.apache.causeway.applib.value.Blob;
+
+@Repository
+public class StaffMemberRepository {
+
+    @Inject private RepositoryService repositoryService;
+
+    public StaffMember create(final String name, final Department department) {
+        return create(name, department, null);
+    }
+    public StaffMember create(final String name, final Department department, 
final Blob photo) {
+        StaffMember staffMember = new StaffMember(name, department, 
Grade.LECTURER);
+        staffMember.setPhoto(photo);
+        department.new addStaffMember().act(staffMember);
+        repositoryService.persistAndFlush(staffMember);
+        return staffMember;
+    }
+
+    public List<StaffMember> findAll() {
+        return repositoryService.allInstances(StaffMember.class).stream()
+                .sorted()
+                .collect(Collectors.toList());
+    }
+
+    public void removeAll(){
+        repositoryService.removeAll(StaffMember.class);
+    }
+
+    public StaffMember findByName(final String name){
+        return findAll().stream()
+                .filter(dept -> dept.getName().equals(name))
+                .sorted()
+                .findFirst()
+                .orElse(null);
+    }
+
+    public List<StaffMember> findByNameMatching(final String name){
+        return findAll().stream()
+                .filter(dept -> dept.getName().contains(name))
+                .sorted()
+                .collect(Collectors.toList());
+    }
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/Abstract_IntegTest.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/Abstract_IntegTest.java
new file mode 100644
index 0000000000..2d80b3dc94
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/Abstract_IntegTest.java
@@ -0,0 +1,61 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.e2e;
+
+import javax.inject.Inject;
+
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+import org.apache.causeway.applib.services.bookmark.BookmarkService;
+import org.apache.causeway.core.config.CausewayConfiguration;
+import 
org.apache.causeway.persistence.jpa.eclipselink.CausewayModulePersistenceJpaEclipselink;
+import org.apache.causeway.viewer.graphql.viewer.test2.domain.UniversityModule;
+import 
org.apache.causeway.viewer.graphql.viewer.test2.domain.dept.DepartmentRepository;
+import 
org.apache.causeway.viewer.graphql.viewer.test2.domain.dept.DeptHeadRepository;
+import 
org.apache.causeway.viewer.graphql.viewer.test2.domain.dept.StaffMemberRepository;
+import 
org.apache.causeway.viewer.graphql.viewer.testsupport.CausewayViewerGraphqlIntegTestAbstract;
+
+
+@Import({
+        UniversityModule.class,
+        CausewayModulePersistenceJpaEclipselink.class,
+})
+public abstract class Abstract_IntegTest extends 
CausewayViewerGraphqlIntegTestAbstract {
+
+    protected Abstract_IntegTest() {
+        super(Abstract_IntegTest.class);
+    }
+
+    @DynamicPropertySource
+    static void apiVariant(final DynamicPropertyRegistry registry) {
+        registry.add("causeway.viewer.graphql.api-scope", 
CausewayConfiguration.Viewer.Graphql.ApiScope.VIEW_MODELS::name);
+        registry.add("causeway.viewer.graphql.api-variant", 
CausewayConfiguration.Viewer.Graphql.ApiVariant.QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT::name);
+        
registry.add("causeway.viewer.graphql.schema.rich.enable-scenario-testing", () 
-> Boolean.TRUE);
+        registry.add("causeway.viewer.graphql.resources.response-type", 
CausewayConfiguration.Viewer.Graphql.ResponseType.ATTACHMENT::name);
+    }
+
+    @Inject protected DepartmentRepository departmentRepository;
+    @Inject protected DeptHeadRepository deptHeadRepository;
+    @Inject protected StaffMemberRepository staffMemberRepository;
+    @Inject protected BookmarkService bookmarkService;
+
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_decimals._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_decimals._.gql
new file mode 100644
index 0000000000..d85e0e7e3e
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_decimals._.gql
@@ -0,0 +1,24 @@
+{
+  rich {
+    university_calc_Calculator {
+      addBigDecimals {
+        invoke(x: "1.1", y: "2.2") {
+          args {
+            x
+            y
+          }
+          results
+        }
+      }
+      addBigDecimals {
+        invoke(x: "1.1", y: "2.2") {
+          args {
+            x
+            y
+          }
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_decimals.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_decimals.approved.json
new file mode 100644
index 0000000000..4741f93156
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_decimals.approved.json
@@ -0,0 +1,17 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "addBigDecimals" : {
+          "invoke" : {
+            "args" : {
+              "x" : "1.1",
+              "y" : "2.2"
+            },
+            "results" : "3.3"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_integers._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_integers._.gql
new file mode 100644
index 0000000000..cd3d224da6
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_integers._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      addBigIntegers {
+        invoke(x: "1", y: "2") {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_integers.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_integers.approved.json
new file mode 100644
index 0000000000..315e98b2f9
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_big_integers.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "addBigIntegers" : {
+          "invoke" : {
+            "results" : "3"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_double_wrappers._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_double_wrappers._.gql
new file mode 100644
index 0000000000..73536f7453
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_double_wrappers._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      addDoubleWrappers {
+        invoke(x: 1.1, y: 2.2) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_double_wrappers.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_double_wrappers.approved.json
new file mode 100644
index 0000000000..4c39ad89af
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_double_wrappers.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "addDoubleWrappers" : {
+          "invoke" : {
+            "results" : 3.3000000000000003
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_doubles._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_doubles._.gql
new file mode 100644
index 0000000000..05a63e0f72
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_doubles._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      addDoubles {
+        invoke(x: 1.1, y: 2.2) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_doubles.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_doubles.approved.json
new file mode 100644
index 0000000000..55aa93ad16
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_doubles.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "addDoubles" : {
+          "invoke" : {
+            "results" : 3.3000000000000003
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_float_wrappers._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_float_wrappers._.gql
new file mode 100644
index 0000000000..5d283c9f22
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_float_wrappers._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      addFloatWrappers {
+        invoke(x: 1.1, y: 2.2) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_float_wrappers.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_float_wrappers.approved.json
new file mode 100644
index 0000000000..e4deb9f92d
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_float_wrappers.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "addFloatWrappers" : {
+          "invoke" : {
+            "results" : 3.3000002
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_floats._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_floats._.gql
new file mode 100644
index 0000000000..c74ea6f73b
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_floats._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      addFloats {
+        invoke(x: 1.1, y: 2.2) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_floats.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_floats.approved.json
new file mode 100644
index 0000000000..4debea3886
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_floats.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "addFloats" : {
+          "invoke" : {
+            "results" : 3.3000002
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integer_wrappers._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integer_wrappers._.gql
new file mode 100644
index 0000000000..23df33558a
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integer_wrappers._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      addIntegerWrappers {
+        invoke(x: 1, y: 2) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integer_wrappers.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integer_wrappers.approved.json
new file mode 100644
index 0000000000..3ef588b36c
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integer_wrappers.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "addIntegerWrappers" : {
+          "invoke" : {
+            "results" : 3
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integers._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integers._.gql
new file mode 100644
index 0000000000..f2fc6bc58f
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integers._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      addIntegers {
+        invoke(x: 1, y: 2) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integers.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integers.approved.json
new file mode 100644
index 0000000000..c1d6527036
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.add_integers.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "addIntegers" : {
+          "invoke" : {
+            "results" : 3
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_1._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_1._.gql
new file mode 100644
index 0000000000..972bcb320f
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_1._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      and {
+        invoke(x: true, y: true) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_1.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_1.approved.json
new file mode 100644
index 0000000000..97194ca052
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_1.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "and" : {
+          "invoke" : {
+            "results" : true
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_2._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_2._.gql
new file mode 100644
index 0000000000..5ec20044c5
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_2._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      and {
+        invoke(x: true, y: false) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_2.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_2.approved.json
new file mode 100644
index 0000000000..559f39b3fb
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_and_2.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "and" : {
+          "invoke" : {
+            "results" : false
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_not._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_not._.gql
new file mode 100644
index 0000000000..70987f2d6b
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_not._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      not {
+        invoke(x: true) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_not.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_not.approved.json
new file mode 100644
index 0000000000..cd5d8e76f1
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_not.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "not" : {
+          "invoke" : {
+            "results" : false
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_1._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_1._.gql
new file mode 100644
index 0000000000..751d934a89
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_1._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      or {
+        invoke(x: true, y: true) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_1.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_1.approved.json
new file mode 100644
index 0000000000..b005b549cd
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_1.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "or" : {
+          "invoke" : {
+            "results" : true
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_2._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_2._.gql
new file mode 100644
index 0000000000..e78bb39775
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_2._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      or {
+        invoke(x: true, y: false) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_2.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_2.approved.json
new file mode 100644
index 0000000000..b005b549cd
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.boolean_or_2.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "or" : {
+          "invoke" : {
+            "results" : true
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.concat._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.concat._.gql
new file mode 100644
index 0000000000..32bd0f108e
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.concat._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      concat {
+        invoke(prefix: "Fizz", suffix: "Buzz") {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.concat.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.concat.approved.json
new file mode 100644
index 0000000000..2ed5392de6
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.concat.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "concat" : {
+          "invoke" : {
+            "results" : "FizzBuzz"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_days._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_days._.gql
new file mode 100644
index 0000000000..fea5812c49
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_days._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      jdk8LocalPlusDays {
+        invoke(date: "2024-01-26", numDays: 2) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_days.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_days.approved.json
new file mode 100644
index 0000000000..e9ec810876
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_days.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "jdk8LocalPlusDays" : {
+          "invoke" : {
+            "results" : "2024-01-28"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_hours_and_minutes._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_hours_and_minutes._.gql
new file mode 100644
index 0000000000..e00ee784c3
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_hours_and_minutes._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      jdk8LocalPlusHoursAndMinutes {
+        invoke(time: "13:35:05", numHours: 2, numMinutes: 20) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_hours_and_minutes.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_hours_and_minutes.approved.json
new file mode 100644
index 0000000000..2a9317e49f
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_local_plus_hours_and_minutes.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "jdk8LocalPlusHoursAndMinutes" : {
+          "invoke" : {
+            "results" : "15:55:05"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_days_and_hours_and_minutes._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_days_and_hours_and_minutes._.gql
new file mode 100644
index 0000000000..87c33879b7
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_days_and_hours_and_minutes._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      jdk8OffsetPlusDaysAndHoursAndMinutes {
+        invoke(dateTime: "2024-08-12T04:05:20-01:00", numDays: 3, numHours: 2, 
numMinutes: 15) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_days_and_hours_and_minutes.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_days_and_hours_and_minutes.approved.json
new file mode 100644
index 0000000000..83d75bae6e
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_days_and_hours_and_minutes.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "jdk8OffsetPlusDaysAndHoursAndMinutes" : {
+          "invoke" : {
+            "results" : "2024-08-15T06:20:20.000-01:00"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_hours_and_minutes._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_hours_and_minutes._.gql
new file mode 100644
index 0000000000..aa88011f7d
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_hours_and_minutes._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      jdk8OffsetPlusHoursAndMinutes {
+        invoke(time: "04:05:20-01:00", numHours: 2, numMinutes: 15) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_hours_and_minutes.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_hours_and_minutes.approved.json
new file mode 100644
index 0000000000..f4b24d3e77
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_offset_plus_hours_and_minutes.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "jdk8OffsetPlusHoursAndMinutes" : {
+          "invoke" : {
+            "results" : "06:20:20-01:00"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_zoned_plus_days_and_hours_and_minutes._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_zoned_plus_days_and_hours_and_minutes._.gql
new file mode 100644
index 0000000000..2875862b5a
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_zoned_plus_days_and_hours_and_minutes._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      jdk8ZonedPlusDaysAndHoursAndMinutes {
+        invoke(dateTime: "2022-04-15T08:20:10+02:00", numDays: 3, numHours: 2, 
numMinutes: 15) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_zoned_plus_days_and_hours_and_minutes.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_zoned_plus_days_and_hours_and_minutes.approved.json
new file mode 100644
index 0000000000..a02148b7f9
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.jdk8_zoned_plus_days_and_hours_and_minutes.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "jdk8ZonedPlusDaysAndHoursAndMinutes" : {
+          "invoke" : {
+            "results" : "2022-04-18T10:35:10+02:00"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_days._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_days._.gql
new file mode 100644
index 0000000000..c2cae30c6f
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_days._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      jodaLocalPlusDays {
+        invoke(date: "2024-01-26", numDays: 2) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_days.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_days.approved.json
new file mode 100644
index 0000000000..2ae1808714
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_days.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "jodaLocalPlusDays" : {
+          "invoke" : {
+            "results" : "2024-01-28"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_hours_and_minutes._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_hours_and_minutes._.gql
new file mode 100644
index 0000000000..87d3ae34cc
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_hours_and_minutes._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      jodaLocalPlusHoursAndMinutes {
+        invoke(time: "13:35:05", numHours: 2, numMinutes: 20) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_hours_and_minutes.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_hours_and_minutes.approved.json
new file mode 100644
index 0000000000..f0d6f31b8c
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_local_plus_hours_and_minutes.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "jodaLocalPlusHoursAndMinutes" : {
+          "invoke" : {
+            "results" : "15:55:05.000"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_plus_days._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_plus_days._.gql
new file mode 100644
index 0000000000..c2cae30c6f
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_plus_days._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      jodaLocalPlusDays {
+        invoke(date: "2024-01-26", numDays: 2) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_plus_days.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_plus_days.approved.json
new file mode 100644
index 0000000000..2ae1808714
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.joda_plus_days.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "jodaLocalPlusDays" : {
+          "invoke" : {
+            "results" : "2024-01-28"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.next_month._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.next_month._.gql
new file mode 100644
index 0000000000..4d5b085bd2
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.next_month._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      nextMonth {
+        invoke(month: JUNE) {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.next_month.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.next_month.approved.json
new file mode 100644
index 0000000000..c921223fab
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.next_month.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "nextMonth" : {
+          "invoke" : {
+            "results" : "JULY"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.scenario_concat._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.scenario_concat._.gql
new file mode 100644
index 0000000000..e87c92d590
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.scenario_concat._.gql
@@ -0,0 +1,20 @@
+{
+  rich {
+    Scenario(name: "Fizz and buzz are concatenated together"){
+      Name
+      When {
+        university_calc_Calculator {
+          concat {
+            invoke(prefix: "Fizz", suffix: "Buzz") {
+              args {
+                prefix
+                suffix
+              }
+              results
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.scenario_concat.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.scenario_concat.approved.json
new file mode 100644
index 0000000000..f881e49802
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.scenario_concat.approved.json
@@ -0,0 +1,22 @@
+{
+  "data" : {
+    "rich" : {
+      "Scenario" : {
+        "Name" : "Fizz and buzz are concatenated together",
+        "When" : {
+          "university_calc_Calculator" : {
+            "concat" : {
+              "invoke" : {
+                "args" : {
+                  "prefix" : "Fizz",
+                  "suffix" : "Buzz"
+                },
+                "results" : "FizzBuzz"
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_locale._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_locale._.gql
new file mode 100644
index 0000000000..60f83ecb00
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_locale._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      someLocale {
+        invoke {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_locale.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_locale.approved.json
new file mode 100644
index 0000000000..7ff2e97cc7
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_locale.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "someLocale" : {
+          "invoke" : {
+            "results" : "en_GB"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_url._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_url._.gql
new file mode 100644
index 0000000000..e25df11d43
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_url._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      someUrl {
+        invoke {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_url.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_url.approved.json
new file mode 100644
index 0000000000..894d36e3d2
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_url.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "someUrl" : {
+          "invoke" : {
+            "results" : "https://causeway.apache.org";
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_uuid._.gql
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_uuid._.gql
new file mode 100644
index 0000000000..26a82eb19a
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_uuid._.gql
@@ -0,0 +1,11 @@
+{
+  rich {
+    university_calc_Calculator {
+      someUuid {
+        invoke {
+          results
+        }
+      }
+    }
+  }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_uuid.approved.json
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_uuid.approved.json
new file mode 100644
index 0000000000..272ac56ae5
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.each.some_uuid.approved.json
@@ -0,0 +1,13 @@
+{
+  "data" : {
+    "rich" : {
+      "university_calc_Calculator" : {
+        "someUuid" : {
+          "invoke" : {
+            "results" : "91be0d2d-1752-4962-ad2c-89a7ef73a656"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.java
new file mode 100644
index 0000000000..cf30d1f5f1
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/e2e/calc/Calculator_IntegTest.java
@@ -0,0 +1,41 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.e2e.calc;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.apache.causeway.viewer.graphql.viewer.test2.e2e.Abstract_IntegTest;
+
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.TestFactory;
+
+
+//NOT USING @Transactional since we are running server within same transaction 
otherwise
+@Order(30)
+public class Calculator_IntegTest extends Abstract_IntegTest {
+
+    @Override
+    @TestFactory
+    public Iterable<DynamicTest> each() throws IOException, URISyntaxException 
{
+        return super.each();
+    }
+
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/schema/PrintSchemaIntegTest.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/schema/PrintSchemaIntegTest.java
new file mode 100644
index 0000000000..f58d891fc2
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/schema/PrintSchemaIntegTest.java
@@ -0,0 +1,66 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.schema;
+
+import graphql.schema.idl.SchemaPrinter;
+
+import lombok.val;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import 
org.apache.causeway.persistence.jpa.eclipselink.CausewayModulePersistenceJpaEclipselink;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.context.annotation.Import;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+import org.apache.causeway.core.config.CausewayConfiguration;
+import org.apache.causeway.viewer.graphql.viewer.test2.domain.UniversityModule;
+import 
org.apache.causeway.viewer.graphql.viewer.testsupport.schema.PrintSchemaIntegTestAbstract;
+
+import static 
org.apache.causeway.commons.internal.assertions._Assert.assertNotNull;
+
+@Import({
+        UniversityModule.class,
+        CausewayModulePersistenceJpaEclipselink.class,
+})
+@EnableJpaRepositories
+public class PrintSchemaIntegTest extends PrintSchemaIntegTestAbstract {
+
+    @DynamicPropertySource
+    static void apiVariant(final DynamicPropertyRegistry registry) {
+        registry.add("causeway.viewer.graphql.api-scope", 
CausewayConfiguration.Viewer.Graphql.ApiScope.VIEW_MODELS::name);
+        registry.add("causeway.viewer.graphql.api-variant", 
CausewayConfiguration.Viewer.Graphql.ApiVariant.QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT::name);
+        
registry.add("causeway.viewer.graphql.schema.rich.enable-scenario-testing", () 
-> Boolean.TRUE);
+        registry.add("causeway.viewer.graphql.resources.response-type", 
CausewayConfiguration.Viewer.Graphql.ResponseType.ATTACHMENT::name);
+    }
+
+    @Disabled
+    @Override
+    protected void schema() throws Exception {
+        super.schema();
+    }
+}
diff --git 
a/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/schema/VerifySchemaIntegTest.java
 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/schema/VerifySchemaIntegTest.java
new file mode 100644
index 0000000000..4f5e619e1b
--- /dev/null
+++ 
b/viewers/graphql/test2/src/test/java/org/apache/causeway/viewer/graphql/viewer/test2/schema/VerifySchemaIntegTest.java
@@ -0,0 +1,76 @@
+/*
+ *  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.causeway.viewer.graphql.viewer.test2.schema;
+
+import graphql.schema.idl.SchemaPrinter;
+
+import lombok.val;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.apache.causeway.viewer.graphql.viewer.test2.e2e.Abstract_IntegTest;
+
+import org.approvaltests.Approvals;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.context.annotation.Import;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+import org.apache.causeway.core.config.CausewayConfiguration;
+import 
org.apache.causeway.persistence.jpa.eclipselink.CausewayModulePersistenceJpaEclipselink;
+import org.apache.causeway.viewer.graphql.viewer.test2.domain.UniversityModule;
+import 
org.apache.causeway.viewer.graphql.viewer.testsupport.schema.PrintSchemaIntegTestAbstract;
+
+import static 
org.apache.causeway.commons.internal.assertions._Assert.assertNotNull;
+
+@Import({
+        UniversityModule.class,
+        CausewayModulePersistenceJpaEclipselink.class,
+})
+@EnableJpaRepositories
+public class VerifySchemaIntegTest extends Abstract_IntegTest {
+
+    @DynamicPropertySource
+    static void apiVariant(final DynamicPropertyRegistry registry) {
+        registry.add("causeway.viewer.graphql.api-scope", 
CausewayConfiguration.Viewer.Graphql.ApiScope.VIEW_MODELS::name);
+        registry.add("causeway.viewer.graphql.api-variant", 
CausewayConfiguration.Viewer.Graphql.ApiVariant.QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT::name);
+        
registry.add("causeway.viewer.graphql.schema.rich.enable-scenario-testing", () 
-> Boolean.TRUE);
+        registry.add("causeway.viewer.graphql.resources.response-type", 
CausewayConfiguration.Viewer.Graphql.ResponseType.ATTACHMENT::name);
+    }
+
+    @Disabled
+    @Test
+    protected void schema() throws Exception {
+
+        val graphQL = graphQlSourceForCauseway.graphQl();
+        val graphQLSchema = graphQL.getGraphQLSchema();
+
+        val printer = new SchemaPrinter();
+        val submit = printer.print(graphQLSchema);
+
+        Approvals.verify(submit);
+    }
+
+}
diff --git 
a/viewers/graphql/test2/src/test/resources/application-test.properties 
b/viewers/graphql/test2/src/test/resources/application-test.properties
new file mode 100644
index 0000000000..a86c70af91
--- /dev/null
+++ b/viewers/graphql/test2/src/test/resources/application-test.properties
@@ -0,0 +1,30 @@
+#logging.level.org.springframework=DEBUG
+#logging.level.graphql=DEBUG
+
+
+
+
+# One of logging libraries (slf4j, jul, common, sysout)
+decorator.datasource.datasource-proxy.logging=slf4j
+
+decorator.datasource.datasource-proxy.query.enable-logging=true
+decorator.datasource.datasource-proxy.query.log-level=debug
+# Logger name to log all queries, default depends on chosen logging, e.g. 
net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener
+decorator.datasource.datasource-proxy.query.logger-name=
+
+decorator.datasource.datasource-proxy.slow-query.enable-logging=true
+decorator.datasource.datasource-proxy.slow-query.log-level=warn
+decorator.datasource.datasource-proxy.slow-query.logger-name=
+# Number of seconds to consider query as slow and log it
+decorator.datasource.datasource-proxy.slow-query.threshold=300
+
+decorator.datasource.datasource-proxy.multiline=true
+
+# Formats the SQL for better readability. Uses Hibernate's formatter if 
present on the class path. If you opted in for a different JPA provider you 
need to add https://github.com/vertical-blank/sql-formatter as a runtime 
dependency to your app  to enable this.
+# Mutually exclusive with json-format=true
+decorator.datasource.datasource-proxy.format-sql=false
+decorator.datasource.datasource-proxy.json-format=false
+
+# Enable Query Metrics
+decorator.datasource.datasource-proxy.count-query=false
+
diff --git a/viewers/graphql/test2/src/test/resources/junit-platform.properties 
b/viewers/graphql/test2/src/test/resources/junit-platform.properties
new file mode 100644
index 0000000000..f61913bd47
--- /dev/null
+++ b/viewers/graphql/test2/src/test/resources/junit-platform.properties
@@ -0,0 +1,2 @@
+# ClassOrderer$OrderAnnotation sorts classes based on their @Order annotation
+junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$OrderAnnotation
\ No newline at end of file

Reply via email to