| // Copyright (C) 2023 The Android Open Source Project |
| // |
| // 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 com.googlesource.gerrit.plugins.replication.pull.api; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static org.apache.http.HttpStatus.SC_OK; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import com.google.common.collect.Lists; |
| import com.google.gerrit.extensions.restapi.MergeConflictException; |
| import com.google.gerrit.extensions.restapi.Response; |
| import com.google.gerrit.extensions.restapi.RestApiException; |
| import com.google.gerrit.server.project.ProjectResource; |
| import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData; |
| import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionInput; |
| import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionObjectData; |
| import java.util.Collections; |
| import java.util.List; |
| import javax.servlet.http.HttpServletResponse; |
| import org.eclipse.jgit.lib.Constants; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.junit.MockitoJUnitRunner; |
| import org.mockito.stubbing.Answer; |
| |
| @RunWith(MockitoJUnitRunner.class) |
| public class BatchApplyObjectActionTest { |
| |
| private static final long DUMMY_EVENT_TIMESTAMP = 1684875939; |
| |
| private BatchApplyObjectAction batchApplyObjectAction; |
| private static final String LABEL = "instance-2-label"; |
| private static final String REF_NAME = "refs/heads/master"; |
| private static final String REF_META_NAME = "refs/meta/version"; |
| private static final String SAMPLE_COMMIT_OBJECT_ID = "9f8d52853089a3cf00c02ff7bd0817bd4353a95a"; |
| private static final String SAMPLE_TREE_OBJECT_ID = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; |
| |
| private static final String SAMPLE_COMMIT_CONTENT = |
| "tree " |
| + SAMPLE_TREE_OBJECT_ID |
| + "\n" |
| + "parent 20eb48d28be86dc88fb4bef747f08de0fbefe12d\n" |
| + "author Gerrit User 1000000 <1000000@69ec38f0-350e-4d9c-96d4-bc956f2faaac> 1610471611 +0100\n" |
| + "committer Gerrit Code Review <root@maczech-XPS-15> 1610471611 +0100\n" |
| + "\n" |
| + "Update patch set 1\n" |
| + "\n" |
| + "Change has been successfully merged by Administrator\n" |
| + "\n" |
| + "Patch-set: 1\n" |
| + "Status: merged\n" |
| + "Tag: autogenerated:gerrit:merged\n" |
| + "Reviewer: Gerrit User 1000000 <1000000@69ec38f0-350e-4d9c-96d4-bc956f2faaac>\n" |
| + "Label: SUBM=+1\n" |
| + "Submission-id: 1904-1610471611558-783c0a2f\n" |
| + "Submitted-with: OK\n" |
| + "Submitted-with: OK: Code-Review: Gerrit User 1000000 <1000000@69ec38f0-350e-4d9c-96d4-bc956f2faaac>"; |
| |
| @Mock private ApplyObjectAction applyObjectAction; |
| @Mock private ProjectResource projectResource; |
| |
| @Before |
| public void setup() { |
| batchApplyObjectAction = new BatchApplyObjectAction(applyObjectAction); |
| } |
| |
| @Test |
| public void shouldDelegateToApplyObjectActionForEveryRevision() throws RestApiException { |
| RevisionInput first = |
| new RevisionInput(LABEL, REF_NAME, DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| RevisionInput second = |
| new RevisionInput(LABEL, "foo", DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| |
| batchApplyObjectAction.apply(projectResource, List.of(first, second)); |
| |
| verify(applyObjectAction).apply(projectResource, first); |
| verify(applyObjectAction).apply(projectResource, second); |
| } |
| |
| @Test |
| public void shouldReturnOkResponseCodeWhenAllRevisionsAreProcessedSuccessfully() |
| throws RestApiException { |
| RevisionInput first = |
| new RevisionInput(LABEL, REF_NAME, DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| RevisionInput second = |
| new RevisionInput(LABEL, "foo", DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| |
| when(applyObjectAction.apply(projectResource, first)) |
| .thenAnswer((Answer<Response<?>>) invocation -> Response.created(first)); |
| when(applyObjectAction.apply(projectResource, second)) |
| .thenAnswer((Answer<Response<?>>) invocation -> Response.created(second)); |
| |
| Response<?> response = batchApplyObjectAction.apply(projectResource, List.of(first, second)); |
| |
| assertThat(response.statusCode()).isEqualTo(SC_OK); |
| } |
| |
| @Test |
| public void shouldReturnAListWithAllTheRevisionsInResponseBodyOnSuccess() |
| throws RestApiException { |
| RevisionInput first = |
| new RevisionInput(LABEL, REF_NAME, DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| RevisionInput second = |
| new RevisionInput(LABEL, "foo", DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| Response<?> firstResponse = Response.created(first); |
| Response<?> secondResponse = Response.created(second); |
| |
| when(applyObjectAction.apply(projectResource, first)) |
| .thenAnswer((Answer<Response<?>>) invocation -> firstResponse); |
| when(applyObjectAction.apply(projectResource, second)) |
| .thenAnswer((Answer<Response<?>>) invocation -> secondResponse); |
| |
| Response<?> response = batchApplyObjectAction.apply(projectResource, List.of(first, second)); |
| |
| assertThat((List<Response<?>>) response.value()) |
| .isEqualTo(List.of(firstResponse, secondResponse)); |
| } |
| |
| @Test |
| public void shouldAcceptAMixOfCreatesAndDeletes() throws RestApiException { |
| RevisionInput delete = new RevisionInput(LABEL, REF_NAME, DUMMY_EVENT_TIMESTAMP, null); |
| RevisionInput create = |
| new RevisionInput(LABEL, "foo", DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| Response<?> deleteResponse = Response.withStatusCode(HttpServletResponse.SC_NO_CONTENT, ""); |
| Response<?> createResponse = Response.created(create); |
| |
| when(applyObjectAction.apply(projectResource, delete)) |
| .thenAnswer((Answer<Response<?>>) invocation -> deleteResponse); |
| when(applyObjectAction.apply(projectResource, create)) |
| .thenAnswer((Answer<Response<?>>) invocation -> createResponse); |
| |
| Response<?> response = batchApplyObjectAction.apply(projectResource, List.of(delete, create)); |
| |
| assertThat((List<Response<?>>) response.value()) |
| .isEqualTo(List.of(deleteResponse, createResponse)); |
| } |
| |
| @Test |
| public void shouldReturnOneOkCodeEvenIfInputContainsBothCreatesAndDeletes() |
| throws RestApiException { |
| RevisionInput create = |
| new RevisionInput(LABEL, REF_NAME, DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| RevisionInput delete = new RevisionInput(LABEL, REF_META_NAME, DUMMY_EVENT_TIMESTAMP + 1, null); |
| |
| List<RevisionInput> inputs = List.of(create, delete); |
| |
| Response<?> response = batchApplyObjectAction.apply(projectResource, inputs); |
| |
| assertThat(response.statusCode()).isEqualTo(SC_OK); |
| } |
| |
| @Test(expected = RestApiException.class) |
| public void shouldThrowARestApiExceptionIfProcessingFailsForAnyOfTheRevisions() |
| throws RestApiException { |
| RevisionInput good = |
| new RevisionInput(LABEL, REF_NAME, DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| RevisionInput bad = |
| new RevisionInput(LABEL, "bad", DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| |
| when(applyObjectAction.apply(projectResource, good)) |
| .thenAnswer((Answer<Response<?>>) invocation -> Response.created(good)); |
| when(applyObjectAction.apply(projectResource, bad)) |
| .thenThrow(new MergeConflictException("BOOM")); |
| |
| batchApplyObjectAction.apply(projectResource, List.of(good, bad)); |
| } |
| |
| @Test |
| public void shouldStopProcessingWhenAFailureOccurs() throws RestApiException { |
| RevisionInput good = |
| new RevisionInput(LABEL, REF_NAME, DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| RevisionInput bad = |
| new RevisionInput(LABEL, "bad", DUMMY_EVENT_TIMESTAMP, createSampleRevisionData()); |
| |
| when(applyObjectAction.apply(projectResource, bad)) |
| .thenThrow(new MergeConflictException("BOOM")); |
| |
| try { |
| batchApplyObjectAction.apply(projectResource, List.of(bad, good)); |
| } catch (MergeConflictException e) { |
| verify(applyObjectAction, never()).apply(projectResource, good); |
| } |
| } |
| |
| private RevisionData createSampleRevisionData() { |
| RevisionObjectData commitData = |
| new RevisionObjectData( |
| SAMPLE_COMMIT_OBJECT_ID, Constants.OBJ_COMMIT, SAMPLE_COMMIT_CONTENT.getBytes()); |
| RevisionObjectData treeData = |
| new RevisionObjectData(SAMPLE_TREE_OBJECT_ID, Constants.OBJ_TREE, new byte[] {}); |
| return createSampleRevisionData(commitData, treeData); |
| } |
| |
| private RevisionData createSampleRevisionData( |
| RevisionObjectData commitData, RevisionObjectData treeData) { |
| return new RevisionData(Collections.emptyList(), commitData, treeData, Lists.newArrayList()); |
| } |
| } |