22
33import static org .assertj .core .api .Assertions .assertThat ;
44import static org .assertj .core .api .Assertions .assertThatThrownBy ;
5+ import static org .mockito .Mockito .mock ;
56
67import com .retailsvc .http .JsonMapper ;
78import com .retailsvc .http .MethodNotAllowedException ;
1516import com .retailsvc .http .spec .PathTemplate ;
1617import com .retailsvc .http .spec .Server ;
1718import com .retailsvc .http .spec .Spec ;
19+ import com .retailsvc .http .spec .schema .BooleanSchema ;
20+ import com .retailsvc .http .spec .schema .IntegerSchema ;
21+ import com .retailsvc .http .spec .schema .NumberSchema ;
1822import com .retailsvc .http .spec .schema .StringSchema ;
1923import com .retailsvc .http .spec .schema .TypeName ;
2024import com .retailsvc .http .validate .DefaultValidator ;
3438class RequestPreparationFilterTest {
3539
3640 private HttpExchange exchange (String method , String path , byte [] body ) {
37- HttpExchange ex = Mockito . mock (HttpExchange .class );
41+ HttpExchange ex = mock (HttpExchange .class );
3842 Mockito .when (ex .getRequestMethod ()).thenReturn (method );
3943 Mockito .when (ex .getRequestURI ()).thenReturn (URI .create (path ));
4044 Mockito .when (ex .getRequestHeaders ()).thenReturn (new Headers ());
@@ -80,7 +84,7 @@ void successPathBindsRequestContextDuringChain() throws Exception {
8084 AtomicReference <String > seenOpId = new AtomicReference <>();
8185 AtomicReference <Map <String , String >> seenPathParams = new AtomicReference <>();
8286
83- Filter .Chain chain = Mockito . mock (Filter .Chain .class );
87+ Filter .Chain chain = mock (Filter .Chain .class );
8488 Mockito .doAnswer (
8589 inv -> {
8690 seenOpId .set (Request .operationId ());
@@ -112,7 +116,7 @@ void unknownPathThrowsNotFound() {
112116 Filter f = newFilter (spec );
113117
114118 HttpExchange ex = exchange ("GET" , "/missing" , new byte [0 ]);
115- assertThatThrownBy (() -> f .doFilter (ex , Mockito . mock (Filter .Chain .class )))
119+ assertThatThrownBy (() -> f .doFilter (ex , mock (Filter .Chain .class )))
116120 .isInstanceOf (NotFoundException .class );
117121 }
118122
@@ -131,7 +135,7 @@ void wrongMethodThrowsMethodNotAllowed() {
131135 Filter f = newFilter (spec );
132136
133137 HttpExchange ex = exchange ("POST" , "/x" , new byte [0 ]);
134- assertThatThrownBy (() -> f .doFilter (ex , Mockito . mock (Filter .Chain .class )))
138+ assertThatThrownBy (() -> f .doFilter (ex , mock (Filter .Chain .class )))
135139 .isInstanceOf (MethodNotAllowedException .class );
136140 }
137141
@@ -152,9 +156,136 @@ void invalidQueryParamThrowsValidation() {
152156 Filter f = newFilter (spec );
153157
154158 HttpExchange ex = exchange ("GET" , "/x?q=ab" , new byte [0 ]);
155- assertThatThrownBy (() -> f .doFilter (ex , Mockito . mock (Filter .Chain .class )))
159+ assertThatThrownBy (() -> f .doFilter (ex , mock (Filter .Chain .class )))
156160 .isInstanceOf (ValidationException .class )
157161 .extracting (t -> ((ValidationException ) t ).error ().pointer ())
158162 .isEqualTo ("/query/q" );
159163 }
164+
165+ @ Test
166+ void integerQueryParamIsCoercedFromStringBeforeValidation () throws Exception {
167+ var intSchema = new IntegerSchema (Set .of (TypeName .INTEGER ), 1L , 100L , null , null , null , null );
168+ var op =
169+ new Operation (
170+ "a" ,
171+ HttpMethod .GET ,
172+ PathTemplate .compile ("/x" ),
173+ Optional .empty (),
174+ List .of (new Parameter ("n" , Parameter .Location .QUERY , true , intSchema )),
175+ Map .of ());
176+ Spec spec = specWith (op );
177+ Filter f = newFilter (spec );
178+
179+ HttpExchange ex = exchange ("GET" , "/x?n=42" , new byte [0 ]);
180+ Filter .Chain chain = mock (Filter .Chain .class );
181+ f .doFilter (ex , chain );
182+ Mockito .verify (chain ).doFilter (ex );
183+ }
184+
185+ @ Test
186+ void integerQueryParamRejectsNonNumericString () {
187+ var intSchema = new IntegerSchema (Set .of (TypeName .INTEGER ), null , null , null , null , null , null );
188+ var op =
189+ new Operation (
190+ "a" ,
191+ HttpMethod .GET ,
192+ PathTemplate .compile ("/x" ),
193+ Optional .empty (),
194+ List .of (new Parameter ("n" , Parameter .Location .QUERY , true , intSchema )),
195+ Map .of ());
196+ Spec spec = specWith (op );
197+ Filter f = newFilter (spec );
198+
199+ HttpExchange ex = exchange ("GET" , "/x?n=abc" , new byte [0 ]);
200+ assertThatThrownBy (() -> f .doFilter (ex , mock (Filter .Chain .class )))
201+ .isInstanceOf (ValidationException .class )
202+ .extracting (t -> ((ValidationException ) t ).error ().keyword ())
203+ .isEqualTo ("type" );
204+ }
205+
206+ @ Test
207+ void numberQueryParamIsCoercedFromStringBeforeValidation () throws Exception {
208+ var numSchema = new NumberSchema (Set .of (TypeName .NUMBER ), null , null , null , null , null , null );
209+ var op =
210+ new Operation (
211+ "a" ,
212+ HttpMethod .GET ,
213+ PathTemplate .compile ("/x" ),
214+ Optional .empty (),
215+ List .of (new Parameter ("n" , Parameter .Location .QUERY , true , numSchema )),
216+ Map .of ());
217+ Spec spec = specWith (op );
218+ Filter f = newFilter (spec );
219+
220+ HttpExchange ex = exchange ("GET" , "/x?n=1.5" , new byte [0 ]);
221+ Filter .Chain chain = mock (Filter .Chain .class );
222+ f .doFilter (ex , chain );
223+ Mockito .verify (chain ).doFilter (ex );
224+ }
225+
226+ @ Test
227+ void numberQueryParamRejectsNonNumericString () {
228+ var numSchema = new NumberSchema (Set .of (TypeName .NUMBER ), null , null , null , null , null , null );
229+ var op =
230+ new Operation (
231+ "a" ,
232+ HttpMethod .GET ,
233+ PathTemplate .compile ("/x" ),
234+ Optional .empty (),
235+ List .of (new Parameter ("n" , Parameter .Location .QUERY , true , numSchema )),
236+ Map .of ());
237+ Spec spec = specWith (op );
238+ Filter f = newFilter (spec );
239+
240+ HttpExchange ex = exchange ("GET" , "/x?n=abc" , new byte [0 ]);
241+ assertThatThrownBy (() -> f .doFilter (ex , mock (Filter .Chain .class )))
242+ .isInstanceOf (ValidationException .class )
243+ .extracting (t -> ((ValidationException ) t ).error ().keyword ())
244+ .isEqualTo ("type" );
245+ }
246+
247+ @ Test
248+ void booleanQueryParamCoercesTrueAndFalse () throws Exception {
249+ var boolSchema = new BooleanSchema (Set .of (TypeName .BOOLEAN ));
250+ var op =
251+ new Operation (
252+ "a" ,
253+ HttpMethod .GET ,
254+ PathTemplate .compile ("/x" ),
255+ Optional .empty (),
256+ List .of (new Parameter ("b" , Parameter .Location .QUERY , true , boolSchema )),
257+ Map .of ());
258+ Spec spec = specWith (op );
259+ Filter f = newFilter (spec );
260+
261+ Filter .Chain trueChain = mock (Filter .Chain .class );
262+ Filter .Chain falseChain = mock (Filter .Chain .class );
263+ HttpExchange trueEx = exchange ("GET" , "/x?b=true" , new byte [0 ]);
264+ HttpExchange falseEx = exchange ("GET" , "/x?b=false" , new byte [0 ]);
265+ f .doFilter (trueEx , trueChain );
266+ f .doFilter (falseEx , falseChain );
267+ Mockito .verify (trueChain ).doFilter (trueEx );
268+ Mockito .verify (falseChain ).doFilter (falseEx );
269+ }
270+
271+ @ Test
272+ void booleanQueryParamRejectsNonBooleanString () {
273+ var boolSchema = new BooleanSchema (Set .of (TypeName .BOOLEAN ));
274+ var op =
275+ new Operation (
276+ "a" ,
277+ HttpMethod .GET ,
278+ PathTemplate .compile ("/x" ),
279+ Optional .empty (),
280+ List .of (new Parameter ("b" , Parameter .Location .QUERY , true , boolSchema )),
281+ Map .of ());
282+ Spec spec = specWith (op );
283+ Filter f = newFilter (spec );
284+
285+ HttpExchange ex = exchange ("GET" , "/x?b=yes" , new byte [0 ]);
286+ assertThatThrownBy (() -> f .doFilter (ex , mock (Filter .Chain .class )))
287+ .isInstanceOf (ValidationException .class )
288+ .extracting (t -> ((ValidationException ) t ).error ().keyword ())
289+ .isEqualTo ("type" );
290+ }
160291}
0 commit comments