Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle 401 errors on Google Video uploads correctly #1386

Merged
merged 2 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ public static PhotosLibraryClient buildPhotosLibraryClient(
return PhotosLibraryClient.initialize(settings);
}

private static boolean isTokenException(String message) {
return message.contains("invalid_grant")
|| message.contains("The upload could not be initialized. Unauthorized");
}

/**
* Uploads `video` via {@link com.google.photos.library.v1.PhotosLibraryClient} APIs.
*
Expand Down Expand Up @@ -278,10 +283,8 @@ public static Pair<String, Long> uploadVideo(
String message = cause.getMessage();
if (message.contains("The upload url is either finalized or rejected by the server")) {
throw new UploadErrorException("Upload was terminated because of error", cause);
} else if (message.contains("invalid_grant")) {
} else if (isTokenException(message)) {
throw new InvalidTokenException("Token has been expired or revoked", cause);
} else if (message.contains("The upload could not be initialized. Unauthorized")) {
throw new InvalidTokenException("uploadVideo could not be initialized. Unauthorized", cause);
}
}

Expand All @@ -296,7 +299,7 @@ public static Pair<String, Long> uploadVideo(
// temp check as exception is not captured and wrapped into UploadMediaItemResponse
Throwable cause = ex.getCause();
String message = cause.getMessage();
if (message.contains("invalid_grant")) {
if (isTokenException(message)) {
throw new InvalidTokenException("Token has been expired or revoked", cause);
}
throw new IOException("An error was encountered while uploading the video.", cause);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
Expand All @@ -31,6 +32,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.google.api.gax.rpc.ApiException;
import com.google.api.gax.rpc.StatusCode;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import com.google.photos.library.v1.PhotosLibraryClient;
Expand All @@ -56,13 +59,16 @@
import org.datatransferproject.spi.cloud.storage.TemporaryPerJobDataStore.InputStreamWrapper;
import org.datatransferproject.spi.transfer.idempotentexecutor.InMemoryIdempotentImportExecutor;
import org.datatransferproject.spi.transfer.provider.ImportResult;
import org.datatransferproject.spi.transfer.types.InvalidTokenException;
import org.datatransferproject.types.common.models.videos.VideoAlbum;
import org.datatransferproject.types.common.models.videos.VideoModel;
import org.datatransferproject.types.common.models.videos.VideosContainerResource;
import org.datatransferproject.types.transfer.auth.TokensAndUrlAuthData;
import org.datatransferproject.types.transfer.errors.ErrorDetail;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.rules.TemporaryFolder;
import org.mockito.ArgumentMatchers;

Expand All @@ -85,6 +91,15 @@ public class GoogleVideosImporterTest {
private PhotosLibraryClient client;
private UUID jobId;

class TestStatusCode implements StatusCode {
calumcalder marked this conversation as resolved.
Show resolved Hide resolved
public StatusCode.Code getCode() {
return StatusCode.Code.PERMISSION_DENIED;
}

public Object getTransportCode() {
return 401;
}
}

@BeforeEach
public void setUp() throws Exception {
Expand Down Expand Up @@ -473,4 +488,76 @@ public void importSameVideoInTwoDifferentAlbums() throws Exception {
assertEquals(64L, bytes,"Expected the number of bytes to be the two files of 32L.");
assertEquals(0, executor.getErrors().size(),"Expected executor to have no errors.");
}

@ParameterizedTest
@ValueSource(strings = {"invalid_grant", "The upload could not be initialized. Unauthorized"})
public void refreshTokenErrorsThrowInvalidTokenException(String errorMessage) throws Exception {
VideoModel videoModel =
new VideoModel(
VIDEO_TITLE,
VIDEO_URI,
VIDEO_DESCRIPTION,
MP4_MEDIA_TYPE,
"dataId",
"album1",
false,
null);

when(client.uploadMediaItem(any()))
.thenThrow(
new ApiException(
errorMessage, new Exception(errorMessage), new TestStatusCode(), false));

InMemoryIdempotentImportExecutor executor =
new InMemoryIdempotentImportExecutor(mock(Monitor.class));
assertThrows(
InvalidTokenException.class,
() ->
googleVideosImporter.importItem(
jobId,
executor,
mock(TokensAndUrlAuthData.class),
new VideosContainerResource(List.of(), List.of(videoModel))));
}

@ParameterizedTest
@ValueSource(strings = {"invalid_grant", "The upload could not be initialized. Unauthorized"})
public void refreshTokenErrorsThrowInvalidTokenExceptionWhenErrorReturned(String errorMessage)
throws Exception {
VideoModel videoModel =
new VideoModel(
VIDEO_TITLE,
VIDEO_URI,
VIDEO_DESCRIPTION,
MP4_MEDIA_TYPE,
"dataId",
"album1",
false,
null);

when(client.uploadMediaItem(any()))
.thenReturn(
UploadMediaItemResponse.newBuilder()
.setError(
UploadMediaItemResponse.Error.newBuilder()
.setCause(
new ApiException(
errorMessage,
new Exception(errorMessage),
new TestStatusCode(),
false))
.build())
.build());

InMemoryIdempotentImportExecutor executor =
new InMemoryIdempotentImportExecutor(mock(Monitor.class));
assertThrows(
InvalidTokenException.class,
() ->
googleVideosImporter.importItem(
jobId,
executor,
mock(TokensAndUrlAuthData.class),
new VideosContainerResource(List.of(), List.of(videoModel))));
}
}
Loading