| // Copyright (C) 2020 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 com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.Mockito.*; |
| |
| import com.google.gerrit.reviewdb.client.Project; |
| import com.google.gerrit.server.events.RefUpdatedEvent; |
| import com.google.gerrit.server.project.ProjectConfig; |
| import com.google.gerrit.testing.InMemoryRepositoryManager; |
| import com.google.gerrit.testing.InMemoryTestEnvironment; |
| import com.google.inject.Inject; |
| import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper; |
| import com.googlesource.gerrit.plugins.multisite.forwarder.Context; |
| import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture; |
| import com.googlesource.gerrit.plugins.replication.RefReplicatedEvent; |
| import com.googlesource.gerrit.plugins.replication.ReplicationState; |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Optional; |
| import org.apache.commons.io.IOUtils; |
| import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; |
| import org.eclipse.jgit.junit.TestRepository; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectLoader; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.transport.RemoteRefUpdate; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.junit.MockitoJUnitRunner; |
| |
| @RunWith(MockitoJUnitRunner.class) |
| public class ProjectVersionRefUpdateTest implements RefFixture { |
| |
| @Rule public InMemoryTestEnvironment testEnvironment = new InMemoryTestEnvironment(); |
| |
| @Mock RefUpdatedEvent refUpdatedEvent; |
| @Mock SharedRefDatabaseWrapper sharedRefDb; |
| |
| @Inject private ProjectConfig.Factory projectConfigFactory; |
| @Inject private InMemoryRepositoryManager repoManager; |
| |
| private TestRepository<InMemoryRepository> repo; |
| private ProjectConfig project; |
| private RevCommit masterCommit; |
| |
| @Before |
| public void setUp() throws Exception { |
| InMemoryRepository inMemoryRepo = repoManager.createRepository(A_TEST_PROJECT_NAME_KEY); |
| project = projectConfigFactory.create(A_TEST_PROJECT_NAME_KEY); |
| project.load(inMemoryRepo); |
| repo = new TestRepository<>(inMemoryRepo); |
| masterCommit = repo.branch("master").commit().create(); |
| } |
| |
| @After |
| public void tearDown() { |
| Context.unsetForwardedEvent(); |
| } |
| |
| @Test |
| public void producerShouldUpdateProjectVersionUponRefUpdatedEvent() throws IOException { |
| Context.setForwardedEvent(false); |
| when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class))) |
| .thenReturn(true); |
| when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY); |
| when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME); |
| |
| new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refUpdatedEvent); |
| |
| Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); |
| |
| verify(sharedRefDb, atMost(1)) |
| .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)); |
| |
| assertThat(ref).isNotNull(); |
| |
| ObjectLoader loader = repo.getRepository().open(ref.getObjectId()); |
| String storedVersion = IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()); |
| assertThat(Long.parseLong(storedVersion)).isEqualTo(masterCommit.getCommitTime()); |
| } |
| |
| @Test |
| public void producerShouldNotUpdateProjectVersionUponSequenceRefUpdatedEvent() |
| throws IOException { |
| Context.setForwardedEvent(false); |
| when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY); |
| when(refUpdatedEvent.getRefName()).thenReturn("refs/sequences/changes"); |
| |
| new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refUpdatedEvent); |
| |
| Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); |
| assertThat(ref).isNull(); |
| } |
| |
| @Test |
| public void shouldNotUpdateProjectVersionWhenProjectDoesntExist() throws IOException { |
| Context.setForwardedEvent(false); |
| when(refUpdatedEvent.getProjectNameKey()) |
| .thenReturn(new Project.NameKey("aNonExistentProject")); |
| when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME); |
| |
| new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refUpdatedEvent); |
| |
| Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); |
| assertThat(ref).isNull(); |
| } |
| |
| @Test |
| public void consumerShouldUpdateProjectVersionUponRefReplicatedEvent() throws IOException { |
| Context.setForwardedEvent(true); |
| RefReplicatedEvent refReplicatedEvent = |
| new RefReplicatedEvent( |
| A_TEST_PROJECT_NAME, |
| A_TEST_REF_NAME, |
| "targetNode", |
| ReplicationState.RefPushResult.SUCCEEDED, |
| RemoteRefUpdate.Status.OK); |
| |
| new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refReplicatedEvent); |
| |
| Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); |
| assertThat(ref).isNotNull(); |
| |
| verify(sharedRefDb, never()) |
| .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)); |
| |
| ObjectLoader loader = repo.getRepository().open(ref.getObjectId()); |
| String storedVersion = IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()); |
| assertThat(Long.parseLong(storedVersion)) |
| .isEqualTo(Integer.toUnsignedLong(masterCommit.getCommitTime())); |
| } |
| |
| @Test |
| public void consumerShouldNotUpdateProjectVersionUponFailedRefReplicatedEvent() |
| throws IOException { |
| Context.setForwardedEvent(true); |
| RefReplicatedEvent refReplicatedEvent = |
| new RefReplicatedEvent( |
| A_TEST_PROJECT_NAME, |
| A_TEST_REF_NAME, |
| "targetNode", |
| ReplicationState.RefPushResult.SUCCEEDED, |
| RemoteRefUpdate.Status.REJECTED_OTHER_REASON); |
| |
| new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refReplicatedEvent); |
| |
| Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); |
| assertThat(ref).isNull(); |
| } |
| |
| @Test |
| public void consumerShouldNotUpdateProjectVersionUponSequenceRefReplicatedEvent() |
| throws IOException { |
| Context.setForwardedEvent(true); |
| RefReplicatedEvent refReplicatedEvent = |
| new RefReplicatedEvent( |
| A_TEST_PROJECT_NAME, |
| "refs/sequences/groups", |
| "targetNode", |
| ReplicationState.RefPushResult.SUCCEEDED, |
| RemoteRefUpdate.Status.OK); |
| |
| new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refReplicatedEvent); |
| |
| Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); |
| assertThat(ref).isNull(); |
| } |
| |
| @Test |
| public void getRemoteProjectVersionShouldReturnCorrectValue() throws IOException { |
| updateLocalVersion(); |
| Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); |
| when(sharedRefDb.get(A_TEST_PROJECT_NAME_KEY, MULTI_SITE_VERSIONING_REF, ObjectId.class)) |
| .thenReturn(Optional.of(ref.getObjectId())); |
| |
| Optional<Long> version = |
| new ProjectVersionRefUpdate(repoManager, sharedRefDb) |
| .getProjectRemoteVersion(A_TEST_PROJECT_NAME); |
| |
| assertThat(version.isPresent()).isTrue(); |
| assertThat(version.get()).isEqualTo(masterCommit.getCommitTime()); |
| } |
| |
| @Test |
| public void getLocalProjectVersionShouldReturnCorrectValue() throws IOException { |
| updateLocalVersion(); |
| Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF); |
| |
| Optional<Long> version = |
| new ProjectVersionRefUpdate(repoManager, sharedRefDb) |
| .getProjectLocalVersion(A_TEST_PROJECT_NAME); |
| |
| assertThat(version.isPresent()).isTrue(); |
| assertThat(version.get()).isEqualTo(masterCommit.getCommitTime()); |
| } |
| |
| private void updateLocalVersion() { |
| Context.setForwardedEvent(true); |
| RefReplicatedEvent refReplicatedEvent = |
| new RefReplicatedEvent( |
| A_TEST_PROJECT_NAME, |
| A_TEST_REF_NAME, |
| "targetNode", |
| ReplicationState.RefPushResult.SUCCEEDED, |
| RemoteRefUpdate.Status.OK); |
| new ProjectVersionRefUpdate(repoManager, sharedRefDb).onEvent(refReplicatedEvent); |
| } |
| } |