// Copyright 2018 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package google.registry.export; import static com.google.common.truth.Truth.assertThat; import static google.registry.export.CheckBackupAction.CHECK_BACKUP_KINDS_TO_LOAD_PARAM; import static google.registry.export.CheckBackupAction.CHECK_BACKUP_NAME_PARAM; import static google.registry.testing.JUnitBackports.assertThrows; import static google.registry.testing.TaskQueueHelper.assertNoTasksEnqueued; import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.client.http.HttpHeaders; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.common.collect.ImmutableSet; import google.registry.export.datastore.DatastoreAdmin; import google.registry.export.datastore.DatastoreAdmin.Get; import google.registry.export.datastore.Operation; import google.registry.request.Action.Method; import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.NoContentException; import google.registry.request.HttpException.NotModifiedException; import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; import google.registry.testing.TaskQueueHelper.TaskMatcher; import google.registry.testing.TestDataHelper; import org.joda.time.DateTime; import org.joda.time.Duration; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; /** Unit tests for {@link CheckBackupAction}. */ @RunWith(JUnit4.class) public class CheckBackupActionTest { static final DateTime START_TIME = DateTime.parse("2014-08-01T01:02:03Z"); static final DateTime COMPLETE_TIME = START_TIME.plus(Duration.standardMinutes(30)); static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withTaskQueue().build(); @Rule public final MockitoRule mocks = MockitoJUnit.rule(); @Mock private DatastoreAdmin datastoreAdmin; @Mock private Get getNotFoundBackupProgressRequest; @Mock private Get getBackupProgressRequest; private Operation backupOperation; private final FakeResponse response = new FakeResponse(); private final FakeClock clock = new FakeClock(COMPLETE_TIME.plusMillis(1000)); private final CheckBackupAction action = new CheckBackupAction(); @Before public void before() throws Exception { action.requestMethod = Method.POST; action.datastoreAdmin = datastoreAdmin; action.clock = clock; action.backupName = "some_backup"; action.kindsToLoadParam = "one,two"; action.response = response; when(datastoreAdmin.get(anyString())).thenReturn(getBackupProgressRequest); when(getBackupProgressRequest.execute()).thenAnswer(arg -> backupOperation); } private void setPendingBackup() throws Exception { backupOperation = JSON_FACTORY.fromString( TestDataHelper.loadFile( CheckBackupActionTest.class, "backup_operation_in_progress.json"), Operation.class); } private void setCompleteBackup() throws Exception { backupOperation = JSON_FACTORY.fromString( TestDataHelper.loadFile(CheckBackupActionTest.class, "backup_operation_success.json"), Operation.class); } private void setBackupNotFound() throws Exception { when(datastoreAdmin.get(anyString())).thenReturn(getNotFoundBackupProgressRequest); when(getNotFoundBackupProgressRequest.execute()) .thenThrow( new GoogleJsonResponseException( new GoogleJsonResponseException.Builder(404, "NOT_FOUND", new HttpHeaders()) .setMessage("No backup found"), null)); } private static void assertLoadTaskEnqueued(String id, String folder, String kinds) { assertTasksEnqueued( "export-snapshot", new TaskMatcher() .url("/_dr/task/uploadDatastoreBackup") .method("POST") .param("id", id) .param("folder", folder) .param("kinds", kinds)); } @Test public void testSuccess_enqueuePollTask() { CheckBackupAction.enqueuePollTask("some_backup_name", ImmutableSet.of("one", "two", "three")); assertTasksEnqueued( CheckBackupAction.QUEUE, new TaskMatcher() .url(CheckBackupAction.PATH) .param(CHECK_BACKUP_NAME_PARAM, "some_backup_name") .param(CHECK_BACKUP_KINDS_TO_LOAD_PARAM, "one,two,three") .method("POST")); } @Test public void testPost_forPendingBackup_returnsNotModified() throws Exception { setPendingBackup(); NotModifiedException thrown = assertThrows(NotModifiedException.class, action::run); assertThat(thrown) .hasMessageThat() .contains("Datastore backup some_backup still in progress: Progress: N/A"); } @Test public void testPost_forStalePendingBackupBackup_returnsNoContent() throws Exception { setPendingBackup(); clock.setTo( START_TIME .plus(Duration.standardHours(20)) .plus(Duration.standardMinutes(3)) .plus(Duration.millis(1234))); NoContentException thrown = assertThrows(NoContentException.class, action::run); assertThat(thrown) .hasMessageThat() .contains( "Datastore backup some_backup abandoned - " + "not complete after 20 hours, 3 minutes and 1 second. Progress: Progress: N/A"); } @Test public void testPost_forCompleteBackup_enqueuesLoadTask() throws Exception { setCompleteBackup(); action.run(); assertLoadTaskEnqueued( "2014-08-01T01:02:03_99364", "gs://registry-project-id-datastore-export-test/2014-08-01T01:02:03_99364", "one,two"); } @Test public void testPost_forCompleteBackup_withExtraKindsToLoad_enqueuesLoadTask() throws Exception { setCompleteBackup(); action.kindsToLoadParam = "one,foo"; action.run(); assertLoadTaskEnqueued( "2014-08-01T01:02:03_99364", "gs://registry-project-id-datastore-export-test/2014-08-01T01:02:03_99364", "one"); } @Test public void testPost_forCompleteBackup_withEmptyKindsToLoad_skipsLoadTask() throws Exception { setCompleteBackup(); action.kindsToLoadParam = ""; action.run(); assertNoTasksEnqueued("export-snapshot"); } @Test public void testPost_forBadBackup_returnsBadRequest() throws Exception { setBackupNotFound(); BadRequestException thrown = assertThrows(BadRequestException.class, action::run); assertThat(thrown).hasMessageThat().contains("Bad backup name some_backup: No backup found"); } @Test public void testGet_returnsInformation() throws Exception { setCompleteBackup(); action.requestMethod = Method.GET; action.run(); assertThat(response.getPayload()) .isEqualTo( TestDataHelper.loadFile( CheckBackupActionTest.class, "pretty_printed_success_backup_operation.json") .trim()); } @Test public void testGet_forBadBackup_returnsError() throws Exception { setBackupNotFound(); action.requestMethod = Method.GET; BadRequestException thrown = assertThrows(BadRequestException.class, action::run); assertThat(thrown).hasMessageThat().contains("Bad backup name some_backup: No backup found"); } }