1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.spring.web.reactive.multipart;
18
19 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertNotNull;
23 import static org.junit.jupiter.api.Assertions.assertNull;
24 import static org.junit.jupiter.api.Assertions.assertTrue;
25 import static org.mockito.ArgumentMatchers.any;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.when;
28
29 import eu.maxschuster.dataurl.DataUrl;
30 import eu.maxschuster.dataurl.DataUrlBuilder;
31 import eu.maxschuster.dataurl.DataUrlEncoding;
32 import eu.maxschuster.dataurl.DataUrlSerializer;
33 import java.io.File;
34 import java.io.IOException;
35 import java.net.MalformedURLException;
36 import java.nio.charset.StandardCharsets;
37 import java.nio.file.Files;
38 import java.nio.file.Path;
39 import java.nio.file.StandardOpenOption;
40 import java.util.List;
41 import java.util.UUID;
42 import org.bremersee.spring.web.multipart.FileAwareMultipartFile;
43 import org.junit.jupiter.api.Test;
44 import org.springframework.http.HttpHeaders;
45 import org.springframework.http.MediaType;
46 import org.springframework.http.codec.multipart.FilePart;
47 import org.springframework.http.codec.multipart.FormFieldPart;
48 import org.springframework.http.codec.multipart.Part;
49 import org.springframework.util.LinkedMultiValueMap;
50 import org.springframework.util.MultiValueMap;
51 import org.springframework.web.multipart.MultipartException;
52 import org.springframework.web.multipart.MultipartFile;
53 import reactor.core.publisher.Flux;
54 import reactor.core.publisher.Mono;
55 import reactor.test.StepVerifier;
56
57
58
59
60
61
62 class MultipartFileBuilderImplTest {
63
64
65
66
67 @Test
68 void buildWithNull() {
69 MultipartFileBuilder builder = new MultipartFileBuilderImpl();
70 StepVerifier
71 .create(builder.build((Part) null))
72 .assertNext(multipartFile -> {
73 assertTrue(multipartFile.isEmpty());
74 assertNull(multipartFile.getContentType());
75 assertNull(multipartFile.getOriginalFilename());
76 assertEquals(0L, multipartFile.getSize());
77 })
78 .verifyComplete();
79 }
80
81
82
83
84 @Test
85 void buildWithFilePart() {
86 MultipartFileBuilder builder = new MultipartFileBuilderImpl(
87 System.getProperty("java.io.tmpdir"));
88 final byte[] value = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8);
89 StepVerifier
90 .create(builder.build(createFilePart(value, "file", MediaType.TEXT_PLAIN, "test.txt")))
91 .assertNext(multipartFile -> {
92 try {
93 assertFalse(multipartFile.isEmpty());
94 assertEquals("file", multipartFile.getName());
95 assertEquals(MediaType.TEXT_PLAIN_VALUE, multipartFile.getContentType());
96 assertEquals("test.txt", multipartFile.getOriginalFilename());
97 assertEquals(value.length, (int) multipartFile.getSize());
98 assertArrayEquals(value, multipartFile.getBytes());
99
100 } catch (IOException e) {
101 throw new MultipartException("Fatal error", e);
102 } finally {
103 FileAwareMultipartFile.delete(multipartFile);
104 }
105 })
106 .verifyComplete();
107 }
108
109
110
111
112
113
114 @Test
115 void buildWithFormFieldPart() throws MalformedURLException {
116 MultipartFileBuilder builder = new MultipartFileBuilderImpl(
117 new File(System.getProperty("java.io.tmpdir")));
118 final byte[] value = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8);
119 DataUrl dataUrl = new DataUrlBuilder()
120 .setData(value)
121 .setCharset(StandardCharsets.UTF_8.name())
122 .setEncoding(DataUrlEncoding.BASE64)
123 .setMimeType(MediaType.IMAGE_PNG_VALUE)
124 .build();
125 StepVerifier
126 .create(
127 builder.build(createFormFieldPart(new DataUrlSerializer().serialize(dataUrl), "file")))
128 .assertNext(multipartFile -> {
129 try {
130 assertFalse(multipartFile.isEmpty());
131 assertEquals("file", multipartFile.getName());
132 assertEquals(MediaType.IMAGE_PNG_VALUE, multipartFile.getContentType());
133 assertEquals(value.length, (int) multipartFile.getSize());
134 assertArrayEquals(value, multipartFile.getBytes());
135 } catch (IOException e) {
136 throw new MultipartException("Fatal error", e);
137 } finally {
138 FileAwareMultipartFile.delete(multipartFile);
139 }
140 })
141 .verifyComplete();
142 }
143
144
145
146
147 @Test
148 void buildFromFlux() {
149 MultipartFileBuilder builder = new MultipartFileBuilderImpl();
150 final byte[] value = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8);
151 StepVerifier.create(builder
152 .build(Flux.just(createFilePart(value, "file", MediaType.TEXT_PLAIN, "test.txt"))))
153 .assertNext(multipartFile -> {
154 try {
155 assertFalse(multipartFile.isEmpty());
156 assertEquals("file", multipartFile.getName());
157 assertEquals(MediaType.TEXT_PLAIN_VALUE, multipartFile.getContentType());
158 assertEquals("test.txt", multipartFile.getOriginalFilename());
159 assertEquals(value.length, (int) multipartFile.getSize());
160 assertArrayEquals(value, multipartFile.getBytes());
161
162 } catch (IOException e) {
163 throw new MultipartException("Fatal error", e);
164 } finally {
165 FileAwareMultipartFile.delete(multipartFile);
166 }
167 })
168 .verifyComplete();
169
170 StepVerifier.create(builder.build(Flux.empty()))
171 .assertNext(multipartFile -> assertTrue(multipartFile.isEmpty()))
172 .verifyComplete();
173 }
174
175
176
177
178
179
180 @Test
181 void buildListFromFlux() throws IOException {
182 final byte[] content0 = "image-content".getBytes(StandardCharsets.UTF_8);
183 Part part1 = createFilePart(content0, "part1", MediaType.IMAGE_JPEG, "img.jpg");
184 final byte[] content1 = "text".getBytes(StandardCharsets.UTF_8);
185 DataUrl dataUrl = new DataUrlBuilder()
186 .setData(content1)
187 .setCharset(StandardCharsets.UTF_8.name())
188 .setEncoding(DataUrlEncoding.BASE64)
189 .setMimeType("text/plain")
190 .build();
191 Part part2 = createFormFieldPart(new DataUrlSerializer().serialize(dataUrl), "part2");
192
193 MultipartFileBuilder builder = new MultipartFileBuilderImpl();
194 StepVerifier.create(builder
195 .buildList(Flux.just(part1, part2)))
196 .assertNext(multipartFiles -> {
197 try {
198 assertEquals(2, multipartFiles.size());
199
200 MultipartFile obj0 = MultipartFileBuilder.getMultipartFile(multipartFiles, 0);
201 assertNotNull(obj0);
202 assertEquals("part1", obj0.getName());
203 assertEquals(MediaType.IMAGE_JPEG_VALUE, obj0.getContentType());
204 assertEquals(content0.length, (int) obj0.getSize());
205 assertArrayEquals(content0, obj0.getBytes());
206
207 MultipartFile obj1 = MultipartFileBuilder.getMultipartFile(multipartFiles, 1);
208 assertNotNull(obj1);
209 assertEquals("part2", obj1.getName());
210 assertEquals(MediaType.TEXT_PLAIN_VALUE, obj1.getContentType());
211 assertEquals(content1.length, (int) obj1.getSize());
212 assertArrayEquals(content1, obj1.getBytes());
213
214 } catch (IOException e) {
215 throw new MultipartException("Internal error", e);
216 } finally {
217 multipartFiles.forEach(FileAwareMultipartFile::delete);
218 }
219 })
220 .verifyComplete();
221 }
222
223
224
225
226
227
228 @Test
229 void buildMapFromFluxArray() throws IOException {
230 final byte[] content0 = "image-content".getBytes(StandardCharsets.UTF_8);
231 Part part1 = createFilePart(content0, "part1", MediaType.IMAGE_JPEG, "img.jpg");
232 final byte[] content1 = "text".getBytes(StandardCharsets.UTF_8);
233 DataUrl dataUrl = new DataUrlBuilder()
234 .setData(content1)
235 .setCharset(StandardCharsets.UTF_8.name())
236 .setEncoding(DataUrlEncoding.BASE64)
237 .setMimeType("text/plain")
238 .build();
239 Part part2 = createFormFieldPart(new DataUrlSerializer().serialize(dataUrl), "part2");
240
241 MultipartFileBuilder builder = new MultipartFileBuilderImpl();
242
243 StepVerifier.create(builder.buildMap(Flux.just(part1), Flux.just(part2)))
244 .assertNext(multipartFileMap -> {
245 try {
246 assertEquals(2, multipartFileMap.size());
247
248 MultipartFile obj0 = MultipartFileBuilder.getMultipartFile(multipartFileMap, "part1");
249 assertNotNull(obj0);
250 assertEquals(MediaType.IMAGE_JPEG_VALUE, obj0.getContentType());
251 assertEquals(content0.length, (int) obj0.getSize());
252 assertArrayEquals(content0, obj0.getBytes());
253
254 MultipartFile obj1 = MultipartFileBuilder.getMultipartFile(multipartFileMap, "part2");
255 assertNotNull(obj1);
256 assertEquals(MediaType.TEXT_PLAIN_VALUE, obj1.getContentType());
257 assertEquals(content1.length, (int) obj1.getSize());
258 assertArrayEquals(content1, obj1.getBytes());
259
260 } catch (IOException e) {
261 throw new MultipartException("Internal error", e);
262 } finally {
263 multipartFileMap.values().forEach(FileAwareMultipartFile::delete);
264 }
265 })
266 .verifyComplete();
267 }
268
269
270
271
272
273
274 @Test
275 void buildMultiValueMapFromFluxArray() throws IOException {
276 final byte[] content0 = "image-content".getBytes(StandardCharsets.UTF_8);
277 Part part1 = createFilePart(content0, "part1", MediaType.IMAGE_JPEG, "img.jpg");
278 final byte[] content1 = "text".getBytes(StandardCharsets.UTF_8);
279 DataUrl dataUrl = new DataUrlBuilder()
280 .setData(content1)
281 .setCharset(StandardCharsets.UTF_8.name())
282 .setEncoding(DataUrlEncoding.BASE64)
283 .setMimeType("text/plain")
284 .build();
285 Part part2 = createFormFieldPart(new DataUrlSerializer().serialize(dataUrl), "part2");
286
287 MultipartFileBuilder builder = new MultipartFileBuilderImpl();
288
289 StepVerifier.create(builder.buildMultiValueMap(Flux.just(part1), Flux.just(part2)))
290 .assertNext(multipartFileMap -> {
291 try {
292 assertEquals(2, multipartFileMap.size());
293
294 MultipartFile obj0 = MultipartFileBuilder
295 .getFirstMultipartFile(multipartFileMap, "part1");
296 assertNotNull(obj0);
297 assertEquals(MediaType.IMAGE_JPEG_VALUE, obj0.getContentType());
298 assertEquals(content0.length, (int) obj0.getSize());
299 assertArrayEquals(content0, obj0.getBytes());
300
301 MultipartFile obj1 = MultipartFileBuilder
302 .getFirstMultipartFile(multipartFileMap, "part2");
303 assertNotNull(obj1);
304 assertEquals(MediaType.TEXT_PLAIN_VALUE, obj1.getContentType());
305 assertEquals(content1.length, (int) obj1.getSize());
306 assertArrayEquals(content1, obj1.getBytes());
307
308 } catch (IOException e) {
309 throw new MultipartException("Internal error", e);
310 } finally {
311 multipartFileMap.values()
312 .forEach(multipartFiles -> multipartFiles.forEach(FileAwareMultipartFile::delete));
313 }
314 })
315 .verifyComplete();
316 }
317
318
319
320
321
322
323 @Test
324 void buildList() throws IOException {
325 MultiValueMap<String, Part> multiPartData = new LinkedMultiValueMap<>();
326 final byte[] content0 = "image-content".getBytes(StandardCharsets.UTF_8);
327 multiPartData.set(
328 "part1",
329 createFilePart(content0, "part1", MediaType.IMAGE_JPEG, "img.jpg"));
330 final byte[] content1 = "text".getBytes(StandardCharsets.UTF_8);
331 DataUrl dataUrl = new DataUrlBuilder()
332 .setData(content1)
333 .setCharset(StandardCharsets.UTF_8.name())
334 .setEncoding(DataUrlEncoding.BASE64)
335 .setMimeType("text/plain")
336 .build();
337 multiPartData.set(
338 "part2",
339 createFormFieldPart(new DataUrlSerializer().serialize(dataUrl), "part2"));
340
341 MultipartFileBuilder builder = new MultipartFileBuilderImpl();
342 StepVerifier.create(builder
343 .buildList(multiPartData, "part1", "part2"))
344 .assertNext(multipartFiles -> {
345 try {
346 assertEquals(2, multipartFiles.size());
347
348 MultipartFile obj0 = MultipartFileBuilder.getMultipartFile(multipartFiles, 0);
349 assertNotNull(obj0);
350 assertEquals("part1", obj0.getName());
351 assertEquals(MediaType.IMAGE_JPEG_VALUE, obj0.getContentType());
352 assertEquals(content0.length, (int) obj0.getSize());
353 assertArrayEquals(content0, obj0.getBytes());
354
355 MultipartFile obj1 = MultipartFileBuilder.getMultipartFile(multipartFiles, 1);
356 assertNotNull(obj1);
357 assertEquals("part2", obj1.getName());
358 assertEquals(MediaType.TEXT_PLAIN_VALUE, obj1.getContentType());
359 assertEquals(content1.length, (int) obj1.getSize());
360 assertArrayEquals(content1, obj1.getBytes());
361
362 } catch (IOException e) {
363 throw new MultipartException("Internal error", e);
364 } finally {
365 multipartFiles.forEach(FileAwareMultipartFile::delete);
366 }
367 })
368 .verifyComplete();
369 }
370
371
372
373
374
375
376 @Test
377 void buildLists() throws IOException {
378 MultiValueMap<String, Part> multiPartData = new LinkedMultiValueMap<>();
379 final byte[] content0 = "image-content".getBytes(StandardCharsets.UTF_8);
380 multiPartData.add(
381 "part",
382 createFilePart(content0, "part", MediaType.IMAGE_JPEG, "img.jpg"));
383 final byte[] content1 = "text".getBytes(StandardCharsets.UTF_8);
384 DataUrl dataUrl = new DataUrlBuilder()
385 .setData(content1)
386 .setCharset(StandardCharsets.UTF_8.name())
387 .setEncoding(DataUrlEncoding.BASE64)
388 .setMimeType("text/plain")
389 .build();
390 multiPartData.add(
391 "part",
392 createFormFieldPart(new DataUrlSerializer().serialize(dataUrl), "part"));
393
394 MultipartFileBuilder builder = new MultipartFileBuilderImpl();
395 StepVerifier
396 .create(builder.buildLists(multiPartData, "part"))
397 .assertNext(multipartFiles -> {
398 try {
399 assertEquals(2, multipartFiles.size());
400
401 MultipartFile obj0 = MultipartFileBuilder.getMultipartFile(multipartFiles, 0);
402 assertNotNull(obj0);
403 assertEquals("part", obj0.getName());
404 assertEquals(MediaType.IMAGE_JPEG_VALUE, obj0.getContentType());
405 assertEquals(content0.length, (int) obj0.getSize());
406 assertArrayEquals(content0, obj0.getBytes());
407
408 MultipartFile obj1 = MultipartFileBuilder.getMultipartFile(multipartFiles, 1);
409 assertNotNull(obj1);
410 assertEquals("part", obj1.getName());
411 assertEquals(MediaType.TEXT_PLAIN_VALUE, obj1.getContentType());
412 assertEquals(content1.length, (int) obj1.getSize());
413 assertArrayEquals(content1, obj1.getBytes());
414
415 } catch (IOException e) {
416 throw new MultipartException("Internal error", e);
417 } finally {
418 multipartFiles.forEach(FileAwareMultipartFile::delete);
419 }
420 })
421 .verifyComplete();
422 }
423
424
425
426
427
428
429 @Test
430 void buildMap() throws IOException {
431 MultiValueMap<String, Part> multiPartData = new LinkedMultiValueMap<>();
432 final byte[] content0 = "image-content".getBytes(StandardCharsets.UTF_8);
433 multiPartData.set(
434 "part1",
435 createFilePart(content0, "part1", MediaType.IMAGE_JPEG, "img.jpg"));
436 final byte[] content1 = "text".getBytes(StandardCharsets.UTF_8);
437 DataUrl dataUrl = new DataUrlBuilder()
438 .setData(content1)
439 .setCharset(StandardCharsets.UTF_8.name())
440 .setEncoding(DataUrlEncoding.BASE64)
441 .setMimeType("text/plain")
442 .build();
443 multiPartData.set(
444 "part2",
445 createFormFieldPart(new DataUrlSerializer().serialize(dataUrl), "part2"));
446
447 MultipartFileBuilder builder = new MultipartFileBuilderImpl();
448 StepVerifier.create(builder
449 .buildMap(multiPartData, "part1", "part2"))
450 .assertNext(multipartFileMap -> {
451 try {
452 assertEquals(2, multipartFileMap.size());
453
454 MultipartFile obj0 = MultipartFileBuilder.getMultipartFile(multipartFileMap, "part1");
455 assertNotNull(obj0);
456 assertEquals(MediaType.IMAGE_JPEG_VALUE, obj0.getContentType());
457 assertEquals(content0.length, (int) obj0.getSize());
458 assertArrayEquals(content0, obj0.getBytes());
459
460 MultipartFile obj1 = MultipartFileBuilder.getMultipartFile(multipartFileMap, "part2");
461 assertNotNull(obj1);
462 assertEquals(MediaType.TEXT_PLAIN_VALUE, obj1.getContentType());
463 assertEquals(content1.length, (int) obj1.getSize());
464 assertArrayEquals(content1, obj1.getBytes());
465
466 } catch (IOException e) {
467 throw new MultipartException("Internal error", e);
468 } finally {
469 multipartFileMap.values().forEach(FileAwareMultipartFile::delete);
470 }
471 })
472 .verifyComplete();
473 }
474
475
476
477
478
479
480 @Test
481 void buildMultiValueMap() throws IOException {
482 MultiValueMap<String, Part> multiPartData = new LinkedMultiValueMap<>();
483 final byte[] content0 = "image-content".getBytes(StandardCharsets.UTF_8);
484 multiPartData.add(
485 "part",
486 createFilePart(content0, "part", MediaType.IMAGE_JPEG, "img.jpg"));
487 final byte[] content1 = "text".getBytes(StandardCharsets.UTF_8);
488 DataUrl dataUrl = new DataUrlBuilder()
489 .setData(content1)
490 .setCharset(StandardCharsets.UTF_8.name())
491 .setEncoding(DataUrlEncoding.BASE64)
492 .setMimeType("text/plain")
493 .build();
494 multiPartData.add(
495 "part",
496 createFormFieldPart(new DataUrlSerializer().serialize(dataUrl), "part"));
497
498 MultipartFileBuilder builder = new MultipartFileBuilderImpl();
499 StepVerifier
500 .create(builder.buildMultiValueMap(multiPartData, "part"))
501 .assertNext(map -> {
502 List<MultipartFile> multipartFiles = MultipartFileBuilder.getMultipartFiles(map, "part");
503 assertFalse(multipartFiles.isEmpty());
504 try {
505 assertEquals(2, multipartFiles.size());
506
507 MultipartFile obj0 = MultipartFileBuilder.getMultipartFile(multipartFiles, 0);
508 assertNotNull(obj0);
509 assertEquals("part", obj0.getName());
510 assertEquals(MediaType.IMAGE_JPEG_VALUE, obj0.getContentType());
511 assertEquals(content0.length, (int) obj0.getSize());
512 assertArrayEquals(content0, obj0.getBytes());
513
514 MultipartFile obj1 = MultipartFileBuilder.getMultipartFile(multipartFiles, 1);
515 assertNotNull(obj1);
516 assertEquals("part", obj1.getName());
517 assertEquals(MediaType.TEXT_PLAIN_VALUE, obj1.getContentType());
518 assertEquals(content1.length, (int) obj1.getSize());
519 assertArrayEquals(content1, obj1.getBytes());
520
521 } catch (IOException e) {
522 throw new MultipartException("Internal error", e);
523 } finally {
524 multipartFiles.forEach(FileAwareMultipartFile::delete);
525 }
526 })
527 .verifyComplete();
528 }
529
530 private FilePart createFilePart(byte[] content, String parameterName, MediaType contentType,
531 String filename) {
532 FilePart part = mock(FilePart.class);
533 when(part.name()).thenReturn(parameterName);
534 when(part.filename()).thenReturn(filename);
535 when(part.transferTo(any(File.class))).then(invocationOnMock -> {
536 try {
537 File file = invocationOnMock.getArgument(0);
538 file.deleteOnExit();
539 Files.write(
540 file.toPath(),
541 content,
542 StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
543 } catch (Exception e) {
544 return Mono.error(e);
545 }
546 return Mono.empty();
547 });
548 when(part.transferTo(any(Path.class))).then(invocationOnMock -> {
549 try {
550 Path file = invocationOnMock.getArgument(0);
551 file.toFile().deleteOnExit();
552 Files.write(
553 file,
554 content,
555 StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
556 } catch (Exception e) {
557 return Mono.error(e);
558 }
559 return Mono.empty();
560 });
561 HttpHeaders httpHeaders = new HttpHeaders();
562 httpHeaders.setContentType(contentType);
563 when(part.headers()).thenReturn(httpHeaders);
564 return part;
565 }
566
567 private FormFieldPart createFormFieldPart(String content, String parameterName) {
568 FormFieldPart part = mock(FormFieldPart.class);
569 when(part.name()).thenReturn(parameterName);
570 when(part.value()).thenReturn(content);
571 return part;
572 }
573
574 }