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
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
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 }