You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by bd...@apache.org on 2023/12/15 21:53:48 UTC

(directory-scimple) branch schema-gen-tests created (now e12f8979)

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

bdemers pushed a change to branch schema-gen-tests
in repository https://gitbox.apache.org/repos/asf/directory-scimple.git


      at e12f8979 Add test for Schema generation from Annotations

This branch includes the following new commits:

     new e12f8979 Add test for Schema generation from Annotations

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(directory-scimple) 01/01: Add test for Schema generation from Annotations

Posted by bd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

bdemers pushed a commit to branch schema-gen-tests
in repository https://gitbox.apache.org/repos/asf/directory-scimple.git

commit e12f8979f7d83ccf85fdd80f1f9f4b42b80cd8c2
Author: Brian Demers <bd...@apache.org>
AuthorDate: Thu Dec 14 23:18:17 2023 -0500

    Add test for Schema generation from Annotations
    
    While working on the PatchGenerator I noticed a bug in the type detection.
    This change adds tests and simplifies related logic
---
 .../apache/directory/scim/spec/schema/Schemas.java | 127 +++---
 .../scim/spec/AllSchemaTypesExtension.java         | 153 +++++++
 .../directory/scim/spec/schema/SchemasTest.java    | 441 +++++++++++++++++++++
 .../apache/directory/scim/test/stub/Subobject.java |  83 ++--
 4 files changed, 676 insertions(+), 128 deletions(-)

diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/Schemas.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/Schemas.java
index c2db4bb1..2a9cf7c1 100644
--- a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/Schemas.java
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/Schemas.java
@@ -33,29 +33,33 @@ import org.apache.directory.scim.spec.resources.ScimResource;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.ParameterizedType;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.*;
 
 @Slf4j
 public final class Schemas {
-
-  private static final String STRING_TYPE_IDENTIFIER = "class java.lang.String";
-  private static final String STRING_TYPE = "java.lang.String"; // TODO this is ugly
-  private static final String CHARACTER_ARRAY_TYPE_IDENTIFIER = "class [C";
-  private static final String BIG_C_CHARACTER_ARRAY_TYPE_IDENTIFIER = "class [Ljava.lang.Character;";
-  private static final String INT_TYPE_IDENTIFIER = "int";
-  private static final String INTEGER_TYPE_IDENTIFIER = "class java.lang.Integer";
-  private static final String FLOAT_TYPE_IDENTIFIER = "float";
-  private static final String BIG_F_FLOAT_TYPE_IDENTIFIER = "class java.lang.Float";
-  private static final String DOUBLE_TYPE_IDENTIFIER = "double";
-  private static final String BIG_D_DOUBLE_TYPE_IDENTIFIER = "class java.lang.Double";
-  private static final String BOOLEAN_TYPE_IDENTIFIER = "boolean";
-  private static final String BIG_B_BOOLEAN_TYPE_IDENTIFIER = "class java.lang.Boolean";
-  private static final String LOCAL_TIME_TYPE_IDENTIFER = "class java.time.LocalTime";
-  private static final String LOCAL_DATE_TYPE_IDENTIFER = "class java.time.LocalDate";
-  private static final String LOCAL_DATE_TIME_TYPE_IDENTIFIER = "class java.time.LocalDateTime";
-  private static final String DATE_TYPE_IDENTIFIER = "class java.util.Date";
-  private static final String BYTE_ARRAY_TYPE_IDENTIFIER = "class [B";
-  private static final String RESOURCE_REFERENCE_TYPE_IDENTIFIER = "class org.apache.directory.scim.spec.schema.ResourceReference$ReferenceType";
+  private final static Map<Class<?>, Schema.Attribute.Type> CLASS_TO_TYPE = new HashMap<>() {{
+    put(String.class, Schema.Attribute.Type.STRING);
+    put(Character.class, Schema.Attribute.Type.STRING);
+    put(Integer.class, Schema.Attribute.Type.INTEGER);
+    put(int.class, Schema.Attribute.Type.INTEGER);
+    put(Double.class, Schema.Attribute.Type.DECIMAL);
+    put(double.class, Schema.Attribute.Type.DECIMAL);
+    put(Float.class, Schema.Attribute.Type.DECIMAL);
+    put(float.class, Schema.Attribute.Type.DECIMAL);
+    put(Boolean.class, Schema.Attribute.Type.BOOLEAN);
+    put(boolean.class, Schema.Attribute.Type.BOOLEAN);
+    put(LocalTime.class, Schema.Attribute.Type.DATE_TIME);
+    put(LocalDate.class, Schema.Attribute.Type.DATE_TIME);
+    put(LocalDateTime.class, Schema.Attribute.Type.DATE_TIME);
+    put(Date.class, Schema.Attribute.Type.DATE_TIME);
+    put(Instant.class, Schema.Attribute.Type.DATE_TIME);
+    put(byte[].class, Schema.Attribute.Type.BINARY);
+    put(ResourceReference.ReferenceType.class, Schema.Attribute.Type.REFERENCE);
+  }};
 
   private Schemas() {}
 
@@ -120,7 +124,7 @@ public final class Schemas {
 
       log.debug("++++++++++++++++++++ Processing field " + f.getName());
       if (sa == null) {
-        log.debug("Attribute " + f.getName() + " did not have a ScimAttribute annotation");
+        log.debug("Attribute {} did not have a ScimAttribute annotation", f.getName());
         continue;
       }
 
@@ -146,7 +150,7 @@ public final class Schemas {
 
       List<String> canonicalTypes = null;
       Field [] enumFields = sa.canonicalValueEnum().getFields();
-      log.debug("Gathered fields of off the enum, there are " + enumFields.length + " " + sa.canonicalValueEnum().getName());
+      log.debug("Gathered fields of off the enum, there are {} {}", enumFields.length, sa.canonicalValueEnum().getName());
 
       if (enumFields.length != 0) {
 
@@ -174,79 +178,39 @@ public final class Schemas {
       if (canonicalTypes.isEmpty() || (canonicalTypes.size() == 1 && canonicalTypes.get(0).isEmpty())) {
         attribute.setCanonicalValues(null);
       } else {
-        attribute.setCanonicalValues(new HashSet<String>(canonicalTypes));
+        attribute.setCanonicalValues(new HashSet<>(canonicalTypes));
       }
 
       attribute.setCaseExact(sa.caseExact());
       attribute.setDescription(sa.description());
 
-      String typeName = null;
+      Class<?> typeClass;
       if (Collection.class.isAssignableFrom(f.getType())) {
-        log.debug("We have a collection");
+        log.debug("Attribute: '{}' is a collection", attributeName);
         ParameterizedType stringListType = (ParameterizedType) f.getGenericType();
-        Class<?> attributeContainedClass = (Class<?>) stringListType.getActualTypeArguments()[0];
-        typeName = attributeContainedClass.getTypeName();
+        typeClass = (Class<?>) stringListType.getActualTypeArguments()[0];
         attribute.setMultiValued(true);
       } else if (f.getType().isArray()) {
-        log.debug("We have an array");
-        Class<?> componentType = f.getType().getComponentType();
-        typeName = componentType.getTypeName();
-        attribute.setMultiValued(true);
+        log.debug("Attribute: '{}' is an array", attributeName);
+        typeClass = f.getType().getComponentType();
+
+        // special case for byte[]
+        if (typeClass == byte.class) {
+          typeClass = byte[].class;
+        } else {
+          attribute.setMultiValued(true);
+        }
       } else {
-        typeName = f.getType().toString();
+        typeClass = f.getType();
         attribute.setMultiValued(false);
       }
 
-      // attribute.setType(sa.type());
-      boolean attributeIsAString = false;
-      log.debug("Attempting to set the attribute type, raw value = " + typeName);
-      switch (typeName) {
-        case STRING_TYPE_IDENTIFIER:
-        case STRING_TYPE:
-        case CHARACTER_ARRAY_TYPE_IDENTIFIER:
-        case BIG_C_CHARACTER_ARRAY_TYPE_IDENTIFIER:
-          log.debug("Setting type to String");
-          attribute.setType(Schema.Attribute.Type.STRING);
-          attributeIsAString = true;
-          break;
-        case INT_TYPE_IDENTIFIER:
-        case INTEGER_TYPE_IDENTIFIER:
-          log.debug("Setting type to integer");
-          attribute.setType(Schema.Attribute.Type.INTEGER);
-          break;
-        case FLOAT_TYPE_IDENTIFIER:
-        case BIG_F_FLOAT_TYPE_IDENTIFIER:
-        case DOUBLE_TYPE_IDENTIFIER:
-        case BIG_D_DOUBLE_TYPE_IDENTIFIER:
-          log.debug("Setting type to decimal");
-          attribute.setType(Schema.Attribute.Type.DECIMAL);
-          break;
-        case BOOLEAN_TYPE_IDENTIFIER:
-        case BIG_B_BOOLEAN_TYPE_IDENTIFIER:
-          log.debug("Setting type to boolean");
-          attribute.setType(Schema.Attribute.Type.BOOLEAN);
-          break;
-        case BYTE_ARRAY_TYPE_IDENTIFIER:
-          log.debug("Setting type to binary");
-          attribute.setType(Schema.Attribute.Type.BINARY);
-          break;
-        case DATE_TYPE_IDENTIFIER:
-        case LOCAL_DATE_TIME_TYPE_IDENTIFIER:
-        case LOCAL_TIME_TYPE_IDENTIFER:
-        case LOCAL_DATE_TYPE_IDENTIFER:
-          log.debug("Setting type to date time");
-          attribute.setType(Schema.Attribute.Type.DATE_TIME);
-          break;
-        case RESOURCE_REFERENCE_TYPE_IDENTIFIER:
-          log.debug("Setting type to reference");
-          attribute.setType(Schema.Attribute.Type.REFERENCE);
-          break;
-        default:
-          log.debug("Setting type to complex");
-          attribute.setType(Schema.Attribute.Type.COMPLEX);
-      }
+      log.debug("Attempting to set the attribute type, raw value = {}", typeClass);
+      Schema.Attribute.Type type = CLASS_TO_TYPE.getOrDefault(typeClass, Schema.Attribute.Type.COMPLEX);
+      attribute.setType(type);
+
       if (f.getAnnotation(ScimResourceIdReference.class) != null) {
-        if (attributeIsAString) {
+        if (type == Schema.Attribute.Type.STRING) {
           attribute.setScimResourceIdReference(true);
         } else {
           log.warn("Field annotated with @ScimResourceIdReference must be a string: {}", f);
@@ -267,7 +231,6 @@ public final class Schemas {
       attribute.setReturned(sa.returned());
       attribute.setUniqueness(sa.uniqueness());
 
-      //if (sa.type().equals(Type.COMPLEX))
       ScimType st = f.getType().getAnnotation(ScimType.class);
 
       if (attribute.getType() == Schema.Attribute.Type.COMPLEX || st != null) {
@@ -291,7 +254,7 @@ public final class Schemas {
     }
 
     attributeList.sort(Comparator.comparing(o -> o.name));
-    log.debug("Returning " + attributeList.size() + " attributes");
+    log.debug("Returning {} attributes", attributeList.size());
     return attributeList;
   }
 
diff --git a/scim-spec/scim-spec-schema/src/test/java/org/apache/directory/scim/spec/AllSchemaTypesExtension.java b/scim-spec/scim-spec-schema/src/test/java/org/apache/directory/scim/spec/AllSchemaTypesExtension.java
new file mode 100644
index 00000000..2beab5b3
--- /dev/null
+++ b/scim-spec/scim-spec-schema/src/test/java/org/apache/directory/scim/spec/AllSchemaTypesExtension.java
@@ -0,0 +1,153 @@
+/*
+* 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.directory.scim.spec;
+
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
+import jakarta.xml.bind.annotation.XmlElement;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import lombok.Data;
+import org.apache.directory.scim.spec.annotation.ScimAttribute;
+import org.apache.directory.scim.spec.annotation.ScimExtensionType;
+import org.apache.directory.scim.spec.resources.ScimExtension;
+import org.apache.directory.scim.spec.schema.ResourceReference;
+import org.apache.directory.scim.spec.schema.Schema;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * An extension that can be used to validate schema generation for each type of attribute.
+ */
+@XmlRootElement( name = "AllSchemaTypesExtension", namespace = "https://directory.apache.org/scimple/test/extensions" )
+@XmlAccessorType(XmlAccessType.NONE)
+@Data
+@ScimExtensionType(id = AllSchemaTypesExtension.SCHEMA_URN, description="All Schema Attribute Types", name="AllSchemaTypesExtension", required=true)
+public class AllSchemaTypesExtension implements ScimExtension {
+  
+  public static final String  SCHEMA_URN = "urn:mem:params:scim:schemas:extension:AllSchemaTypesExtension";
+
+  @ScimAttribute(description = "One String")
+  @XmlElement
+  private String string1;
+
+  @ScimAttribute(caseExact = true)
+  @XmlElement
+  private String string2;
+
+  @ScimAttribute
+  @XmlElement
+  private List<String> stringList1;
+
+  @ScimAttribute(required = true)
+  @XmlElement
+  private Boolean boolean1;
+
+  @ScimAttribute(uniqueness = Schema.Attribute.Uniqueness.SERVER)
+  @XmlElement
+  private List<Boolean> booleanList1;
+
+  @ScimAttribute(required = true)
+  @XmlElement
+  private boolean boolean2;
+
+  @ScimAttribute(mutability = Schema.Attribute.Mutability.IMMUTABLE)
+  @XmlElement
+  private boolean[] booleanArray1;
+
+  @ScimAttribute
+  @XmlElement
+  private Double decimal1;
+
+  @ScimAttribute(required = true)
+  @XmlElement
+  private double decimal2;
+
+  @ScimAttribute
+  @XmlElement
+  private List<Double> decimalList1;
+
+  @ScimAttribute
+  @XmlElement
+  private double[] decimalArray1;
+
+  @ScimAttribute
+  @XmlElement
+  private Integer integer1;
+
+  @ScimAttribute(required = true)
+  @XmlElement
+  private int integer2;
+
+  @ScimAttribute
+  @XmlElement
+  private List<Integer> integerList1;
+
+  @ScimAttribute
+  @XmlElement
+  private int[] integerArray1;
+
+  @ScimAttribute
+  @XmlElement
+  private Date date1;
+
+  @ScimAttribute
+  @XmlElement
+  private List<Date> dateList1;
+
+  @ScimAttribute
+  @XmlElement
+  private Date[] dateArray1;
+
+  @ScimAttribute
+  @XmlElement
+  private Instant instant1;
+
+  @ScimAttribute
+  @XmlElement
+  private List<Instant> instantList1;
+
+  @ScimAttribute
+  @XmlElement
+  private Instant[] instantArray1;
+
+  @ScimAttribute
+  @XmlElement
+  private byte[] binary1;
+
+  @ScimAttribute
+  @XmlElement
+  private List<byte[]> binaryList1;
+
+  @ScimAttribute
+  @XmlElement
+  private ResourceReference.ReferenceType ref1;
+
+  @ScimAttribute
+  @XmlElement
+  private List<ResourceReference.ReferenceType> refList1;
+
+  @Override
+  public String getUrn() {
+    return SCHEMA_URN;
+  }
+
+}
diff --git a/scim-spec/scim-spec-schema/src/test/java/org/apache/directory/scim/spec/schema/SchemasTest.java b/scim-spec/scim-spec-schema/src/test/java/org/apache/directory/scim/spec/schema/SchemasTest.java
new file mode 100644
index 00000000..0229bff4
--- /dev/null
+++ b/scim-spec/scim-spec-schema/src/test/java/org/apache/directory/scim/spec/schema/SchemasTest.java
@@ -0,0 +1,441 @@
+/*
+ * 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.directory.scim.spec.schema;
+
+import org.apache.directory.scim.spec.AllSchemaTypesExtension;
+import org.junit.jupiter.api.Test;
+
+public class SchemasTest {
+
+  private Schema schema = Schemas.schemaForExtension(AllSchemaTypesExtension.class);
+
+  @Test
+  public void string1Attribute() {
+    assertThat(schema.getAttribute("string1"))
+      .hasName("string1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.STRING)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("One String")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void string2Attribute() {
+    assertThat(schema.getAttribute("string2"))
+      .hasName("string2")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.STRING)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(true)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void stringListAttribute() {
+    assertThat(schema.getAttribute("stringList1"))
+      .hasName("stringList1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.STRING)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void booleanAttribute_required() {
+    assertThat(schema.getAttribute("boolean1"))
+      .hasName("boolean1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.BOOLEAN)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("")
+      .isRequired(true)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void booleanListAttribute_serverUnique() {
+    assertThat(schema.getAttribute("booleanList1"))
+      .hasName("booleanList1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.BOOLEAN)
+      .hasUniqueness(Schema.Attribute.Uniqueness.SERVER)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void boolean2Attribute() {
+    assertThat(schema.getAttribute("boolean2"))
+      .hasName("boolean2")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.BOOLEAN)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("")
+      .isRequired(true)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void booleanArray1Attribute() {
+    assertThat(schema.getAttribute("booleanArray1"))
+      .hasName("booleanArray1")
+      .hasMutability(Schema.Attribute.Mutability.IMMUTABLE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.BOOLEAN)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void decimal1Attribute() {
+    assertThat(schema.getAttribute("decimal1"))
+      .hasName("decimal1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.DECIMAL)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void decimal2Attribute() {
+    assertThat(schema.getAttribute("decimal2"))
+      .hasName("decimal2")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.DECIMAL)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("")
+      .isRequired(true)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void decimalList1Attribute() {
+    assertThat(schema.getAttribute("decimalList1"))
+      .hasName("decimalList1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.DECIMAL)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void decimalArray1Attribute() {
+    assertThat(schema.getAttribute("decimalArray1"))
+      .hasName("decimalArray1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.DECIMAL)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void integer1Attribute() {
+    assertThat(schema.getAttribute("integer1"))
+      .hasName("integer1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.INTEGER)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void integer2Attribute() {
+    assertThat(schema.getAttribute("integer2"))
+      .hasName("integer2")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.INTEGER)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("")
+      .isRequired(true)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void integerList1Attribute() {
+    assertThat(schema.getAttribute("integerList1"))
+      .hasName("integerList1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.INTEGER)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void integerArray1Attribute() {
+    assertThat(schema.getAttribute("integerArray1"))
+      .hasName("integerArray1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.INTEGER)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void date1Attribute() {
+    assertThat(schema.getAttribute("date1"))
+      .hasName("date1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.DATE_TIME)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void dateList1Attribute() {
+    assertThat(schema.getAttribute("dateList1"))
+      .hasName("dateList1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.DATE_TIME)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void dateArray1Attribute() {
+    assertThat(schema.getAttribute("dateArray1"))
+      .hasName("dateArray1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.DATE_TIME)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void instant1Attribute() {
+    assertThat(schema.getAttribute("instant1"))
+      .hasName("instant1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.DATE_TIME)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void instantList1Attribute() {
+    assertThat(schema.getAttribute("instantList1"))
+      .hasName("instantList1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.DATE_TIME)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void instantArray1Attribute() {
+    assertThat(schema.getAttribute("instantArray1"))
+      .hasName("instantArray1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.DATE_TIME)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void binary1Attribute() {
+    assertThat(schema.getAttribute("binary1"))
+      .hasName("binary1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.BINARY)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void ref1Attribute() {
+    assertThat(schema.getAttribute("ref1"))
+      .hasName("ref1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.REFERENCE)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(false)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+  @Test
+  public void refList1Attribute() {
+    assertThat(schema.getAttribute("refList1"))
+      .hasName("refList1")
+      .hasMutability(Schema.Attribute.Mutability.READ_WRITE)
+      .hasReturned(Schema.Attribute.Returned.DEFAULT)
+      .hasType(Schema.Attribute.Type.REFERENCE)
+      .hasUniqueness(Schema.Attribute.Uniqueness.NONE)
+      .isMultiValued(true)
+      .hasDescription("")
+      .isRequired(false)
+      .isCaseExact(false)
+      .hasSubAttributes(null)
+      .hasCanonicalValues(null)
+      .hasReferenceTypes(null);
+  }
+
+
+  private AttributeAssert assertThat(Schema.Attribute attribute) {
+    return new AttributeAssert(attribute);
+  }
+}
diff --git a/scim-test/src/main/java/org/apache/directory/scim/test/stub/Subobject.java b/scim-test/src/main/java/org/apache/directory/scim/test/stub/Subobject.java
index 4e098201..0061aa6c 100644
--- a/scim-test/src/main/java/org/apache/directory/scim/test/stub/Subobject.java
+++ b/scim-test/src/main/java/org/apache/directory/scim/test/stub/Subobject.java
@@ -23,7 +23,9 @@ import jakarta.xml.bind.annotation.XmlElement;
 import org.apache.directory.scim.spec.annotation.ScimAttribute;
 
 import java.io.Serializable;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 public class Subobject implements Serializable {
 
@@ -53,6 +55,10 @@ public class Subobject implements Serializable {
   @XmlElement
   private List<String> list2;
 
+  @ScimAttribute
+  @XmlElement
+  private byte[] binary1;
+
   public String getString1() {
     return this.string1;
   }
@@ -107,55 +113,40 @@ public class Subobject implements Serializable {
     return this;
   }
 
-  public boolean equals(final Object o) {
-    if (o == this) return true;
-    if (!(o instanceof Subobject)) return false;
-    final Subobject other = (Subobject) o;
-    if (!other.canEqual((Object) this)) return false;
-    final Object this$string1 = this.getString1();
-    final Object other$string1 = other.getString1();
-    if (this$string1 == null ? other$string1 != null : !this$string1.equals(other$string1)) return false;
-    final Object this$string2 = this.getString2();
-    final Object other$string2 = other.getString2();
-    if (this$string2 == null ? other$string2 != null : !this$string2.equals(other$string2)) return false;
-    final Object this$boolean1 = this.getBoolean1();
-    final Object other$boolean1 = other.getBoolean1();
-    if (this$boolean1 == null ? other$boolean1 != null : !this$boolean1.equals(other$boolean1)) return false;
-    final Object this$boolean2 = this.getBoolean2();
-    final Object other$boolean2 = other.getBoolean2();
-    if (this$boolean2 == null ? other$boolean2 != null : !this$boolean2.equals(other$boolean2)) return false;
-    final Object this$list1 = this.getList1();
-    final Object other$list1 = other.getList1();
-    if (this$list1 == null ? other$list1 != null : !this$list1.equals(other$list1)) return false;
-    final Object this$list2 = this.getList2();
-    final Object other$list2 = other.getList2();
-    if (this$list2 == null ? other$list2 != null : !this$list2.equals(other$list2)) return false;
-    return true;
-  }
-
-  protected boolean canEqual(final Object other) {
-    return other instanceof Subobject;
+  public byte[] getBinary1() {
+    return binary1;
   }
 
-  public int hashCode() {
-    final int PRIME = 59;
-    int result = 1;
-    final Object $string1 = this.getString1();
-    result = result * PRIME + ($string1 == null ? 43 : $string1.hashCode());
-    final Object $string2 = this.getString2();
-    result = result * PRIME + ($string2 == null ? 43 : $string2.hashCode());
-    final Object $boolean1 = this.getBoolean1();
-    result = result * PRIME + ($boolean1 == null ? 43 : $boolean1.hashCode());
-    final Object $boolean2 = this.getBoolean2();
-    result = result * PRIME + ($boolean2 == null ? 43 : $boolean2.hashCode());
-    final Object $list1 = this.getList1();
-    result = result * PRIME + ($list1 == null ? 43 : $list1.hashCode());
-    final Object $list2 = this.getList2();
-    result = result * PRIME + ($list2 == null ? 43 : $list2.hashCode());
-    return result;
+  public Subobject setBinary1(byte[] binary1) {
+    this.binary1 = binary1;
+    return this;
   }
 
-  public String toString() {
-    return "Subobject(string1=" + this.getString1() + ", string2=" + this.getString2() + ", boolean1=" + this.getBoolean1() + ", boolean2=" + this.getBoolean2() + ", list1=" + this.getList1() + ", list2=" + this.getList2() + ")";
+  @Override
+  public boolean equals(Object object) {
+    if (this == object) return true;
+    if (object == null || getClass() != object.getClass()) return false;
+
+    Subobject subobject = (Subobject) object;
+
+    if (!Objects.equals(string1, subobject.string1)) return false;
+    if (!Objects.equals(string2, subobject.string2)) return false;
+    if (!Objects.equals(boolean1, subobject.boolean1)) return false;
+    if (!Objects.equals(boolean2, subobject.boolean2)) return false;
+    if (!Objects.equals(list1, subobject.list1)) return false;
+    if (!Objects.equals(list2, subobject.list2)) return false;
+      return Arrays.equals(binary1, subobject.binary1);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = string1 != null ? string1.hashCode() : 0;
+    result = 31 * result + (string2 != null ? string2.hashCode() : 0);
+    result = 31 * result + (boolean1 != null ? boolean1.hashCode() : 0);
+    result = 31 * result + (boolean2 != null ? boolean2.hashCode() : 0);
+    result = 31 * result + (list1 != null ? list1.hashCode() : 0);
+    result = 31 * result + (list2 != null ? list2.hashCode() : 0);
+    result = 31 * result + Arrays.hashCode(binary1);
+    return result;
   }
 }