Skip to content

Commit 6a6f08d

Browse files
committed
test: update Synology tests for SPI exception refactoring and new error handling
Key changes: - Updated exception assertions to use standard SPI exceptions. - Added SynologyDTPService tests for HTTP 413 and 422 scenarios. - Updated method signatures to include throws CopyExceptionWithFailureReason where necessary.
1 parent d8f4c89 commit 6a6f08d

5 files changed

Lines changed: 188 additions & 118 deletions

File tree

extensions/data-transfer/portability-data-transfer-synology/src/test/java/org/datatransferproject/datatransfer/synology/service/SynologyDTPServiceTest.java

Lines changed: 142 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,14 @@
4848
import okhttp3.ResponseBody;
4949
import okio.Buffer;
5050
import org.datatransferproject.api.launcher.Monitor;
51-
import org.datatransferproject.datatransfer.synology.exceptions.SynologyImportException;
52-
import org.datatransferproject.datatransfer.synology.exceptions.SynologyMaxRetriesExceededException;
5351
import org.datatransferproject.datatransfer.synology.utils.TestConfigs;
5452
import org.datatransferproject.spi.cloud.storage.JobStore;
5553
import 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;
5659
import org.datatransferproject.types.common.DownloadableFile;
5760
import org.datatransferproject.types.common.models.media.MediaAlbum;
5861
import 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

Comments
 (0)