blob: d8dabf1ca5a218caaa378a0cced3412ba70b5f15 [file] [log] [blame]
// 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());
}
}