1616
1717package org .springframework .integration .http .dsl ;
1818
19+ import static org .assertj .core .api .Assertions .assertThat ;
1920import static org .springframework .security .test .web .servlet .request .SecurityMockMvcRequestPostProcessors .httpBasic ;
2021import static org .springframework .security .test .web .servlet .setup .SecurityMockMvcConfigurers .springSecurity ;
2122import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .get ;
23+ import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .multipart ;
2224import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .content ;
2325import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .status ;
2426
2527import java .nio .charset .Charset ;
28+ import java .nio .charset .StandardCharsets ;
2629import java .util .Collections ;
2730import java .util .List ;
31+ import java .util .Map ;
2832
2933import org .junit .Before ;
3034import org .junit .Test ;
3135import org .junit .runner .RunWith ;
3236
3337import org .springframework .beans .factory .annotation .Autowired ;
38+ import org .springframework .beans .factory .annotation .Qualifier ;
3439import org .springframework .context .annotation .Bean ;
3540import org .springframework .context .annotation .Configuration ;
41+ import org .springframework .http .MediaType ;
3642import org .springframework .http .ResponseEntity ;
3743import org .springframework .http .client .ClientHttpRequestFactory ;
3844import org .springframework .http .client .ClientHttpResponse ;
4147import org .springframework .integration .dsl .IntegrationFlow ;
4248import org .springframework .integration .dsl .IntegrationFlows ;
4349import org .springframework .integration .dsl .context .IntegrationFlowContext ;
50+ import org .springframework .integration .http .multipart .UploadedMultipartFile ;
4451import org .springframework .integration .http .outbound .HttpRequestExecutingMessageHandler ;
4552import org .springframework .integration .security .channel .ChannelSecurityInterceptor ;
4653import org .springframework .integration .security .channel .SecuredChannel ;
54+ import org .springframework .messaging .Message ;
4755import org .springframework .messaging .MessageChannel ;
56+ import org .springframework .messaging .PollableChannel ;
57+ import org .springframework .mock .web .MockPart ;
4858import org .springframework .security .access .AccessDecisionManager ;
4959import org .springframework .security .access .vote .AffirmativeBased ;
5060import org .springframework .security .access .vote .RoleVoter ;
6474import org .springframework .web .client .DefaultResponseErrorHandler ;
6575import org .springframework .web .client .HttpClientErrorException ;
6676import org .springframework .web .context .WebApplicationContext ;
77+ import org .springframework .web .multipart .MultipartResolver ;
78+ import org .springframework .web .multipart .support .StandardServletMultipartResolver ;
79+ import org .springframework .web .servlet .DispatcherServlet ;
6780
6881/**
6982 * @author Artem Bilan
@@ -107,20 +120,14 @@ public void testHttpProxyFlow() throws Exception {
107120 get ("/service" )
108121 .with (httpBasic ("admin" , "admin" ))
109122 .param ("name" , "foo" ))
110- .andExpect (
111- content ()
112- .string ("FOO" ));
123+ .andExpect (content ().string ("FOO" ));
113124
114125 this .mockMvc .perform (
115126 get ("/service" )
116127 .with (httpBasic ("user" , "user" ))
117128 .param ("name" , "name" ))
118- .andExpect (
119- status ()
120- .isForbidden ())
121- .andExpect (
122- content ()
123- .string ("Error" ));
129+ .andExpect (status ().isForbidden ())
130+ .andExpect (content ().string ("Error" ));
124131 }
125132
126133 @ Test
@@ -137,21 +144,59 @@ public void testDynamicHttpEndpoint() throws Exception {
137144
138145 this .mockMvc .perform (
139146 get ("/dynamic" )
140- .with (httpBasic ("admin " , "admin " ))
147+ .with (httpBasic ("user " , "user " ))
141148 .param ("name" , "BAR" ))
142- .andExpect (
143- content ()
144- .string ("bar" ));
149+ .andExpect (content ().string ("bar" ));
145150
146151 flowRegistration .destroy ();
147152
148153 this .mockMvc .perform (
149154 get ("/dynamic" )
150- .with (httpBasic ("admin " , "admin " ))
155+ .with (httpBasic ("user " , "user " ))
151156 .param ("name" , "BAZ" ))
152- .andExpect (
153- status ()
154- .isNotFound ());
157+ .andExpect (status ().isNotFound ());
158+ }
159+
160+ @ Autowired
161+ @ Qualifier ("multiPartFilesChannel" )
162+ private PollableChannel multiPartFilesChannel ;
163+
164+ @ Test
165+ @ SuppressWarnings ("unchecked" )
166+ public void testMultiPartFiles () throws Exception {
167+ MockPart mockPart1 = new MockPart ("a1" , "file1" , "ABC" .getBytes (StandardCharsets .UTF_8 ));
168+ mockPart1 .getHeaders ().setContentType (MediaType .TEXT_PLAIN );
169+ MockPart mockPart2 = new MockPart ("a1" , "file2" , "DEF" .getBytes (StandardCharsets .UTF_8 ));
170+ mockPart2 .getHeaders ().setContentType (MediaType .TEXT_PLAIN );
171+ this .mockMvc .perform (
172+ multipart ("/multiPartFiles" )
173+ .part (mockPart1 , mockPart2 )
174+ .with (httpBasic ("user" , "user" )))
175+ .andExpect (status ().isOk ());
176+
177+ Message <?> result = this .multiPartFilesChannel .receive (10_000 );
178+
179+ assertThat (result )
180+ .isNotNull ()
181+ .extracting (Message ::getPayload )
182+ .satisfies ((payload ) ->
183+ assertThat ((Map <String , ?>) payload )
184+ .hasSize (1 )
185+ .extracting ((map ) -> map .get ("a1" ))
186+ .asList ()
187+ .hasSize (2 )
188+ .satisfies ((list ) -> {
189+ assertThat (list )
190+ .element (0 )
191+ .extracting ((file ) ->
192+ ((UploadedMultipartFile ) file ).getOriginalFilename ())
193+ .isEqualTo ("file1" );
194+ assertThat (list )
195+ .element (1 )
196+ .extracting ((file ) ->
197+ ((UploadedMultipartFile ) file ).getOriginalFilename ())
198+ .isEqualTo ("file2" );
199+ }));
155200 }
156201
157202 @ Configuration
@@ -237,6 +282,19 @@ public IntegrationFlow httpProxyErrorFlow() {
237282 new ResponseEntity <>(p .getResponseBodyAsString (), p .getStatusCode ()));
238283 }
239284
285+ @ Bean
286+ public IntegrationFlow multiPartFilesFlow () {
287+ return IntegrationFlows
288+ .from (Http .inboundChannelAdapter ("/multiPartFiles" ))
289+ .channel ((c ) -> c .queue ("multiPartFilesChannel" ))
290+ .get ();
291+ }
292+
293+ @ Bean (name = DispatcherServlet .MULTIPART_RESOLVER_BEAN_NAME )
294+ public MultipartResolver multipartResolver () {
295+ return new StandardServletMultipartResolver ();
296+ }
297+
240298 @ Bean
241299 public AccessDecisionManager accessDecisionManager () {
242300 return new AffirmativeBased (Collections .singletonList (new RoleVoter ()));
0 commit comments