2020import static org .mockito .Mockito .*;
2121
2222import java .util .Collections ;
23+ import java .util .List ;
2324import java .util .Map ;
2425import java .util .Optional ;
26+ import java .util .function .Function ;
2527
2628import org .junit .jupiter .api .BeforeEach ;
2729import org .junit .jupiter .api .Test ;
2830import org .junit .jupiter .api .extension .ExtendWith ;
31+ import org .mockito .ArgumentCaptor ;
2932import org .mockito .Mock ;
3033import org .mockito .Mockito ;
3134import org .mockito .junit .jupiter .MockitoExtension ;
3235import org .springframework .core .MethodParameter ;
36+ import org .springframework .data .mapping .context .PersistentEntities ;
37+ import org .springframework .data .mongodb .core .convert .MongoCustomConversions ;
38+ import org .springframework .data .mongodb .core .mapping .MongoMappingContext ;
3339import org .springframework .data .querydsl .QuerydslPredicateExecutor ;
3440import org .springframework .data .querydsl .QuerydslRepositoryInvokerAdapter ;
3541import org .springframework .data .querydsl .SimpleEntityPathResolver ;
4147import org .springframework .data .repository .support .Repositories ;
4248import org .springframework .data .repository .support .RepositoryInvoker ;
4349import org .springframework .data .repository .support .RepositoryInvokerFactory ;
50+ import org .springframework .data .rest .tests .mongodb .Profile ;
4451import org .springframework .data .rest .tests .mongodb .QUser ;
4552import org .springframework .data .rest .tests .mongodb .Receipt ;
4653import org .springframework .data .rest .tests .mongodb .ReceiptRepository ;
4754import org .springframework .data .rest .tests .mongodb .User ;
55+ import org .springframework .data .rest .webmvc .json .MappedProperties ;
4856import org .springframework .test .util .ReflectionTestUtils ;
57+ import org .springframework .util .MultiValueMap ;
4958
59+ import com .fasterxml .jackson .databind .ObjectMapper ;
5060import com .querydsl .core .types .Predicate ;
5161
5262/**
@@ -68,15 +78,28 @@ class QuerydslAwareRootResourceInformationHandlerMethodArgumentResolverUnitTests
6878 @ Mock MethodParameter parameter ;
6979
7080 QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver resolver ;
81+ Function <Class <?>, MappedProperties > jacksonPropertiesLookup ;
7182
7283 @ BeforeEach
7384 void setUp () {
7485
7586 QuerydslBindingsFactory factory = new QuerydslBindingsFactory (SimpleEntityPathResolver .INSTANCE );
7687 ReflectionTestUtils .setField (factory , "repositories" , Optional .of (repositories ));
7788
89+ MongoCustomConversions conversions = new MongoCustomConversions (Collections .emptyList ());
90+ MongoMappingContext mappingContext = new MongoMappingContext ();
91+ mappingContext .setSimpleTypeHolder (conversions .getSimpleTypeHolder ());
92+ mappingContext .getPersistentEntity (User .class );
93+ mappingContext .getPersistentEntity (Receipt .class );
94+ mappingContext .getPersistentEntity (Profile .class );
95+ PersistentEntities entities = new PersistentEntities (List .of (mappingContext ));
96+
97+ ObjectMapper mapper = new ObjectMapper ();
98+ this .jacksonPropertiesLookup = type -> entities .getPersistentEntity (type )
99+ .map (entity -> MappedProperties .forSerialization (entity , mapper )).orElse (null );
100+
78101 this .resolver = new QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver (repositories , invokerFactory ,
79- resourceMetadataResolver , builder , factory );
102+ resourceMetadataResolver , builder , factory , jacksonPropertiesLookup );
80103
81104 when (builder .getPredicate (any (), any (), any ())).thenReturn (mock (Predicate .class ));
82105 when (parameter .hasParameterAnnotation (QuerydslPredicate .class )).thenReturn (true );
@@ -116,8 +139,50 @@ void invokesCustomizationOnRepositoryIfItImplementsCustomizer() {
116139 verify (repository , times (1 )).customize (Mockito .any (QuerydslBindings .class ), Mockito .any (QUser .class ));
117140 }
118141
142+ @ Test // GH-2572
143+ void doesNotExposeJsonIgnoredPropertiesAsFilterKeys () {
144+
145+ Object repository = mock (QuerydslUserRepository .class );
146+ when (repositories .getRepositoryFor (User .class )).thenReturn (Optional .of (repository ));
147+
148+ Map <String , String []> parameters = Map .of ("ignored" , new String [] { "candidate-value" });
149+
150+ ArgumentCaptor <MultiValueMap <String , String >> captor = ArgumentCaptor .captor ();
151+ when (builder .getPredicate (any (), captor .capture (), any ())).thenReturn (mock (Predicate .class ));
152+
153+ resolver .postProcess (parameter , invoker , User .class , parameters );
154+
155+ // Querydsl never receives a parameter that maps to a @JsonIgnore-annotated property: it could otherwise be used
156+ // as a server-side filter key (and as an existence oracle) for a value the framework refuses to serialize.
157+ assertThat (captor .getValue ()).doesNotContainKey ("ignored" );
158+ }
159+
160+ @ Test // GH-2572
161+ void translatesJacksonRenamedPropertyToPersistentPropertyName () {
162+
163+ Object repository = mock (QuerydslProfileRepository .class );
164+ when (repositories .getRepositoryFor (Profile .class )).thenReturn (Optional .of (repository ));
165+
166+ // Profile.aliased is exposed as "renamed" via @JsonProperty("renamed"). A request that addresses the public alias
167+ // must reach Querydsl under the underlying domain property name; the bare Java field name must not be accepted.
168+ Map <String , String []> parameters = Map .of (
169+ "renamed" , new String [] { "value" },
170+ "aliased" , new String [] { "ignored" });
171+
172+ ArgumentCaptor <MultiValueMap <String , String >> captor = ArgumentCaptor .captor ();
173+ when (builder .getPredicate (any (), captor .capture (), any ())).thenReturn (mock (Predicate .class ));
174+
175+ resolver .postProcess (parameter , invoker , Profile .class , parameters );
176+
177+ MultiValueMap <String , String > forwarded = captor .getValue ();
178+ assertThat (forwarded ).doesNotContainKey ("renamed" );
179+ assertThat (forwarded .get ("aliased" )).containsExactly ("value" );
180+ }
181+
119182 interface QuerydslUserRepository extends QuerydslPredicateExecutor <User > {}
120183
184+ interface QuerydslProfileRepository extends QuerydslPredicateExecutor <Profile > {}
185+
121186 interface QuerydslCustomizingUserRepository
122187 extends QuerydslPredicateExecutor <User >, QuerydslBinderCustomizer <QUser > {}
123188}
0 commit comments