| // Copyright (C) 2019 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.multisite.validation; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static org.hamcrest.CoreMatchers.nullValue; |
| import static org.hamcrest.CoreMatchers.sameInstance; |
| import static org.mockito.Mockito.any; |
| import static org.mockito.Mockito.argThat; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.doThrow; |
| import static org.mockito.Mockito.eq; |
| import static org.mockito.Mockito.lenient; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyZeroInteractions; |
| |
| import com.google.gerrit.reviewdb.client.Project; |
| import com.google.gerrit.server.events.RefReceivedEvent; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.gerrit.server.git.validators.ValidationMessage; |
| import com.google.gerrit.server.validators.ValidationException; |
| import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase; |
| import java.io.IOException; |
| import java.util.List; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.RefDatabase; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.transport.ReceiveCommand; |
| import org.eclipse.jgit.transport.ReceiveCommand.Type; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.ExpectedException; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentMatcher; |
| import org.mockito.Mock; |
| import org.mockito.junit.MockitoJUnitRunner; |
| |
| @RunWith(MockitoJUnitRunner.class) |
| public class InSyncChangeValidatorTest { |
| static final String PROJECT_NAME = "AProject"; |
| static final Project.NameKey PROJECT_NAMEKEY = new Project.NameKey(PROJECT_NAME); |
| static final String REF_NAME = "refs/heads/master"; |
| static final String REF_PATCHSET_NAME = "refs/changes/45/1245/1"; |
| static final String REF_PATCHSET_META_NAME = "refs/changes/45/1245/1/meta"; |
| static final ObjectId REF_OBJID = ObjectId.fromString("f2ffe80abb77223f3f8921f3f068b0e32d40f798"); |
| static final ObjectId REF_OBJID_OLD = |
| ObjectId.fromString("a9a7a6fd1e9ad39a13fef5e897dc6d932a3282e1"); |
| static final ReceiveCommand RECEIVE_COMMAND_CREATE_REF = |
| new ReceiveCommand(ObjectId.zeroId(), REF_OBJID, REF_NAME, Type.CREATE); |
| static final ReceiveCommand RECEIVE_COMMAND_UPDATE_REF = |
| new ReceiveCommand(REF_OBJID_OLD, REF_OBJID, REF_NAME, Type.UPDATE); |
| static final ReceiveCommand RECEIVE_COMMAND_DELETE_REF = |
| new ReceiveCommand(REF_OBJID_OLD, ObjectId.zeroId(), REF_NAME, Type.DELETE); |
| static final ReceiveCommand RECEIVE_COMMAND_CREATE_PATCHSET_REF = |
| new ReceiveCommand(ObjectId.zeroId(), REF_OBJID, REF_PATCHSET_NAME, Type.CREATE); |
| static final ReceiveCommand RECEIVE_COMMAND_CREATE_PATCHSET_META_REF = |
| new ReceiveCommand(ObjectId.zeroId(), REF_OBJID, REF_PATCHSET_META_NAME, Type.CREATE); |
| |
| @Rule public ExpectedException expectedException = ExpectedException.none(); |
| |
| @Mock SharedRefDatabase dfsRefDatabase; |
| |
| @Mock Repository repo; |
| |
| @Mock RefDatabase localRefDatabase; |
| |
| @Mock GitRepositoryManager repoManager; |
| |
| private InSyncChangeValidator validator; |
| |
| static class TestRef implements Ref { |
| private final String name; |
| private final ObjectId objectId; |
| |
| public TestRef(String name, ObjectId objectId) { |
| super(); |
| this.name = name; |
| this.objectId = objectId; |
| } |
| |
| @Override |
| public String getName() { |
| return name; |
| } |
| |
| @Override |
| public boolean isSymbolic() { |
| return false; |
| } |
| |
| @Override |
| public Ref getLeaf() { |
| return null; |
| } |
| |
| @Override |
| public Ref getTarget() { |
| return null; |
| } |
| |
| @Override |
| public ObjectId getObjectId() { |
| return objectId; |
| } |
| |
| @Override |
| public ObjectId getPeeledObjectId() { |
| return null; |
| } |
| |
| @Override |
| public boolean isPeeled() { |
| return false; |
| } |
| |
| @Override |
| public Storage getStorage() { |
| return Storage.LOOSE; |
| } |
| } |
| |
| static class RefMatcher implements ArgumentMatcher<Ref> { |
| private final String name; |
| private final ObjectId objectId; |
| |
| public RefMatcher(String name, ObjectId objectId) { |
| super(); |
| this.name = name; |
| this.objectId = objectId; |
| } |
| |
| @Override |
| public boolean matches(Ref that) { |
| if (that == null) { |
| return false; |
| } |
| |
| return name.equals(that.getName()) && objectId.equals(that.getObjectId()); |
| } |
| } |
| |
| public static Ref eqRef(String name, ObjectId objectId) { |
| return argThat(new RefMatcher(name, objectId)); |
| } |
| |
| Ref testRef = new TestRef(REF_NAME, REF_OBJID); |
| RefReceivedEvent testRefReceivedEvent = |
| new RefReceivedEvent() { |
| |
| @Override |
| public String getRefName() { |
| return command.getRefName(); |
| } |
| |
| @Override |
| public com.google.gerrit.reviewdb.client.Project.NameKey getProjectNameKey() { |
| return PROJECT_NAMEKEY; |
| } |
| }; |
| |
| @Before |
| public void setUp() throws IOException { |
| doReturn(testRef).when(dfsRefDatabase).newRef(REF_NAME, REF_OBJID); |
| doReturn(repo).when(repoManager).openRepository(PROJECT_NAMEKEY); |
| doReturn(localRefDatabase).when(repo).getRefDatabase(); |
| lenient() |
| .doThrow(new NullPointerException("oldRef is null")) |
| .when(dfsRefDatabase) |
| .compareAndPut(any(), eq(null), any()); |
| lenient() |
| .doThrow(new NullPointerException("newRef is null")) |
| .when(dfsRefDatabase) |
| .compareAndPut(any(), any(), eq(null)); |
| lenient() |
| .doThrow(new NullPointerException("project name is null")) |
| .when(dfsRefDatabase) |
| .compareAndPut(eq(null), any(), any()); |
| |
| validator = new InSyncChangeValidator(dfsRefDatabase, repoManager); |
| repoManager.createRepository(PROJECT_NAMEKEY); |
| } |
| |
| @Test |
| public void shouldNotVerifyStatusOfImmutablePatchSetRefs() throws Exception { |
| testRefReceivedEvent.command = RECEIVE_COMMAND_CREATE_PATCHSET_REF; |
| final List<ValidationMessage> validationMessages = |
| validator.onRefOperation(testRefReceivedEvent); |
| |
| assertThat(validationMessages).isEmpty(); |
| |
| verifyZeroInteractions(dfsRefDatabase); |
| } |
| |
| @Test |
| public void shouldVerifyStatusOfPatchSetMetaRefs() throws Exception { |
| testRefReceivedEvent.command = RECEIVE_COMMAND_CREATE_PATCHSET_META_REF; |
| |
| Ref testRefMeta = new TestRef(REF_PATCHSET_META_NAME, REF_OBJID); |
| doReturn(testRefMeta).when(dfsRefDatabase).newRef(REF_PATCHSET_META_NAME, REF_OBJID); |
| |
| validator.onRefOperation(testRefReceivedEvent); |
| |
| verify(dfsRefDatabase) |
| .compareAndCreate(eq(PROJECT_NAME), eqRef(REF_PATCHSET_META_NAME, REF_OBJID)); |
| } |
| |
| @Test |
| public void shouldInsertNewRefInDfsDatabaseWhenHandlingRefCreationEvents() throws Exception { |
| testRefReceivedEvent.command = RECEIVE_COMMAND_CREATE_REF; |
| |
| final List<ValidationMessage> validationMessages = |
| validator.onRefOperation(testRefReceivedEvent); |
| |
| assertThat(validationMessages).isEmpty(); |
| verify(dfsRefDatabase).compareAndCreate(eq(PROJECT_NAME), eqRef(REF_NAME, REF_OBJID)); |
| } |
| |
| @Test |
| public void shouldFailRefCreationIfInsertANewRefInDfsDatabaseFails() throws Exception { |
| testRefReceivedEvent.command = RECEIVE_COMMAND_CREATE_REF; |
| |
| IllegalArgumentException alreadyInDb = new IllegalArgumentException("obj is already in db"); |
| |
| doThrow(alreadyInDb) |
| .when(dfsRefDatabase) |
| .compareAndCreate(eq(PROJECT_NAME), eqRef(REF_NAME, REF_OBJID)); |
| |
| expectedException.expect(ValidationException.class); |
| expectedException.expectCause(sameInstance(alreadyInDb)); |
| |
| validator.onRefOperation(testRefReceivedEvent); |
| } |
| |
| @Test |
| public void shouldUpdateRefInDfsDatabaseWhenHandlingRefUpdateEvents() throws Exception { |
| testRefReceivedEvent.command = RECEIVE_COMMAND_UPDATE_REF; |
| doReturn(new TestRef(REF_NAME, REF_OBJID_OLD)).when(localRefDatabase).getRef(REF_NAME); |
| doReturn(true) |
| .when(dfsRefDatabase) |
| .compareAndPut( |
| eq(PROJECT_NAME), eqRef(REF_NAME, REF_OBJID_OLD), eqRef(REF_NAME, REF_OBJID)); |
| |
| final List<ValidationMessage> validationMessages = |
| validator.onRefOperation(testRefReceivedEvent); |
| |
| assertThat(validationMessages).isEmpty(); |
| verify(dfsRefDatabase) |
| .compareAndPut( |
| eq(PROJECT_NAME), eqRef(REF_NAME, REF_OBJID_OLD), eqRef(REF_NAME, REF_OBJID)); |
| } |
| |
| @Test |
| public void shouldFailRefUpdateIfRefUpdateInDfsRefDatabaseReturnsFalse() throws Exception { |
| testRefReceivedEvent.command = RECEIVE_COMMAND_UPDATE_REF; |
| doReturn(new TestRef(REF_NAME, REF_OBJID_OLD)).when(localRefDatabase).getRef(REF_NAME); |
| doReturn(false) |
| .when(dfsRefDatabase) |
| .compareAndPut( |
| eq(PROJECT_NAME), eqRef(REF_NAME, REF_OBJID_OLD), eqRef(REF_NAME, REF_OBJID)); |
| expectedException.expect(ValidationException.class); |
| expectedException.expectCause(nullValue(Exception.class)); |
| |
| validator.onRefOperation(testRefReceivedEvent); |
| } |
| |
| @Test |
| public void shouldFailRefUpdateIfRefIsNotInDfsRefDatabase() throws Exception { |
| testRefReceivedEvent.command = RECEIVE_COMMAND_UPDATE_REF; |
| doReturn(null).when(localRefDatabase).getRef(REF_NAME); |
| |
| expectedException.expect(ValidationException.class); |
| expectedException.expectCause(nullValue(Exception.class)); |
| |
| validator.onRefOperation(testRefReceivedEvent); |
| } |
| |
| @Test |
| public void shouldDeleteRefInDfsDatabaseWhenHandlingRefDeleteEvents() throws Exception { |
| testRefReceivedEvent.command = RECEIVE_COMMAND_DELETE_REF; |
| doReturn(new TestRef(REF_NAME, REF_OBJID_OLD)).when(localRefDatabase).getRef(REF_NAME); |
| doReturn(true) |
| .when(dfsRefDatabase) |
| .compareAndRemove(eq(PROJECT_NAME), eqRef(REF_NAME, REF_OBJID_OLD)); |
| |
| final List<ValidationMessage> validationMessages = |
| validator.onRefOperation(testRefReceivedEvent); |
| |
| assertThat(validationMessages).isEmpty(); |
| |
| verify(dfsRefDatabase).compareAndRemove(eq(PROJECT_NAME), eqRef(REF_NAME, REF_OBJID_OLD)); |
| } |
| |
| @Test |
| public void shouldFailRefDeletionIfRefDeletionInDfsRefDatabaseReturnsFalse() throws Exception { |
| testRefReceivedEvent.command = RECEIVE_COMMAND_DELETE_REF; |
| doReturn(new TestRef(REF_NAME, REF_OBJID_OLD)).when(localRefDatabase).getRef(REF_NAME); |
| doReturn(false) |
| .when(dfsRefDatabase) |
| .compareAndRemove(eq(PROJECT_NAME), eqRef(REF_NAME, REF_OBJID_OLD)); |
| |
| expectedException.expect(ValidationException.class); |
| expectedException.expectCause(nullValue(Exception.class)); |
| |
| validator.onRefOperation(testRefReceivedEvent); |
| } |
| |
| @Test |
| public void shouldFailRefDeletionIfRefIsNotInDfsDatabase() throws Exception { |
| testRefReceivedEvent.command = RECEIVE_COMMAND_DELETE_REF; |
| doReturn(null).when(localRefDatabase).getRef(REF_NAME); |
| |
| expectedException.expect(ValidationException.class); |
| expectedException.expectCause(nullValue(Exception.class)); |
| |
| validator.onRefOperation(testRefReceivedEvent); |
| } |
| } |