View Javadoc
1   package de.dlr.shepard.data.file.services;
2   
3   import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4   import static org.junit.jupiter.api.Assertions.assertEquals;
5   import static org.junit.jupiter.api.Assertions.assertThrows;
6   import static org.mockito.ArgumentMatchers.any;
7   import static org.mockito.ArgumentMatchers.eq;
8   import static org.mockito.Mockito.doAnswer;
9   import static org.mockito.Mockito.doThrow;
10  import static org.mockito.Mockito.mock;
11  import static org.mockito.Mockito.mockStatic;
12  import static org.mockito.Mockito.verify;
13  import static org.mockito.Mockito.when;
14  
15  import com.mongodb.client.FindIterable;
16  import com.mongodb.client.MongoCollection;
17  import com.mongodb.client.MongoCursor;
18  import com.mongodb.client.MongoDatabase;
19  import com.mongodb.client.gridfs.GridFSBucket;
20  import com.mongodb.client.gridfs.GridFSBuckets;
21  import com.mongodb.client.gridfs.GridFSDownloadStream;
22  import com.mongodb.client.gridfs.GridFSFindIterable;
23  import com.mongodb.client.gridfs.model.GridFSFile;
24  import com.mongodb.client.model.Filters;
25  import de.dlr.shepard.common.mongoDB.NamedInputStream;
26  import de.dlr.shepard.common.util.DateHelper;
27  import de.dlr.shepard.common.util.UUIDHelper;
28  import de.dlr.shepard.data.file.entities.ShepardFile;
29  import io.quarkus.test.InjectMock;
30  import io.quarkus.test.component.QuarkusComponentTest;
31  import jakarta.inject.Inject;
32  import jakarta.inject.Named;
33  import jakarta.ws.rs.NotFoundException;
34  import jakarta.xml.bind.DatatypeConverter;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.security.DigestInputStream;
38  import java.security.MessageDigest;
39  import java.security.NoSuchAlgorithmException;
40  import java.util.Arrays;
41  import java.util.Date;
42  import java.util.UUID;
43  import org.bson.Document;
44  import org.bson.types.ObjectId;
45  import org.junit.jupiter.api.BeforeAll;
46  import org.junit.jupiter.api.BeforeEach;
47  import org.junit.jupiter.api.Test;
48  import org.mockito.invocation.InvocationOnMock;
49  import org.mockito.stubbing.Answer;
50  
51  @QuarkusComponentTest
52  public class FileServiceTest {
53  
54    @InjectMock
55    @Named("mongoDatabase")
56    MongoDatabase mongoDatabase;
57  
58    @InjectMock
59    private DateHelper dateHelper;
60  
61    @InjectMock
62    private UUIDHelper uuidhelper;
63  
64    @Inject
65    FileService fileService;
66  
67    @InjectMock
68    private MongoCollection<Document> collection;
69  
70    @InjectMock
71    private GridFSBucket gridBucket;
72  
73    @BeforeEach
74    public void setupMongoDBClient() {
75      when(GridFSBuckets.create(mongoDatabase)).thenReturn(gridBucket);
76    }
77  
78    @BeforeAll
79    public static void setupGridFSBucket() {
80      mockStatic(GridFSBuckets.class);
81    }
82  
83    @Test
84    public void createFileContainerTest() {
85      var uuid = UUID.randomUUID();
86      when(uuidhelper.getUUID()).thenReturn(uuid);
87      var result = fileService.createFileContainer();
88      var expectedUUID = "FileContainer" + uuid.toString();
89      assertEquals(expectedUUID, result);
90      verify(mongoDatabase).createCollection(expectedUUID);
91    }
92  
93    @Test
94    public void getExistingFileTest() {
95      String containerId = "FileContainerdc824045-9137-4051-8981-c528e6b91fbe";
96      ObjectId fileoid = new ObjectId("60b73212cfa45d2d5baa795d");
97      String name = "name";
98      String md5 = "md5";
99      Date date = new Date();
100     Document file = mock(Document.class);
101     @SuppressWarnings("unchecked")
102     FindIterable<Document> collectionReturn = mock(FindIterable.class);
103 
104     when(mongoDatabase.getCollection(containerId)).thenReturn(collection);
105     when(collection.find(Filters.eq("_id", fileoid))).thenReturn(collectionReturn);
106     when(collectionReturn.first()).thenReturn(file);
107     when(file.getObjectId("_id")).thenReturn(fileoid);
108     when(file.getString("name")).thenReturn(name);
109     when(file.getDate("createdAt")).thenReturn(date);
110     when(file.getString("md5")).thenReturn(md5);
111 
112     var expected = new ShepardFile(fileoid.toHexString(), date, name, md5);
113     var result = fileService.getFile(containerId, fileoid.toHexString());
114     assertEquals(expected, result);
115   }
116 
117   @Test
118   public void getNonExistingContainerFileTest() {
119     String nonExistingContainerId = "FileContainer123";
120     String fileoid = "60b73212cfa45d2d5baa795d";
121 
122     doThrow(new IllegalArgumentException()).when(mongoDatabase).getCollection(nonExistingContainerId);
123 
124     assertThrows(NotFoundException.class, () -> fileService.getFile(nonExistingContainerId, fileoid));
125   }
126 
127   @Test
128   public void getNonExistingFileTest() {
129     String containerId = "FileContainerdc824045-9137-4051-8981-c528e6b91fbe";
130     String nonExistingFileoid = "60b73212cfa45d2d5baa795b";
131     @SuppressWarnings("unchecked")
132     FindIterable<Document> collectionReturn = mock(FindIterable.class);
133 
134     when(mongoDatabase.getCollection(containerId)).thenReturn(collection);
135     when(collection.find(Filters.eq("_id", new ObjectId(nonExistingFileoid)))).thenReturn(collectionReturn);
136     when(collectionReturn.first()).thenReturn(null);
137     assertThrows(NotFoundException.class, () -> fileService.getFile(containerId, nonExistingFileoid));
138   }
139 
140   @Test
141   public void createFileTest() throws NoSuchAlgorithmException, IOException {
142     ObjectId oid = new ObjectId();
143     ObjectId moid = new ObjectId();
144     String fileName = "fileName";
145     String md5 = DatatypeConverter.printHexBinary(MessageDigest.getInstance("MD5").digest());
146     InputStream inputStream = mock(InputStream.class);
147     String mongoid = "mongoid";
148     Date date = new Date();
149 
150     when(dateHelper.getDate()).thenReturn(date);
151     when(mongoDatabase.getCollection(mongoid)).thenReturn(collection);
152 
153     when(gridBucket.withChunkSizeBytes(1024 * 1024)).thenReturn(gridBucket);
154     when(gridBucket.uploadFromStream(eq(fileName), any(DigestInputStream.class))).thenReturn(oid);
155     doAnswer(
156       new Answer<Void>() {
157         @Override
158         public Void answer(InvocationOnMock invocation) throws Throwable {
159           Object[] args = invocation.getArguments();
160           ((Document) args[0]).append("_id", moid);
161           return null;
162         }
163       }
164     )
165       .when(collection)
166       .insertOne(any(Document.class));
167 
168     var newFile = new Document("_id", moid)
169       .append("name", fileName)
170       .append("FileMongoId", oid.toHexString())
171       .append("createdAt", date)
172       .append("md5", md5);
173     var expected = new ShepardFile(moid.toHexString(), date, fileName, md5);
174     var result = fileService.createFile(mongoid, fileName, inputStream);
175     assertEquals(expected, result);
176     verify(collection).insertOne(newFile);
177   }
178 
179   @Test
180   public void createNonExistingMongoidFileTest() {
181     String fileName = "fileName";
182     InputStream inputStream = mock(InputStream.class);
183     String nonExistingMongoid = "mongoid";
184 
185     doThrow(new IllegalArgumentException()).when(mongoDatabase).getCollection(nonExistingMongoid);
186 
187     assertThrows(NotFoundException.class, () -> fileService.createFile(nonExistingMongoid, fileName, inputStream));
188   }
189 
190   @Test
191   public void deleteExistingFileContainerTest() {
192     String existingMongoOid = "60b73212cfa45d2d5baa795d";
193     ObjectId oid = new ObjectId();
194     @SuppressWarnings("unchecked")
195     FindIterable<Document> collectionReturn = mock(FindIterable.class);
196     Document doc = mock(Document.class);
197     mockIterable(collectionReturn, doc);
198 
199     when(mongoDatabase.getCollection(existingMongoOid)).thenReturn(collection);
200     when(collection.find()).thenReturn(collectionReturn);
201     when(doc.getString("FileMongoId")).thenReturn(oid.toHexString());
202 
203     assertDoesNotThrow(() -> fileService.deleteFileContainer(existingMongoOid));
204     verify(gridBucket).delete(oid);
205   }
206 
207   @Test
208   public void deleteNonExistingFileContainerTest() {
209     String nonExistingMongoOid = "60b73212cfa45d2d5baa795d";
210 
211     doThrow(new IllegalArgumentException()).when(mongoDatabase).getCollection(nonExistingMongoOid);
212 
213     assertThrows(NotFoundException.class, () -> fileService.deleteFileContainer(nonExistingMongoOid));
214   }
215 
216   @Test
217   public void getPayloadTest() {
218     String containerId = "4";
219     String fileoid = "60b73212cfa45d2d5baa795d";
220     String fileName = "FileName";
221     Long fileSize = 123L;
222     @SuppressWarnings("unchecked")
223     FindIterable<Document> collectionReturn = mock(FindIterable.class);
224     Document doc = mock(Document.class);
225     ObjectId oid = new ObjectId();
226     GridFSDownloadStream stream = mock(GridFSDownloadStream.class);
227     GridFSFindIterable filesCollectionReturn = mock(GridFSFindIterable.class);
228     GridFSFile gridFsFile = mock(GridFSFile.class);
229 
230     when(mongoDatabase.getCollection(containerId)).thenReturn(collection);
231     when(collection.find(Filters.eq("_id", new ObjectId(fileoid)))).thenReturn(collectionReturn);
232     when(collectionReturn.first()).thenReturn(doc);
233     when(doc.getString("FileMongoId")).thenReturn(oid.toHexString());
234     when(doc.getString("name")).thenReturn(fileName);
235     when(gridBucket.openDownloadStream(oid)).thenReturn(stream);
236     when(gridBucket.find(Filters.eq("_id", oid))).thenReturn(filesCollectionReturn);
237     when(filesCollectionReturn.first()).thenReturn(gridFsFile);
238     when(gridFsFile.getLength()).thenReturn(fileSize);
239 
240     var expected = new NamedInputStream(fileoid, stream, fileName, fileSize);
241     var result = fileService.getPayload(containerId, fileoid);
242     assertEquals(expected, result);
243   }
244 
245   @Test
246   public void getPayloadNonExistingContainerIdTest() {
247     String nonExistingContainerId = "4";
248     String fileoid = "60b73212cfa45d2d5baa795d";
249 
250     doThrow(new IllegalArgumentException()).when(mongoDatabase).getCollection(nonExistingContainerId);
251 
252     assertThrows(NotFoundException.class, () -> fileService.getPayload(nonExistingContainerId, fileoid));
253   }
254 
255   @Test
256   public void getPayloadNonExistingFileOidTest() {
257     String containerId = "4";
258     String fileoid = "60b73212cfa45d2d5baa795d";
259 
260     when(mongoDatabase.getCollection(containerId)).thenReturn(collection);
261     @SuppressWarnings("unchecked")
262     FindIterable<Document> emptyCollectionReturn = mock(FindIterable.class);
263     when(collection.find(Filters.eq("_id", new ObjectId(fileoid)))).thenReturn(emptyCollectionReturn);
264     when(emptyCollectionReturn.first()).thenReturn(null);
265 
266     assertThrows(NotFoundException.class, () -> fileService.getPayload(containerId, fileoid));
267   }
268 
269   @Test
270   public void deletePayloadTest() {
271     String fileOid = "60b73212cfa45d2d5baa795a";
272     String mongoOid = "60b73212cfa45d2d5baa795b";
273     Document doc = mock(Document.class);
274     ObjectId oid = new ObjectId("60b73212cfa45d2d5baa795c");
275 
276     when(mongoDatabase.getCollection(mongoOid)).thenReturn(collection);
277     when(collection.findOneAndDelete(Filters.eq("_id", oid))).thenReturn(doc);
278     when(doc.getString("FileMongoId")).thenReturn(fileOid);
279 
280     assertDoesNotThrow(() -> fileService.deleteFile(mongoOid, oid.toHexString()));
281     verify(gridBucket).delete(new ObjectId(fileOid));
282   }
283 
284   @Test
285   public void deletePayloadTest_collectionIsNull() {
286     String mongoOid = "60b73212cfa45d2d5baa795b";
287     ObjectId oid = new ObjectId("60b73212cfa45d2d5baa795c");
288 
289     doThrow(new IllegalArgumentException()).when(mongoDatabase).getCollection(mongoOid);
290 
291     assertThrows(NotFoundException.class, () -> fileService.deleteFile(mongoOid, oid.toHexString()));
292   }
293 
294   @Test
295   public void deletePayloadTest_documentIsNull() {
296     String mongoOid = "60b73212cfa45d2d5baa795b";
297     ObjectId oid = new ObjectId("60b73212cfa45d2d5baa795c");
298 
299     when(mongoDatabase.getCollection(mongoOid)).thenReturn(collection);
300     when(collection.findOneAndDelete(Filters.eq("_id", oid))).thenReturn(null);
301 
302     assertThrows(NotFoundException.class, () -> fileService.deleteFile(mongoOid, oid.toHexString()));
303   }
304 
305   /**
306    * From https://www.batey.info/mocking-iterable-objects-generically.html
307    */
308   @SuppressWarnings("unchecked")
309   private static void mockIterable(FindIterable<Document> iterable, Document... values) {
310     MongoCursor<Document> mockIterator = mock(MongoCursor.class);
311     when(iterable.iterator()).thenReturn(mockIterator);
312 
313     if (values.length == 0) {
314       when(mockIterator.hasNext()).thenReturn(false);
315     } else if (values.length == 1) {
316       when(mockIterator.hasNext()).thenReturn(true, false);
317       when(mockIterator.next()).thenReturn(values[0]);
318     } else {
319       // build boolean array for hasNext()
320       Boolean[] hasNextResponses = new Boolean[values.length];
321       for (int i = 0; i < hasNextResponses.length - 1; i++) {
322         hasNextResponses[i] = true;
323       }
324       hasNextResponses[hasNextResponses.length - 1] = false;
325       when(mockIterator.hasNext()).thenReturn(true, hasNextResponses);
326       Document[] valuesMinusTheFirst = Arrays.copyOfRange(values, 1, values.length);
327       when(mockIterator.next()).thenReturn(values[0], valuesMinusTheFirst);
328     }
329   }
330 }