@@ -7,6 +7,7 @@ import graphql.schema.fetching.ConfusedPojo
77import graphql.schema.somepackage.ClassWithDFEMethods
88import graphql.schema.somepackage.ClassWithInterfaces
99import graphql.schema.somepackage.ClassWithInteritanceAndInterfaces
10+ import graphql.schema.somepackage.InterfaceInheritanceHolder
1011import graphql.schema.somepackage.RecordLikeClass
1112import graphql.schema.somepackage.RecordLikeTwoClassesDown
1213import graphql.schema.somepackage.TestClass
@@ -788,6 +789,124 @@ class PropertyDataFetcherTest extends Specification {
788789
789790 class OtherObject extends BaseObject {}
790791
792+ def " fetch via public interface method on non-public class - issue 4278" () {
793+ given :
794+ // TreeMap.Entry is a package-private class implementing the public Map.Entry interface
795+ // On Java 16+, setAccessible fails on JDK internal classes, so the only way to invoke
796+ // getValue() is by finding it through the public Map.Entry interface
797+ PropertyDataFetcherHelper . setUseLambdaFactory(false )
798+ PropertyDataFetcher . clearReflectionCache()
799+
800+ def treeMap = new TreeMap<String , String > ()
801+ treeMap. put(" testKey" , " testValue" )
802+ def entry = treeMap. entrySet(). iterator(). next()
803+ def environment = env(" value" , entry)
804+
805+ when :
806+ def result = fetcher. get(environment)
807+
808+ then :
809+ result == " testValue"
810+
811+ where :
812+ fetcher | _
813+ new PropertyDataFetcher (" value" ) | _
814+ SingletonPropertyDataFetcher . singleton() | _
815+ }
816+
817+ def " fetch via public interface method on non-public class for key - issue 4278" () {
818+ given :
819+ PropertyDataFetcherHelper . setUseLambdaFactory(false )
820+ PropertyDataFetcher . clearReflectionCache()
821+
822+ def treeMap = new TreeMap<String , String > ()
823+ treeMap. put(" testKey" , " testValue" )
824+ def entry = treeMap. entrySet(). iterator(). next()
825+ def environment = env(" key" , entry)
826+
827+ when :
828+ def result = fetcher. get(environment)
829+
830+ then :
831+ result == " testKey"
832+
833+ where :
834+ fetcher | _
835+ new PropertyDataFetcher (" key" ) | _
836+ SingletonPropertyDataFetcher . singleton() | _
837+ }
838+
839+ def " fetch method from public interface through package-private interface chain" () {
840+ given :
841+ // PackagePrivateChainImpl (package-private) implements PackagePrivateMiddleInterface (package-private)
842+ // which extends PublicBaseInterface (public) — defines getBaseValue()
843+ // The recursive interface search must traverse through the package-private middle interface
844+ PropertyDataFetcherHelper . setUseLambdaFactory(false )
845+ PropertyDataFetcher . clearReflectionCache()
846+
847+ def obj = InterfaceInheritanceHolder . createChainImpl()
848+ def environment = env(" baseValue" , obj)
849+
850+ when :
851+ def result = new PropertyDataFetcher (" baseValue" ). get(environment)
852+
853+ then :
854+ result == " baseValue"
855+ }
856+
857+ def " fetch method through diamond interface inheritance" () {
858+ given :
859+ // DiamondImpl (package-private) implements both PackagePrivateBranchA and PackagePrivateBranchB
860+ // Both are package-private interfaces extending PublicBaseInterface (public) — defines getBaseValue()
861+ // The search must find getBaseValue() through either branch
862+ PropertyDataFetcherHelper . setUseLambdaFactory(false )
863+ PropertyDataFetcher . clearReflectionCache()
864+
865+ def obj = InterfaceInheritanceHolder . createDiamondImpl()
866+
867+ expect :
868+ new PropertyDataFetcher (property). get(env(property, obj)) == expected
869+
870+ where :
871+ property | expected
872+ " baseValue" | " diamondBaseValue"
873+ }
874+
875+ def " fetch via public interface method with DataFetchingEnvironment parameter on non-public class" () {
876+ given :
877+ // PackagePrivateDfeImpl implements PublicDfeInterface which declares getDfeValue(DataFetchingEnvironment)
878+ // This exercises the dfeInUse path in findMethodOnPublicInterfaces (lines 262-267)
879+ PropertyDataFetcherHelper . setUseLambdaFactory(false )
880+ PropertyDataFetcher . clearReflectionCache()
881+
882+ def obj = InterfaceInheritanceHolder . createDfeImpl()
883+ def environment = env(" dfeValue" , obj)
884+
885+ when :
886+ def result = new PropertyDataFetcher (" dfeValue" ). get(environment)
887+
888+ then :
889+ result == " dfeValue"
890+ }
891+
892+ def " fetch via interface search hits NoSuchMethodException and continues to next interface" () {
893+ given :
894+ // PackagePrivateMultiInterfaceImpl implements PublicInterfaceWithoutTarget (no getBaseValue)
895+ // and PublicBaseInterface (has getBaseValue). The search must hit NoSuchMethodException
896+ // on the first interface and continue to find it on the second.
897+ PropertyDataFetcherHelper . setUseLambdaFactory(false )
898+ PropertyDataFetcher . clearReflectionCache()
899+
900+ def obj = InterfaceInheritanceHolder . createMultiInterfaceImpl()
901+ def environment = env(" baseValue" , obj)
902+
903+ when :
904+ def result = new PropertyDataFetcher (" baseValue" ). get(environment)
905+
906+ then :
907+ result == " foundViaSecondInterface"
908+ }
909+
791910 def " Can access private property from base class that starts with i in Turkish" () {
792911 // see https://github.com/graphql-java/graphql-java/issues/3385
793912 given :
0 commit comments