4848import okhttp3 .ResponseBody ;
4949import okio .Buffer ;
5050import org .datatransferproject .api .launcher .Monitor ;
51- import org .datatransferproject .datatransfer .synology .exceptions .SynologyImportException ;
52- import org .datatransferproject .datatransfer .synology .exceptions .SynologyMaxRetriesExceededException ;
5351import org .datatransferproject .datatransfer .synology .utils .TestConfigs ;
5452import org .datatransferproject .spi .cloud .storage .JobStore ;
5553import org .datatransferproject .spi .cloud .storage .TemporaryPerJobDataStore .InputStreamWrapper ;
54+ import org .datatransferproject .spi .transfer .types .CopyExceptionWithFailureReason ;
55+ import org .datatransferproject .spi .transfer .types .DestinationMemoryFullException ;
56+ import org .datatransferproject .spi .transfer .types .InvalidTokenException ;
57+ import org .datatransferproject .spi .transfer .types .NoNasInAccountException ;
58+ import org .datatransferproject .spi .transfer .types .UploadErrorException ;
5659import org .datatransferproject .types .common .DownloadableFile ;
5760import org .datatransferproject .types .common .models .media .MediaAlbum ;
5861import org .datatransferproject .types .common .models .photos .PhotoModel ;
@@ -85,7 +88,7 @@ public class SynologyDTPServiceTest {
8588 @ Mock private OkHttpClient client ;
8689
8790 @ BeforeEach
88- public void setUp () {
91+ public void setUp () throws InvalidTokenException {
8992 lenient ().when (tokenManager .getAccessToken (jobId )).thenReturn ("mockAccessToken" );
9093 lenient ()
9194 .when (transferServiceConfig .getServiceConfig ())
@@ -102,7 +105,7 @@ public class AddItemToAlbum {
102105 private final String itemId = "testItem" ;
103106
104107 @ Test
105- public void shouldSendPostRequestWithCorrectFormBody () {
108+ public void shouldSendPostRequestWithCorrectFormBody () throws CopyExceptionWithFailureReason {
106109 SynologyDTPService spyService = Mockito .spy (dtpService );
107110
108111 doReturn (Map .of ("success" , true )).when (spyService ).sendPostRequest (anyString (), any (), any ());
@@ -127,17 +130,16 @@ public void shouldSendPostRequestWithCorrectFormBody() {
127130 }
128131
129132 @ Test
130- public void shouldThrowExceptionIfSendPostRequestFailed () {
133+ public void shouldThrowExceptionIfSendPostRequestFailed ()
134+ throws CopyExceptionWithFailureReason {
131135 SynologyDTPService spyService = Mockito .spy (dtpService );
132136
133- doThrow (
134- new SynologyMaxRetriesExceededException (
135- "MockException" , TestConfigs .TEST_MAX_ATTEMPTS , new Exception ("Failed" )))
137+ doThrow (new UploadErrorException ("MockException" , null ))
136138 .when (spyService )
137139 .sendPostRequest (anyString (), any (), any ());
138140
139141 assertThrows (
140- SynologyMaxRetriesExceededException .class ,
142+ UploadErrorException .class ,
141143 () -> spyService .addItemToAlbum (albumId , itemId , jobId ),
142144 "MockException" );
143145 }
@@ -150,7 +152,7 @@ public class CreateAlbum {
150152 private final MediaAlbum album = new MediaAlbum (albumId , albumName , "" );
151153
152154 @ Test
153- public void shouldSendPostRequestWithCorrectFormBody () {
155+ public void shouldSendPostRequestWithCorrectFormBody () throws CopyExceptionWithFailureReason {
154156 SynologyDTPService spyService = Mockito .spy (dtpService );
155157 Map <String , Object > dataMap = Map .of ("album_id" , albumId );
156158
@@ -179,19 +181,16 @@ public void shouldSendPostRequestWithCorrectFormBody() {
179181 }
180182
181183 @ Test
182- public void shouldThrowExceptionIfSendPostRequestFailed () {
184+ public void shouldThrowExceptionIfSendPostRequestFailed ()
185+ throws CopyExceptionWithFailureReason {
183186 SynologyDTPService spyService = Mockito .spy (dtpService );
184187
185- doThrow (
186- new SynologyMaxRetriesExceededException (
187- "MockException" , TestConfigs .TEST_MAX_ATTEMPTS , new Exception ("Failed" )))
188+ doThrow (new UploadErrorException ("MockException" , null ))
188189 .when (spyService )
189190 .sendPostRequest (anyString (), any (), any ());
190191
191192 assertThrows (
192- SynologyMaxRetriesExceededException .class ,
193- () -> spyService .createAlbum (album , jobId ),
194- "MockException" );
193+ UploadErrorException .class , () -> spyService .createAlbum (album , jobId ), "MockException" );
195194 }
196195 }
197196
@@ -207,8 +206,10 @@ public class CreateMedia {
207206 public Stream <Object > provideMediaObjectsInTempStore () {
208207 Date uploadedTime = new Date (uploadedTimestampInSeconds * 1000 );
209208 return Stream .of (
210- new PhotoModel (itemName , fetchUrl , description , "mediaType" , itemId , null , true , uploadedTime ),
211- new VideoModel (itemName , fetchUrl , description , "format" , itemId , null , true , uploadedTime ));
209+ new PhotoModel (
210+ itemName , fetchUrl , description , "mediaType" , itemId , null , true , uploadedTime ),
211+ new VideoModel (
212+ itemName , fetchUrl , description , "format" , itemId , null , true , uploadedTime ));
212213 }
213214
214215 public Stream <Object > provideMediaObjectsWithoutDescriptionAndUploadedTimeInTempStore () {
@@ -226,7 +227,7 @@ public Stream<Object> provideMediaObjectsNotInTempStore() {
226227 @ ParameterizedTest (name = "shouldGetStreamFromJobStoreIfMediaItemIsInTempStore [{index}] {0}" )
227228 @ MethodSource ("provideMediaObjectsInTempStore" )
228229 public void shouldGetStreamFromJobStoreIfMediaItemIsInTempStore (DownloadableFile item )
229- throws IOException {
230+ throws IOException , CopyExceptionWithFailureReason {
230231 byte [] mockImage = new byte [] {1 , 2 , 3 };
231232 InputStream mockInputStream = new ByteArrayInputStream (mockImage );
232233 InputStreamWrapper streamWrapper = mock (InputStreamWrapper .class );
@@ -250,7 +251,7 @@ public void shouldGetStreamFromJobStoreIfMediaItemIsInTempStore(DownloadableFile
250251 @ ParameterizedTest (name = "shouldGetStreamFromUrlIfPhotoIsNotInTempStore [{index}] {0}" )
251252 @ MethodSource ("provideMediaObjectsNotInTempStore" )
252253 public void shouldGetStreamFromUrlIfPhotoIsNotInTempStore (DownloadableFile item )
253- throws IOException {
254+ throws IOException , CopyExceptionWithFailureReason {
254255 byte [] mockImage = new byte [] {1 , 2 , 3 };
255256 SynologyDTPService spyService = Mockito .spy (dtpService );
256257 InputStream mockInputStream = new ByteArrayInputStream (mockImage );
@@ -271,7 +272,8 @@ public void shouldGetStreamFromUrlIfPhotoIsNotInTempStore(DownloadableFile item)
271272
272273 // VideoModel.contentUrl can't be null
273274 @ Test
274- public void shouldReturnNullWhenPhotoIsNotInTempStoreAndHasNoUrl () {
275+ public void shouldReturnNullWhenPhotoIsNotInTempStoreAndHasNoUrl ()
276+ throws CopyExceptionWithFailureReason {
275277 PhotoModel photo =
276278 new PhotoModel (itemName , null , description , "mediaType" , itemId , null , false );
277279 SynologyDTPService spyService = Mockito .spy (dtpService );
@@ -281,20 +283,21 @@ public void shouldReturnNullWhenPhotoIsNotInTempStoreAndHasNoUrl() {
281283
282284 @ ParameterizedTest (name = "shouldThrowExceptionIfNewURLFailed [{index}] {0}" )
283285 @ MethodSource ("provideMediaObjectsNotInTempStore" )
284- public void shouldThrowExceptionIfNewURLFailed (DownloadableFile item ) throws IOException {
286+ public void shouldThrowExceptionIfNewURLFailed (DownloadableFile item )
287+ throws IOException , CopyExceptionWithFailureReason {
285288 SynologyDTPService spyService = Mockito .spy (dtpService );
286289 doThrow (new MalformedURLException ("Failed to create url for photo" ))
287290 .when (spyService )
288291 .getInputStream (fetchUrl );
289292
290293 if (item instanceof PhotoModel ) {
291294 assertThrows (
292- SynologyImportException .class ,
295+ UploadErrorException .class ,
293296 () -> spyService .createPhoto ((PhotoModel ) item , jobId ),
294297 "Failed to create url for photo" );
295298 } else if (item instanceof VideoModel ) {
296299 assertThrows (
297- SynologyImportException .class ,
300+ UploadErrorException .class ,
298301 () -> spyService .createVideo ((VideoModel ) item , jobId ),
299302 "Failed to create url for video" );
300303 }
@@ -305,29 +308,30 @@ public void shouldThrowExceptionIfNewURLFailed(DownloadableFile item) throws IOE
305308 @ ParameterizedTest (name = "shouldThrowExceptionIfFailedToGetStream [{index}] {0}" )
306309 @ MethodSource ("provideMediaObjectsInTempStore" )
307310 public void shouldThrowExceptionIfFailedToGetStream (DownloadableFile item )
308- throws IOException , SynologyMaxRetriesExceededException {
311+ throws IOException , CopyExceptionWithFailureReason {
309312 SynologyDTPService spyService = Mockito .spy (dtpService );
310313 when (jobStore .getStream (jobId , fetchUrl )).thenThrow (new IOException ("Failed to get stream" ));
311314
312315 if (item instanceof PhotoModel ) {
313316 assertThrows (
314- SynologyImportException .class ,
317+ UploadErrorException .class ,
315318 () -> spyService .createPhoto ((PhotoModel ) item , jobId ),
316319 "Failed to create input stream for photo" );
317320 } else if (item instanceof VideoModel ) {
318321 assertThrows (
319- SynologyImportException .class ,
322+ UploadErrorException .class ,
320323 () -> spyService .createVideo ((VideoModel ) item , jobId ),
321324 "Failed to create input stream for video" );
322325 }
323326 verify (spyService , never ()).sendPostRequest (anyString (), any (), any ());
324327 }
325328
326329 @ ParameterizedTest (
327- name = "shouldSendPostRequestWithCorrectFormBodyWithDescriptionAndUploadedTime [{index}] {0}" )
330+ name =
331+ "shouldSendPostRequestWithCorrectFormBodyWithDescriptionAndUploadedTime [{index}] {0}" )
328332 @ MethodSource ("provideMediaObjectsInTempStore" )
329- public void shouldSendPostRequestWithCorrectFormBodyWithDescriptionAndUploadedTime (DownloadableFile item )
330- throws IOException {
333+ public void shouldSendPostRequestWithCorrectFormBodyWithDescriptionAndUploadedTime (
334+ DownloadableFile item ) throws IOException , CopyExceptionWithFailureReason {
331335 byte [] mockImage = new byte [] {1 , 2 , 3 };
332336 InputStream mockInputStream = new ByteArrayInputStream (mockImage );
333337 InputStreamWrapper streamWrapper = mock (InputStreamWrapper .class );
@@ -383,10 +387,12 @@ public void shouldSendPostRequestWithCorrectFormBodyWithDescriptionAndUploadedTi
383387 }
384388
385389 @ ParameterizedTest (
386- name = "shouldSendPostRequestWithCorrectFormBodyWithoutDescriptionAndUploadedTime [{index}] {0}" )
390+ name =
391+ "shouldSendPostRequestWithCorrectFormBodyWithoutDescriptionAndUploadedTime [{index}]"
392+ + " {0}" )
387393 @ MethodSource ("provideMediaObjectsWithoutDescriptionAndUploadedTimeInTempStore" )
388394 public void shouldSendPostRequestWithCorrectFormBodyWithoutDescription (DownloadableFile item )
389- throws IOException {
395+ throws IOException , CopyExceptionWithFailureReason {
390396 byte [] mockImage = new byte [] {1 , 2 , 3 };
391397 InputStream mockInputStream = new ByteArrayInputStream (mockImage );
392398 InputStreamWrapper streamWrapper = mock (InputStreamWrapper .class );
@@ -443,26 +449,26 @@ public void shouldSendPostRequestWithCorrectFormBodyWithoutDescription(Downloada
443449 @ ParameterizedTest (name = "shouldThrowExceptionIfSendPostRequestFailed [{index}] {0}" )
444450 @ MethodSource ("provideMediaObjectsInTempStore" )
445451 public void shouldThrowExceptionIfSendPostRequestFailed (DownloadableFile item )
446- throws IOException {
452+ throws IOException , CopyExceptionWithFailureReason {
447453 byte [] mockImage = new byte [] {1 , 2 , 3 };
448454 InputStream mockInputStream = new ByteArrayInputStream (mockImage );
449455 InputStreamWrapper streamWrapper = mock (InputStreamWrapper .class );
450456 SynologyDTPService spyService = Mockito .spy (dtpService );
451457
452458 when (jobStore .getStream (jobId , fetchUrl )).thenReturn (streamWrapper );
453459 when (streamWrapper .getStream ()).thenReturn (mockInputStream );
454- doThrow (new SynologyMaxRetriesExceededException ("MockException" , 3 , new Exception ( "Failed" ) ))
460+ doThrow (new UploadErrorException ("MockException" , null ))
455461 .when (spyService )
456462 .sendPostRequest (anyString (), any (), any ());
457463
458464 if (item instanceof PhotoModel ) {
459465 assertThrows (
460- SynologyMaxRetriesExceededException .class ,
466+ UploadErrorException .class ,
461467 () -> spyService .createPhoto ((PhotoModel ) item , jobId ),
462468 "MockException" );
463469 } else if (item instanceof VideoModel ) {
464470 assertThrows (
465- SynologyMaxRetriesExceededException .class ,
471+ UploadErrorException .class ,
466472 () -> spyService .createVideo ((VideoModel ) item , jobId ),
467473 "MockException" );
468474 }
@@ -474,7 +480,7 @@ public class SendPostRequestTest {
474480 private RequestBody requestBody = RequestBody .create (MediaType .parse ("application/json" ), "{}" );
475481
476482 @ Test
477- public void shouldRetryIfGotError () throws IOException {
483+ public void shouldRetryIfGotError () throws IOException , CopyExceptionWithFailureReason {
478484 Call mockCall = mock (Call .class );
479485
480486 Response mockResponseFail = mock (Response .class );
@@ -512,7 +518,7 @@ public void shouldThrowExceptionIfReachMaxRetries() throws IOException {
512518 when (mockCall .execute ()).thenReturn (mockResponseFail );
513519
514520 assertThrows (
515- SynologyMaxRetriesExceededException .class ,
521+ UploadErrorException .class ,
516522 () -> dtpService .sendPostRequest (TestConfigs .TEST_C2_BASE_URL , requestBody , jobId ),
517523 String .format ("Failed to send POST request %d times" , TestConfigs .TEST_MAX_ATTEMPTS ));
518524 verify (tokenManager , never ())
@@ -522,7 +528,7 @@ public void shouldThrowExceptionIfReachMaxRetries() throws IOException {
522528
523529 @ Test
524530 public void shouldRefreshTokenAndRetryIfGotUnauthorizedHttpError ()
525- throws IOException , JsonProcessingException {
531+ throws IOException , JsonProcessingException , CopyExceptionWithFailureReason {
526532 Call mockCall = mock (Call .class );
527533
528534 Response mockResponseFail = mock (Response .class );
@@ -565,7 +571,7 @@ public void shouldInvokeRefreshTokenOnlyOnceIfGotUnauthorizedMultipleTimes()
565571 .thenReturn (false );
566572
567573 assertThrows (
568- SynologyMaxRetriesExceededException .class ,
574+ UploadErrorException .class ,
569575 () -> dtpService .sendPostRequest (TestConfigs .TEST_C2_BASE_URL , requestBody , jobId ),
570576 String .format ("Failed to send POST request %d times" , TestConfigs .TEST_MAX_ATTEMPTS ));
571577
@@ -588,7 +594,7 @@ public void shouldThrowExceptionIfParseResponseFailed() throws IOException {
588594 .thenThrow (new IOException ("Error when call response.body.string()" ));
589595
590596 assertThrows (
591- SynologyMaxRetriesExceededException .class ,
597+ UploadErrorException .class ,
592598 () -> dtpService .sendPostRequest (TestConfigs .TEST_C2_BASE_URL , requestBody , jobId ),
593599 String .format ("Failed to send POST request %d times" , TestConfigs .TEST_MAX_ATTEMPTS ));
594600 verify (tokenManager , never ())
@@ -597,7 +603,8 @@ public void shouldThrowExceptionIfParseResponseFailed() throws IOException {
597603 }
598604
599605 @ Test
600- public void shouldReturnResponseData () throws IOException , JsonProcessingException {
606+ public void shouldReturnResponseData ()
607+ throws IOException , JsonProcessingException , CopyExceptionWithFailureReason {
601608 Response mockResponse = mock (Response .class );
602609 ResponseBody mockResponseBody = mock (ResponseBody .class );
603610 Call mockCall = mock (Call .class );
@@ -615,5 +622,97 @@ public void shouldReturnResponseData() throws IOException, JsonProcessingExcepti
615622 .refreshToken (any (UUID .class ), eq (client ), any (ObjectMapper .class ));
616623 verify (client , times (1 )).newCall (any (Request .class ));
617624 }
625+
626+ @ Test
627+ public void shouldThrowExceptionIfGot413 () throws IOException {
628+ Call mockCall = mock (Call .class );
629+
630+ Response mockResponseFail = mock (Response .class );
631+ when (mockResponseFail .code ()).thenReturn (413 );
632+ when (mockResponseFail .isSuccessful ()).thenReturn (false );
633+
634+ when (client .newCall (any (Request .class ))).thenReturn (mockCall );
635+ when (mockCall .execute ()).thenReturn (mockResponseFail );
636+
637+ assertThrows (
638+ DestinationMemoryFullException .class ,
639+ () -> dtpService .sendPostRequest (TestConfigs .TEST_C2_BASE_URL , requestBody , jobId ));
640+ }
641+
642+ @ Test
643+ public void shouldCallCheckUnprocessableContentIfGot422 ()
644+ throws IOException , CopyExceptionWithFailureReason {
645+ Call mockCall = mock (Call .class );
646+ SynologyDTPService spyService = Mockito .spy (dtpService );
647+
648+ Response mockResponseFail = mock (Response .class );
649+ ResponseBody mockResponseBody = mock (ResponseBody .class );
650+ when (mockResponseFail .code ()).thenReturn (422 );
651+ when (mockResponseFail .isSuccessful ()).thenReturn (false );
652+ when (mockResponseFail .body ()).thenReturn (mockResponseBody );
653+ when (mockResponseBody .string ())
654+ .thenReturn (
655+ "{\" error\" :{\" code\" :2000,\" message\" :\" User has not claimed C2 Storage"
656+ + " quota.\" }}" );
657+
658+ when (client .newCall (any (Request .class ))).thenReturn (mockCall );
659+ when (mockCall .execute ()).thenReturn (mockResponseFail );
660+
661+ assertThrows (
662+ NoNasInAccountException .class ,
663+ () -> spyService .sendPostRequest (TestConfigs .TEST_C2_BASE_URL , requestBody , jobId ));
664+ }
665+ }
666+
667+ @ Nested
668+ public class CheckUnprocessableContentTest {
669+ private Response mockResponse (String jsonBody ) {
670+ ResponseBody body = ResponseBody .create (MediaType .parse ("application/json" ), jsonBody );
671+ Response response = mock (Response .class );
672+ when (response .body ()).thenReturn (body );
673+ return response ;
674+ }
675+
676+ @ Test
677+ public void shouldThrowExceptionIfErrorCodeIs2000 () throws CopyExceptionWithFailureReason {
678+ String jsonBody =
679+ "{\" error\" :{\" code\" : 2000,\" message\" :\" User has not claimed C2 Storage quota.\" }}" ;
680+ Response response = mockResponse (jsonBody );
681+
682+ assertThrows (
683+ NoNasInAccountException .class , () -> dtpService .checkUnprocessableContent (response ));
684+ }
685+
686+ @ Test
687+ public void shouldThrowExceptionIfErrorCodeIs2001 () throws CopyExceptionWithFailureReason {
688+ String jsonBody =
689+ "{\" error\" :{\" code\" : 2001,\" message\" :\" C2 Storage quota is not enough.\" }}" ;
690+ Response response = mockResponse (jsonBody );
691+
692+ assertThrows (
693+ NoNasInAccountException .class , () -> dtpService .checkUnprocessableContent (response ));
694+ }
695+
696+ @ Test
697+ public void shouldNotThrowExceptionIfNoErrorCode () throws CopyExceptionWithFailureReason {
698+ String jsonBody = "{\" data\" :{\" album_id\" :\" test_album_id\" }}" ;
699+ Response response = mockResponse (jsonBody );
700+ dtpService .checkUnprocessableContent (response );
701+ }
702+
703+ @ Test
704+ public void shouldNotThrowExceptionIfErrorCodeIsDifferent ()
705+ throws CopyExceptionWithFailureReason {
706+ String jsonBody = "{\" error\" :{\" code\" :\" 1000\" ,\" message\" :\" Some other error.\" }}" ;
707+ Response response = mockResponse (jsonBody );
708+ dtpService .checkUnprocessableContent (response );
709+ }
710+
711+ @ Test
712+ public void shouldNotThrowExceptionIfBodyIsInvalidJson () throws CopyExceptionWithFailureReason {
713+ String jsonBody = "invalid-json" ;
714+ Response response = mockResponse (jsonBody );
715+ dtpService .checkUnprocessableContent (response );
716+ }
618717 }
619718}
0 commit comments