View Javadoc
1   /*
2    * Copyright 2020-2026 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.minio;
18  
19  import static org.awaitility.Awaitility.await;
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  import static org.junit.jupiter.api.Assertions.fail;
28  import static org.mockito.ArgumentMatchers.any;
29  import static org.mockito.Mockito.mock;
30  import static org.mockito.Mockito.when;
31  
32  import io.minio.BucketExistsArgs;
33  import io.minio.CloseableIterator;
34  import io.minio.ComposeObjectArgs;
35  import io.minio.CopyObjectArgs;
36  import io.minio.DeleteBucketCorsArgs;
37  import io.minio.DeleteBucketEncryptionArgs;
38  import io.minio.DeleteBucketLifecycleArgs;
39  import io.minio.DeleteBucketNotificationArgs;
40  import io.minio.DeleteBucketPolicyArgs;
41  import io.minio.DeleteBucketReplicationArgs;
42  import io.minio.DeleteBucketTagsArgs;
43  import io.minio.DeleteObjectLockConfigurationArgs;
44  import io.minio.DeleteObjectTagsArgs;
45  import io.minio.DisableObjectLegalHoldArgs;
46  import io.minio.DownloadObjectArgs;
47  import io.minio.EnableObjectLegalHoldArgs;
48  import io.minio.GetBucketCorsArgs;
49  import io.minio.GetBucketEncryptionArgs;
50  import io.minio.GetBucketLifecycleArgs;
51  import io.minio.GetBucketNotificationArgs;
52  import io.minio.GetBucketPolicyArgs;
53  import io.minio.GetBucketReplicationArgs;
54  import io.minio.GetBucketTagsArgs;
55  import io.minio.GetBucketVersioningArgs;
56  import io.minio.GetObjectAclArgs;
57  import io.minio.GetObjectArgs;
58  import io.minio.GetObjectAttributesArgs;
59  import io.minio.GetObjectAttributesResponse;
60  import io.minio.GetObjectLockConfigurationArgs;
61  import io.minio.GetObjectResponse;
62  import io.minio.GetObjectRetentionArgs;
63  import io.minio.GetObjectTagsArgs;
64  import io.minio.GetPresignedObjectUrlArgs;
65  import io.minio.HeadObjectResponse;
66  import io.minio.Http;
67  import io.minio.IsObjectLegalHoldEnabledArgs;
68  import io.minio.ListBucketsArgs;
69  import io.minio.ListObjectsArgs;
70  import io.minio.ListenBucketNotificationArgs;
71  import io.minio.MakeBucketArgs;
72  import io.minio.MinioClient;
73  import io.minio.ObjectWriteResponse;
74  import io.minio.PostPolicy;
75  import io.minio.PromptObjectArgs;
76  import io.minio.PromptObjectResponse;
77  import io.minio.PutObjectArgs;
78  import io.minio.PutObjectFanOutArgs;
79  import io.minio.PutObjectFanOutResponse;
80  import io.minio.RemoveBucketArgs;
81  import io.minio.RemoveObjectArgs;
82  import io.minio.RemoveObjectsArgs;
83  import io.minio.RestoreObjectArgs;
84  import io.minio.Result;
85  import io.minio.SelectObjectContentArgs;
86  import io.minio.SelectResponseStream;
87  import io.minio.SetBucketCorsArgs;
88  import io.minio.SetBucketEncryptionArgs;
89  import io.minio.SetBucketLifecycleArgs;
90  import io.minio.SetBucketNotificationArgs;
91  import io.minio.SetBucketPolicyArgs;
92  import io.minio.SetBucketReplicationArgs;
93  import io.minio.SetBucketTagsArgs;
94  import io.minio.SetBucketVersioningArgs;
95  import io.minio.SetObjectLockConfigurationArgs;
96  import io.minio.SetObjectRetentionArgs;
97  import io.minio.SetObjectTagsArgs;
98  import io.minio.SourceObject;
99  import io.minio.StatObjectArgs;
100 import io.minio.StatObjectResponse;
101 import io.minio.Time;
102 import io.minio.UploadObjectArgs;
103 import io.minio.errors.ErrorResponseException;
104 import io.minio.messages.AccessControlList;
105 import io.minio.messages.AccessControlList.Grant;
106 import io.minio.messages.AccessControlList.Grantee;
107 import io.minio.messages.AccessControlList.Permission;
108 import io.minio.messages.AccessControlList.Type;
109 import io.minio.messages.AccessControlPolicy;
110 import io.minio.messages.CORSConfiguration;
111 import io.minio.messages.CORSConfiguration.CORSRule;
112 import io.minio.messages.DeleteRequest.Object;
113 import io.minio.messages.DeleteResult;
114 import io.minio.messages.ErrorResponse;
115 import io.minio.messages.Filter;
116 import io.minio.messages.GetObjectAttributesOutput;
117 import io.minio.messages.InputSerialization;
118 import io.minio.messages.InputSerialization.JsonType;
119 import io.minio.messages.Item;
120 import io.minio.messages.LifecycleConfiguration;
121 import io.minio.messages.ListAllMyBucketsResult;
122 import io.minio.messages.NotificationConfiguration;
123 import io.minio.messages.NotificationRecords;
124 import io.minio.messages.ObjectLockConfiguration;
125 import io.minio.messages.OutputSerialization;
126 import io.minio.messages.Owner;
127 import io.minio.messages.ReplicationConfiguration;
128 import io.minio.messages.Retention;
129 import io.minio.messages.RetentionMode;
130 import io.minio.messages.SseConfiguration;
131 import io.minio.messages.Status;
132 import io.minio.messages.Tags;
133 import io.minio.messages.VersioningConfiguration;
134 import java.io.ByteArrayInputStream;
135 import java.io.File;
136 import java.io.InputStream;
137 import java.net.URI;
138 import java.nio.charset.StandardCharsets;
139 import java.nio.file.Files;
140 import java.nio.file.StandardOpenOption;
141 import java.text.SimpleDateFormat;
142 import java.time.ZonedDateTime;
143 import java.util.ArrayList;
144 import java.util.Collections;
145 import java.util.Date;
146 import java.util.HashMap;
147 import java.util.LinkedList;
148 import java.util.List;
149 import java.util.Locale;
150 import java.util.Map;
151 import java.util.Optional;
152 import java.util.Random;
153 import java.util.UUID;
154 import okhttp3.Headers;
155 import okhttp3.HttpUrl;
156 import okhttp3.Protocol;
157 import okhttp3.Request;
158 import okhttp3.Response;
159 import org.apache.commons.logging.Log;
160 import org.apache.commons.logging.LogFactory;
161 import org.assertj.core.api.Assertions;
162 import org.junit.jupiter.api.AfterAll;
163 import org.junit.jupiter.api.BeforeAll;
164 import org.junit.jupiter.api.BeforeEach;
165 import org.junit.jupiter.api.MethodOrderer;
166 import org.junit.jupiter.api.Order;
167 import org.junit.jupiter.api.Test;
168 import org.junit.jupiter.api.TestInstance;
169 import org.junit.jupiter.api.TestInstance.Lifecycle;
170 import org.junit.jupiter.api.TestMethodOrder;
171 import org.mockito.Mockito;
172 import org.springframework.http.HttpStatus;
173 import org.springframework.http.MediaType;
174 import org.springframework.util.FileCopyUtils;
175 
176 /**
177  * The minio template test.
178  *
179  * @author Christian Bremer
180  */
181 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
182 @TestInstance(Lifecycle.PER_CLASS) // allows us to use @BeforeAll with a non-static method
183 class MinioTemplateTest {
184 
185   private static final Log log = LogFactory.getLog(MinioTemplateTest.class);
186 
187   private static final SimpleDateFormat SDF = new SimpleDateFormat(
188       "yyyy-MM-dd-HH-mm-ss", Locale.GERMANY);
189 
190   private static final String DEFAULT_BUCKET = "bremersee-"
191       + SDF.format(new Date()) + "-"
192       + UUID.randomUUID().toString().replace("-", "").toLowerCase();
193 
194   private MinioTemplate playMinio;
195 
196   private MinioClient mockClient;
197 
198   private MinioTemplate mockMinio;
199 
200   private boolean playMinioEnabled = true;
201 
202   /**
203    * Sets up.
204    */
205   @BeforeAll
206   void setUp() {
207     String testExecutor = System.getProperty(
208         "org.bremersee.test.executor",
209         "NOT_SPECIFIED");
210     log.info(String.format("JUnit test executor is %s", testExecutor));
211     if ("BUILD_SYSTEM".equals(testExecutor)) {
212       log.info("Test executor is build system, so mocked minio client will be used.");
213       playMinioEnabled = false;
214     } else {
215       log.info("Test executor is unknown, trying to use play minio client ...");
216       try {
217         MinioClient playMinioClient = MinioClient.builder()
218             .endpoint(HttpUrl.get("https://play.min.io"))
219             .credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG")
220             .build();
221         playMinio = new MinioTemplate(playMinioClient);
222         if (playMinio.bucketExists(BucketExistsArgs.builder().bucket(DEFAULT_BUCKET).build())) {
223           playMinio.removeBucket(RemoveBucketArgs.builder().bucket(DEFAULT_BUCKET).build());
224         }
225         playMinio.makeBucket(MakeBucketArgs.builder().bucket(DEFAULT_BUCKET).build());
226         playMinioEnabled = true;
227         log.info("Using play minio client.");
228 
229       } catch (Exception e) {
230         playMinioEnabled = false;
231         log.warn("Using play minio client is not possible. Using mocked minio client.", e);
232       }
233     }
234   }
235 
236   /**
237    * After all.
238    */
239   @AfterAll
240   void afterAll() {
241     if (playMinioEnabled
242         && playMinio.bucketExists(BucketExistsArgs.builder().bucket(DEFAULT_BUCKET).build())) {
243       playMinio.removeBucket(RemoveBucketArgs.builder().bucket(DEFAULT_BUCKET).build());
244     }
245   }
246 
247   /**
248    * Before each.
249    */
250   @BeforeEach
251   void beforeEach() {
252     mockClient = Mockito.mock(MinioClient.class);
253     mockMinio = new MinioTemplate(mockClient);
254   }
255 
256   private String newBucketName() {
257     return "bremersee-" + UUID.randomUUID().toString().toLowerCase();
258   }
259 
260   private MinioTemplate minioTemplate(String methodName, MockMinioClientConfigurator config) {
261     if (playMinioEnabled) {
262       log.info(String.format("Running '%s' with play.min.io", methodName));
263       return playMinio;
264     }
265     log.info(String.format("Running '%s' with mocked minio client", methodName));
266     Optional.ofNullable(config)
267         .ifPresent(c -> c.configureMock(mockClient));
268     return mockMinio;
269   }
270 
271   private MinioTemplate minioMockTemplate(String methodName, MockMinioClientConfigurator config) {
272     log.info(String.format("Running '%s' with mocked minio client", methodName));
273     Optional.ofNullable(config)
274         .ifPresent(c -> c.configureMock(mockClient));
275     return mockMinio;
276   }
277 
278   /**
279    * Clone minio template.
280    */
281   @Order(1)
282   @Test
283   void copyMinioTemplate() {
284     assertNotNull(mockMinio.copy());
285     assertNotNull(mockMinio.copy(new DefaultMinioErrorHandler()));
286   }
287 
288   /**
289    * Make and remove bucket.
290    */
291   @Order(10)
292   @Test
293   void makeAndRemoveBucket() {
294     String bucketName = newBucketName();
295 
296     MinioTemplate minio = minioTemplate("makeAndRemoveBucket", mock -> {
297       when(mock.bucketExists(any(BucketExistsArgs.class))).thenReturn(true);
298       ListAllMyBucketsResult.Bucket bucket = Mockito.mock(ListAllMyBucketsResult.Bucket.class);
299       when(bucket.name()).thenReturn(bucketName);
300       when(bucket.creationDate()).thenReturn(ZonedDateTime.now());
301       when(mock.listBuckets(any(ListBucketsArgs.class))).thenReturn(List.of(new Result<>(bucket)));
302     });
303 
304     minio.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
305     assertTrue(minio.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()));
306 
307     Iterable<Result<ListAllMyBucketsResult.Bucket>> buckets = minio
308         .listBuckets(ListBucketsArgs.builder().build());
309     assertNotNull(buckets);
310     List<ListAllMyBucketsResult.Bucket> bucketList = new ArrayList<>();
311     for (Result<ListAllMyBucketsResult.Bucket> result : buckets) {
312       try {
313         bucketList.add(result.get());
314       } catch (Exception e) {
315         // ignored
316       }
317     }
318     assertFalse(bucketList.isEmpty());
319     assertTrue(bucketList.stream().anyMatch(bucket -> bucket.name().equals(bucketName)));
320 
321     minio.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
322     if (playMinioEnabled) {
323       assertFalse(minio.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()));
324     }
325   }
326 
327   /**
328    * Make and remove bucket with object lock.
329    */
330   @Order(12)
331   @Test
332   void makeAndRemoveBucketWithObjectLock() {
333     String bucketName = newBucketName();
334 
335     MinioTemplate minio = minioTemplate("makeAndRemoveBucketWithObjectLock", mock -> {
336       ListAllMyBucketsResult.Bucket bucket = Mockito.mock(ListAllMyBucketsResult.Bucket.class);
337       when(bucket.name()).thenReturn(bucketName);
338       when(bucket.creationDate()).thenReturn(ZonedDateTime.now());
339       when(mock.listBuckets()).thenReturn(List.of(bucket));
340       when(mock.bucketExists(any(BucketExistsArgs.class))).thenReturn(false);
341     });
342 
343     minio.makeBucket(MakeBucketArgs.builder()
344         .bucket(bucketName)
345         .objectLock(true)
346         .build());
347     List<ListAllMyBucketsResult.Bucket> buckets = minio.listBuckets();
348     assertNotNull(buckets);
349     assertFalse(buckets.isEmpty());
350     assertTrue(buckets.stream().anyMatch(bucket -> bucket.name().equals(bucketName)));
351 
352     minio.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
353     assertFalse(minio.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()));
354   }
355 
356   /**
357    * Bucket encryption.
358    */
359   @Order(20)
360   @Test
361   void bucketEncryption() {
362     final String bucket = newBucketName();
363 
364     MinioTemplate minio = minioTemplate("bucketEncryption", mock ->
365         when(mock.getBucketEncryption(any(GetBucketEncryptionArgs.class)))
366             .thenReturn(SseConfiguration.newConfigWithSseS3Rule())
367     );
368 
369     minio.makeBucket(MakeBucketArgs.builder()
370         .bucket(bucket)
371         .objectLock(true)
372         .build());
373 
374     try {
375       minio.setBucketEncryption(SetBucketEncryptionArgs.builder()
376           .bucket(bucket)
377           .config(SseConfiguration.newConfigWithSseS3Rule())
378           .build());
379 
380       SseConfiguration config = minio.getBucketEncryption(GetBucketEncryptionArgs.builder()
381           .bucket(bucket)
382           .build());
383       assertNotNull(config);
384 
385       minio.deleteBucketEncryption(DeleteBucketEncryptionArgs.builder()
386           .bucket(bucket)
387           .build());
388 
389     } finally {
390       minio.removeBucket(RemoveBucketArgs.builder()
391           .bucket(bucket)
392           .build());
393     }
394   }
395 
396   /**
397    * Bucket lifecycle.
398    */
399   @Order(30)
400   @Test
401   void bucketLifecycle() {
402     final String bucket = newBucketName();
403     final LifecycleConfiguration.Rule rule0 = new LifecycleConfiguration.Rule(
404         Status.ENABLED,
405         null,
406         new LifecycleConfiguration.Expiration((ZonedDateTime) null, 365, null, null),
407         new Filter("logs/"),
408         null,
409         null,
410         null,
411         null
412     );
413     final LifecycleConfiguration config = new LifecycleConfiguration(List.of(rule0));
414 
415     MinioTemplate minio = minioTemplate("bucketLifecycle", mock ->
416         when(mock.getBucketLifecycle(any(GetBucketLifecycleArgs.class)))
417             .thenReturn(config)
418     );
419 
420     minio.makeBucket(MakeBucketArgs.builder()
421         .bucket(bucket)
422         .objectLock(true)
423         .build());
424 
425     try {
426       minio.setBucketLifecycle(SetBucketLifecycleArgs.builder()
427           .bucket(bucket)
428           .config(config)
429           .build());
430 
431       Optional<LifecycleConfiguration> optionalConfig = minio.getBucketLifecycle(
432           GetBucketLifecycleArgs.builder()
433               .bucket(bucket)
434               .build());
435       assertTrue(optionalConfig.isPresent());
436       LifecycleConfiguration readConfig = optionalConfig.get();
437       assertFalse(readConfig.rules().isEmpty());
438       LifecycleConfiguration.Rule readRule0 = readConfig.rules().getFirst();
439       assertEquals(Status.ENABLED, readRule0.status());
440 
441       minio.deleteBucketLifecycle(DeleteBucketLifecycleArgs.builder()
442           .bucket(bucket)
443           .build());
444       optionalConfig = minio.getBucketLifecycle(GetBucketLifecycleArgs.builder()
445           .bucket(bucket)
446           .build());
447       if (playMinioEnabled) {
448         assertFalse(optionalConfig.isPresent());
449       }
450 
451     } finally {
452       minio.removeBucket(RemoveBucketArgs.builder()
453           .bucket(bucket)
454           .build());
455     }
456   }
457 
458   /**
459    * Bucket notification.
460    *
461    * @throws Exception the exception
462    */
463   @Order(40)
464   @Test
465   void bucketNotification() throws Exception {
466     NotificationConfiguration config = new NotificationConfiguration(
467         null,
468         null,
469         null,
470         null);
471     when(mockClient.getBucketNotification(any(GetBucketNotificationArgs.class)))
472         .thenReturn(config);
473     mockMinio.setBucketNotification(SetBucketNotificationArgs.builder()
474         .bucket(DEFAULT_BUCKET)
475         .config(config)
476         .build());
477     NotificationConfiguration readConfig = mockMinio.getBucketNotification(
478         GetBucketNotificationArgs.builder()
479             .bucket(DEFAULT_BUCKET)
480             .build());
481     assertNotNull(readConfig);
482 
483     mockMinio.deleteBucketNotification(DeleteBucketNotificationArgs.builder()
484         .bucket(DEFAULT_BUCKET)
485         .build());
486 
487     when(mockClient.listenBucketNotification(any(ListenBucketNotificationArgs.class)))
488         .thenReturn(new CloseableIterator<>() {
489 
490           private boolean hasNext = true;
491 
492           @Override
493           public void close() {
494             // nothing to do
495           }
496 
497           @Override
498           public boolean hasNext() {
499             return hasNext;
500           }
501 
502           @Override
503           public Result<NotificationRecords> next() {
504             if (hasNext) {
505               NotificationRecords records = new NotificationRecords();
506               Result<NotificationRecords> result = new Result<>(records);
507               hasNext = false;
508               return result;
509             }
510             return null;
511           }
512         });
513 
514     try (CloseableIterator<Result<NotificationRecords>> in = mockMinio
515         .listenBucketNotification(ListenBucketNotificationArgs.builder()
516             .bucket(DEFAULT_BUCKET)
517             .events(new String[]{"s3:ObjectCreated:*", "s3:ObjectAccessed:*"})
518             .prefix("")
519             .suffix("")
520             .build())) {
521       assertNotNull(in);
522       assertTrue(in.hasNext());
523     }
524   }
525 
526   /**
527    * Bucket policy.
528    *
529    * @throws Exception the exception
530    */
531   @Order(50)
532   @Test
533   void bucketPolicy() throws Exception {
534     String policy = "{}";
535     when(mockClient.getBucketPolicy(any(GetBucketPolicyArgs.class))).thenReturn(policy);
536     mockMinio.setBucketPolicy(SetBucketPolicyArgs.builder()
537         .bucket(DEFAULT_BUCKET)
538         .config(policy)
539         .build());
540     String readPolicy = mockMinio.getBucketPolicy(GetBucketPolicyArgs.builder()
541         .bucket(DEFAULT_BUCKET)
542         .build());
543     assertEquals(policy, readPolicy);
544     mockMinio.deleteBucketPolicy(DeleteBucketPolicyArgs.builder()
545         .bucket(DEFAULT_BUCKET)
546         .build());
547   }
548 
549   /**
550    * Bucket replication.
551    *
552    * @throws Exception the exception
553    */
554   @Order(60)
555   @Test
556   void bucketReplication() throws Exception {
557     Map<String, String> tags = new HashMap<>();
558     tags.put("key1", "value1");
559     tags.put("key2", "value2");
560     ReplicationConfiguration.Rule rule =
561         new ReplicationConfiguration.Rule(
562             Status.DISABLED,
563             new ReplicationConfiguration.Destination(
564                 null,
565                 null,
566                 "REPLACE-WITH-ACTUAL-DESTINATION-BUCKET-ARN",
567                 null,
568                 null,
569                 null,
570                 null),
571             null,
572             null,
573             new Filter(new Filter.And("TaxDocs", tags, null, null)),
574             "rule1",
575             null,
576             1,
577             null,
578             null);
579     List<ReplicationConfiguration.Rule> rules = new LinkedList<>();
580     rules.add(rule);
581     ReplicationConfiguration config = new ReplicationConfiguration("REPLACE-WITH-ACTUAL-ROLE",
582         rules);
583 
584     when(mockClient.getBucketReplication(any(GetBucketReplicationArgs.class))).thenReturn(config);
585 
586     mockMinio.setBucketReplication(SetBucketReplicationArgs.builder()
587         .bucket(DEFAULT_BUCKET)
588         .config(config)
589         .build());
590     Optional<ReplicationConfiguration> optCfg = mockMinio.getBucketReplication(
591         GetBucketReplicationArgs.builder()
592             .bucket(DEFAULT_BUCKET)
593             .build());
594     assertTrue(optCfg.isPresent());
595     mockMinio.deleteBucketReplication(DeleteBucketReplicationArgs.builder()
596         .bucket(DEFAULT_BUCKET)
597         .build());
598   }
599 
600   /**
601    * Bucket cors.
602    */
603   @Order(65)
604   @Test
605   void bucketCors() {
606     List<CORSRule> rules = List.of(
607         new CORSRule(
608             List.of("*"),
609             List.of("PUT", "POST", "DELETE"),
610             List.of("*"),
611             null,
612             null,
613             3000
614         )
615     );
616     CORSConfiguration corsConfiguration = new CORSConfiguration(rules);
617     MinioTemplate minio = minioTemplate("bucketCors", mock ->
618         when(mock.getBucketCors(any(GetBucketCorsArgs.class)))
619             .thenReturn(corsConfiguration)
620     );
621 
622     SetBucketCorsArgs setBucketCorsArgs = SetBucketCorsArgs.builder()
623         .bucket(DEFAULT_BUCKET)
624         .config(corsConfiguration)
625         .build();
626     minio.setBucketCors(setBucketCorsArgs);
627 
628     GetBucketCorsArgs getBucketCorsArgs = GetBucketCorsArgs.builder()
629         .bucket(DEFAULT_BUCKET)
630         .build();
631     CORSConfiguration actual = minio.getBucketCors(getBucketCorsArgs);
632     Assertions.assertThat(actual)
633         .usingRecursiveComparison()
634         .isEqualTo(corsConfiguration);
635 
636     DeleteBucketCorsArgs deleteBucketCorsArgs = DeleteBucketCorsArgs.builder()
637         .bucket(DEFAULT_BUCKET)
638         .build();
639     minio.deleteBucketCors(deleteBucketCorsArgs);
640   }
641 
642   /**
643    * Bucket tags.
644    */
645   @Order(70)
646   @Test
647   void bucketTags() {
648     Map<String, String> tagMap = Collections.singletonMap("my-key", "my-value");
649     MinioTemplate minio = minioTemplate("bucketTags", mock ->
650         when(mock.getBucketTags(any(GetBucketTagsArgs.class)))
651             .thenReturn(Tags.newBucketTags(tagMap))
652     );
653     minio.setBucketTags(SetBucketTagsArgs.builder()
654         .bucket(DEFAULT_BUCKET)
655         .tags(tagMap)
656         .build());
657     Tags tags = minio.getBucketTags(GetBucketTagsArgs.builder()
658         .bucket(DEFAULT_BUCKET)
659         .build());
660     assertNotNull(tags);
661     assertTrue(tags.get().containsKey("my-key"));
662     assertTrue(tags.get().containsValue("my-value"));
663 
664     minio.deleteBucketTags(DeleteBucketTagsArgs.builder()
665         .bucket(DEFAULT_BUCKET)
666         .build());
667   }
668 
669   /**
670    * Bucket versioning.
671    */
672   @Order(80)
673   @Test
674   void bucketVersioning() {
675 
676     final String bucketName = newBucketName();
677     final VersioningConfiguration config = new VersioningConfiguration(
678         VersioningConfiguration.Status.ENABLED, null, null, false);
679     MinioTemplate minio = minioTemplate("bucketVersioning", mock ->
680         when(mock.getBucketVersioning(any(GetBucketVersioningArgs.class))).thenReturn(config)
681     );
682 
683     minio.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
684     try {
685       minio.setBucketVersioning(SetBucketVersioningArgs.builder()
686           .bucket(bucketName)
687           .config(config)
688           .build());
689 
690       VersioningConfiguration readConfig = minio.getBucketVersioning(
691           GetBucketVersioningArgs.builder()
692               .bucket(bucketName)
693               .build());
694 
695       assertNotNull(readConfig);
696       assertEquals(VersioningConfiguration.Status.ENABLED, readConfig.status());
697 
698     } finally {
699       minio.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
700     }
701   }
702 
703   /**
704    * Object lock configuration.
705    */
706   @Order(90)
707   @Test
708   void objectLockConfiguration() {
709 
710     final String bucketName = newBucketName();
711     final ObjectLockConfiguration config = new ObjectLockConfiguration(
712         RetentionMode.COMPLIANCE,
713         new ObjectLockConfiguration.RetentionDurationDays(100));
714     MinioTemplate minio = minioTemplate("objectLockConfiguration", mock ->
715         when(mock.getObjectLockConfiguration(any(GetObjectLockConfigurationArgs.class))).thenReturn(
716             config)
717     );
718 
719     minio.makeBucket(MakeBucketArgs.builder()
720         .bucket(bucketName)
721         .objectLock(true)
722         .build());
723     try {
724       minio.setObjectLockConfiguration(SetObjectLockConfigurationArgs.builder()
725           .bucket(bucketName)
726           .config(config)
727           .build());
728 
729       ObjectLockConfiguration readConfig = minio.getObjectLockConfiguration(
730           GetObjectLockConfigurationArgs.builder()
731               .bucket(bucketName)
732               .build());
733       assertNotNull(readConfig);
734       assertEquals(RetentionMode.COMPLIANCE, readConfig.mode());
735 
736       minio.deleteObjectLockConfiguration(DeleteObjectLockConfigurationArgs.builder()
737           .bucket(bucketName)
738           .build());
739 
740     } finally {
741       minio.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
742     }
743   }
744 
745 
746   /**
747    * Put and remove object.
748    *
749    * @throws Exception the exception
750    */
751   @Order(1010)
752   @Test
753   void putAndRemoveObject() throws Exception {
754 
755     String objectName = UUID.randomUUID() + ".txt";
756 
757     MinioTemplate minio = minioTemplate("putAndRemoveObject", mock -> {
758       when(mock.putObject(any(PutObjectArgs.class)))
759           .thenReturn(new ObjectWriteResponse(
760               Headers.of(Collections.emptyMap()),
761               DEFAULT_BUCKET,
762               null,
763               objectName,
764               null,
765               null
766           ));
767       when(mock.listObjects(any(ListObjectsArgs.class))).thenReturn(Collections.emptyList());
768     });
769     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
770     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
771         .bucket(DEFAULT_BUCKET)
772         .object(objectName)
773         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
774         .contentType(MediaType.TEXT_PLAIN_VALUE)
775         .build());
776     assertNotNull(response);
777 
778     boolean contains = false;
779     Iterable<Result<Item>> results;
780     if (playMinioEnabled) {
781       results = minio
782           .listObjects(ListObjectsArgs.builder().bucket(DEFAULT_BUCKET).build());
783       for (Result<Item> result : results) {
784         Item item = result.get();
785         if (objectName.equals(item.objectName())) {
786           contains = true;
787           break;
788         }
789       }
790       assertTrue(contains);
791     }
792 
793     minio.removeObject(RemoveObjectArgs.builder()
794         .bucket(DEFAULT_BUCKET)
795         .object(objectName)
796         .build());
797 
798     results = minio
799         .listObjects(ListObjectsArgs.builder().bucket(DEFAULT_BUCKET).build());
800     contains = false;
801     for (Result<Item> result : results) {
802       Item item = result.get();
803       if (objectName.equals(item.objectName())) {
804         contains = true;
805         break;
806       }
807     }
808     assertFalse(contains);
809   }
810 
811   /**
812    * Upload object.
813    *
814    * @throws Exception the exception
815    */
816   @Order(1020)
817   @Test
818   void uploadObject() throws Exception {
819 
820     String objectName = UUID.randomUUID() + ".txt";
821     MinioTemplate minio = minioTemplate("uploadObject", mock -> {
822       when(mock.uploadObject(any(UploadObjectArgs.class)))
823           .thenReturn(new ObjectWriteResponse(
824               Headers.of(Collections.emptyMap()),
825               DEFAULT_BUCKET,
826               null,
827               objectName,
828               null,
829               null
830           ));
831       when(mock.getPresignedObjectUrl(any(GetPresignedObjectUrlArgs.class)))
832           .thenReturn("http://localhost/somewhere");
833     });
834     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
835     try {
836       File file = File.createTempFile(
837           "junit", ".tmp", new File(System.getProperty("java.io.tmpdir")));
838       FileCopyUtils.copy(
839           value,
840           Files.newOutputStream(
841               file.toPath(),
842               StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING));
843       ObjectWriteResponse response = minio.uploadObject(
844           UploadObjectArgs.builder()
845               .bucket(DEFAULT_BUCKET)
846               .object(objectName)
847               .filename(file.getAbsolutePath())
848               .contentType(MediaType.TEXT_PLAIN_VALUE)
849               .build(),
850           DeleteMode.ALWAYS);
851       assertNotNull(response);
852 
853       assertFalse(file.exists());
854       String url = minio.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
855           .bucket(DEFAULT_BUCKET)
856           .object(objectName)
857           .method(Http.Method.GET)
858           .expiry(3600 * 24)
859           .build());
860       assertNotNull(url);
861       if (playMinioEnabled) {
862         try (InputStream in = new URI(url).toURL().openStream()) {
863           byte[] readBytes = FileCopyUtils.copyToByteArray(in);
864           assertArrayEquals(value, readBytes);
865         }
866       }
867 
868     } finally {
869       minio.removeObject(RemoveObjectArgs.builder()
870           .bucket(DEFAULT_BUCKET)
871           .object(objectName)
872           .build());
873     }
874   }
875 
876   /**
877    * Download object.
878    *
879    * @throws Exception the exception
880    */
881   @Order(1030)
882   @Test
883   void downloadObject() throws Exception {
884 
885     String objectName = UUID.randomUUID() + ".txt";
886     MinioTemplate minio = minioTemplate("downloadObject", mock -> {
887       when(mock.putObject(any(PutObjectArgs.class)))
888           .thenReturn(new ObjectWriteResponse(
889               Headers.of(Collections.emptyMap()),
890               DEFAULT_BUCKET,
891               null,
892               objectName,
893               null,
894               null
895           ));
896       when(mock.uploadObject(any(UploadObjectArgs.class)))
897           .thenReturn(new ObjectWriteResponse(
898               Headers.of(Collections.emptyMap()),
899               DEFAULT_BUCKET,
900               null,
901               objectName,
902               null,
903               null
904           ));
905     });
906     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
907     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
908         .bucket(DEFAULT_BUCKET)
909         .object(objectName)
910         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
911         .contentType(MediaType.TEXT_PLAIN_VALUE)
912         .build());
913     assertNotNull(response);
914 
915     File file = null;
916     try {
917       file = File.createTempFile(
918           "junit", ".tmp", new File(System.getProperty("java.io.tmpdir")));
919       if (playMinioEnabled) {
920         Files.delete(file.toPath());
921       }
922       minio.downloadObject(DownloadObjectArgs.builder()
923           .bucket(DEFAULT_BUCKET)
924           .object(objectName)
925           .filename(file.getAbsolutePath())
926           .build());
927       if (playMinioEnabled) {
928         byte[] fileBytes = FileCopyUtils.copyToByteArray(file);
929         assertArrayEquals(value, fileBytes);
930       }
931 
932     } finally {
933       if (file != null) {
934         Files.delete(file.toPath());
935       }
936       minio.removeObject(RemoveObjectArgs.builder()
937           .bucket(DEFAULT_BUCKET)
938           .object(objectName)
939           .build());
940     }
941   }
942 
943   /**
944    * Presigned object url.
945    *
946    * @throws Exception the exception
947    */
948   @Order(1040)
949   @Test
950   void presignedObjectUrl() throws Exception {
951 
952     String objectName = UUID.randomUUID() + ".txt";
953     MinioTemplate minio = minioTemplate("presignedObjectUrl", mock -> {
954       when(mock.putObject(any(PutObjectArgs.class)))
955           .thenReturn(new ObjectWriteResponse(
956               Headers.of(Collections.emptyMap()),
957               DEFAULT_BUCKET,
958               null,
959               objectName,
960               null,
961               null
962           ));
963       when(mock.getPresignedObjectUrl(any(GetPresignedObjectUrlArgs.class)))
964           .thenReturn("http://localhost/somewhere");
965     });
966     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
967     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
968         .bucket(DEFAULT_BUCKET)
969         .object(objectName)
970         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
971         .contentType(MediaType.TEXT_PLAIN_VALUE)
972         .build());
973     assertNotNull(response);
974 
975     try {
976       String url = minio.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
977           .bucket(DEFAULT_BUCKET)
978           .object(objectName)
979           .method(Http.Method.GET)
980           .expiry(3600 * 24)
981           .build());
982       assertNotNull(url);
983       if (playMinioEnabled) {
984         try (InputStream in = new URI(url).toURL().openStream()) {
985           byte[] readBytes = FileCopyUtils.copyToByteArray(in);
986           assertArrayEquals(value, readBytes);
987         }
988       }
989 
990     } finally {
991       minio.removeObject(RemoveObjectArgs.builder()
992           .bucket(DEFAULT_BUCKET)
993           .object(objectName)
994           .build());
995     }
996   }
997 
998   /**
999    * Presigned post form data.
1000    */
1001   @Order(1050)
1002   @Test
1003   void presignedPostFormData() {
1004 
1005     String objectName = UUID.randomUUID() + ".txt";
1006     MinioTemplate minio = minioTemplate("presignedPostFormData", mock ->
1007         when(mock.getPresignedPostFormData(any(PostPolicy.class)))
1008             .thenReturn(Map.of("key", "value"))
1009     );
1010 
1011     PostPolicy policy = new PostPolicy(DEFAULT_BUCKET, ZonedDateTime.now().plusDays(7));
1012     policy.addEqualsCondition("key", objectName);
1013     policy.addStartsWithCondition("Content-Type", "text/");
1014     policy.addContentLengthRangeCondition(8, 8 * 1024);
1015 
1016     Map<String, String> formData = minio.getPresignedPostFormData(policy);
1017     assertNotNull(formData);
1018     assertFalse(formData.isEmpty());
1019   }
1020 
1021   /**
1022    * Stat object.
1023    */
1024   @Order(1060)
1025   @Test
1026   void statObject() {
1027     String objectName = UUID.randomUUID() + ".txt";
1028     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
1029     MinioTemplate minio = minioTemplate("statObject", mock -> {
1030       when(mock.putObject(any(PutObjectArgs.class)))
1031           .thenReturn(new ObjectWriteResponse(
1032               Headers.of(Collections.emptyMap()),
1033               DEFAULT_BUCKET,
1034               null,
1035               objectName,
1036               null,
1037               null
1038           ));
1039       when(mock.statObject(any(StatObjectArgs.class)))
1040           .thenReturn(new StatObjectResponse(new HeadObjectResponse(
1041               Headers.of(Map.of(
1042                   "Content-Length", String.valueOf(value.length),
1043                   "Last-Modified", ZonedDateTime.now().format(Time.HTTP_HEADER_DATE_FORMAT))),
1044               DEFAULT_BUCKET,
1045               null,
1046               objectName
1047           )));
1048     });
1049     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
1050         .bucket(DEFAULT_BUCKET)
1051         .object(objectName)
1052         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
1053         .contentType(MediaType.TEXT_PLAIN_VALUE)
1054         .build());
1055     assertNotNull(response);
1056 
1057     try {
1058       StatObjectResponse statObject = minio.statObject(StatObjectArgs.builder()
1059           .bucket(DEFAULT_BUCKET)
1060           .object(objectName)
1061           .build());
1062       assertNotNull(statObject);
1063       assertEquals(objectName, statObject.object());
1064 
1065     } finally {
1066       minio.removeObject(RemoveObjectArgs.builder()
1067           .bucket(DEFAULT_BUCKET)
1068           .object(objectName)
1069           .build());
1070     }
1071   }
1072 
1073   /**
1074    * Object exists.
1075    */
1076   @Order(1070)
1077   @Test
1078   void objectExists() {
1079     String objectName = UUID.randomUUID() + ".txt";
1080     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
1081     MinioTemplate minio = minioTemplate("objectExists", mock -> {
1082       when(mock.putObject(any(PutObjectArgs.class)))
1083           .thenReturn(new ObjectWriteResponse(
1084               Headers.of(Collections.emptyMap()),
1085               DEFAULT_BUCKET,
1086               null,
1087               objectName,
1088               null,
1089               null
1090           ));
1091       when(mock.statObject(any(StatObjectArgs.class)))
1092           .thenReturn(new StatObjectResponse(new HeadObjectResponse(
1093               Headers.of(Map.of(
1094                   "Content-Length", String.valueOf(value.length),
1095                   "Last-Modified", ZonedDateTime.now().format(Time.HTTP_HEADER_DATE_FORMAT))),
1096               DEFAULT_BUCKET,
1097               null,
1098               objectName
1099           )));
1100     });
1101     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
1102         .bucket(DEFAULT_BUCKET)
1103         .object(objectName)
1104         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
1105         .contentType(MediaType.TEXT_PLAIN_VALUE)
1106         .build());
1107     assertNotNull(response);
1108 
1109     try {
1110       assertTrue(minio.objectExists(StatObjectArgs.builder()
1111           .bucket(DEFAULT_BUCKET)
1112           .object(objectName)
1113           .build()));
1114 
1115     } finally {
1116       minio.removeObject(RemoveObjectArgs.builder()
1117           .bucket(DEFAULT_BUCKET)
1118           .object(objectName)
1119           .build());
1120     }
1121   }
1122 
1123   /**
1124    * Object not exists.
1125    */
1126   @Order(1080)
1127   @Test
1128   void objectNotExists() {
1129     String objectName = UUID.randomUUID() + ".nil";
1130     MinioTemplate minio = minioTemplate("objectNotExists", mock ->
1131         when(mock.statObject(any(StatObjectArgs.class)))
1132             .thenThrow(new ErrorResponseException(
1133                 new ErrorResponse("NoSuchKey", "message", DEFAULT_BUCKET, objectName, "123", "456",
1134                     "789"),
1135                 new Response.Builder()
1136                     .protocol(Protocol.HTTP_1_1)
1137                     .code(500)
1138                     .message("Internal server error")
1139                     .request(new Request.Builder()
1140                         .url("https://example.org")
1141                         .build())
1142                     .build(),
1143                 HttpStatus.NOT_FOUND.getReasonPhrase()))
1144     );
1145     assertFalse(minio.objectExists(StatObjectArgs.builder()
1146         .bucket(DEFAULT_BUCKET)
1147         .object(objectName)
1148         .build()));
1149   }
1150 
1151   /**
1152    * Gets object.
1153    *
1154    * @throws Exception the exception
1155    */
1156   @Order(1090)
1157   @Test
1158   void getObject() throws Exception {
1159     String objectName = UUID.randomUUID() + ".txt";
1160     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
1161     MinioTemplate minio = minioTemplate("getObject", mock -> {
1162       when(mock.putObject(any(PutObjectArgs.class)))
1163           .thenReturn(new ObjectWriteResponse(
1164               Headers.of(Collections.emptyMap()),
1165               DEFAULT_BUCKET,
1166               null,
1167               objectName,
1168               null,
1169               null
1170           ));
1171       when(mock.getObject(any(GetObjectArgs.class)))
1172           .then(invocationOnMock -> new GetObjectResponse(
1173               Headers.of(Collections.emptyMap()),
1174               DEFAULT_BUCKET,
1175               null,
1176               objectName,
1177               new ByteArrayInputStream(value)
1178           ));
1179     });
1180     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
1181         .bucket(DEFAULT_BUCKET)
1182         .object(objectName)
1183         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
1184         .contentType(MediaType.TEXT_PLAIN_VALUE)
1185         .build());
1186     assertNotNull(response);
1187 
1188     try (InputStream in = minio.getObject(
1189         GetObjectArgs.builder().bucket(DEFAULT_BUCKET).object(objectName).build())) {
1190       byte[] readValue = FileCopyUtils.copyToByteArray(in);
1191       assertArrayEquals(value, readValue);
1192 
1193     } finally {
1194       minio.removeObject(RemoveObjectArgs.builder()
1195           .bucket(DEFAULT_BUCKET)
1196           .object(objectName)
1197           .build());
1198     }
1199   }
1200 
1201   /**
1202    * Select object content.
1203    *
1204    * @throws Exception the exception
1205    */
1206   @Order(1100)
1207   @Test
1208   void selectObjectContent() throws Exception {
1209     when(mockClient.selectObjectContent(any(SelectObjectContentArgs.class)))
1210         .thenAnswer(invocationOnMock -> new SelectResponseStream(
1211             new ByteArrayInputStream(new byte[0])));
1212     try (SelectResponseStream stream = mockMinio.selectObjectContent(
1213         SelectObjectContentArgs.builder()
1214             .bucket(DEFAULT_BUCKET)
1215             .object(UUID.randomUUID().toString())
1216             .sqlExpression("select * from S3Object")
1217             .inputSerialization(InputSerialization.newJSON(null, JsonType.DOCUMENT))
1218             .outputSerialization(OutputSerialization.newJSON(null))
1219             .build())) {
1220       assertNotNull(stream);
1221     }
1222   }
1223 
1224   /**
1225    * Compose object.
1226    *
1227    * @throws Exception the exception
1228    */
1229   @Order(1110)
1230   @Test
1231   void composeObject() throws Exception {
1232 
1233     String destBucket = newBucketName();
1234     String destObjectName = UUID.randomUUID() + ".txt";
1235 
1236     MinioTemplate minio = minioTemplate("composeObject", mock -> {
1237       when(mock.putObject(any(PutObjectArgs.class)))
1238           .thenReturn(new ObjectWriteResponse(
1239               Headers.of(Collections.emptyMap()),
1240               DEFAULT_BUCKET,
1241               null,
1242               "any.dat",
1243               null,
1244               null
1245           ));
1246       when(mock.composeObject(any(ComposeObjectArgs.class)))
1247           .thenReturn(new ObjectWriteResponse(
1248               Headers.of(Collections.emptyMap()),
1249               destBucket,
1250               null,
1251               destObjectName,
1252               null,
1253               null
1254           ));
1255       when(mock.getPresignedObjectUrl(any(GetPresignedObjectUrlArgs.class)))
1256           .thenReturn("http://localhost/somewhere");
1257     });
1258 
1259     Random random = new Random();
1260     final int size = 5242882;
1261     String objectName0 = UUID.randomUUID() + ".dat";
1262     byte[] value0 = new byte[size];
1263     random.nextBytes(value0);
1264     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
1265         .bucket(DEFAULT_BUCKET)
1266         .object(objectName0)
1267         .stream(new ByteArrayInputStream(value0), (long) value0.length, -1L)
1268         .contentType(MediaType.APPLICATION_OCTET_STREAM_VALUE)
1269         .build());
1270     assertNotNull(response);
1271 
1272     String objectName1 = UUID.randomUUID() + ".dat";
1273     byte[] value1 = new byte[size];
1274     random.nextBytes(value1);
1275     response = minio.putObject(PutObjectArgs.builder()
1276         .bucket(DEFAULT_BUCKET)
1277         .object(objectName1)
1278         .stream(new ByteArrayInputStream(value1), (long) value1.length, -1L)
1279         .contentType(MediaType.APPLICATION_OCTET_STREAM_VALUE)
1280         .build());
1281     assertNotNull(response);
1282 
1283     boolean destObjectExists = false;
1284     minio.makeBucket(MakeBucketArgs.builder().bucket(destBucket).build());
1285     try {
1286       // Version 9.0.1 needs a mutable list
1287       List<SourceObject> sources = new ArrayList<>(List.of(
1288           SourceObject.builder()
1289               .bucket(DEFAULT_BUCKET)
1290               .object(objectName0)
1291               .build(),
1292           SourceObject.builder()
1293               .bucket(DEFAULT_BUCKET)
1294               .object(objectName1)
1295               .build()));
1296       response = minio.composeObject(ComposeObjectArgs.builder()
1297           .bucket(destBucket)
1298           .object(destObjectName)
1299           .sources(sources)
1300           .build());
1301       assertNotNull(response);
1302       destObjectExists = true;
1303 
1304       String url = minio.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
1305           .bucket(destBucket)
1306           .object(destObjectName)
1307           .method(Http.Method.GET)
1308           .expiry(3600 * 24)
1309           .build());
1310       assertNotNull(url);
1311       if (playMinioEnabled) {
1312         try (InputStream in = new URI(url).toURL().openStream()) {
1313           byte[] readBytes = FileCopyUtils.copyToByteArray(in);
1314           assertEquals(2 * size, readBytes.length);
1315         }
1316       }
1317 
1318     } finally {
1319       minio.removeObject(RemoveObjectArgs.builder()
1320           .bucket(DEFAULT_BUCKET)
1321           .object(objectName0)
1322           .build());
1323       minio.removeObject(RemoveObjectArgs.builder()
1324           .bucket(DEFAULT_BUCKET)
1325           .object(objectName1)
1326           .build());
1327       if (destObjectExists) {
1328         minio.removeObject(RemoveObjectArgs.builder()
1329             .bucket(destBucket)
1330             .object(destObjectName)
1331             .build());
1332       }
1333       minio.removeBucket(RemoveBucketArgs.builder().bucket(destBucket).build());
1334     }
1335   }
1336 
1337   /**
1338    * Copy object.
1339    */
1340   @Order(1120)
1341   @Test
1342   void copyObject() {
1343 
1344     String objectName = UUID.randomUUID() + ".txt";
1345     String destBucket = newBucketName();
1346     String destObjectName = UUID.randomUUID() + ".txt";
1347     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
1348 
1349     MinioTemplate minio = minioTemplate("copyObject", mock -> {
1350       when(mockClient.putObject(any(PutObjectArgs.class)))
1351           .thenReturn(new ObjectWriteResponse(
1352               Headers.of(Collections.emptyMap()),
1353               DEFAULT_BUCKET,
1354               null,
1355               objectName,
1356               null,
1357               "1234"
1358           ));
1359       when(mock.copyObject(any(CopyObjectArgs.class)))
1360           .thenReturn(new ObjectWriteResponse(
1361               Headers.of(Collections.emptyMap()),
1362               destBucket,
1363               null,
1364               destObjectName,
1365               null,
1366               null));
1367       when(mock.statObject(any(StatObjectArgs.class)))
1368           .thenReturn(new StatObjectResponse(new HeadObjectResponse(
1369               Headers.of(Map.of(
1370                   "Content-Length", String.valueOf(value.length),
1371                   "Last-Modified", ZonedDateTime.now().format(Time.HTTP_HEADER_DATE_FORMAT))),
1372               destBucket,
1373               null,
1374               destObjectName
1375           )));
1376       when(mock.removeObjects(any(RemoveObjectsArgs.class)))
1377           .thenReturn(Collections.emptyList());
1378     });
1379 
1380     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
1381         .bucket(DEFAULT_BUCKET)
1382         .object(objectName)
1383         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
1384         .contentType(MediaType.TEXT_PLAIN_VALUE)
1385         .build());
1386     assertNotNull(response);
1387 
1388     boolean destObjectExists = false;
1389     minio.makeBucket(MakeBucketArgs.builder().bucket(destBucket).build());
1390     try {
1391       ObjectWriteResponse copyResponse = minio.copyObject(CopyObjectArgs.builder()
1392           .bucket(destBucket)
1393           .object(destObjectName)
1394           .source(SourceObject.builder()
1395               .bucket(DEFAULT_BUCKET)
1396               .object(objectName)
1397               .build())
1398           .build());
1399       assertNotNull(copyResponse);
1400 
1401       StatObjectResponse objectStat = minio.statObject(StatObjectArgs.builder()
1402           .bucket(destBucket)
1403           .object(destObjectName)
1404           .build());
1405       assertNotNull(objectStat);
1406       destObjectExists = true;
1407       assertEquals(value.length, (int) objectStat.size());
1408 
1409       Iterable<Result<DeleteResult.Error>> errors = minio.removeObjects(RemoveObjectsArgs
1410           .builder()
1411           .bucket(destBucket)
1412           .objects(Collections.singletonList(new Object(destObjectName)))
1413           .build());
1414       boolean hasError = false;
1415       for (Result<DeleteResult.Error> error : errors) {
1416         hasError = error != null;
1417       }
1418       assertFalse(hasError);
1419 
1420       if (playMinioEnabled) {
1421         StatObjectArgs statObject = StatObjectArgs.builder()
1422             .bucket(destBucket)
1423             .object(destObjectName)
1424             .build();
1425         assertThrows(MinioException.class, () -> minio.statObject(statObject));
1426       }
1427       destObjectExists = false;
1428 
1429     } finally {
1430       minio.removeObject(RemoveObjectArgs.builder()
1431           .bucket(DEFAULT_BUCKET)
1432           .object(objectName)
1433           .build());
1434       if (destObjectExists) {
1435         minio.removeObject(RemoveObjectArgs.builder()
1436             .bucket(destBucket)
1437             .object(destObjectName)
1438             .build());
1439       }
1440       minio.removeBucket(RemoveBucketArgs.builder().bucket(destBucket).build());
1441     }
1442   }
1443 
1444   /**
1445    * Object retention.
1446    */
1447   @Order(1130)
1448   @Test
1449   void objectRetention() {
1450     String bucket = newBucketName();
1451     String objectName = UUID.randomUUID() + ".txt";
1452     ZonedDateTime until = ZonedDateTime.now().plusSeconds(10L);
1453 
1454     MinioTemplate minio = minioTemplate("objectRetention", mock -> {
1455       when(mock.putObject(any(PutObjectArgs.class)))
1456           .thenReturn(new ObjectWriteResponse(
1457               Headers.of(Collections.emptyMap()),
1458               bucket,
1459               null,
1460               objectName,
1461               null,
1462               "1234"
1463           ));
1464       when(mock.getObjectRetention(any(GetObjectRetentionArgs.class)))
1465           .thenReturn(new Retention(RetentionMode.COMPLIANCE, until));
1466     });
1467 
1468     minio.makeBucket(MakeBucketArgs.builder()
1469         .bucket(bucket)
1470         .objectLock(true)
1471         .build());
1472     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
1473     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
1474         .bucket(bucket)
1475         .object(objectName)
1476         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
1477         .contentType(MediaType.TEXT_PLAIN_VALUE)
1478         .build());
1479     assertNotNull(response);
1480 
1481     try {
1482       minio.setObjectRetention(SetObjectRetentionArgs.builder()
1483           .bucket(bucket)
1484           .object(objectName)
1485           .bypassGovernanceMode(true)
1486           .config(new Retention(RetentionMode.COMPLIANCE, until))
1487           .build());
1488 
1489       Retention retention = minio.getObjectRetention(GetObjectRetentionArgs.builder()
1490           .bucket(bucket)
1491           .object(objectName)
1492           .versionId(response.versionId())
1493           .build());
1494       assertNotNull(retention);
1495       assertEquals(RetentionMode.COMPLIANCE, retention.mode());
1496       assertEquals(until.toEpochSecond(), retention.retainUntilDate().toEpochSecond());
1497 
1498       if (playMinioEnabled) {
1499         await().until(() -> ZonedDateTime.now().isAfter(until));
1500       }
1501 
1502     } finally {
1503       minio.removeObject(RemoveObjectArgs.builder()
1504           .bucket(bucket)
1505           .object(objectName)
1506           .versionId(response.versionId())
1507           .bypassGovernanceMode(true)
1508           .build());
1509       minio.removeBucket(RemoveBucketArgs.builder()
1510           .bucket(bucket)
1511           .build());
1512     }
1513   }
1514 
1515   /**
1516    * Object acl.
1517    */
1518   @Order(1133)
1519   @Test
1520   void objectAcl() {
1521 
1522     String bucket = newBucketName();
1523     String objectName = UUID.randomUUID() + ".txt";
1524 
1525     Owner owner = new Owner();
1526     List<Grant> grants = List.of(
1527         new Grant(
1528             new Grantee("CanonicalUser", Type.CANONICAL_USER, null, null, null, null),
1529             Permission.FULL_CONTROL)
1530     );
1531     AccessControlList accessControlList = new AccessControlList(grants);
1532     AccessControlPolicy accessControlPolicy = new AccessControlPolicy(owner, accessControlList);
1533 
1534     MinioTemplate minio = minioTemplate("objectAcl", mock -> {
1535       when(mock.putObject(any(PutObjectArgs.class)))
1536           .thenReturn(new ObjectWriteResponse(
1537               Headers.of(Collections.emptyMap()),
1538               bucket,
1539               null,
1540               objectName,
1541               null,
1542               null
1543           ));
1544       when(mock.getObjectAcl(any(GetObjectAclArgs.class)))
1545           .thenReturn(accessControlPolicy);
1546     });
1547 
1548     minio.makeBucket(MakeBucketArgs.builder()
1549         .bucket(bucket)
1550         .objectLock(false)
1551         .build());
1552     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
1553     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
1554         .bucket(bucket)
1555         .object(objectName)
1556         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
1557         .contentType(MediaType.TEXT_PLAIN_VALUE)
1558         .build());
1559     assertNotNull(response);
1560 
1561     try {
1562       GetObjectAclArgs getObjectAclArgs = GetObjectAclArgs.builder()
1563           .bucket(bucket)
1564           .object(objectName)
1565           .build();
1566       AccessControlPolicy actual = minio.getObjectAcl(getObjectAclArgs);
1567       Assertions.assertThat(actual)
1568           .usingRecursiveComparison()
1569           .isEqualTo(accessControlPolicy);
1570 
1571     } finally {
1572       minio.removeObject(RemoveObjectArgs.builder()
1573           .bucket(bucket)
1574           .object(objectName)
1575           .build());
1576       minio.removeBucket(RemoveBucketArgs.builder()
1577           .bucket(bucket)
1578           .build());
1579     }
1580   }
1581 
1582   /**
1583    * Object acl.
1584    */
1585   @Order(1137)
1586   @Test
1587   void objectAttributes() {
1588 
1589     String bucket = newBucketName();
1590     String objectName = UUID.randomUUID() + ".txt";
1591 
1592     GetObjectAttributesOutput output = mock(GetObjectAttributesOutput.class);
1593     when(output.objectSize()).thenReturn(11L);
1594     GetObjectAttributesResponse attributesResponse = mock(GetObjectAttributesResponse.class);
1595     when(attributesResponse.result()).thenReturn(output);
1596 
1597     MinioTemplate minio = minioTemplate("objectAttributes", mock -> {
1598       when(mock.putObject(any(PutObjectArgs.class)))
1599           .thenReturn(new ObjectWriteResponse(
1600               Headers.of(Collections.emptyMap()),
1601               bucket,
1602               null,
1603               objectName,
1604               null,
1605               null
1606           ));
1607       when(mock.getObjectAttributes(any(GetObjectAttributesArgs.class)))
1608           .thenReturn(attributesResponse);
1609     });
1610 
1611     minio.makeBucket(MakeBucketArgs.builder()
1612         .bucket(bucket)
1613         .objectLock(false)
1614         .build());
1615     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
1616     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
1617         .bucket(bucket)
1618         .object(objectName)
1619         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
1620         .contentType(MediaType.TEXT_PLAIN_VALUE)
1621         .build());
1622     assertNotNull(response);
1623 
1624     try {
1625       GetObjectAttributesArgs getObjectAttributesArgs = GetObjectAttributesArgs.builder()
1626           .bucket(bucket)
1627           .object(objectName)
1628           .objectAttributes(List
1629               .of("ETag", "Checksum", "ObjectParts", "StorageClass", "ObjectSize"))
1630           .build();
1631       GetObjectAttributesResponse actual = minio.getObjectAttributes(getObjectAttributesArgs);
1632       assertNotNull(actual);
1633       assertNotNull(actual.result());
1634       assertEquals(11L, actual.result().objectSize());
1635 
1636     } finally {
1637       minio.removeObject(RemoveObjectArgs.builder()
1638           .bucket(bucket)
1639           .object(objectName)
1640           .build());
1641       minio.removeBucket(RemoveBucketArgs.builder()
1642           .bucket(bucket)
1643           .build());
1644     }
1645   }
1646 
1647   /**
1648    * Object tags.
1649    */
1650   @Order(1140)
1651   @Test
1652   void objectTags() {
1653 
1654     String bucket = newBucketName();
1655     String objectName = UUID.randomUUID() + ".txt";
1656 
1657     MinioTemplate minio = minioTemplate("objectTags", mock -> {
1658       when(mock.putObject(any(PutObjectArgs.class)))
1659           .thenReturn(new ObjectWriteResponse(
1660               Headers.of(Collections.emptyMap()),
1661               bucket,
1662               null,
1663               objectName,
1664               null,
1665               null
1666           ));
1667       when(mock.getObjectTags(any(GetObjectTagsArgs.class)))
1668           .thenReturn(Tags.newObjectTags(Map.of("my-key", "my-value")));
1669     });
1670 
1671     minio.makeBucket(MakeBucketArgs.builder()
1672         .bucket(bucket)
1673         .objectLock(false)
1674         .build());
1675     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
1676     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
1677         .bucket(bucket)
1678         .object(objectName)
1679         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
1680         .contentType(MediaType.TEXT_PLAIN_VALUE)
1681         .build());
1682     assertNotNull(response);
1683     try {
1684       minio.setObjectTags(SetObjectTagsArgs.builder()
1685           .bucket(bucket)
1686           .object(objectName)
1687           .tags(Map.of("my-key", "my-value"))
1688           .build());
1689 
1690       Tags tags = minio.getObjectTags(GetObjectTagsArgs.builder()
1691           .bucket(bucket)
1692           .object(objectName)
1693           .build());
1694       assertNotNull(tags);
1695       assertEquals("my-value", tags.get().get("my-key"));
1696 
1697       minio.deleteObjectTags(DeleteObjectTagsArgs.builder()
1698           .bucket(bucket)
1699           .object(objectName)
1700           .build());
1701 
1702       if (playMinioEnabled) {
1703         tags = minio.getObjectTags(GetObjectTagsArgs.builder()
1704             .bucket(bucket)
1705             .object(objectName)
1706             .build());
1707         assertNotNull(tags);
1708         assertTrue(tags.get().isEmpty());
1709       }
1710 
1711     } finally {
1712       minio.removeObject(RemoveObjectArgs.builder()
1713           .bucket(bucket)
1714           .object(objectName)
1715           .build());
1716       minio.removeBucket(RemoveBucketArgs.builder()
1717           .bucket(bucket)
1718           .build());
1719     }
1720   }
1721 
1722   /**
1723    * Object legal hold.
1724    */
1725   @Order(1150)
1726   @Test
1727   void objectLegalHold() {
1728     String bucket = newBucketName();
1729     String objectName = UUID.randomUUID() + ".txt";
1730 
1731     MinioTemplate minio = minioTemplate("objectLegalHold", mock -> {
1732       when(mock.putObject(any(PutObjectArgs.class)))
1733           .thenReturn(new ObjectWriteResponse(
1734               Headers.of(Collections.emptyMap()),
1735               bucket,
1736               null,
1737               objectName,
1738               null,
1739               null
1740           ));
1741       when(mock.isObjectLegalHoldEnabled(any(IsObjectLegalHoldEnabledArgs.class)))
1742           .thenReturn(true);
1743     });
1744 
1745     minio.makeBucket(MakeBucketArgs.builder()
1746         .bucket(bucket)
1747         .objectLock(true)
1748         .build());
1749     byte[] value = "Hello Minio".getBytes(StandardCharsets.UTF_8);
1750     ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
1751         .bucket(bucket)
1752         .object(objectName)
1753         .stream(new ByteArrayInputStream(value), (long) value.length, -1L)
1754         .contentType(MediaType.TEXT_PLAIN_VALUE)
1755         .build());
1756     assertNotNull(response);
1757 
1758     try {
1759       minio.enableObjectLegalHold(EnableObjectLegalHoldArgs.builder()
1760           .bucket(bucket)
1761           .object(objectName)
1762           .build());
1763 
1764       boolean enabled = minio.isObjectLegalHoldEnabled(IsObjectLegalHoldEnabledArgs.builder()
1765           .bucket(bucket)
1766           .object(objectName)
1767           .build());
1768       assertTrue(enabled);
1769 
1770       minio.disableObjectLegalHold(DisableObjectLegalHoldArgs.builder()
1771           .bucket(bucket)
1772           .object(objectName)
1773           .build());
1774 
1775     } finally {
1776       minio.removeObject(RemoveObjectArgs.builder()
1777           .bucket(bucket)
1778           .object(objectName)
1779           .versionId(response.versionId())
1780           .build());
1781       minio.removeBucket(RemoveBucketArgs.builder()
1782           .bucket(bucket)
1783           .build());
1784     }
1785   }
1786 
1787   /**
1788    * Prompt object.
1789    */
1790   @Order(1200)
1791   @Test
1792   void promptObject() {
1793     PromptObjectResponse response = mock(PromptObjectResponse.class);
1794     MinioTemplate minio = minioMockTemplate(
1795         "promptObject",
1796         minioClient -> when(minioClient.promptObject(any(PromptObjectArgs.class)))
1797             .thenReturn(response));
1798     try (PromptObjectResponse actual = minio.promptObject(mock(PromptObjectArgs.class))) {
1799       assertEquals(response, actual);
1800     } catch (Exception e) {
1801       fail(e.getMessage());
1802     }
1803   }
1804 
1805   /**
1806    * Put object fan out.
1807    */
1808   @Order(1210)
1809   @Test
1810   void putObjectFanOut() {
1811     PutObjectFanOutResponse response = mock(PutObjectFanOutResponse.class);
1812     MinioTemplate minio = minioMockTemplate(
1813         "putObjectFanOut",
1814         minioClient -> when(minioClient.putObjectFanOut(any(PutObjectFanOutArgs.class)))
1815             .thenReturn(response));
1816     PutObjectFanOutResponse actual = minio.putObjectFanOut(mock(PutObjectFanOutArgs.class));
1817     assertEquals(response, actual);
1818   }
1819 
1820   /**
1821    * Restore object.
1822    */
1823   @Order(1220)
1824   @Test
1825   void restoreObject() {
1826     MinioTemplate minio = minioMockTemplate("restoreObject", minioClient -> {
1827     });
1828     assertDoesNotThrow(() -> minio.restoreObject(mock(RestoreObjectArgs.class)));
1829   }
1830 
1831   /**
1832    * The interface Mock minio client configurator.
1833    */
1834   interface MockMinioClientConfigurator {
1835 
1836     /**
1837      * Configure.
1838      *
1839      * @param minioClient the minio client
1840      * @throws Exception the exception
1841      */
1842     void configure(MinioClient minioClient) throws Exception;
1843 
1844     /**
1845      * Configure mock.
1846      *
1847      * @param minioClient the minio client
1848      */
1849     default void configureMock(MinioClient minioClient) {
1850       try {
1851         configure(minioClient);
1852 
1853       } catch (Exception e) {
1854         if (e instanceof RuntimeException) {
1855           throw (RuntimeException) e;
1856         }
1857         throw new RuntimeException(e);
1858       }
1859     }
1860   }
1861 }