1919import static org .mockito .ArgumentMatchers .*;
2020import static org .mockito .Mockito .*;
2121
22+ import tools .jackson .databind .json .JsonMapper ;
23+
2224import java .util .Collections ;
25+ import java .util .List ;
2326import java .util .Map ;
2427import java .util .Optional ;
28+ import java .util .function .Function ;
2529
2630import org .junit .jupiter .api .BeforeEach ;
2731import org .junit .jupiter .api .Test ;
2832import org .junit .jupiter .api .extension .ExtendWith ;
33+ import org .mockito .ArgumentCaptor ;
2934import org .mockito .Mock ;
3035import org .mockito .Mockito ;
3136import org .mockito .junit .jupiter .MockitoExtension ;
3237import org .springframework .core .MethodParameter ;
38+ import org .springframework .data .mapping .context .PersistentEntities ;
39+ import org .springframework .data .mongodb .core .convert .MongoCustomConversions ;
40+ import org .springframework .data .mongodb .core .mapping .MongoMappingContext ;
3341import org .springframework .data .querydsl .QuerydslPredicateExecutor ;
3442import org .springframework .data .querydsl .QuerydslRepositoryInvokerAdapter ;
3543import org .springframework .data .querydsl .SimpleEntityPathResolver ;
4149import org .springframework .data .repository .support .Repositories ;
4250import org .springframework .data .repository .support .RepositoryInvoker ;
4351import org .springframework .data .repository .support .RepositoryInvokerFactory ;
52+ import org .springframework .data .rest .tests .mongodb .Profile ;
4453import org .springframework .data .rest .tests .mongodb .QUser ;
4554import org .springframework .data .rest .tests .mongodb .Receipt ;
4655import org .springframework .data .rest .tests .mongodb .ReceiptRepository ;
4756import org .springframework .data .rest .tests .mongodb .User ;
57+ import org .springframework .data .rest .webmvc .json .MappedJacksonProperties ;
4858import org .springframework .test .util .ReflectionTestUtils ;
59+ import org .springframework .util .MultiValueMap ;
4960
5061import com .querydsl .core .types .Predicate ;
5162
@@ -68,15 +79,28 @@ class QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUnitTests
6879 @ Mock MethodParameter parameter ;
6980
7081 QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver resolver ;
82+ Function <Class <?>, MappedJacksonProperties > jacksonPropertiesLookup ;
7183
7284 @ BeforeEach
7385 void setUp () {
7486
7587 QuerydslBindingsFactory factory = new QuerydslBindingsFactory (SimpleEntityPathResolver .INSTANCE );
7688 ReflectionTestUtils .setField (factory , "repositories" , Optional .of (repositories ));
7789
90+ MongoCustomConversions conversions = new MongoCustomConversions (Collections .emptyList ());
91+ MongoMappingContext mappingContext = new MongoMappingContext ();
92+ mappingContext .setSimpleTypeHolder (conversions .getSimpleTypeHolder ());
93+ mappingContext .getPersistentEntity (User .class );
94+ mappingContext .getPersistentEntity (Receipt .class );
95+ mappingContext .getPersistentEntity (Profile .class );
96+ PersistentEntities entities = new PersistentEntities (List .of (mappingContext ));
97+
98+ JsonMapper mapper = JsonMapper .builder ().build ();
99+ this .jacksonPropertiesLookup = type -> entities .getPersistentEntity (type )
100+ .map (entity -> MappedJacksonProperties .forSerialization (entity , mapper )).orElse (null );
101+
78102 this .resolver = new QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver (repositories , invokerFactory ,
79- resourceMetadataResolver , builder , factory );
103+ resourceMetadataResolver , builder , factory , jacksonPropertiesLookup );
80104
81105 when (builder .getPredicate (any (), any (), any ())).thenReturn (mock (Predicate .class ));
82106 when (parameter .hasParameterAnnotation (QuerydslPredicate .class )).thenReturn (true );
@@ -116,8 +140,50 @@ void invokesCustomizationOnRepositoryIfItImplementsCustomizer() {
116140 verify (repository , times (1 )).customize (Mockito .any (QuerydslBindings .class ), Mockito .any (QUser .class ));
117141 }
118142
143+ @ Test // GH-2572
144+ void doesNotExposeJsonIgnoredPropertiesAsFilterKeys () {
145+
146+ Object repository = mock (QuerydslUserRepository .class );
147+ when (repositories .getRepositoryFor (User .class )).thenReturn (Optional .of (repository ));
148+
149+ Map <String , String []> parameters = Map .of ("ignored" , new String [] { "candidate-value" });
150+
151+ ArgumentCaptor <MultiValueMap <String , String >> captor = ArgumentCaptor .captor ();
152+ when (builder .getPredicate (any (), captor .capture (), any ())).thenReturn (mock (Predicate .class ));
153+
154+ resolver .postProcess (parameter , invoker , User .class , parameters );
155+
156+ // Querydsl never receives a parameter that maps to a @JsonIgnore-annotated property: it could otherwise be used
157+ // as a server-side filter key (and as an existence oracle) for a value the framework refuses to serialize.
158+ assertThat (captor .getValue ()).doesNotContainKey ("ignored" );
159+ }
160+
161+ @ Test // GH-2572
162+ void translatesJacksonRenamedPropertyToPersistentPropertyName () {
163+
164+ Object repository = mock (QuerydslProfileRepository .class );
165+ when (repositories .getRepositoryFor (Profile .class )).thenReturn (Optional .of (repository ));
166+
167+ // Profile.aliased is exposed as "renamed" via @JsonProperty("renamed"). A request that addresses the public alias
168+ // must reach Querydsl under the underlying domain property name; the bare Java field name must not be accepted.
169+ Map <String , String []> parameters = Map .of (
170+ "renamed" , new String [] { "value" },
171+ "aliased" , new String [] { "ignored" });
172+
173+ ArgumentCaptor <MultiValueMap <String , String >> captor = ArgumentCaptor .captor ();
174+ when (builder .getPredicate (any (), captor .capture (), any ())).thenReturn (mock (Predicate .class ));
175+
176+ resolver .postProcess (parameter , invoker , Profile .class , parameters );
177+
178+ MultiValueMap <String , String > forwarded = captor .getValue ();
179+ assertThat (forwarded ).doesNotContainKey ("renamed" );
180+ assertThat (forwarded .get ("aliased" )).containsExactly ("value" );
181+ }
182+
119183 interface QuerydslUserRepository extends QuerydslPredicateExecutor <User > {}
120184
185+ interface QuerydslProfileRepository extends QuerydslPredicateExecutor <Profile > {}
186+
121187 interface QuerydslCustomizingUserRepository
122188 extends QuerydslPredicateExecutor <User >, QuerydslBinderCustomizer <QUser > {}
123189}
0 commit comments