From 63edda9329742d9749ec1fb4989211d9b599f175 Mon Sep 17 00:00:00 2001 From: Shihyu Ho Date: Mon, 20 Jul 2026 17:08:27 +0800 Subject: [PATCH] test(mapper): pin local-fields-only databind contract for inherited criteria fields ReflectionDatabind.of() uses doWithLocalFields, which only visits fields declared directly on the target's concrete class. Fields inherited from a superclass criteria POJO are silently excluded from databinding. Add a test that pins this current behavior with a subclass criteria POJO, so any future switch to doWithFields becomes a deliberate, test-visible decision. Also add a Javadoc note documenting the contract on the affected method. Closes #199 Co-authored-by: Claude Opus 4.8 (1M context) --- .../data/jpa/spec/ReflectionDatabind.java | 9 ++++++ .../data/jpa/spec/ReflectionDatabindTest.java | 32 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/mapper/src/main/java/tw/com/softleader/data/jpa/spec/ReflectionDatabind.java b/mapper/src/main/java/tw/com/softleader/data/jpa/spec/ReflectionDatabind.java index a1cd158..ec2025f 100644 --- a/mapper/src/main/java/tw/com/softleader/data/jpa/spec/ReflectionDatabind.java +++ b/mapper/src/main/java/tw/com/softleader/data/jpa/spec/ReflectionDatabind.java @@ -69,6 +69,15 @@ static List of(@NonNull Object target, @NonNull SkippingStrategy skipp return of(target, skippingStrategy, ReflectionDatabind::new, ReflectionDatabind::new); } + /** + * Builds the field lookup for {@code target}. + * + *

Local-fields-only contract: this uses {@link ReflectionUtils#doWithLocalFields}, + * which visits only fields declared directly on {@code target}'s concrete class. Fields inherited + * from a superclass (e.g. a shared base criteria POJO) are not included and therefore + * are never databound. Switching to {@link ReflectionUtils#doWithFields} to also include + * inherited fields would be a deliberate behavior change. + */ // Visible for testing static List of( @NonNull Object target, diff --git a/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java b/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java index 180ba22..a2e0d19 100644 --- a/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java +++ b/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java @@ -33,6 +33,7 @@ import lombok.AllArgsConstructor; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; +import tw.com.softleader.data.jpa.spec.annotation.Spec; class ReflectionDatabindTest { @@ -81,6 +82,22 @@ void fireOnlyOnce() { }); } + @Test + void inheritedFieldsAreNotDatabound() { + + // Pins the current doWithLocalFields contract: fields inherited from a superclass criteria + // POJO are silently excluded from the field lookup. If this ever needs to change (e.g. to + // support inherited criteria fields via doWithFields), this test must be updated + // deliberately. + var object = new ChildCriteria("childValue", "parentValue"); + + var databind = ReflectionDatabind.of(object, new DefaultSkippingStrategy()); + + assertThat(databind) + .extracting(bind -> bind.getField().getName()) + .containsExactly("@type", "child"); + } + @AllArgsConstructor static class MyObject { @@ -89,4 +106,19 @@ static class MyObject { Optional c; Collection d; } + + static class ParentCriteria { + + @Spec String parent; + } + + static class ChildCriteria extends ParentCriteria { + + String child; + + ChildCriteria(String child, String parent) { + this.child = child; + this.parent = parent; + } + } }