1
+ /*
2
+ * Copyright (C) 2019 The Turms Project
3
+ * https://github.com/turms-im/turms
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ package im .turms .plugin .minio ;
19
+
20
+ import java .io .ByteArrayInputStream ;
21
+ import java .time .Duration ;
22
+ import java .util .Arrays ;
23
+ import java .util .Collections ;
24
+ import java .util .Map ;
25
+
26
+ import io .netty .handler .codec .http .HttpHeaderNames ;
27
+ import io .netty .handler .codec .http .HttpResponseStatus ;
28
+ import org .junit .jupiter .api .BeforeAll ;
29
+ import org .junit .jupiter .api .MethodOrderer ;
30
+ import org .junit .jupiter .api .Order ;
31
+ import org .junit .jupiter .api .Test ;
32
+ import org .junit .jupiter .api .TestMethodOrder ;
33
+ import org .springframework .context .ApplicationContext ;
34
+ import reactor .core .publisher .Mono ;
35
+ import reactor .netty .http .client .HttpClient ;
36
+ import reactor .test .StepVerifier ;
37
+
38
+ import im .turms .plugin .minio .properties .MinioStorageProperties ;
39
+ import im .turms .server .common .access .admin .web .MediaType ;
40
+ import im .turms .server .common .access .admin .web .MediaTypeConst ;
41
+ import im .turms .server .common .infra .cluster .node .Node ;
42
+ import im .turms .server .common .infra .property .TurmsProperties ;
43
+ import im .turms .server .common .infra .property .TurmsPropertiesManager ;
44
+ import im .turms .server .common .infra .property .env .service .ServiceProperties ;
45
+ import im .turms .server .common .infra .property .env .service .business .storage .StorageProperties ;
46
+ import im .turms .server .common .infra .property .env .service .env .database .TurmsMongoProperties ;
47
+ import im .turms .server .common .testing .BaseIntegrationTest ;
48
+ import im .turms .server .common .testing .environment .ServiceTestEnvironmentType ;
49
+ import im .turms .server .common .testing .properties .MinioTestEnvironmentProperties ;
50
+ import im .turms .server .common .testing .properties .TestProperties ;
51
+ import im .turms .service .domain .group .service .GroupMemberService ;
52
+ import im .turms .service .domain .user .service .UserRelationshipService ;
53
+
54
+ import static org .assertj .core .api .Assertions .assertThat ;
55
+ import static org .mockito .Mockito .doReturn ;
56
+ import static org .mockito .Mockito .mock ;
57
+ import static org .mockito .Mockito .spy ;
58
+ import static org .mockito .Mockito .when ;
59
+
60
+ /**
61
+ * @author James Chen
62
+ */
63
+ @ TestMethodOrder (MethodOrderer .OrderAnnotation .class )
64
+ class MinioStorageServiceProviderIT extends BaseIntegrationTest {
65
+
66
+ private static final long USER_ID = 1L ;
67
+ private static final byte [] FILE_FOR_UPLOADING = new byte []{0 , 1 , 2 , 3 };
68
+ private static final String FILE_MEDIA_TYPE = MediaTypeConst .IMAGE_PNG ;
69
+
70
+ private static final Duration TIMEOUT_DURATION = Duration .ofMinutes (1 );
71
+
72
+ private static MinioStorageServiceProvider serviceProvider ;
73
+
74
+ @ BeforeAll
75
+ static void setup () {
76
+ setupTestEnvironment (new TestProperties ().toBuilder ()
77
+ .minio (new MinioTestEnvironmentProperties ().toBuilder ()
78
+ .type (ServiceTestEnvironmentType .CONTAINER )
79
+ .build ())
80
+ .build ());
81
+ }
82
+
83
+ @ Order (0 )
84
+ @ Test
85
+ void test_start () {
86
+ TurmsPropertiesManager turmsPropertiesManager = mock (TurmsPropertiesManager .class );
87
+ when (turmsPropertiesManager .getLocalProperties ())
88
+ .thenReturn (new TurmsProperties ().toBuilder ()
89
+ .service (new ServiceProperties ().toBuilder ()
90
+ .storage (new StorageProperties ().toBuilder ()
91
+ .build ())
92
+ .build ())
93
+ .build ());
94
+
95
+ ApplicationContext applicationContext = mock (ApplicationContext .class );
96
+ when (applicationContext .getBean (Node .class )).thenReturn (mock (Node .class ));
97
+ when (applicationContext .getBean (GroupMemberService .class ))
98
+ .thenReturn (mock (GroupMemberService .class ));
99
+ when (applicationContext .getBean (UserRelationshipService .class ))
100
+ .thenReturn (mock (UserRelationshipService .class ));
101
+ when (applicationContext .getBean (TurmsPropertiesManager .class ))
102
+ .thenReturn (turmsPropertiesManager );
103
+
104
+ MinioStorageServiceProvider provider = spy (new MinioStorageServiceProvider ());
105
+ doReturn (applicationContext ).when (provider )
106
+ .getContext ();
107
+ doReturn (new MinioStorageProperties ().toBuilder ()
108
+ .endpoint (testEnvironmentManager .getMinioUri ())
109
+ .mongo (new TurmsMongoProperties (testEnvironmentManager .getMongoUri ()))
110
+ .build ()).when (provider )
111
+ .loadProperties (MinioStorageProperties .class );
112
+
113
+ StepVerifier .create (provider .start ()
114
+ .timeout (TIMEOUT_DURATION ))
115
+ .verifyComplete ();
116
+
117
+ serviceProvider = provider ;
118
+ }
119
+
120
+ @ Order (100 )
121
+ @ Test
122
+ void test_queryUserProfilePictureUploadInfo () {
123
+ Mono <Map <String , String >> queryUploadInfo = serviceProvider
124
+ .queryUserProfilePictureUploadInfo (USER_ID ,
125
+ null ,
126
+ MediaType .create (MediaTypeConst .IMAGE_PNG ),
127
+ Collections .emptyMap ())
128
+ .timeout (TIMEOUT_DURATION );
129
+ StepVerifier .create (queryUploadInfo )
130
+ .expectNextMatches (uploadInfo -> {
131
+ String resourceId = uploadInfo .remove (MinioStorageServiceProvider .RESOURCE_ID );
132
+ String resourceUploadUrl =
133
+ uploadInfo .remove (MinioStorageServiceProvider .RESOURCE_URL );
134
+ assertThat (resourceId ).as ("The resource ID should not be null" )
135
+ .isNotNull ();
136
+ assertThat (resourceUploadUrl ).as ("The resource upload URL should not be null" )
137
+ .isNotNull ();
138
+
139
+ StepVerifier .create (HttpClient .create ()
140
+ .post ()
141
+ .uri (resourceUploadUrl )
142
+ .sendForm ((request , form ) -> {
143
+ request .requestHeaders ()
144
+ .remove (HttpHeaderNames .TRANSFER_ENCODING );
145
+ form .multipart (true );
146
+ for (Map .Entry <String , String > entry : uploadInfo .entrySet ()) {
147
+ form .attr (entry .getKey (), entry .getValue ());
148
+ }
149
+ form .attr ("Content-Type" , MediaTypeConst .IMAGE_PNG )
150
+ .attr ("key" , resourceId )
151
+ .file ("file" ,
152
+ resourceId ,
153
+ new ByteArrayInputStream (FILE_FOR_UPLOADING ),
154
+ FILE_MEDIA_TYPE );
155
+ })
156
+ .responseSingle ((response , responseBodyMono ) -> {
157
+ HttpResponseStatus status = response .status ();
158
+ if (status .equals (HttpResponseStatus .NO_CONTENT )) {
159
+ return Mono .empty ();
160
+ }
161
+ return responseBodyMono .asString ()
162
+ .switchIfEmpty (
163
+ Mono .defer (() -> Mono .error (new RuntimeException (
164
+ "The response status code should be 200, but got: "
165
+ + status .code ()))))
166
+ .flatMap (body -> Mono .error (new RuntimeException (
167
+ "The response status code should be 200, but got: "
168
+ + status .code ()
169
+ + ". Response body: \" "
170
+ + body
171
+ + "\" " )));
172
+ })
173
+ .timeout (TIMEOUT_DURATION ))
174
+ .verifyComplete ();
175
+ return true ;
176
+ })
177
+ .verifyComplete ();
178
+ }
179
+
180
+ @ Order (101 )
181
+ @ Test
182
+ void test_queryUserProfilePictureDownloadInfo () {
183
+ Mono <Map <String , String >> queryDownloadInfo = serviceProvider
184
+ .queryUserProfilePictureDownloadInfo (USER_ID , USER_ID , Collections .emptyMap ())
185
+ .timeout (TIMEOUT_DURATION );
186
+ StepVerifier .create (queryDownloadInfo )
187
+ .expectNextMatches (downloadInfo -> {
188
+ String resourceDownloadUrl =
189
+ downloadInfo .get (MinioStorageServiceProvider .RESOURCE_URL );
190
+ assertThat (resourceDownloadUrl )
191
+ .as ("The resource download URL should not be null" )
192
+ .isNotNull ();
193
+
194
+ StepVerifier .create (HttpClient .create ()
195
+ .get ()
196
+ .uri (resourceDownloadUrl )
197
+ .responseSingle ((response , responseBodyMono ) -> {
198
+ HttpResponseStatus status = response .status ();
199
+ if (status .equals (HttpResponseStatus .OK )) {
200
+ return responseBodyMono .asByteArray ()
201
+ .switchIfEmpty (Mono
202
+ .defer (() -> Mono .error (new RuntimeException (
203
+ "The downloaded file should be the same with the upload file" ))))
204
+ .flatMap (bytes -> {
205
+ if (Arrays .equals (bytes , FILE_FOR_UPLOADING )) {
206
+ return Mono .empty ();
207
+ }
208
+ return Mono .error (new RuntimeException (
209
+ "The downloaded file should be the same with the upload file" ));
210
+ });
211
+ }
212
+ return responseBodyMono .asString ()
213
+ .switchIfEmpty (
214
+ Mono .defer (() -> Mono .error (new RuntimeException (
215
+ "The response status code should be 200, but got: "
216
+ + status .code ()))))
217
+ .flatMap (body -> Mono .error (new RuntimeException (
218
+ "The response status code should be 200, but got: "
219
+ + status .code ()
220
+ + ". Response body: \" "
221
+ + body
222
+ + "\" " )));
223
+ })
224
+ .timeout (TIMEOUT_DURATION ))
225
+ .verifyComplete ();
226
+ return true ;
227
+ })
228
+ .verifyComplete ();
229
+ }
230
+
231
+ @ Order (102 )
232
+ @ Test
233
+ void test_deleteUserProfilePicture () {
234
+ Mono <Void > deleteUserProfilePicture =
235
+ serviceProvider .deleteUserProfilePicture (USER_ID , Collections .emptyMap ())
236
+ .timeout (TIMEOUT_DURATION );
237
+ StepVerifier .create (deleteUserProfilePicture )
238
+ .verifyComplete ();
239
+
240
+ Mono <Map <String , String >> queryDownloadInfo = serviceProvider
241
+ .queryUserProfilePictureDownloadInfo (USER_ID , USER_ID , Collections .emptyMap ())
242
+ .timeout (TIMEOUT_DURATION );
243
+ StepVerifier .create (queryDownloadInfo )
244
+ .expectNextMatches (downloadInfo -> {
245
+ String resourceDownloadUrl =
246
+ downloadInfo .get (MinioStorageServiceProvider .RESOURCE_URL );
247
+ assertThat (resourceDownloadUrl )
248
+ .as ("The resource download URL should not be null" )
249
+ .isNotNull ();
250
+
251
+ StepVerifier .create (HttpClient .create ()
252
+ .get ()
253
+ .uri (resourceDownloadUrl )
254
+ .response ()
255
+ .timeout (TIMEOUT_DURATION ))
256
+ .expectNextMatches (response -> {
257
+ assertThat (response .status ())
258
+ .isEqualTo (HttpResponseStatus .NOT_FOUND );
259
+ return true ;
260
+ })
261
+ .as ("The deleted file should not be found" )
262
+ .verifyComplete ();
263
+ return true ;
264
+ })
265
+ .verifyComplete ();
266
+ }
267
+
268
+ }
0 commit comments