1
+ package com .quantori .cqp .api .service ;
2
+
3
+ import static org .junit .jupiter .api .Assertions .assertAll ;
4
+ import static org .junit .jupiter .api .Assertions .assertEquals ;
5
+ import static org .junit .jupiter .api .Assertions .assertFalse ;
6
+ import static org .junit .jupiter .api .Assertions .assertThrows ;
7
+ import static org .junit .jupiter .api .Assertions .assertTrue ;
8
+ import static org .junit .jupiter .api .Assertions .fail ;
9
+ import static org .mockito .ArgumentMatchers .any ;
10
+ import static org .mockito .ArgumentMatchers .anyString ;
11
+ import static org .mockito .ArgumentMatchers .eq ;
12
+ import static org .mockito .Mockito .doNothing ;
13
+ import static org .mockito .Mockito .doThrow ;
14
+ import static org .mockito .Mockito .when ;
15
+
16
+ import com .quantori .cqp .api .StorageException ;
17
+ import com .quantori .cqp .api .model .Library ;
18
+ import com .quantori .cqp .api .model .LibraryType ;
19
+ import com .quantori .cqp .api .model .Property ;
20
+ import com .quantori .cqp .api .model .upload .Molecule ;
21
+ import org .junit .jupiter .api .Test ;
22
+ import org .junit .jupiter .api .extension .ExtendWith ;
23
+ import org .junit .jupiter .api .function .Executable ;
24
+ import org .junit .jupiter .params .ParameterizedTest ;
25
+ import org .junit .jupiter .params .provider .EnumSource ;
26
+ import org .mockito .Mock ;
27
+ import org .mockito .junit .jupiter .MockitoExtension ;
28
+
29
+ import java .time .Instant ;
30
+ import java .time .temporal .ChronoUnit ;
31
+ import java .util .LinkedHashMap ;
32
+ import java .util .Map ;
33
+ import java .util .Optional ;
34
+ import java .util .UUID ;
35
+ import java .util .concurrent .ConcurrentHashMap ;
36
+ import java .util .concurrent .CountDownLatch ;
37
+ import java .util .stream .Collectors ;
38
+
39
+
40
+ @ ExtendWith (MockitoExtension .class )
41
+ class StorageLibraryTest {
42
+
43
+ @ Mock
44
+ private StorageLibrary storageLibrary ;
45
+
46
+ @ Mock
47
+ private StorageMolecules storageMolecules ;
48
+
49
+ @ Mock
50
+ private ItemWriter <Molecule > itemWriter ;
51
+
52
+ private final String LIBRARY_ID = UUID .randomUUID ().toString ();
53
+
54
+ @ ParameterizedTest
55
+ @ EnumSource (value = LibraryType .class , names = {"MOLECULES" , "REACTIONS" })
56
+ void testCreateLibrary (LibraryType libraryType ) {
57
+ Instant now = Instant .now ().truncatedTo (ChronoUnit .MICROS );
58
+ String libraryName = "test_create_library_" + libraryType ;
59
+
60
+ Library library = new Library ();
61
+ library .setName (libraryName );
62
+ library .setCreatedStamp (now .plusSeconds (1 ));
63
+ library .setUpdatedStamp (now .plusSeconds (1 ));
64
+
65
+ when (storageLibrary .createLibrary (eq (libraryName ), eq (libraryType ), any ())).thenReturn (library );
66
+
67
+ Library result = storageLibrary .createLibrary (libraryName , libraryType , null );
68
+ assertEquals (libraryName , result .getName ());
69
+ assertEquals (result .getCreatedStamp (), result .getUpdatedStamp ());
70
+ }
71
+
72
+ @ Test
73
+ void testCreateLibrary_withPropertiesMapping () {
74
+ Instant now = Instant .now ().truncatedTo (ChronoUnit .MICROS );
75
+ Map <String , Property > propertiesMapping = Map .of (
76
+ "test1" , new Property ("test 1" , Property .PropertyType .STRING ),
77
+ "test2" , new Property ("test 2" , Property .PropertyType .DATE ),
78
+ "test3" , new Property ("test 3" , Property .PropertyType .DECIMAL )
79
+ );
80
+
81
+ Library library = new Library ();
82
+ library .setName ("test_create_library_properties_mapping" );
83
+ library .setCreatedStamp (now .plusSeconds (1 ));
84
+ library .setUpdatedStamp (now .plusSeconds (1 ));
85
+ library .setId (LIBRARY_ID );
86
+
87
+ when (storageLibrary .createLibrary (anyString (), eq (LibraryType .MOLECULES ), eq (propertiesMapping ), any ()))
88
+ .thenReturn (library );
89
+ when (storageLibrary .getPropertiesMapping (LIBRARY_ID )).thenReturn (propertiesMapping );
90
+
91
+ Library result = storageLibrary .createLibrary ("test_create_library_properties_mapping" ,
92
+ LibraryType .MOLECULES , propertiesMapping , null );
93
+
94
+ assertEquals ("test_create_library_properties_mapping" , result .getName ());
95
+ assertEquals (result .getCreatedStamp (), result .getUpdatedStamp ());
96
+ assertEquals (propertiesMapping , storageLibrary .getPropertiesMapping (result .getId ()));
97
+
98
+ Map <String , Property > newProperties = Map .of (
99
+ "test4" , new Property ("test 4" , Property .PropertyType .STRING ),
100
+ "test5" , new Property ("test 5" , Property .PropertyType .DATE ),
101
+ "test6" , new Property ("test 6" , Property .PropertyType .DECIMAL )
102
+ );
103
+ Map <String , Property > all = new LinkedHashMap <>(propertiesMapping );
104
+ all .putAll (newProperties );
105
+
106
+ when (storageLibrary .addPropertiesMapping (LIBRARY_ID , newProperties )).thenReturn (true );
107
+ when (storageLibrary .getPropertiesMapping (LIBRARY_ID )).thenReturn (all );
108
+
109
+ boolean status = storageLibrary .addPropertiesMapping (LIBRARY_ID , newProperties );
110
+ assertTrue (status );
111
+ assertEquals (all , storageLibrary .getPropertiesMapping (LIBRARY_ID ));
112
+ }
113
+
114
+ @ Test
115
+ void testCreateLibrary_addExistingPropertyMapping () {
116
+ Map <String , Property > initial = Map .of (
117
+ "test1" , new Property ("test 1" , Property .PropertyType .STRING )
118
+ );
119
+ Map <String , Property > duplicate = Map .of (
120
+ "test1" , new Property ("test 1" , Property .PropertyType .STRING )
121
+ );
122
+
123
+ Library library = new Library ();
124
+ library .setId (LIBRARY_ID );
125
+
126
+ when (storageLibrary .createLibrary (any (), any (), any (), any ())).thenReturn (library );
127
+ doThrow (new StorageException ("A library %s already contains the following properties test1" .formatted (LIBRARY_ID )))
128
+ .when (storageLibrary ).addPropertiesMapping (eq (LIBRARY_ID ), eq (duplicate ));
129
+
130
+ Library result = storageLibrary .createLibrary ("lib" , LibraryType .MOLECULES , initial , null );
131
+
132
+ Exception exception = assertThrows (StorageException .class , () ->
133
+ storageLibrary .addPropertiesMapping (result .getId (), duplicate )
134
+ );
135
+ assertEquals ("A library %s already contains the following properties test1" .formatted (LIBRARY_ID ), exception .getMessage ());
136
+ }
137
+
138
+ @ Test
139
+ void testCreateLibrary_updatePropertyMapping () {
140
+ Library library = new Library ();
141
+ library .setId (LIBRARY_ID );
142
+
143
+ Map <String , Property > newMapping = Map .of (
144
+ "test1" , new Property ("new test 1" , Property .PropertyType .DATE , 1 , true , false )
145
+ );
146
+
147
+ when (storageLibrary .createLibrary (any (), any (), any (), any ())).thenReturn (library );
148
+ when (storageLibrary .updatePropertiesMapping (eq (LIBRARY_ID ), eq (newMapping ))).thenReturn (true );
149
+ when (storageLibrary .getPropertiesMapping (eq (LIBRARY_ID ))).thenReturn (
150
+ Map .of ("test1" , new Property ("new test 1" , Property .PropertyType .STRING , 1 , true , false ))
151
+ );
152
+
153
+ Library result = storageLibrary .createLibrary ("lib" , LibraryType .MOLECULES , Map .of (), null );
154
+ boolean status = storageLibrary .updatePropertiesMapping (result .getId (), newMapping );
155
+ assertTrue (status );
156
+
157
+ Map <String , Property > actual = storageLibrary .getPropertiesMapping (result .getId ());
158
+ assertEquals ("new test 1" , actual .get ("test1" ).getName ());
159
+ assertEquals (Property .PropertyType .STRING , actual .get ("test1" ).getType ());
160
+ }
161
+
162
+ @ Test
163
+ void testCreateLibrary_updateNonExistingPropertyMapping () {
164
+ Library library = new Library ();
165
+ library .setId (LIBRARY_ID );
166
+
167
+ Map <String , Property > newMapping = Map .of (
168
+ "test4" , new Property ("test 4" , Property .PropertyType .STRING )
169
+ );
170
+
171
+ when (storageLibrary .createLibrary (any (), any (), any (), any ())).thenReturn (library );
172
+ doThrow (new StorageException ("A library %s does not contain the following properties test4" .formatted (LIBRARY_ID )))
173
+ .when (storageLibrary ).updatePropertiesMapping (eq (LIBRARY_ID ), eq (newMapping ));
174
+
175
+ Library result = storageLibrary .createLibrary ("lib" , LibraryType .MOLECULES , Map .of (), null );
176
+ Exception exception = assertThrows (StorageException .class , () ->
177
+ storageLibrary .updatePropertiesMapping (result .getId (), newMapping )
178
+ );
179
+ assertEquals ("A library %s does not contain the following properties test4" .formatted (LIBRARY_ID ), exception .getMessage ());
180
+ }
181
+
182
+ @ Test
183
+ void testCreateLibrary_updateGeneratedPropertyMapping () {
184
+ Library library = new Library ();
185
+ library .setId (LIBRARY_ID );
186
+
187
+ Molecule m = new Molecule ();
188
+ m .setMolProperties (Map .of ("test1" , "1" , "test2" , "1.0" , "test3" , "some value" ));
189
+ m .setSmiles ("" );
190
+ m .setStructure (new byte [0 ]);
191
+
192
+ doNothing ().when (itemWriter ).write (any ());
193
+ itemWriter .write (m );
194
+
195
+ Map <String , Property > properties = Map .of (
196
+ "test1" , new Property ("test1" , Property .PropertyType .DECIMAL ),
197
+ "test2" , new Property ("test2" , Property .PropertyType .DECIMAL ),
198
+ "test3" , new Property ("test3" , Property .PropertyType .STRING )
199
+ );
200
+
201
+ when (storageLibrary .getPropertiesMapping (LIBRARY_ID )).thenReturn (properties );
202
+ when (storageLibrary .updatePropertiesMapping (LIBRARY_ID , properties )).thenReturn (true );
203
+
204
+ boolean status = storageLibrary .updatePropertiesMapping (LIBRARY_ID , properties );
205
+ assertTrue (status );
206
+ assertEquals (properties , storageLibrary .getPropertiesMapping (LIBRARY_ID ));
207
+ }
208
+
209
+ @ ParameterizedTest
210
+ @ EnumSource (value = LibraryType .class , names = {"MOLECULES" , "REACTIONS" })
211
+ void testUpdateLibrary (LibraryType libraryType ) {
212
+ Library library = new Library ();
213
+ library .setId (LIBRARY_ID );
214
+ library .setName ("test_update_library_" + libraryType );
215
+ library .setStructuresCount (0 );
216
+ Instant now = Instant .now ();
217
+ library .setCreatedStamp (now );
218
+ library .setUpdatedStamp (now );
219
+
220
+ when (storageLibrary .createLibrary (any (), any (), any ())).thenReturn (library );
221
+ when (storageLibrary .updateLibrary (any ())).thenReturn (library );
222
+ when (storageLibrary .getLibraryById (LIBRARY_ID )).thenReturn (Optional .of (library ));
223
+
224
+ Library created = storageLibrary .createLibrary ("lib" , libraryType , null );
225
+ created .setName ("test_update_library_" + libraryType + "_new_name" );
226
+
227
+ Library updated = storageLibrary .updateLibrary (created );
228
+ assertEquals ("test_update_library_" + libraryType + "_new_name" , updated .getName ());
229
+ assertEquals (created , storageLibrary .getLibraryById (LIBRARY_ID ).orElseThrow ());
230
+ }
231
+
232
+ @ Test
233
+ void testUpdateLibrary_parallel () throws InterruptedException {
234
+ String libraryName = "test_update_library_parallel" ;
235
+ Library baseLibrary = new Library ();
236
+ baseLibrary .setId (LIBRARY_ID );
237
+ baseLibrary .setName (libraryName );
238
+
239
+ when (storageLibrary .createLibrary (any (), any (), any ())).thenReturn (baseLibrary );
240
+ when (storageLibrary .updateLibrary (any ())).thenAnswer (invocation -> invocation .getArgument (0 ));
241
+
242
+ Library library = storageLibrary .createLibrary (libraryName , LibraryType .MOLECULES , null );
243
+
244
+ int threadCounts = 10 ;
245
+ CountDownLatch latch = new CountDownLatch (threadCounts );
246
+ Thread [] threads = new Thread [threadCounts ];
247
+ Map <String , String > resultMap = new ConcurrentHashMap <>();
248
+
249
+ for (int i = 0 ; i < threadCounts ; i ++) {
250
+ threads [i ] = new Thread (() -> {
251
+ Library update = new Library ();
252
+ String newName = library .getName () + "_" + UUID .randomUUID ();
253
+ update .setId (library .getId ());
254
+ update .setName (newName );
255
+ latch .countDown ();
256
+ try {
257
+ latch .await ();
258
+ Library updated = storageLibrary .updateLibrary (update );
259
+ resultMap .put (newName , updated .getName ());
260
+ } catch (InterruptedException e ) {
261
+ fail (e );
262
+ }
263
+ });
264
+ threads [i ].start ();
265
+ }
266
+
267
+ for (Thread thread : threads ) {
268
+ thread .join ();
269
+ }
270
+
271
+ assertAll (resultMap .entrySet ().stream ()
272
+ .map (entry -> (Executable ) () -> assertEquals (entry .getKey (), entry .getValue ()))
273
+ .collect (Collectors .toList ()));
274
+ }
275
+
276
+ @ ParameterizedTest
277
+ @ EnumSource (value = LibraryType .class , names = {"MOLECULES" , "REACTIONS" })
278
+ void testDeleteLibrary (LibraryType libraryType ) {
279
+ Library library = new Library ();
280
+ library .setId (LIBRARY_ID );
281
+ library .setName ("lib" );
282
+ library .setType (libraryType );
283
+
284
+ when (storageLibrary .createLibrary (any (), any (), any ())).thenReturn (library );
285
+ when (storageLibrary .deleteLibrary (library )).thenReturn (true ).thenReturn (false );
286
+
287
+ Library result = storageLibrary .createLibrary ("lib" , libraryType , null );
288
+ assertTrue (storageLibrary .deleteLibrary (result ));
289
+ assertFalse (storageLibrary .deleteLibrary (result ));
290
+ }
291
+
292
+ @ ParameterizedTest
293
+ @ EnumSource (value = LibraryType .class , names = {"METRICS" , "ANY" })
294
+ void testDeleteLibraryWrongType (LibraryType libraryType ) {
295
+ Library library = new Library ();
296
+ library .setId ("1" );
297
+ library .setName ("name" );
298
+ library .setType (libraryType );
299
+
300
+ doThrow (new StorageException ("A library type must be molecule or reaction" ))
301
+ .when (storageLibrary ).deleteLibrary (library );
302
+
303
+ assertThrows (StorageException .class , () -> storageLibrary .deleteLibrary (library ));
304
+ }
305
+ }
0 commit comments